<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Johannes Stillig</title>
  <subtitle>Notes on AI, security and technology - and which of it survives a normal Tuesday.</subtitle>
  <link href="https://stillig.net/feed.xml" rel="self" />
  <link href="https://stillig.net/" />
  <updated>2026-08-05T12:00:00Z</updated>
  <id>https://stillig.net/</id>
  <author>
    <name>Johannes Stillig</name>
  </author>
  <entry>
    <title>A judge ran the same prompt three times and got three different answers</title>
    <link href="https://stillig.net/posts/tamper-evident-llm-calls/" />
    <updated>2026-08-05T12:00:00Z</updated>
    <id>https://stillig.net/posts/tamper-evident-llm-calls/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;I&#39;m Johannes. I build an investigations engine where every finding has to trace back to the source bytes it came from, which turned out to be an awkward promise once a language model started helping produce the findings.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In a trust accounting case in New York, an expert witness told the court he had used an AI assistant to cross-check his damages calculation. The judge asked him what he had typed into it. He could not remember. The judge asked what sources it had used. He could not say.&lt;/p&gt;
&lt;p&gt;So the judge typed the question in himself, on a court computer, and got $949,070.97. He ran it again on a second court computer and got $948,209.63. A third returned a little over $951,000.&lt;/p&gt;
&lt;p&gt;Three court-issued machines, one question, three answers. The Surrogate&#39;s Court wrote that while the variations were not large, the fact that there were variations at all called the reliability of the output into question, and held that counsel has an affirmative duty to disclose the use of AI before such evidence is admitted.&lt;/p&gt;
&lt;p&gt;That is &lt;em&gt;Matter of Weber&lt;/em&gt;, decided in October 2024. The expert was not caught fabricating anything. He was caught being unable to say what he asked or what came back.&lt;/p&gt;
&lt;p&gt;I have spent the last month making sure that cannot happen to my product. What I built works, and it proves less than I originally claimed it did, and the gap between those two things is worth writing down.&lt;/p&gt;
&lt;h2 id=&quot;reproducibility-is-the-wrong-thing-to-want-first&quot; tabindex=&quot;-1&quot;&gt;Reproducibility is the wrong thing to want first&lt;/h2&gt;
&lt;p&gt;The instinct, when you notice this problem, is to reach for reproducibility. Pin the parameters, set a seed, get the same answer twice.&lt;/p&gt;
&lt;p&gt;You cannot, and the reason is more interesting than floating point.&lt;/p&gt;
&lt;p&gt;Thinking Machines Lab published the clearest account of it. The usual explanation, that concurrent floating point addition is non-associative, turns out to be wrong: run the same matrix multiplication on the same data repeatedly and you get bitwise identical results every time. The actual culprit is that your output depends on the batch you happened to be in, and the batch depends on who else was hitting the server at that moment. Their measurement: a thousand completions of one prompt at temperature zero produced &lt;strong&gt;eighty unique completions&lt;/strong&gt;. The first 102 tokens were identical every time. Then they diverged.&lt;/p&gt;
&lt;p&gt;From your point of view, the other users of the API are not an input. They are a property of the weather.&lt;/p&gt;
&lt;p&gt;This is fixable, and it has shipped. Batch invariant kernels give bit identical output, and both vLLM and SGLang now expose a deterministic mode. The cost is real, somewhere between a quarter and two thirds of your throughput depending on whose measurement you take, and there is a nastier property underneath: determinism is contagious. One deterministic request joining a batch of ten ordinary ones dropped total server throughput from 931 tokens per second to 415. It is not a per-request option you can quietly buy for yourself.&lt;/p&gt;
&lt;p&gt;And none of it is available to you on a hosted API. The Anthropic Messages API has no seed. On current frontier Claude models you cannot even set temperature: a non-default value returns a 400. Bedrock&#39;s Converse &lt;code&gt;inferenceConfig&lt;/code&gt; has four knobs and none of them is a seed. OpenAI, the one provider that offered a seed and a backend fingerprint, has marked both &lt;code&gt;deprecated: true&lt;/code&gt; in its OpenAPI spec and shipped neither on the Responses API.&lt;/p&gt;
&lt;p&gt;So the industry&#39;s one reproducibility affordance is being withdrawn while everyone talks about AI governance.&lt;/p&gt;
&lt;p&gt;Which is fine, because reproducibility was the wrong thing to want first. &lt;strong&gt;Logging is attestation, not reproduction.&lt;/strong&gt; It proves what you sent and what came back. It does not prove the model would say it again. Those are different properties, and only one of them is available to you today.&lt;/p&gt;
&lt;p&gt;The expert in &lt;em&gt;Weber&lt;/em&gt; did not lose because his answer was irreproducible. He lost because he had no record.&lt;/p&gt;
&lt;h2 id=&quot;nobody-has-specified-what-to-record&quot; tabindex=&quot;-1&quot;&gt;Nobody has specified what to record&lt;/h2&gt;
&lt;p&gt;Here is what surprised me most. I went looking for the standard that says what to capture about a single model call, expecting to find several and have to pick.&lt;/p&gt;
&lt;p&gt;There is not one.&lt;/p&gt;
&lt;p&gt;&amp;quot;Traceability&amp;quot; appears zero times in the NIST AI Risk Management Framework. So does &amp;quot;record&amp;quot;. The Generative AI Profile says &amp;quot;provenance&amp;quot; sixty-seven times, and every one of them is about content provenance or training data, because the problem NIST is solving there is deepfakes, not defensibility. Every AI bill-of-materials format describes the model rather than the call: CycloneDX&#39;s &lt;code&gt;inputs&lt;/code&gt; and &lt;code&gt;outputs&lt;/code&gt; fields hold &lt;em&gt;formats&lt;/em&gt;, not values. Sigstore&#39;s model signing signs a list of file digests, which tells you the weights were not tampered with and nothing about what they did.&lt;/p&gt;
&lt;p&gt;The closest thing to a specification comes from an unexpected direction. OpenTelemetry&#39;s GenAI conventions define exactly the attributes you would want, including the input messages, the output messages and the system instructions. Then the spec says instrumentations should not capture them by default, and lists as option one: do not record instructions, inputs, or outputs.&lt;/p&gt;
&lt;p&gt;That is the best argument against everything I am saying, and it is also my whole point, read from the other end. The standard proves the capability is trivial. It also documents that the deliberate default is the evidentiary hole.&lt;/p&gt;
&lt;p&gt;As for regulation: the EU AI Act does require high-risk systems to support automatic event logging over their lifetime, and requires providers and deployers to keep those logs for at least six months. Two things about that. The only place the Act specifies log &lt;em&gt;content&lt;/em&gt; is a subsection covering remote biometric identification, and it reaches the input data for which a search produced a match, which is a long way from &amp;quot;record the prompt.&amp;quot; And the obligations were due to apply on 2 August 2026, until the Digital Omnibus on AI pushed them to &lt;strong&gt;2 December 2027&lt;/strong&gt;. That amendment came into force on 27 July 2026. I had the August date in a draft of this post three days ago.&lt;/p&gt;
&lt;p&gt;The field-level, binding, per-invocation specification does not exist. If you want the record, you are designing it yourself.&lt;/p&gt;
&lt;p&gt;Or finding the other people who did. After I built mine I came across &lt;a href=&quot;https://github.com/bkuan001/halo-record&quot;&gt;halo-record&lt;/a&gt;, a small open-source project that publishes a schema for exactly this: one record per trust-boundary action, SHA-256 chained over RFC 8785 canonical JSON, an external witness that stores nothing but a record count and a chain fingerprint, RFC 3161 timestamps from an authority the operator does not control. About 4,800 lines of Python, zero dependencies. We had never compared notes, and we converged on the same parts list, down to the same confession: its LIMITS.md opens by stating the system &amp;quot;cannot prove the operator never wrote a record in the first place, or did not delete recent records and re-seal a shorter chain before anyone saw it&amp;quot;, which is precisely the concession my own gap detector makes a section from now. It is the only project in this space I have seen lead with what it cannot prove. Two independent designs arriving at the same shape and the same limits is what a specification looks like just before somebody writes it down.&lt;/p&gt;
&lt;h2 id=&quot;what-i-built&quot; tabindex=&quot;-1&quot;&gt;What I built&lt;/h2&gt;
&lt;p&gt;One seam, and this matters more than any of the cryptography. Every model backend in the system is created through a single factory, and that factory wraps whatever it returns in a capturing proxy. A static analysis test fails the build if anyone constructs a backend another way. There were around eighty call sites when I did this and I changed none of them. Coverage that depends on developers remembering to instrument their call site is coverage that decays every sprint.&lt;/p&gt;
&lt;p&gt;Hashes in the database, bytes elsewhere. Each call writes a seventeen field row: model, parameters exactly as sent, a hash of the prompt, a hash of the output, token counts, pipeline stage, timestamp. The payload bytes go to content addressed storage. That split is not a storage optimisation, it is a privilege decision, and I got it wrong the first time.&lt;/p&gt;
&lt;p&gt;An identity you cannot argue with. The seventeen fields are hashed as sorted-key compact JSON into one envelope hash, and the step id sits deliberately inside that hash. There is a trap on both sides of that choice. Leave the id out and two identical retries collide on the unique index, so an honest retry starts throwing capture failures. Put it in and uniqueness becomes trivially true, which sounds useless until you notice what it turns the index into. A unique constraint over a hash that already contains an identifier is not deduplication any more. It is a tripwire: two rows can never legitimately share one, so a collision means something rewrote history.&lt;/p&gt;
&lt;p&gt;Proof that nothing is missing. This is the part I would most encourage people to copy, because almost everyone skips it. Tamper evidence protects the records that exist and says nothing about the call that never wrote one, which is the failure mode that matters most because it is also the most convenient. So: write an intent row before the call. On success, flip it to captured in the same commit that writes the record. On failure, flip it to failed. A sweeper marks anything still pending past its grace window as a proven gap rather than a suspected one.&lt;/p&gt;
&lt;p&gt;Note what that last mechanism still concedes. It can prove the record is incomplete. It can never prove the record is complete, because the same process writes both the intent and the record.&lt;/p&gt;
&lt;h2 id=&quot;three-claims-wearing-one-word&quot; tabindex=&quot;-1&quot;&gt;Three claims wearing one word&lt;/h2&gt;
&lt;p&gt;I had been using &amp;quot;reproducible&amp;quot; as though it meant one thing. It means three, and they sit on different shelves.&lt;/p&gt;
&lt;p&gt;There is the record being anchored and tamper evident: every step captured, every citation resolving to bytes that still hash to what they hashed to, completeness provable. This ships everywhere and it is the bar that actually matters.&lt;/p&gt;
&lt;p&gt;There is re-executing a step and comparing what comes back. The hard part is not the re-execution, it is the comparison. If a model judges whether two outputs are equivalent then your verification is itself unverifiable, and you have built a tower of turtles. The only honest version compares structure: did the same citations resolve, were the same entities extracted, did the verdict land in the same band. Anything that cannot be diffed deterministically has to be excluded from the claim rather than waved at.&lt;/p&gt;
&lt;p&gt;And there is bit exact replay, which is a property of the deployment rather than the software. Pinned open weights in a deterministic inference mode, yes. Hosted API, no, and no amount of engineering on my side changes that.&lt;/p&gt;
&lt;p&gt;Forensic science worked this out before we did, and the precedent is better than anything in the AI literature. Probabilistic genotyping software is non-deterministic: SWGDAM&#39;s guidelines state that these approaches may not produce the same likelihood ratio from repeat analyses, and require laboratories to demonstrate the range of values and establish an acceptable amount of variation. The international guidance asks that the software offer a stable mode for repeatability testing, which is precisely a seed. Published work finds up to ten-fold swings in log likelihood ratio between runs. And in March 2026 the Third Circuit held, precedentially, that the leading tool may not be perfect but most science is not, and it is reliable enough.&lt;/p&gt;
&lt;p&gt;So the answer to &amp;quot;how can a non-deterministic method be evidence&amp;quot; is not theoretical, and it is not determinism. It is: measure the variation, disclose it, document the process. Note which way that cuts. Forensic science &lt;em&gt;measures&lt;/em&gt; its non-determinism. That is a stronger demand than the one I am making.&lt;/p&gt;
&lt;h2 id=&quot;what-broke&quot; tabindex=&quot;-1&quot;&gt;What broke&lt;/h2&gt;
&lt;p&gt;I wrote the design spec for this and red teamed it the same day, which I now do as a matter of course. The verdict came back REVISE and it was right on every count.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It found a violation of one of my own non-negotiable rules, in my own architecture.&lt;/strong&gt; The spec put captured payloads in an ordinary content addressed bucket. But prompts here routinely carry verbatim text from documents that may turn out to be legally privileged, because classification reads a document&#39;s full text before anything knows whether it is privileged. My own rule says privileged bytes never leave the encrypted boundary. The feature whose entire purpose was defensibility would have created a second copy of privileged material outside the boundary that protects it, reachable with an unremarkable permission.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It found a privacy hole I had walked straight past.&lt;/strong&gt; Analyst chat is private by default here, deliberately, because it contains half-formed hypotheses about real people. Chat runs through the same backend seam as everything else, so capturing payloads would have made every analyst&#39;s private reasoning readable through a different door. Chat stages are hash only now: the record exists, the bytes do not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The invariant I proposed was false the moment I proposed it.&lt;/strong&gt; I wanted a rule saying no backend is ever constructed outside the factory. One module in my own codebase, the multi-vendor benchmark, constructs two of them directly, by design. And the wording was wrong in a way worth repeating: I had written that every call produces a record, when capture is deliberately fail-open. It had to become every call goes through the capturing seam, which is weaker and true.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The headline claim was too strong.&lt;/strong&gt; I had written that the records would be auditable by a third party. They are not. The record is self-reported by the same process that made the call. What it gives you is tamper evidence after capture.&lt;/p&gt;
&lt;p&gt;Then there is the one no review catches. On the first real deployment every single capture marked itself failed. The service role deliberately has no permission to list the bucket, so the existence check I ran before writing returned a permission error rather than a not-found, and the code read that as a failed write. The mechanism was fine. The check in front of it was the bug, and it was invisible until real infrastructure with real least privilege ran it.&lt;/p&gt;
&lt;p&gt;And then the part I found this week, which is worse, because it happened &lt;em&gt;after&lt;/em&gt; all of the above was corrected.&lt;/p&gt;
&lt;p&gt;I went to audit the layer underneath the envelopes and read my own user-facing documentation. It says the product maintains a cryptographic chain of custody for every file and action, that this makes investigations legally admissible, that provenance records cannot be modified or deleted, and, under a heading reading &lt;em&gt;Independent verification&lt;/em&gt;, that write-once checkpoints provide an external trust anchor.&lt;/p&gt;
&lt;p&gt;Every one of those sentences outruns the mechanism. The chain is cryptographic over provenance events, but the link from source bytes to extracted text has no hash at all. The immutability trigger is disabled, table-wide, during engagement deletion. And the external anchor is written and never read: the code lists the checkpoint objects and returns their keys and timestamps, and nothing anywhere fetches a body and compares it to live state. Which means a chain truncated at the tail would still verify, because the verifier checks sequence contiguity, hash linkage and per-event hashes, and a truncated chain satisfies all three. The only record of the expected length is inside the checkpoint nobody reads.&lt;/p&gt;
&lt;p&gt;The specification got corrected in July. The claim drifted back out in the documentation. That is the failure mode of this entire subject in one example, and I own both ends of it.&lt;/p&gt;
&lt;h2 id=&quot;what-is-still-unsolved&quot; tabindex=&quot;-1&quot;&gt;What is still unsolved&lt;/h2&gt;
&lt;p&gt;The record is anchored to me. All of it, my system attesting to my system. I know the shape of the fix, and it got cheaper while I was writing this: SCITT became a standards-track RFC in June 2026, so there is now a specified transparency-log format for exactly this, and no shipping system anchors inference records in one. A qualified timestamp is cheaper still and does something legally distinct: under eIDAS it carries a presumption of accuracy that shifts the burden onto whoever challenges it. I ran one while researching this, against a free public authority. It took under a second and about four kilobytes.&lt;/p&gt;
&lt;p&gt;There is one anchor cheaper than either, and halo-record&#39;s README states it better than I had managed: &amp;quot;a witness you run yourself commits history to you; committing it to your customer requires a witness they have reason to trust.&amp;quot; The customer is the counterparty who will one day ask the question, so hand the customer the chain head. A fingerprint and a record count, delivered on a schedule; each one must extend a chain containing the last, so a rewrite breaks the next delivery, and a missed delivery is itself a visible event. It costs a hash and an email. I started building it the week I read that sentence.&lt;/p&gt;
&lt;p&gt;Nothing proves the model was invoked with those bytes. I have the request I say I sent and the response I say I got. A sufficiently motivated me could have written both. Transparency logs make retroactive tampering evident and do nothing about contemporaneous lying.&lt;/p&gt;
&lt;p&gt;I assumed nobody had solved this. I was wrong, and the correction is the most useful thing I learned. Signed per-inference receipts ship today, from two companies you have probably not heard of, binding a hash of the request and a hash of the response into a signature rooted in hardware attestation, with an independent verifier and published test vectors. Not from Anthropic, OpenAI, Google or AWS. None of the four returns any signature, digest or attestation over an inference; you get an opaque request id, and their usage APIs report aggregate token counts with no content at any granularity. The provider will not sell you the evidence even if you want to buy it. Meanwhile AWS logs full request and response bodies for Bedrock and signs none of it, while CloudTrail, in the same cloud, ships hourly SHA-256 digest files signed with a managed key. They know how. They have not done it for inference.&lt;/p&gt;
&lt;p&gt;The primitive is not exotic either. A confidential-computing enclave can request an attestation document after generating a response, and there is a 512-byte field in it for exactly this. Kilobytes and seconds, and no money.&lt;/p&gt;
&lt;p&gt;And the smallest gap bothers me most. My records pin a model identifier, not a model version, because the backends do not surface one. A hosted identifier is an alias and aliases move. So I can prove what I asked and what came back, and not which weights answered. Even the two vendors shipping receipts bind a model &lt;em&gt;name string&lt;/em&gt;; one of their own surveys concedes that no system verifies weights provenance today. For a record whose purpose is to survive a question asked eighteen months later, that is the one I would close first, and nobody has.&lt;/p&gt;
&lt;h2 id=&quot;the-strongest-argument-against-all-of-this&quot; tabindex=&quot;-1&quot;&gt;The strongest argument against all of this&lt;/h2&gt;
&lt;p&gt;Somebody should make it, so I will.&lt;/p&gt;
&lt;p&gt;The panic is overstated. There is a public database of court decisions involving AI-hallucinated material: 1,812 of them worldwide, 909 in the first seven months of 2026. That sounds like a wave until you notice that 1,060 involve self-represented litigants rather than lawyers, and only 126 produced a professional sanction. Against roughly 272,000 civil and 74,000 criminal federal filings in a single year, that is around a tenth of a percent.&lt;/p&gt;
&lt;p&gt;More pointedly: not one of those cases would have been saved by a prompt log. The duty under Rule 11 is on the lawyer to make a reasonable inquiry. A log showing &amp;quot;I asked a chatbot for cases and it gave me these&amp;quot; is the confession, not the defence.&lt;/p&gt;
&lt;p&gt;And logging is itself a liability. Data protection law requires you to collect what is necessary and no more. Log everything and a preservation order can reach it: a court has already ordered an AI provider to preserve and segregate output logs indefinitely, notwithstanding the technical difficulty and notwithstanding its contractual privacy promises to its users.&lt;/p&gt;
&lt;p&gt;Three more concessions while I am at it. The leading US decision holding that an expert&#39;s AI prompts are discoverable was stayed two weeks after it issued. The proposed federal rule of evidence for machine-generated evidence was held back in June 2026 rather than advanced, with commenters arguing there is no problem yet to address. And the EU logging duty I would have cited as imminent is now eighteen months out.&lt;/p&gt;
&lt;p&gt;Here is why I am building it anyway. Authentication is a low bar and it is not the bar that matters. You can hash-authenticate a machine-generated record under the federal rules, and the Advisory Committee said plainly what that buys you: the certification establishes only that the output came from the computer. It does not touch reliability. The record is necessary and nowhere near sufficient, and the &lt;em&gt;Weber&lt;/em&gt; expert failed the necessary part.&lt;/p&gt;
&lt;p&gt;The honest version is that I am not building this because a regulator will ask. I am building it because a specific, foreseeable conversation happens, in which somebody asks how a finding was produced, and the answer is either a record or a shrug. I have watched a room go quiet over a smaller question than that.&lt;/p&gt;
&lt;h2 id=&quot;what-to-take-from-this&quot; tabindex=&quot;-1&quot;&gt;What to take from this&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Logging is attestation, not reproduction. It proves what you sent and what came back, never that the model would say it again.&lt;/li&gt;
&lt;li&gt;Provenance is solvable today. Reproducibility mostly is not, on a hosted API, and the seed parameters that existed are being deprecated.&lt;/li&gt;
&lt;li&gt;One seam beats a hundred instrumented call sites. If coverage depends on people remembering, coverage decays.&lt;/li&gt;
&lt;li&gt;Write an intent before the call, or you cannot detect the call that never got recorded.&lt;/li&gt;
&lt;li&gt;A proven gap and a suspected gap are different products. Build the one that can say incomplete as a fact.&lt;/li&gt;
&lt;li&gt;Never say reproducible unqualified. Say which of the three you mean.&lt;/li&gt;
&lt;li&gt;Measure your non-determinism and publish the range. Forensic science has done this for a decade, and courts accepted it.&lt;/li&gt;
&lt;li&gt;Write-once anchors you never read back are decoration. Mine is not read back, so a chain truncated at the tail verifies perfectly. halo-record&#39;s &lt;code&gt;anchor --check&lt;/code&gt; does read it back, count and head, and a truncated tail fails there - which is the minimum, and still only means something if the witness is independent of you.&lt;/li&gt;
&lt;li&gt;Attest to the integrity of the record, never the correctness of the conclusion. The record is yours to prove. The method is a human&#39;s to defend.&lt;/li&gt;
&lt;li&gt;The claim drifts even after you correct the spec. Mine drifted into the user guide, which is the document customers actually read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have anchored a record like this to something outside your own infrastructure, or persuaded a provider to sign anything at all about an inference, I would genuinely like to hear how. This is the audit half of a promise I make in my day job, that a finding traces back to the bytes it came from even when a model helped produce it: &lt;a href=&quot;https://www.tidebreak.ai&quot;&gt;that work is here&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The attacker can prove what they took. You can only prove what you logged.</title>
    <link href="https://stillig.net/posts/prove-what-they-took/" />
    <updated>2026-07-29T18:00:00Z</updated>
    <id>https://stillig.net/posts/prove-what-they-took/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;I&#39;m Johannes. I have spent eighteen years on the response side of security, and the thing I keep returning to is that a control is only worth what you can prove about it afterwards.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In roughly one breach in five, the first person to tell the world what happened to your customers&#39; data is the person who took it.&lt;/p&gt;
