# What should travel when you share an agent

Author: Chris Chabot
Canonical URL: https://chabot.dev/blog/what-should-travel-when-you-share-an-agent/
Published: 2026-11-03

A blueprint shares an agent's code and the connections it needs, but never its history, credentials or schedules, and the recipient wires in their own grants.

Sharing an agent sounds like sharing a document until you list what a working agent is made of. Some of it you would happily hand over: the code, the prompts, the skills. The rest has accumulated by working for you: a chat history holding whatever you told it, a database of working state, live connections to your GitHub and your Google Drive, schedules that fire on your behalf. Copy the whole directory and a colleague has your conversations and your access. Copy too little and they have something that doesn’t run.

A blueprint draws the line in one place. It exports the agent’s code and a list of what that code needs, and, in the words of its documentation, “never its state, chat history, credentials, or live connections.” The person receiving it wires up their own accounts, and the resulting instance inherits no schedules, no credentials and no data.

The design isn’t mine. It comes from [Cloudflare OS](https://github.com/cloudflare/cloudflare-os), an Apache-2.0 project whose gadgets are private per-user instances and whose gatekeepers put a capability-based security layer, with simulated-outcome approvals, between agents and the outside world. What I did in August 2026 was adopt those ideas and port them into source packages for Ferrite, my own application platform, where an application carries one dependency and these packages own no clock, no entropy and no I/O. Every timestamp arrives as a parameter, and so does the randomness for a share key.

## Code and a list of holes

A blueprint holds two things. The first is its files, on a field whose comment reads “NEVER state or credentials.” The second is its binding requirements, which say that this code needs, for example, a GitHub connection, without saying whose. To run it, the recipient creates an instance and wires each binding to a grant id they supply themselves. Instantiation refuses a binding the blueprint never declared and reports any declared binding that is missing. Beyond that, the comment on the function is the whole policy: “Nothing else transfers — no schedules, no data, no creds.”

A benchmark application demonstrates the journey with fixture identities, the traditional Alice and Bob. Alice reads issues through a scoped connection, reserves and settles budget, submits a pull request as a simulated, approvable action, approves it by digest, reverts it, and publishes a blueprint with a `github` binding. Bob instantiates that blueprint with `bob-grant-1`. He receives the code and a hole shaped like a GitHub connection, which his own grant fills, and none of Alice’s budget, approvals, pull requests or connection come with it. A test asserts the journey against a fixture backend, so no real GitHub was harmed or involved.

## Making the line hold

A rule like this is only as good as its behavior when someone is in a hurry, so every archive is screened at publish and again at import, on the grounds that “an archive is untrusted input,” and a match rejects it outright, which is what the code means by “structural, not advisory.” Paths are checked for `.sqlite`, `.db`, `chat-history`, `secret`, `credential`, `.pem` and `token`. Contents are checked for PEM private keys under any label, for vendor prefixes like `ghp_`, `github_pat_` and `xoxb-` at a word boundary, for `sk-`, `AKIA` and `AIza` followed by the payload length each format should have, and for the `SQLite format 3` header. The error says why: “looks like state or a credential … blueprints carry code only.” The matching is hand-written, since the package takes no regex dependency, and archives are capped at 4 MiB, the limit of the upstream `.gadget` format.

The tests are the best documentation of how fiddly this is. A naive search for `sk-` trips over `risk-analysis`, `task-list` and `sk-learn`, and all three have to be accepted. `Weighp_value` happens to contain `ghp_` and has to pass, while `x="ghp_…"` and `pre_ghp_…` have to be caught. A PEM key under an innocent filename has to be caught too, which is why paths and contents are screened separately.

All of that is a heuristic. A credential in a format the screen doesn’t recognize will pass, and “structural” describes where the check sits, not how much it catches. What makes that tolerable is that the credential shouldn’t be in the agent’s files in the first place. In the gatekeeper design, a service sits between the platform and one external service and owns its credentials, its resource scoping, its observation log and its approval queue, and “the credential never enters a sandbox.” The Google connector, for instance, runs the OAuth flow through a token vault with nonce-protected callbacks and holds the token itself, and persistence only ever sees SHA-256 digests. The screen is the backstop for the day someone pastes a key into a config file anyway.

## What a binding gets wired to

The grant is the real unit of authority. It is scoped, it expires and it is checked against a revocation epoch, with revocation checked before expiry and admission failing closed. The module’s documentation says this is “deliberately NOT a new token scheme,” because the platform already had a capability model and the port had no business adding a parallel one.

The connector contracts show what that authority looks like as an interface. A GitHub connection is scoped to specific repositories and a Google connection to specific documents, spreadsheets and Drive folders, introduced by URL and never to a whole account. Reads pass through an observation check before any data is returned, and writes go into an approval queue with a locally simulated outcome, so the agent keeps working while a human decides later. Each action knows how to undo itself: a pull request reverts by closing, a comment by being deleted, a document append by restoring the prior revision. Schedules get similar treatment, since registering one doesn’t activate it and it is admitted again at delivery, and persistent hooks stay disabled until a human enables them. A recipient’s instance therefore starts with nothing live that its new owner didn’t turn on.

## Sharing the blueprint itself

Three further patterns deal with who can reach a blueprint, not what is inside it. Share keys are generated from 128 bits of caller-supplied entropy and stored only as SHA-256 digests under a pepper, so the server can’t reconstruct a link, and a test asserts that only digests are stored.

Revocation works by reachability. A permission graph records how each party came to have access, access is whatever a breadth-first walk from the owner can still reach, a role is capped by the role of whoever granted it, and the strongest of several paths wins. Revoking an edge therefore removes access at the next open for everyone who could only be reached through it, with no cascade to run, and a preview clones the graph and diffs the reachable set, so you can see the blast radius before you commit.

Authorization is shaped by type. Opening a shared surface returns one of two types, a build session or a use session, and the use session fails closed on every mutation. Using two types, instead of one type with a role check, means a new method on the shared trait forces both to answer, so the forgotten permission check becomes a compile error.

## Knowledge and history

Two neighboring problems get the same treatment: what a shared agent may know, and who may see what it did. The context library handles the first by keeping organizational knowledge outside the deployed agent definition. Its premise is that metric definitions and review practices change faster than agent deployments, so they become a runtime service of public and private collections, with skills as `SKILL.md` files whose frontmatter is validated. The library is “strictly READ-ONLY for agents; writes are a human/publish path,” and its catalogs are capped, truncated and treated as “UNTRUSTED display data” that an agent must not execute or obey.

Observer access handles the second. Reads are recorded when they happen and verified again when something is shared, so an observer sees a history “with their OWN account” or not at all. The tests state the rule from both sides: a public history verifies for any observer, because “sharing widens nothing,” and a private one refuses Bob. Showing someone an agent’s work doesn’t launder access to what the agent read.
