Your AI Agent Shouldn’t Be Allowed to Write Whatever It Wants

Building a Write-Side Custody gate in Go

AI memory systems spend most of their design budget on retrieval. Which vector database? How should we chunk? Which embedding model? What should top_k be?

Those are useful questions, but they all arrive after something more consequential has already happened: the system decided that some piece of information deserved to become memory.

Consider an agent researching vendors for regulated workloads. It finds this statement:

Vendor X is approved for regulated workloads.

The source is Vendor X’s own marketing site.

The statement might be true. It might even be current. But the source does not have the authority to establish organizational security policy. If our agent writes it directly into durable memory, better retrieval will not save us. We have only made questionable evidence easier to find.

The problem is not storage. It is admission.

I’ve been calling the architectural boundary responsible for that decision Write-Side Custody. Let’s build a small one in Go.

What Write-Side Custody Does

A storage API answers a mechanical question:

Can I persist this object?

Write-Side Custody asks a different set:

  • Who is trying to write this?
  • Where did the information come from?
  • What authority is being claimed?
  • Is that source allowed to establish that authority?
  • Does policy permit this class of information to become durable?
  • What evidence should survive the decision?

Only after those are answered should storage become involved.

Flowchart showing a proposed write travelling from an agent or application into a Write-Side Custody gate. The gate routes accepted writes to durable memory and rejected writes to discard, while a dotted line records the decision in a Reasoning Ledger.

Note that the Reasoning Ledger does not make the decision. Custody enforces. The ledger witnesses. That separation matters a great deal once these systems have to be examined later.

Start With the Proposed Write

Go gives us a useful property for this experiment: we can make the things crossing our boundary explicit.

type ProposedWrite struct {
    Content          string
    Source           string
    ProducedBy       string
    ClaimedAuthority string
}

Our research agent might produce:

write := ProposedWrite{
    Content:          "Vendor X is approved for regulated workloads.",
    Source:           "https://vendorx.example.com/why-vendorx",
    ProducedBy:       "research-agent-run-4471",
    ClaimedAuthority: "security-policy",
}

Nothing in this structure says the statement is false, and that’s intentional. Write-Side Custody is not a universal truth detector. It determines whether a proposed write satisfies the rules governing this particular memory system.

For this system, a vendor marketing page cannot establish internal security policy. So we need policy.

Make Authority Explicit

First, two types. Authorities and source classes are different kinds of thing, and there is no situation in which we want to accidentally use one where the other belongs:

type Authority string
type SourceType string

Now we can define which source classes may establish which authorities:

type Policy struct {
    AuthoritySources map[Authority][]SourceType
}

var policy = Policy{
    AuthoritySources: map[Authority][]SourceType{
        "security-policy": {
            "internal-security-policy",
            "security-authority",
        },
        "user-preference": {
            "user",
        },
        "application-state": {
            "application",
            "runtime",
        },
    },
}

In production this comes from a policy service or configuration layer rather than a Go literal. The important part is that the relationship exists independently of whatever the agent claims. The agent does not get to decide that a marketing page constitutes security authority simply because it found one saying something useful.

Give the Gate a Verdict

type Verdict string

const (
    Allow Verdict = "ALLOW"
    Deny  Verdict = "DENY"
)

type CustodyDecision struct {
    Verdict   Verdict
    Reason    string
    Timestamp time.Time
}

Now the gate itself:

func EvaluateWrite(
    write ProposedWrite,
    sourceType SourceType,
    policy Policy,
) CustodyDecision {
    allowedSources, ok :=
        policy.AuthoritySources[Authority(write.ClaimedAuthority)]

    if !ok {
        return CustodyDecision{
            Verdict:   Deny,
            Reason:    "unknown claimed authority",
            Timestamp: time.Now().UTC(),
        }
    }

    for _, allowed := range allowedSources {
        if sourceType == allowed {
            return CustodyDecision{
                Verdict:   Allow,
                Reason:    "source may establish claimed authority",
                Timestamp: time.Now().UTC(),
            }
        }
    }

    return CustodyDecision{
        Verdict:   Deny,
        Reason:    "source cannot establish claimed authority",
        Timestamp: time.Now().UTC(),
    }
}

Two decisions in there are worth surfacing.

The first is that conversion on the map lookup. ProposedWrite holds plain strings because that’s what arrives over the wire, deserialized from JSON we did not write. Authority(write.ClaimedAuthority) is the moment an untrusted string becomes a term in our governance vocabulary, and it happens inside the gate rather than at the edge of the process. That’s the right place for it. Custody is precisely the layer where foreign input earns domain meaning.