&lt;p&gt;IBM&#39;s numbers this year: 50% of breaches are found by the organisation&#39;s own security teams, 31% by a benign third party, and 19% are disclosed by the attacker. That last group is the one worth sitting with. It means the opening account of the incident is written by someone with a motive, a timeline that suits them, and a folder of your data to quote from.&lt;/p&gt;
&lt;p&gt;Everyone prepares for the regulator. The regulator gives you a private conversation, a phased process, and lawyers on both sides who understand that facts take time. That is the easy adversary. Nothing about the public version of a breach works like that.&lt;/p&gt;
&lt;p&gt;Here is the asymmetry nobody plans for, and it is not about security at all.&lt;/p&gt;
&lt;p&gt;The attacker can prove what they took. They have it. They can post a thousand records and let anyone verify the claim in an afternoon.&lt;/p&gt;
&lt;p&gt;You can only prove what you logged.&lt;/p&gt;
&lt;p&gt;If you did not log it, you cannot rebut them. You can deny, and a denial without evidence is a press release. Their claim comes with a sample. Yours comes with a spokesperson. There is no contest.&lt;/p&gt;
&lt;h2 id=&quot;the-ninety-minute-silence&quot; tabindex=&quot;-1&quot;&gt;The ninety minute silence&lt;/h2&gt;
&lt;p&gt;The shape of a bad breach is always the same and it is not the shape people expect.&lt;/p&gt;
&lt;p&gt;Nobody argues about whether to contain. That part is well drilled and everybody knows their lines. The hard question lands about ninety minutes in, usually from the general counsel or the comms lead, and it is always a version of: what did they actually take?&lt;/p&gt;
&lt;p&gt;Then the room goes quiet.&lt;/p&gt;
&lt;p&gt;Not because anybody is incompetent. Because answering it requires knowing which systems were touched, what lived on them, which records were read rather than merely reachable, and whether anything left the estate. That is four separate evidence problems, and every one of them is answerable only from data that somebody chose to collect, at a retention period somebody chose, long before there was a question.&lt;/p&gt;
&lt;p&gt;I have sat in that silence more than once. It is the most expensive thirty seconds of the whole incident, because everything public downstream is decided in it.&lt;/p&gt;
&lt;h2 id=&quot;absence-of-evidence-is-not-neutral&quot; tabindex=&quot;-1&quot;&gt;Absence of evidence is not neutral&lt;/h2&gt;
&lt;p&gt;This is the part I would most like people to take away, because it runs against instinct.&lt;/p&gt;
&lt;p&gt;Security teams treat &amp;quot;we have not found evidence of exfiltration&amp;quot; as a neutral, honest, cautious statement. It is not neutral. In public it reads as &amp;quot;we do not know,&amp;quot; and into that gap flows the worst plausible number.&lt;/p&gt;
&lt;p&gt;Once a figure is circulating, you inherit it. If a leak site claims four million records and you cannot say otherwise with evidence, four million is the number in every article, every customer email, every board pack, and every counterparty&#39;s risk review for the next two years. You can correct it later. Corrections never travel as far as the original, and the people who mattered most have already updated.&lt;/p&gt;
&lt;p&gt;So the practical stakes are not the fine. They are these, and all of them are decided by whether you can produce a credible, specific, fast answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Whether you notify everyone or the affected. One of those is survivable.&lt;/li&gt;
&lt;li&gt;Whether your largest customers hear a number from you or read one somewhere else.&lt;/li&gt;
&lt;li&gt;Whether &amp;quot;we contained it in days and here is exactly what was in scope&amp;quot; is available to you as a sentence at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a period during my career I ran a cyber practice inside a law firm, acting for people whose exposure was reputational long before it was regulatory. That work changes how you see this. For those clients the fine was a rounding error. The story was the whole event, and the story was decided almost entirely by who could evidence their version first.&lt;/p&gt;
&lt;h2 id=&quot;the-clock-you-are-actually-racing&quot; tabindex=&quot;-1&quot;&gt;The clock you are actually racing&lt;/h2&gt;
&lt;p&gt;The 172 is the number that makes this hard.&lt;/p&gt;
&lt;p&gt;In the best measured case this year, where an organisation&#39;s own security team found the breach itself, it took 172 days to identify and another 52 to contain. That is the good outcome. Third party and supply chain compromise took the longest of anything: 196 days to identify, 71 to contain, 267 in total, on data that is yours and logs that belong to somebody else.&lt;/p&gt;
&lt;p&gt;So the attacker has had most of a year inside an estate you were not watching closely enough to notice, and you have somewhere between hours and a couple of days before the public version sets.&lt;/p&gt;
&lt;p&gt;You are not racing the regulator&#39;s clock. You are racing the first article.&lt;/p&gt;
&lt;p&gt;And it does not end quickly. 86% of breached organisations reported operational disruption. 65% said they had not fully recovered. 45% raised prices afterwards, about a third of them by more than 15%, which is the part your customers experience directly and remember longer than the incident.&lt;/p&gt;
&lt;h2 id=&quot;you-cannot-investigate-your-way-out-of-logs-you-never-kept&quot; tabindex=&quot;-1&quot;&gt;You cannot investigate your way out of logs you never kept&lt;/h2&gt;
&lt;p&gt;The instinct after a breach is to buy investigation: forensics, threat intel, a bigger bridge call. That is the wrong end of the problem. The speed of your answer was set months earlier by a handful of unglamorous decisions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Know what data you hold and where.&lt;/strong&gt; Not a data map produced once for an audit and never reconciled since. Something that would let you say in an afternoon what categories of personal data sit on a given host. Most organisations discover mid-incident that this document has been wrong for two years.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Log reads, not just writes.&lt;/strong&gt; Nearly everyone logs authentication and change. Far fewer log access to the data itself at a granularity that separates &amp;quot;this account could have read ten million records&amp;quot; from &amp;quot;this account did read four thousand.&amp;quot; That distinction is the difference between a notification to everybody and a notification to the affected. It is the most commonly missing piece I have seen, and it is the one that decides your public number.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set retention longer than your detection time.&lt;/strong&gt; If logs roll at 30 or 90 days and breaches are found at 172, you have built a system that guarantees the evidence expires before the question is asked. Cheapest fix on this list, most often skipped, because retention is a line item nobody has to defend until the week it decides everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get evidence rights from vendors in the contract.&lt;/strong&gt; Not a security questionnaire. A clause covering what they preserve, for how long, and how fast they hand it over. Ask at renewal, when you have leverage, rather than at 2am, when you have none. Given supply chain compromise takes 267 days, this is where the gap is widest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rehearse the scope question on its own.&lt;/strong&gt; Most tabletops rehearse containment and comms, which are the parts you are already good at. Run one where the only objective is to establish what was taken, and put a clock on it. The result is usually clarifying and mildly humiliating.&lt;/p&gt;
&lt;p&gt;None of that is a product and all of it is boring. That is the point. The organisations that hold the narrative are not the ones with the best incident response. They are the ones who made dull decisions about evidence eighteen months earlier.&lt;/p&gt;
&lt;h2 id=&quot;the-evidence-that-arrives-late&quot; tabindex=&quot;-1&quot;&gt;The evidence that arrives late&lt;/h2&gt;
&lt;p&gt;Everything above depends on decisions you already made, or did not. There is one source of evidence that does not work that way, and it is the one victims handle worst.&lt;/p&gt;
&lt;p&gt;When data is posted on a leak site, sold, or sent to you as proof of the claim, that dump is evidence. It is the attacker&#39;s own account of what they took, in the most verifiable form that exists: the data itself. It owes nothing to your retention policy or your logging coverage. It is simply there, and it is frequently the fastest available route to a number you can actually stand behind.&lt;/p&gt;
&lt;p&gt;Almost nobody can use it in time.&lt;/p&gt;
&lt;p&gt;It shows up as hundreds of gigabytes, sometimes terabytes, of mixed and broken formats. Some of it is junk. Some of it is recycled from older breaches and pasted in to inflate the claim. Some of it is fabricated outright for the same reason. Establishing which records are real, which are yours, which are still current, and which actual people are behind them is a data engineering problem of real size, and it lands on a security team during the worst week of its year, usually with a spreadsheet and a deadline measured in hours.&lt;/p&gt;
&lt;p&gt;So the clock runs. Weeks go by on &amp;quot;we are investigating the scope&amp;quot; while the attacker&#39;s number, which cost them an afternoon to assert, stands unchallenged and hardens into the accepted figure.&lt;/p&gt;
&lt;p&gt;That gap is closable. It is a processing problem, not a mystery, and the reason it stays open is that almost nobody has treated it as one.&lt;/p&gt;
&lt;h2 id=&quot;what-i-got-wrong&quot; tabindex=&quot;-1&quot;&gt;What I got wrong&lt;/h2&gt;
&lt;p&gt;I would rather not fill this section with other people&#39;s incidents. That is cheap, and it spends confidentiality that is not mine to spend. So here is mine, and it is the part of my own career I am least comfortable with.&lt;/p&gt;
&lt;p&gt;I started out breaking into things. Years of hands-on penetration testing before I ever sat on the response side of anything.&lt;/p&gt;
&lt;p&gt;A penetration test answers one question well: can someone get in. The report says how, and what was reached, and it is graded on the depth of access demonstrated. I do not remember ever writing a section on whether the client could have reconstructed what I did after I left. Nobody asked for one. It was not what the engagement was for.&lt;/p&gt;
&lt;p&gt;So I spent the formative years of my career producing exactly one half of the problem I am now describing to you.&lt;/p&gt;
&lt;p&gt;I always knew precisely what I had touched, because I kept a log of my own work. The client got my report, which is not the same thing at all. It is my account, offered voluntarily, by someone with an interest in it being impressive. A real intruder writes no report.&lt;/p&gt;
&lt;p&gt;That asymmetry sat in front of me for years and I read it as a scoring system rather than a defect. It took being on the other side, watching a room go quiet, to notice that the thing I was best at was precisely the thing that left the client with no evidence of their own.&lt;/p&gt;
&lt;p&gt;I do not think that is only a personal failing. The whole offensive industry still works this way. We prove access. We almost never test whether the target could have told.&lt;/p&gt;
&lt;h2 id=&quot;what-is-still-unsolved&quot; tabindex=&quot;-1&quot;&gt;What is still unsolved&lt;/h2&gt;
&lt;p&gt;The honest frontier is not better logging. It is knowing, before an incident, whether the evidence you hold would actually answer the question.&lt;/p&gt;
&lt;p&gt;You can audit whether logging is enabled. You cannot easily audit whether your logs would let you establish, inside a day, which records left the estate. Those are different properties and only the second one matters at 2am. Nearly every organisation I have looked at assumes it has the second because it has measured the first.&lt;/p&gt;
&lt;p&gt;I do not have a good test for it either. If you have built one, I would genuinely like to hear how it works.&lt;/p&gt;
&lt;h2 id=&quot;what-to-take-from-this&quot; tabindex=&quot;-1&quot;&gt;What to take from this&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In about one breach in five, the first public account is written by the attacker.&lt;/li&gt;
&lt;li&gt;The attacker can prove what they took. You can only prove what you logged.&lt;/li&gt;
&lt;li&gt;Absence of evidence is not neutral. In public it defaults to their number.&lt;/li&gt;
&lt;li&gt;&amp;quot;We are investigating the scope&amp;quot; is a sentence you inherit for two years.&lt;/li&gt;
&lt;li&gt;You cannot investigate your way out of logs you never kept. Evidence is a decision you made six months ago.&lt;/li&gt;
&lt;li&gt;Retention shorter than your detection time guarantees the evidence expires before the question arrives.&lt;/li&gt;
&lt;li&gt;Log reads, not just writes. &amp;quot;Could have accessed&amp;quot; and &amp;quot;did access&amp;quot; are different notifications.&lt;/li&gt;
&lt;li&gt;Auditing that logging is on is not the same as auditing that it would answer the question.&lt;/li&gt;
&lt;li&gt;The dump is evidence too, and it is the only evidence that arrives after the decisions were made. Being able to read it fast is worth more than most of your tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything above is checkable, which is the standard I try to hold my own work to. It is also why I spend my days building an investigations engine that turns a dump like that into an answer in hours instead of weeks, with every finding traced back to the source bytes it came from, because the difference between a victim who can say what was taken and one who cannot is mostly a data problem nobody has been treating as one: &lt;a href=&quot;https://www.tidebreak.ai&quot;&gt;that work is here&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;source&quot; tabindex=&quot;-1&quot;&gt;Source&lt;/h2&gt;
&lt;p&gt;IBM, &lt;em&gt;Cost of a Data Breach Report 2025&lt;/em&gt;. Detection split (Figure 13), lifecycle by detection method (Figure 15), lifecycle by attack vector (Figure 10), operational disruption and recovery (pages 22 and 41), post-breach pricing (Figures 36 and 37). https://www.ibm.com/reports/data-breach&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I let AI agents run my life. They can read everything and send nothing.</title>
    <link href="https://stillig.net/posts/i-let-ai-agents-run-my-life/" />
    <updated>2026-07-28T18:00:00Z</updated>
    <id>https://stillig.net/posts/i-let-ai-agents-run-my-life/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;I&#39;m Johannes. I build systems that run on their own without going off the rails: a security product during the day, and this at night. If there&#39;s a thread through everything I make, it&#39;s that autonomy should be earned structurally, not hoped for.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Right now, while I&#39;m writing this, software I built is reading my email. It knows my calendar. It has my contacts. Every morning it hands me a brief of what matters, and overnight it files what came in, drafts the replies I owe, and keeps my tasks in sync across my devices. I did not check any of it this morning. I did not have to.&lt;/p&gt;
