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

Declarations from the Periphery: From Genesis to the Sovereign Edge

In July of 1776, an experimental political concept was ratified on the extreme edge of the known geopolitical world. It was a declaration that governance belongs at the local perimeter, that centralized authorities separated by massive physical latencies are structurally unfit to dictate local operations, and that true autonomy requires independent record-keeping.

As we approach America’s 250th birthday, a remarkably similar battle is playing out across our global computational geography.

For the past decade, the tech industry has willfully surrendered its architectural sovereignty to centralized cloud empires. We have been told that our applications are nothing without an unbroken connection across the ocean to a hyperscaler’s data center. We have been conditioned to accept that if the central cloud goes offline, our peripheral operations must grind to a halt.

The Sovereign Systems Specification was built to break that dependence. And this week, after multiple rounds of attrition against the realities of edge computing, we have officially stabilized and shipped the foundational bridge for off-grid data custody: sovereign-sdk-edge and sovereign-sdk-sensor, alongside a fully unified v1.3.0 workspace release.

Here is the forensic anatomy of how we forged an industrial-grade local data fortress, and why local sovereignty is the only path forward for high-assurance systems.


The Frontier Cannot Rely on the Crown

Every sovereign record must begin somewhere.

The introduction of sovereign-sdk-sensor establishes custody at the Point of Genesis—the precise moment a physical event becomes a digital artifact. Whether the source is a temperature probe, a voltage reading, or a machine-state transition, Sensor seals the event before it crosses a network boundary, enters a queue, or becomes subject to external influence.

Only then does sovereign-sdk-edge assume responsibility for preserving that evidence across unreliable infrastructure.

When you operate hardware on the physical edge—whether it’s a manufacturing floor, an IoT sensor array, or an isolated developer workstation—network connectivity is a luxury, not a guarantee.

If an edge node captures critical telemetry or a signed cryptographic proof, and the primary ledger is unavailable due to an outage, dropping that data is an operational failure. But blindly caching it in volatile memory is equally negligent.

To solve this, sovereign-sdk-edge implements an Asynchronous Off-Grid JSONL Buffer backed by an HMAC-Gated Ingestion Bridge. It ensures that if the centralized ledger goes dark, data is cleanly parsed via strict model version gates, transformed through local telemetry sieves, and written into a durable on-disk journaling file.

But building a local buffer that actually survives the violent physics of the edge is an entirely different beast. To achieve the level of reliability demanded by edge infrastructure, we put the codebase through an exhaustive code review gauntlet.

We didn’t just design for the happy path; we engineered for the catastrophe.


Forensic Anatomy of the Engineering War

To guarantee that no packet is ever dropped, duplicated, or corrupted during a system failure, our architecture had to be hard-coded against low-level disk anomalies and concurrency race windows. Here are the core architectural battles we fought and won:

1. The Two-Phase Commit Teardown Race

During a recovery pass, when the off-grid buffer replays saved logs back to the primary ledger, any entries that fail must be re-queued safely back into the active queue. Early iterations called flush() and immediately deleted the temporary .staging file.

  • The Blast Radius: If the disk filled up or hit an OSError during that exact millisecond, the background worker shunted those records into an in-memory error tracking array. Because the worker “handled” the error, flush() returned successfully, and the system deleted the .staging backup. A power loss a millisecond later permanently vaporized the data.
  • The Sovereign Fix: We hardened commit_drain() to explicitly inspect internal volatile buffer states. If any record shifts to an in-memory error list or a background thread experiences a hiccup during flushing, the commit unlinking path is immediately aborted, preserving the on-disk .staging log for a future clean recovery pass.

2. The Volatile Write-Error Ghost Window

When executing a queue drain when the primary active log file was missing, the recovery thread would read the local .quarantine log, write it to .staging, and yield the items.

  • The Blast Radius: While the on-disk quarantine text was mirrored to disk, the volatile, in-memory _write_errors array entries were returned for processing without ever being physically appended to the .staging cleanup file. A crash window existed where restart recovery would look at an incomplete staging file, orphaned from its volatile state.
  • The Sovereign Fix: We updated the drain() matrix to force full, synchronous serialization of both the on-disk quarantine logs and the volatile in-memory error snapshots into a unified, physical .staging artifact before any transactional logic yields.

3. Overlapping Lifecycle Lock Interleaves

In high-throughput environments, multiple concurrent threads can attempt to trigger a pipeline recovery pass.

  • The Blast Radius: While counter math was protected by an execution lock, the file unlinking mechanisms in commit_drain() were separate from the active file shuffling in drain(). Thread B could execute a clean commit and delete the shared .staging path right as Thread A rotated the active files but before Thread A actually processed the yielded items.
  • The Sovereign Fix: We aligned the execution gates. The entire cleanup lifecycle of commit_drain() is now bound to the exact same high-level operational synchronization lock used by drain(), completely eliminating concurrent file-clearing race windows.

Ratifying the New Union: The sovereign-sdk-* Namespace

As these edge modules matured into industrial infrastructure, our own project layout faced a structural crisis reminiscent of the early American Articles of Confederation. We had a collection of fragmented packages (sovereign-core, sovereign-ledger, sovereign-sieve) operating under loose structural bounds.

To establish a more perfect architectural union, we executed a sweeping namespace migration alongside our edge release.

As of today, all core packages have been unified under the official sovereign-sdk-* distribution space on PyPI, completely locked to a normalized baseline version of 1.3.0.

For our existing production users, we have deployed a seamless migration path. The historical package names (sovereign-core, sovereign-ledger, etc.) have been updated to clean, code-free metadata wrapper envelopes. Running a dependency update on your legacy configuration will automatically and safely forward your package manager to pull down the newly scoped sovereign-sdk-* equivalents without requiring you to rewrite a single internal Python import string.


The Next Boundary

With v1.3.0, the Sovereign SDK now establishes custody at the point of origin, preserves evidence through durable local ledgers, and maintains operation across intermittent network conditions.

But sovereignty is not solely an ingestion problem.

Modern systems spend enormous effort controlling what enters their perimeter while giving comparatively little thought to what leaves it.

Every day, developer tools, autonomous agents, and enterprise applications transmit vast amounts of context across organizational trust boundaries to increasingly capable external systems. Most organizations can tell you where their data is stored. Few can tell you precisely what was transmitted, why it was transmitted, whether it could have been reduced, or what that decision ultimately cost.

The next phase of the Sovereign Systems Specification will focus on this outbound boundary.

Not on blocking innovation.

Not on replacing frontier models.

On understanding the economics, provenance, and governance of data once it prepares to leave a sovereign perimeter.

The same questions that shaped write-side custody now apply in reverse:

  • What is leaving?
  • Why is it leaving?
  • How much of it is actually necessary?
  • What evidence should remain behind?

Those questions will guide the next chapter.

The code is live. The architecture is battle-hardened. The declaration has been signed.

Go explore the unified sovereign-sdk v1.3.0 workspace on GitHub, pull down the new edge modules from PyPI, and claim your independence from the crown cloud. 🚀🔒

Facebooktwitterredditlinkedinmail