The second is that sourceType is a separate parameter. It is not a field on ProposedWrite.

That is deliberate. Source classification is a judgment about the write, not a property the writer gets to assert about itself. If sourceType lived on the struct, our agent could label its own marketing page internal-security-policy and the gate would cheerfully agree. The classifier belongs to the custody layer, or to a runtime component that can independently observe where the content came from.

Small signature choice. Most of the security property.

Our vendor claim now reaches the boundary:

decision := EvaluateWrite(write, "vendor-marketing", policy)

fmt.Println(decision.Verdict)
fmt.Println(decision.Reason)

And receives:

DENY
source cannot establish claimed authority

The statement never becomes durable memory. We did not store questionable evidence and hope retrieval would eventually sort things out. We governed the write while the evidence and its provenance were still in hand.

The full gate, the policy, and a table-driven test suite covering the cases above are in memory-stack-patterns. Standard library only, so go test ./... and go run ./cmd/demo work on a clean checkout with nothing to install.

Don’t Throw Away the Rejection

Rejecting a write does not make the decision useless.

Imagine someone asks six months later:

Why doesn’t the system remember that Vendor X was approved?

“I don’t know” is not a satisfying answer, and in a regulated environment it isn’t an acceptable one either.

The custody decision is observable system behavior, which makes it a candidate for a Reasoning Ledger record:

type LedgerEntry struct {
    ID               string
    Timestamp        time.Time
    Actor            string
    Action           string
    Verdict          Verdict
    Reason           string
    Source           string
    ClaimedAuthority string
}

Our gate emits:

entry := LedgerEntry{
    ID:               newID(),
    Timestamp:        decision.Timestamp,
    Actor:            write.ProducedBy,
    Action:           "durable-memory-write",
    Verdict:          decision.Verdict,
    Reason:           decision.Reason,
    Source:           write.Source,
    ClaimedAuthority: write.ClaimedAuthority,
}

(newID is a few lines over crypto/rand, which keeps the whole example dependency-free.)

This entry is deliberately simplified. A ledger you would actually rely on needs a canonical serialization, a hash chain linking each entry to its predecessor, and some defense against tail truncation, because an append-only log that anyone can quietly shorten is not append-only.

Even the timestamp is less innocent than it looks. time.Now() gives you whatever precision the host clock offers, and JSON drops trailing zeros, so two entries can serialize at different widths. Hash a chain over a non-deterministic encoding and you have hashed nothing. The repo linked above truncates to a fixed precision and formats with a fixed-width layout for exactly that reason.

The Python implementation in the Sovereign Systems SDK does all three, which is part of why the Go exercise interests me. The hard parts are already solved somewhere. The open question is what happens to the boundary when it moves.

What matters here is the shape of what survives. The rejected statement still doesn’t enter memory. What persists is evidence that a write was proposed, evaluated, and rejected under a named rule. That is a different kind of information than the claim itself, and it’s the kind that answers questions later.

The Agent Doesn’t Get to Grade Its Own Homework

There’s a further boundary hiding in the payload. Suppose our agent sends:

{
  "content": "Vendor X is approved for regulated workloads.",
  "source": "https://vendorx.example.com/why-vendorx",
  "claimed_authority": "security-policy",
  "retrieval_method": "fresh",
  "policy_verified": true
}

Should we believe the last two fields?

There is an epistemic difference between:

The agent says it performed a fresh retrieval.

and:

The runtime that performed the HTTP request witnessed a fresh retrieval.

The same distinction applies to tool execution, timestamps, approval events, and policy versions. A stronger custody boundary therefore doesn’t only ask whether a record may be written. It asks:

Is this writer authorized to assert this particular kind of claim?

The agent legitimately owns claims about itself: its decision, the alternatives it considered, its confidence, the unknowns it identified. The runtime should mint the facts it can independently witness. Custody should not promote the former into the latter merely because both arrived in valid JSON.

This is the same principle as the sourceType parameter, applied one level up.

Why Do This at Write Time?

You could defer all of this to retrieval. Store everything, attach metadata, and let the reader decide what governs.

But then every questionable write becomes something every future reader has to reason around. It consumes storage. It becomes eligible for retrieval. It competes for context. It can be summarized, embedded, and propagated into records that no longer carry its provenance. And once provenance is gone, a future system may not have enough information to work out why the record was questionable in the first place.