&lt;p&gt;If that sentence makes you slightly uncomfortable, good. It should, and the discomfort is the whole point of the design.&lt;/p&gt;
&lt;p&gt;Everyone who builds a &amp;quot;second brain&amp;quot; builds one to &lt;em&gt;remember&lt;/em&gt;: a place to capture notes and organize them so a human can read them back later. Storage. Mine does almost none of that. I built one to &lt;em&gt;act&lt;/em&gt;. It is ten small autonomous agents, running on a schedule on my own laptop, that do the boring, consequential, easy-to-drop work of a day: triage the inbox, file what&#39;s filable, draft what needs a human, brief me, and once a week think about my own notes and tell me what I&#39;m circling.&lt;/p&gt;
&lt;p&gt;The idea of an external memory is old, and it has a good lineage. Vannevar Bush imagined the Memex in 1945. Douglas Engelbart spent the 1960s on a research program he simply called augmenting human intellect. Niklas Luhmann kept a paper slip-box of ninety thousand notes that out-wrote entire departments, a method Sönke Ahrens later made usable for the rest of us. And more recently Tiago Forte gave the whole thing a name, the Second Brain, while tools like Roam and Obsidian handed a generation of us bidirectional links and a cult. I have read most of it, and I owe it. But notice what every one of those is about: remembering. Capturing, connecting, recalling, so a human can think better later. I wanted the other end of the problem. I did not need help remembering. I needed the remembering to do something without me.&lt;/p&gt;
&lt;p&gt;Handing that much of your life to AI agents is either reckless or it isn&#39;t, and the difference is entirely in two design decisions. The first: it can read everything and send nothing. It will draft an email; it will never send one. Anything that would reach another person waits for me. The second: none of it touches a cloud. Not &amp;quot;we promise not to look&amp;quot; - it physically cannot leave the machine, and I&#39;ll show you exactly why in a moment.&lt;/p&gt;
&lt;p&gt;This is the same bet I make when I let AI agents commit to my codebase overnight, just pointed at my life instead of my work: give them real autonomy, and engineer it so the autonomy is safe rather than trusted. And as with anything I actually run, I&#39;ll tell you which parts earn their keep and which parts I built, admired, and quietly switched off.&lt;/p&gt;
&lt;h2 id=&quot;a-normal-morning&quot; tabindex=&quot;-1&quot;&gt;A normal morning&lt;/h2&gt;
&lt;p&gt;Here is what it actually looks like, because the abstraction is less convincing than the Tuesday.&lt;/p&gt;
&lt;p&gt;I wake up and there is a brief waiting. Not a wall of everything, a brief: what&#39;s on today, what came in overnight and where it went, who I owe a reply, the two things I flagged that are now due. The inbox has already sorted itself while I slept, most of it filed to the right place, a couple of things deliberately held back because they needed a decision only I could make. My tasks are on my phone, in sync, because something reconciled them at 6am. A draft or two sits ready, written with the context already pulled in, waiting for me to read, fix a line, and send.&lt;/p&gt;
&lt;p&gt;The difference from before is not that I do less work. It&#39;s that I start the day oriented instead of excavating. I used to open a full inbox and spend the first hour reconstructing where I was. Now the reconstruction happened without me, and I get to start at the part that needs a human.&lt;/p&gt;
&lt;h2 id=&quot;read-everything-send-nothing&quot; tabindex=&quot;-1&quot;&gt;Read everything, send nothing&lt;/h2&gt;
&lt;p&gt;That morning only works because the agents can see the whole picture: the mail, the calendar, the notes, the running record of what&#39;s owed. Broad read access is the entire reason the thing is useful. It is also, obviously, the scary part, so here is the rule that makes the scary part safe.&lt;/p&gt;
&lt;p&gt;Reading is reversible. If an agent mis-files a note or misreads a thread, the worst case is I move something back, and I&#39;ve lost thirty seconds. Sending is not reversible. An AI that emails the wrong person the wrong thing at 3am cannot be un-emailed, and no amount of &amp;quot;it&#39;s usually right&amp;quot; makes that risk acceptable when the downside is a relationship or a deal. So I made the one irreversible action the one thing the system cannot do on its own.&lt;/p&gt;
&lt;p&gt;Every outward action is drafted and queued. The email is written and sits until I press send. The follow-up is composed, with the context, and waits for my yes. The human is the only path from the system to another person, and as a second layer, a draft can only even be addressed to someone already in my world, never a new recipient the system invented. This is exactly the shape of how I let agents work on my code: they can commit all day, but they cannot deploy. Automate the reversible, put a human on the irreversible, and you can hand over far more than feels comfortable without handing over the thing that can actually hurt you.&lt;/p&gt;
&lt;h2 id=&quot;private-by-construction-not-by-promise&quot; tabindex=&quot;-1&quot;&gt;Private by construction, not by promise&lt;/h2&gt;
&lt;p&gt;Here is the part I care about most, and the part almost every other version of this skips.&lt;/p&gt;
&lt;p&gt;Your email, your calendar, your contacts, the running record of who you owe what: that is the single most sensitive corpus you own. It is a map of your relationships, your money, your health, your leverage. The default move in 2026 is to pipe all of it into someone else&#39;s model, because that is where the convenient assistant lives, and to accept a sentence in a terms-of-service page as the entire privacy architecture. Most people make that data-flow decision without ever noticing they made it.&lt;/p&gt;
&lt;p&gt;I build a company whose whole premise is that a claim is only as good as the evidence you can trace it to. I was not going to run my own life on a promise I can&#39;t verify.&lt;/p&gt;
&lt;p&gt;So Brain is local. The agents run on my laptop, against files on my disk, and the assistant they talk to is reached in a way that keeps the corpus on the machine. That is a stance, not a feature, and stances are easy to say and easy to quietly break the first time cloud sync would be convenient. So I made it structural. The safety net that versions and backs up the whole vault every hour has one hard rule wired into it: if it ever finds that the vault has been pointed at a remote, any remote, it refuses to run and says so. The system that protects my data is built to stop working the instant that data could leave. Privacy here is not something I intend. It is something the code enforces against my own future laziness.&lt;/p&gt;
&lt;p&gt;That is the difference I want you to take from this. &amp;quot;We don&#39;t train on your data&amp;quot; is a promise, and a promise is a thing you audit and hope about. &amp;quot;The data physically cannot leave this machine&amp;quot; is a property, and a property is a thing you can check. When the corpus is your entire personal life, I think that difference is the only one that matters, and I think most people have it exactly backwards: they will agonize over a password and then hand a decade of their correspondence to whichever assistant had the nicest onboarding.&lt;/p&gt;
&lt;p&gt;It costs me things to run it this way, and I owe you honesty about those.&lt;/p&gt;
&lt;h2 id=&quot;what-running-it-locally-actually-costs&quot; tabindex=&quot;-1&quot;&gt;What running it locally actually costs&lt;/h2&gt;
&lt;p&gt;Local is not free and it is not automatically virtuous, and pretending otherwise is how you lose the reader who has actually tried it.&lt;/p&gt;
&lt;p&gt;It lives on one machine. When I&#39;m out with just my phone, the agents are not running for me in the moment; they run on a schedule, where the data is. I gave up the seamless assistant-on-every-device experience that a hosted service hands you for nothing. I gave up someone else keeping it running, which means when an integration breaks there is no support desk, there is me on a Sunday. And I pay for it in exactly the friction you&#39;d expect: the edges that a polished cloud product would have smoothed over, I smooth over myself or route around.&lt;/p&gt;
&lt;p&gt;I took that trade on purpose, and only because of what the corpus is. For my entire personal and professional correspondence, I will pay real friction for a privacy property I can verify. For a shopping list I would not bother, and I&#39;d use whatever was easiest. That&#39;s the honest calculus: local is right when the data is sensitive enough to earn the friction, and the mistake is treating it as a religion instead of a trade.&lt;/p&gt;
&lt;h2 id=&quot;what-i-built-and-dont-use&quot; tabindex=&quot;-1&quot;&gt;What I built and don&#39;t use&lt;/h2&gt;
&lt;p&gt;Every post like this is a victory lap, and every system like this is half-abandoned. So let me tell you which parts of mine actually survive contact with a normal week, because the honest answer is not the flattering one.&lt;/p&gt;
&lt;p&gt;The parts I use every day are embarrassingly boring. The morning brief. The inbox getting triaged and filed while I sleep. My tasks staying in sync so I stop dropping things. That&#39;s it. That&#39;s the core, and if I&#39;d stopped there I&#39;d have ninety percent of the value for a fraction of the code.&lt;/p&gt;
&lt;p&gt;The parts I&#39;m proudest of are the parts I barely touch. I built a &amp;quot;thinking&amp;quot; layer that runs structured passes over my own notes: challenge an assumption, connect two ideas, surface the patterns I&#39;m circling. It is the most intellectually satisfying thing in the whole system and it is the thing I open least, because it turns out the bottleneck in my thinking was never a lack of prompts to think harder. I built a reconciliation engine that detects duplicate and drifting notes and proposes merges, production-grade, the kind of thing you&#39;d want across a million records. My vault has a few hundred notes. I built it because I &lt;em&gt;could&lt;/em&gt;, and because it mirrored a real problem in the product I sell, and it solves a problem I do not have. And I built a whole graphical cockpit to see all of this at a glance, and then I kept living in the terminal, because the terminal was already where I was.&lt;/p&gt;
&lt;p&gt;Then there are the edges that just quietly don&#39;t work and that I route around instead of fixing. A calendar integration that needs re-consenting more often than it should. A capture path I set up, tested once, and never wired all the way through. None of it is broken enough to force a fix, which is exactly why it never gets one. The gap between &amp;quot;works in a demo&amp;quot; and &amp;quot;works on a Tuesday when I&#39;m busy&amp;quot; is where personal software goes to die, and mine has its share of graves.&lt;/p&gt;
&lt;p&gt;If there&#39;s a lesson in that, and I think there is, it&#39;s this: the usefulness of a feature turned out to be almost inversely proportional to how clever it felt to build. The dull scheduled job that files an email is load-bearing. The elegant engine that reasons about my notes is a trophy. I keep both, but I&#39;ve stopped confusing which is which, and I no longer let the fun of building a thing stand in for evidence that I&#39;ll use it.&lt;/p&gt;
&lt;h2 id=&quot;what-to-take-from-this&quot; tabindex=&quot;-1&quot;&gt;What to take from this&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Build systems that act, not archive. The value is in the doing, not the storing.&lt;/li&gt;
&lt;li&gt;Put the human on the only outbound path. Automate the reversible, gate the irreversible.&lt;/li&gt;
&lt;li&gt;Enforce privacy as a property, not a promise. If the data can leave, eventually it will.&lt;/li&gt;
&lt;li&gt;The boring scheduled job is load-bearing; the clever engine is a trophy. Do not confuse them.&lt;/li&gt;
&lt;li&gt;Local is a deliberate trade, not a default virtue. Make it where the data earns the friction.&lt;/li&gt;
&lt;li&gt;Most of the value is in the least impressive tenth. Build that first, and maybe only that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ve been running this for a while now, and the honest state of it is: the boring core is indispensable and the clever parts are a museum. If you&#39;ve built something like this and made the clever parts &lt;em&gt;stick&lt;/em&gt; past the honeymoon, I would genuinely like to know what survived for you, because mostly they haven&#39;t for me.&lt;/p&gt;
&lt;p&gt;And if the instinct underneath all of this is familiar to you - keep the sensitive corpus where you can verify what happens to it, make every claim traceable back to its source, never let the system take the one action you can&#39;t undo - that is not just how I run my own life. It is the whole idea behind what I build during the day. &lt;a href=&quot;https://www.tidebreak.ai&quot;&gt;That&#39;s here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update, 6 August 2026: the overnight codebase agents I mention above - the ones that &amp;quot;commit all day, but cannot deploy&amp;quot; - now have their guts in the open. I&#39;ve extracted and sanitized the workflow that runs them: an autonomous pipeline that burns a backlog down through isolated git worktrees, reviews its own branches, and never deploys or opens a pull request on its own. It&#39;s on my GitHub, with a walk-through of why each safety rail exists: &lt;a href=&quot;https://github.com/jxsath/workbench/tree/main/overnight-burndown&quot;&gt;the overnight burn-down&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;on-whose-shoulders-this-stands&quot; tabindex=&quot;-1&quot;&gt;On whose shoulders this stands&lt;/h2&gt;
&lt;p&gt;The second-brain idea has a fifty-year lineage, and this post is a departure from it, not a discovery. The sources, if you want them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vannevar Bush, &amp;quot;As We May Think,&amp;quot; &lt;em&gt;The Atlantic&lt;/em&gt;, 1945 - the Memex. https://www.w3.org/History/1945/vbush&lt;/li&gt;
&lt;li&gt;Douglas Engelbart, &amp;quot;Augmenting Human Intellect: A Conceptual Framework,&amp;quot; SRI, 1962. https://www.dougengelbart.org/pubs/augment-3906.html&lt;/li&gt;
&lt;li&gt;Sönke Ahrens, &lt;em&gt;How to Take Smart Notes&lt;/em&gt;, 2017 - the modern systematization of Niklas Luhmann&#39;s Zettelkasten.&lt;/li&gt;
&lt;li&gt;Michael Nielsen, &amp;quot;Augmenting Long-term Memory,&amp;quot; 2018. https://augmentingcognition.com/ltm.html&lt;/li&gt;
&lt;li&gt;Andy Matuschak &amp;amp; Michael Nielsen, &amp;quot;How can we develop transformative tools for thought?&amp;quot;, 2019. https://numinous.productions/ttft/&lt;/li&gt;
&lt;li&gt;Tiago Forte, &amp;quot;Building a Second Brain: The Definitive Introductory Guide.&amp;quot; https://fortelabs.com/blog/basboverview/&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>AI writes code faster than you can review it. Here is what I did about it.</title>
    <link href="https://stillig.net/posts/ai-writes-faster-than-you-can-review/" />
    <updated>2026-07-28T09:00:00Z</updated>
    <id>https://stillig.net/posts/ai-writes-faster-than-you-can-review/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;I&#39;m Johannes. I&#39;m building Tidebreak, an investigations engine where every finding traces back to the source bytes it came from. That obsession with provenance is also why I can only write about my own engineering one way: with the parts I got wrong left in.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the last twelve weeks I made 4,111 commits to a codebase that is now about 455,000 lines: a real production system, 154 database tables, six hundred-odd endpoints, the works. I wrote almost none of it by hand.&lt;/p&gt;