A bad write today becomes bad context tomorrow.

Write-Side Custody puts the decision at the moment the system has the best possible view of what it is admitting.

Why Go?

None of this architecture requires Go, which is partly why I wanted to build it in Go.

A custody gate is a boundary service, and Go fits that role: explicit data structures, unremarkable HTTP services, small deployable binaries, and a type system strong enough to make the important distinctions visible without taking over the implementation.

The Authority and SourceType declarations we needed earlier are the clearest example. They cost one line each, and in exchange the compiler now refuses to let a source class be used where an authority belongs. That distinction would otherwise have lived in a variable name and a hope.

The same move applies elsewhere:

type Verdict string
type ActorType string

At which point the function signatures start expressing the vocabulary of the governance system rather than just its plumbing. EvaluateWrite doesn’t take three strings. It takes a proposed write, a source classification, and a policy, and no caller can shuffle them by accident.

Go didn’t create the architecture. It made the contracts hard to leave implicit.

Memory Begins Before Storage

Vector databases are very good at answering questions about similarity. They cannot tell us whether something deserved to become memory.

That’s an architecture decision, and by the time retrieval surfaces the problem, the questionable record may already have shaped dozens of others.

Give the proposed write provenance. Give the boundary policy. Give the decision evidence. Then let storage do what storage is good at.

Store what survived.


One thing I keep turning over: this boundary shouldn’t depend on Go. If Write-Side Custody only makes sense inside one language, it isn’t much of an architectural boundary. I’m curious what it would look like elsewhere. Would Rust’s type system make an invalid custody decision impossible to construct rather than merely inconvenient? Would Pydantic and FastAPI make the policy check feel so natural you’d stop noticing you were doing governance at all? If you’ve built something like this in your stack, I’d like to hear how the boundary changed shape.

Disclosure: I maintain the Sovereign Systems specification and SDK, which is where the vocabulary in this post comes from. The Go code here is a reference implementation written to test whether the idea travels, not a product.

Facebooktwitterredditlinkedinmail

Write-Side Custody: Trust Begins Before Memory

Part 5 of the Building the AI Memory Stack series

The previous articles introduced the Reasoning Ledger and then worked through what a single ledger record should contain. Together they answered a hard question: why did this decision happen, and how do we design a record that can be trusted to say so honestly.

That raises the question underneath all of it. A record is only as good as what was allowed to become one in the first place.

Imagine reviewing an AI-assisted deployment six months later, the same approval the Reasoning Ledger recorded earlier in this series. The ledger says it was approved. The evidence looks complete, the timestamps make sense, everything appears correct. Then you notice that one of the “authoritative” policy records the decision relied on was written by a tool run that no one had authorized, from a source that should never have been treated as governing.

Nothing was tampered with after the fact. The record is faithful. The problem is earlier than that: a write that should never have been accepted became institutional memory, and every decision downstream inherited it.

At that point the problem is no longer memory. It is admission.

Memory Alone Cannot Create Trust

Throughout this series we have gradually separated the responsibilities of an AI system. The Context Window enables execution. Active Working Memory assembles context. Durable Memory preserves knowledge. The Reasoning Ledger explains and records decisions.

None of those layers decides whether a given piece of information deserved to enter the system at all. Knowing what happened is valuable. Ensuring that only trustworthy information became part of what the system knows is what makes the rest of it worth trusting.

Diagram of the AI memory stack showing where Write-Side Custody sits. A proposed write enters Write-Side Custody. Accepted writes flow down into Durable Memory, then Active Working Memory, then the Context Window, then Model Inference. A separate dashed branch runs from Write-Side Custody to the Reasoning Ledger, labeled "decision witnessed," showing that the ledger observes the custody decision rather than sitting in the write path.

The Landfill Problem

Organizations rarely fail because they cannot remember enough. They struggle because they cannot distinguish trusted institutional knowledge from accumulated noise.

This is the failure mode that should motivate everything else in this article. A durable memory that accepts every write eventually becomes a landfill: technically complete, searchable, and useless, because the trustworthy records and the junk are indistinguishable once they are all sitting in the same store with the same retrieval priority. Every low-quality write does not just take up space. It becomes a candidate for future retrieval, future reasoning, and future decisions. A bad write today is bad context tomorrow.

The instinct is to fix this on the read side, with better ranking and filtering. That helps at the margin and loses the war, because by the time a bad record is competing for retrieval, it already looks like everything else. The cheaper and more durable fix is not to accept it in the first place. That decision has to happen at the boundary, before the write becomes memory.

Trust Begins Before Storage

A common architectural assumption is that integrity can be added later. Encrypt the database, protect the backups, restrict access. Those are all worthwhile, and they are also too late. By the time information reaches storage, the most important decision has already been made: whether this write should have been accepted at all.

The Sovereign Systems Specification calls the responsibility for that decision Write-Side Custody. It sits before information becomes durable memory, and it asks a different question than a storage layer does. A storage layer asks whether this can be persisted. Custody asks whether it is legitimate.

Diagram showing how Write-Side Custody evaluates incoming information. Observations, tool results, and policies flow into a Write-Side Custody decision point. Verified writes proceed to Durable Memory, rejected writes are discarded, and in all cases the custody decision is recorded to the Reasoning Ledger.

Instead of “can this be stored,” custody asks whether the information is authoritative, whether the source is trusted, whether the supporting evidence was preserved, whether the write violates policy, and whether this should become institutional knowledge at all. The answers determine whether the write is accepted, rejected, or accepted with qualification.

What a Custody Decision Looks Like

Principles are easy to nod along to and hard to picture, so here is a concrete one.

An agent finishes a tool run and tries to write a new record into durable memory:

proposed_write:
  content: "Vendor X is approved for regulated workloads."
  claimed_authority: security-policy
  source: web-fetch:vendor-x-marketing-page
  produced_by: research-agent-run-4471

At the boundary, custody evaluates it rather than storing it:

custody_decision:
  accepted: false
  reason: authority-mismatch
  detail: >
    Record claims security-policy authority, but the source is an
    external marketing page, not the security team's policy service.
    A regulated-workload approval cannot be granted by the artifact
    it would benefit.
  action: discard
  recorded_to_ledger: true

The write never reaches durable memory. The content might even be true, and it still fails, because custody is not judging whether the claim is plausible. It is judging whether this source is allowed to establish that claim as governing knowledge. An approval that arrives from the thing being approved does not get to become institutional memory, no matter how confident the text sounds.

Notice the last field. The rejection did not vanish. It became an event the Reasoning Ledger preserves, which is the point of the next section.

Custody Enforces. The Ledger Witnesses.

There is an apparent contradiction here worth resolving directly, because a reader following the series will feel it.

The Reasoning Ledger, by design, does not enforce. It witnesses. Its records stay examinable precisely because the ledger cannot block anything. Write-Side Custody, by contrast, exists to enforce. It rejects writes. So which is it, do these layers block things or not?

Both, and that is the whole architecture. Custody is the enforcement boundary. The ledger is the witness that records what the boundary decided. When custody rejects the vendor-approval write, custody makes the decision and stops the write, and the ledger records that a write was proposed, evaluated, and rejected, along with the reason. Neither layer does the other’s job. The enforcer never becomes the historian, and the historian never gets a veto. That separation is exactly why each one can be trusted at what it does: an enforcer that also wrote the only account of its own decisions would be unauditable, and a witness that could block actions would stop being a neutral record.

This is the same “separate custody, one interface” principle from earlier in the series, seen from the write side. Different responsibilities, deliberately kept in different layers, cooperating at a boundary.

Every Write Is a Governance Event

Traditional applications treat writes as database operations. Agentic systems should treat them as governance events, because in an agentic system a write is not just data at rest. It is a future participant in reasoning.

Every accepted write influences future retrieval, shapes future reasoning, and becomes a candidate for future decisions. That is a much larger claim than “this row was inserted.” It means the moment of admission is the cheapest and most powerful place to protect everything downstream. A policy check at the boundary is worth more than a great deal of read-side cleverness later, because it prevents the bad record from ever competing for attention in the first place.

Custody is what keeps durable memory intentional rather than merely persistent. It is the mechanism that slows the entropy from trusted knowledge toward landfill, one admission decision at a time.

Looking Ahead

Write-Side Custody decides what is allowed to become memory. It does not, on its own, prove that an accepted record is still byte-for-byte what was written.

Suppose someone asks a harder question six months later: not “should this have been accepted,” which custody answers, but “can you prove this exact record has not been altered since.” That is a different guarantee, and it needs more than an admission policy. It needs evidence that survives independent of the store that holds it.

That is where Forensic Receipts enter the architecture, and where the next article takes us.

Facebooktwitterredditlinkedinmail