&lt;p&gt;That sentence is either exciting or alarming to you, and the honest answer is that it should be both. Because the thing nobody tells you about coding with AI is that the writing was never the bottleneck, and it was never the danger either. Agents will produce correct-looking code faster than any human can read it. So the moment you let them run, both the ceiling on your speed and the thing most likely to hurt you move downstream, past the keystroke, into a place you can no longer watch by eye.&lt;/p&gt;
&lt;p&gt;I learned this the way you learn most things: by getting it wrong first. What kept the codebase from quietly rotting under that volume was not better prompts. It was a set of guardrails that make the rules the system depends on impossible to break silently, and an autonomous loop that is built so it &lt;em&gt;cannot&lt;/em&gt; do the unsafe thing rather than &lt;em&gt;trusted&lt;/em&gt; not to. This is a post about those, and about the parts that broke anyway, because a post that only shows the parts that worked is not worth your time.&lt;/p&gt;
&lt;h2 id=&quot;the-failure-that-scares-me-is-the-quiet-one&quot; tabindex=&quot;-1&quot;&gt;The failure that scares me is the quiet one&lt;/h2&gt;
&lt;p&gt;Everyone writes about AI hallucinating a bug. That is the boring failure, and your tests catch it. The failure that actually costs you is silent.&lt;/p&gt;
&lt;p&gt;An agent, asked to add a feature, notices there is already a scoring function two directories over and writes a second one that is subtly different. Or it returns a cleaner error message on a permission check and, in doing so, tells an attacker that a record exists. Or it reads a user id where the rule of the system is to read an organization id, and eleven months of careful isolation gets a hairline crack.&lt;/p&gt;
&lt;p&gt;None of these fail a test, because a test encodes what you thought of, not what you forgot. At forty-eight commits a day you will not catch them by reading diffs, because you will not read the diffs. The problem is not correctness in the small. It is review bandwidth and invariant drift, and neither of those is fixed by a smarter model.&lt;/p&gt;
&lt;h2 id=&quot;invariants-as-guardrails-not-comments&quot; tabindex=&quot;-1&quot;&gt;Invariants as guardrails, not comments&lt;/h2&gt;
&lt;p&gt;So the first real discipline is this: write down the rules the system cannot survive breaking, and then make each one break the build if it is violated, rather than living in a comment that hopes an agent reads it.&lt;/p&gt;
&lt;p&gt;A few of mine, kept general:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross-tenant access returns &amp;quot;not found,&amp;quot; never &amp;quot;forbidden.&amp;quot; A forbidden response is more helpful to a user and it silently confirms the record exists. An agent optimizing for a clear error message will reach for forbidden every time. A guard rejects it, so the helpful-but-wrong version never lands.&lt;/li&gt;
&lt;li&gt;One capability has exactly one implementation. The second copy of a scoring or de-duplication function is a build failure, not a code-review note. This is the single most useful rule I have: it turns &amp;quot;please reuse the existing thing&amp;quot; from a hope into a constraint, and agents respond to constraints, not pleas.&lt;/li&gt;
&lt;li&gt;Some numbers may never be collapsed. A risk signal I track on two independent axes has to stay two numbers through every layer, because averaging them hides the case that matters. That is enforced in code, not prose, because prose is exactly what a fast agent skims.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rule that makes the whole thing scale is the meta-rule: every non-negotiable ships with an automated backstop, or it does not ship. A rule an agent can violate without the build going red is not a rule. It is a wish, and wishes do not survive contact with an automated contributor that has read your style but not your scars.&lt;/p&gt;
&lt;p&gt;I will be honest about the cost. This is real work paid up front, and below a certain velocity it is not worth it, because you would just read the diffs. The trade flips the moment the machine is committing faster than you can review. Then the guardrails are not bureaucracy. They are the only reason you can let go of the wheel at all.&lt;/p&gt;
&lt;h2 id=&quot;an-autonomous-loop-that-cannot-hurt-you&quot; tabindex=&quot;-1&quot;&gt;An autonomous loop that cannot hurt you&lt;/h2&gt;
&lt;p&gt;The guardrails let agents commit safely. The next piece let them work while I sleep.&lt;/p&gt;
&lt;p&gt;Every night a loop runs over the backlog: it triages what is worth doing, de-risks each candidate, builds it, reviews the result, and integrates what passes. The interesting thing about it is not that it is careful. It is that it is built so carefulness is not required. It structurally cannot deploy. It cannot push a red main. It cannot open its own pull request or approve its own work. If the final gate comes back red, it hard-resets the entire batch rather than salvage a piece it should not trust.&lt;/p&gt;
&lt;p&gt;That is the whole philosophy in one line: I did not make the loop trustworthy, I made trust unnecessary. An autonomous process you have to trust is one you have to watch, and a process you have to watch is not autonomous. The only version that lets you actually walk away is the one where the worst thing it can do is waste a night&#39;s compute.&lt;/p&gt;
&lt;h2 id=&quot;isolation-and-specialists&quot; tabindex=&quot;-1&quot;&gt;Isolation and specialists&lt;/h2&gt;
&lt;p&gt;Two smaller pieces hold that up. Parallel agents each work in their own isolated copy of the repository, so two of them cannot clobber each other&#39;s changes, and one checkout is reserved for integration only, guarded so no feature work lands there by accident. And the agents are not interchangeable: there is one that builds, one that reviews, one that plans, and the reviewer is pinned to the strongest model available, unconditionally. You can cost-optimize a lot of things in a setup like this. The gate is not one of them.&lt;/p&gt;
&lt;h2 id=&quot;the-gate-is-one-local-script&quot; tabindex=&quot;-1&quot;&gt;The gate is one local script&lt;/h2&gt;
&lt;p&gt;My continuous integration is deliberately dull. One script is the entire gate, and it runs the same way on my machine as anywhere else; the hosted CI mostly just dispatches it. That sounds unambitious until you remember who the primary user of your gate now is. It is not you, running it a few times a day. It is a machine, iterating against it a hundred times a night. Determinism and a single source of truth stop being nice-to-haves and start being the thing that keeps the whole loop from chasing ghosts.&lt;/p&gt;
&lt;h2 id=&quot;what-broke&quot; tabindex=&quot;-1&quot;&gt;What broke&lt;/h2&gt;
&lt;p&gt;If the rest of this reads like it worked, let me correct the impression, because the interesting part is where it did not.&lt;/p&gt;
&lt;p&gt;I did not know what an overnight run cost. One of them burned 550 million tokens, and I learned that number by counting it by hand, afterward, when something felt off. There was no hard budget ceiling on unattended work at the time, nothing that would have stopped a loop that misjudged its own progress from running until morning. The industry has a small graveyard of these stories, some with four and five-figure price tags. I got a cheap lesson, and the real fix, a spend cap that does not depend on the agent&#39;s own judgment, came after the scare, not before it.&lt;/p&gt;
&lt;p&gt;A nightly run crashed once and left a handful of issues marked in-progress, half-built, in a state the next morning&#39;s triage could not see. So the loop kept skipping them, because as far as it could tell someone was already on them. Nobody was. I built the self-heal that resets orphaned work on an abnormal restart because of that morning, not in anticipation of it. Almost every guardrail in this system has that shape: it is a scar, not a prediction.&lt;/p&gt;
&lt;p&gt;Two agents sharing one working directory have tangled each other&#39;s changes more than once. Discipline, it turns out, is not structure. A rule that says each agent gets its own workspace is only as good as the enforcement behind it, and I have watched a second session quietly undo the first&#39;s work while both believed they were alone. That gap is not fully closed. I am telling you because a post that pretends otherwise is not worth reading.&lt;/p&gt;
&lt;p&gt;And when I finally audited my own setup against how the best teams do this, the verdict was uncomfortable and correct: my automation was ahead of almost anyone&#39;s, and my visibility was the weakest part of the whole thing. I had been flying a fast aircraft with half the instruments dark. The token-count-by-hand story is just the most quotable symptom of that.&lt;/p&gt;
&lt;p&gt;I lead with these because the numbers at the top of this post are only worth anything if you believe the person reporting them.&lt;/p&gt;
&lt;h2 id=&quot;where-the-bottleneck-actually-went&quot; tabindex=&quot;-1&quot;&gt;Where the bottleneck actually went&lt;/h2&gt;
&lt;p&gt;Here is the part I have not solved. The question stopped being &amp;quot;can I run more agents&amp;quot; a while ago. It is now &amp;quot;can I review and merge fast enough,&amp;quot; and past a handful of parallel agents, the answer is me. I am the ceiling. More parallelism just grows the queue of things waiting on my attention.&lt;/p&gt;
&lt;p&gt;The honest frontier from here is not a better prompt or a bigger fan-out. It is a real automated quality gate that earns enough trust to merge without me, and the tooling to review in batches instead of one change at a time. That is the actual hard problem in AI-assisted development at volume, and anyone selling you &amp;quot;just add more agents&amp;quot; has not hit the wall yet.&lt;/p&gt;
&lt;h2 id=&quot;what-to-take-from-this&quot; tabindex=&quot;-1&quot;&gt;What to take from this&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Machine-enforce your invariants, or they will drift. A rule the build does not check is a wish.&lt;/li&gt;
&lt;li&gt;Make the dangerous action structurally impossible, not policy-forbidden. Do not build a careful autonomous loop; build one that cannot do the unsafe thing.&lt;/li&gt;
&lt;li&gt;Never cost-optimize the reviewer.&lt;/li&gt;
&lt;li&gt;Put a hard ceiling on anything that runs unattended, independent of the agent&#39;s own judgment.&lt;/li&gt;
&lt;li&gt;Instrument before you scale. Automation without visibility is just a faster way to be surprised.&lt;/li&gt;
&lt;li&gt;Your bottleneck is review, not generation. Spend your best effort there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is finished, and my real bottleneck now is the one I just described. If you have actually solved review-at-machine-speed, I would genuinely like to hear how, because I have not.&lt;/p&gt;
&lt;p&gt;And if you came for the engineering but your real world is investigations, breach data, financial crime, anything where a conclusion is only as good as the evidence beneath it: that is what I am building Tidebreak for, and the same discipline in this post is the reason every claim it makes traces back to a source. &lt;a href=&quot;https://www.tidebreak.ai&quot;&gt;See it here.&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hello, world</title>
    <link href="https://stillig.net/posts/hello-world/" />
    <updated>2026-07-28T00:00:00Z</updated>
    <id>https://stillig.net/posts/hello-world/</id>
    <content type="html">&lt;p&gt;This is the first post. If you are reading it, the pipeline works.&lt;/p&gt;
&lt;h2 id=&quot;what-this-proves&quot; tabindex=&quot;-1&quot;&gt;What this proves&lt;/h2&gt;
&lt;p&gt;The build renders Markdown, highlights code at build time, generates a tag
archive, and puts the result behind a CDN with a content policy strict enough
to block its own stylesheet if anything is misconfigured.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://stillig.net/posts/hello-world/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-it-does-not-prove&quot; tabindex=&quot;-1&quot;&gt;What it does not prove&lt;/h2&gt;
&lt;p&gt;That anything worth reading is here yet.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;It has, once. See &lt;code&gt;scripts/deploy.sh&lt;/code&gt; for the full account. &lt;a href=&quot;https://stillig.net/posts/hello-world/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
</feed>