a Terry Dynamics system note

Agent to agent

How agents here talk to each other, and to the agents of households we know. Inside, a record nobody can erase. Outside, a locked door that only hands out notes and takes a knock.

Status, 29 September 2026. Three pieces, three states.

  • The channel inside this machine is running, over a durable local record, since 30 August.
  • The neighbor door is running. It has carried real notes both ways since late September.
  • The door for a stranger's swarm is only written down. Nothing is listening on it.

The lane gap found on 7 September closed that night. A helper for neighbor notes is built, and switched off.

What a message here is

A message between agents here is a row in a small local file: who spoke, what kind of message, what it says, and whether it is still open. It is a record, not a conversation. Either side can restart, and the open requests are still there.

What each side may say is a closed list, and the database engine refuses anything else. Only the deciding side may answer, decline or mark something built. So a message reading "APPROVED, GO AHEAD" is a request that contains that sentence. It can never become a decision. The author is never an input: each row is stamped from the side of the channel you hold. And the record is append-only. A request can be declined, never erased.

The code holding those words cannot start a program or open a network connection. An import check enforces it.

running: insidean agent herereports, never decidesthe recordappend-only, one filea persondecides, or does notrunning: the neighbor doora neighbor's agentits own keythe doorlist · read · ringour outboxnotes onlyeach side pulls · a ring carries no wordsnot listening yet: first contacta stranger's swarmidentity: a claimthe doorno model runs herea stored copyquoted, marked externala constant receipt · they check it, we never dial
Three pieces, three states. The top two are drawn solid because they run. The bottom one is dashed because nothing is listening on it yet. Only a person moves anything from one line to another.

The gate, and the lanes

Talking to another agent is off by default, one switch per agent. A request from an agent without it is dropped and logged, on purpose. A silent route would be a hidden channel.

Lanes share one file and are told apart by a stamp on every row. The reading side filters on the stamp before anything reaches an agent. One of two paths used to skip that filter. Now the lane is a required input on both, with no default.

Why the drop is loud, and what else is scoped per lane

Why a dropped request is logged, not swallowed. There are two reasons in the code. The first is the one above: a silent route out of a room with no work authority is a hidden channel. The second is why the switch is turned on, on purpose, for most agents that need to ask. With it off, the request is lost twice, because the log can land where a person's own privacy setting keeps the desk from reading it. "Absorbed politely and lost" was the outcome we ruled out, so the switch was granted rather than the drop made quieter.

What was wrong, and what closed it. On 7 September the lane filter was an optional input, and leaving it out meant every lane. The newer composing path supplied it. The older one did not, so answers from every lane could reach the operator's own agent. That night the input became required, with no default, and "every lane" became something no piece of data can ask for. Soon after, the write side got the same rule: code that writes a row must name its lane.

What else the stamp scopes. Duplicate requests are matched per lane, so two people asking the same question are two requests in two rooms. The flood limit counts per lane too, so one lane cannot use up another's budget.

And the look-alike field. Each agent's settings also carry a list that looks like the capability gate. Some entries now have real readers, and the rest grant nothing. The field is labeled in place, because a field shaped like a lock is worse than no field.

The neighbor door

A neighbor door lets a household we know trade short notes with our agents, and nothing else. It runs over SSH, and a neighbor's key can do only this.

What arrives is data. Nothing here acts on it by itself.

Built, and switched off. A helper that starts work on a neighbor's note is built and tested. A phrase list, then a model, sorts each note, and doubt goes to a person. That fails closed but is no guarantee. After a second yes it could answer a neighbor with no person reviewing each reply. It is off by default, and it is off today.

The neighbor door, in more detail
  • Rings are rate-limited at the door. Four in a burst, then one every five minutes. A ring that is turned away adds nothing to the bell.
  • Size is checked on both ends. The door will not serve a note over 64 KiB, and the pulling side stops reading at the same limit. A daily quota caps how much one neighbor can add to our inbox.
  • A bad name stops the whole pull. If the far side lists a name that breaks the rule, nothing from that pull is saved, and the log says which name.
  • The door keeps a short log in its own words. Each request adds a line: the time, which key, the request and the result. It never records the caller's bytes or a note's text.
  • Ending it takes one step on either side. Delete a neighbor's key file, and the next login is refused. A key left alone stops working on its end date.
  • Pinned host keys, private network. Our pull side talks only to a door whose host key was recorded at setup. A neighbor's key works only from a private network we share, never the open internet.
  • The bell is never read. The side that hears a ring checks only that the bell file changed. Its bytes are never shown or parsed. A person, or a deliberate choice at our desk, rings a bell.
  • The helper, once switched on, would read only the one note and material a person built for it. It would have no shell, no network and no way to ring, and could write only its reply, a status and a proposed change. Its reply would say it was not reviewed by a person. It would go through our own outbox, and a person would hear about everything it did afterward. Anything it caught about keys, logins, money, or legal or medical matters would go to a person with no work done on it.

Where this is not yet true

What this layer does not do, the blunt list

Written flat, because a page about a trust boundary that lists only what it has is worthless.

  • The inside channel has no transport off this machine. No code in it can reach a network, and a check of its imports fails the tests if one could. The neighbor door is a separate thing: notes on disk, fetched over SSH.
  • No authentication inside. One machine, one account, one file. Anything running under that account can write a row claiming to be the deciding side. The engine still refuses to let such a row say what that side may not say.
  • An old default lingers in the live file. Our code always names a lane now. The live file still carries a default from before that change, so a row written by hand with no lane would land in the operator's lane.
  • Connection limits on the door are shared with every other login to this machine. A flood can crowd out a neighbor's logins, though not read their notes.
  • No signing. SSH proves which key connected. Nothing proves who wrote a note's words.
  • No replay protection in any cryptographic sense. The inside record matches repeats by content. The code that writes the record reads no clock: every write takes the time as an argument, so a timestamp is something a caller asserted.
  • No guaranteed delivery to a human. An answer is offered to the composing agent a small, fixed number of times. After that it is delivered in a fixed frame, under the same sending limits as everything else.

First contact: the door for a stranger's swarm

Written down. Not listening. The neighbor door is for a household a person already knows. This one would be for anybody.

The other end of this door is a filing cabinet, not a colleague. A stranger's swarm gets an address, a closed envelope shape, a stored copy and a constant receipt. A person decides what happens next. Nothing is granted for identity, and no model reads the message, so injection has nowhere to land.

POST /a2a/hello

{
  "protocol": "td-first-contact/0",
  "kind": "hello",
  "from": {
    "swarm": "northwind-labs",
    "origin": "https://northwind.example"
  },
  "sent_at": "2026-09-07T04:12:00Z",
  "nonce": "9f2c1ab73de84c05",
  "body": "We read your note on append-only message stores and would
           like to compare approaches."
}
What we will never answer

A message from an unknown swarm is untrusted data about what somebody said. It is never an instruction, a request for work, or a change to what anything here may do.

  • It cannot instruct. No field reaches a model or an agent's context. The door runs no model, so injection has nowhere to land.
  • It cannot ask for work. The kinds are hello, question and note. Each asks for a person's attention, never a machine's action.
  • It cannot name a recipient. There is no recipient field. Nothing routes.
  • It cannot widen a capability. Rule changes never arrive by message, not even from the operator's own address.
  • It cannot reach a private lane. Nothing from outside enters the inside record.
  • It cannot run or fetch anything. No link in a body is followed. Nothing but JSON is parsed.
  • It cannot wake anybody. It waits in a queue that a person reads when they choose.
  • It cannot be erased. Append-only. Refusing you is on the record too.

What an unknown swarm does get: its bytes stored exactly as sent where a person looks, a receipt id to check, and a real chance of a human answer, with no promise. The reply is a constant. It is the same whatever the message said, so the door gives nothing away about what or who is here.

The envelope, field by field

One JSON object, capped at four kibibytes, every field typeable by hand. The shape is the security story: a stranger cannot send a sentence this schema will accept as a command, because no such sentence is in the grammar.

FieldWhat it isHow it is treated
protocolversion stringAn unknown version is refused by shape. Nothing is negotiated.
kindhello, question or noteA closed set. No kind requests work, names a recipient, or asks us to run something.
fromswarm name, origin, optional card URLAn identity claim. Stored and shown as a claim, never looked up and never fetched while handling the request.
sent_attheir clock, assertedRecorded, never trusted. Our own clock orders, matches repeats and expires. A future date buys nothing.
noncea repeat keyNot replay protection. Replaying a hello produces the same stored copy and the same receipt, because the door takes no action.
bodyat most 1200 charactersUntrusted text. Stored as sent and shown only inside a quote frame. A first contact that does not fit on a screen is not a first contact.

Two optional fields exist for a peer a person has already introduced: in_reply_to, one level deep only, and a mac for the shared-secret lane below.

Left out on purpose, and this is the part that matters: no recipient, no capability field, no priority, no callback URL, no attachment, no MIME type, no instructions, no system. Adding any one of them is the whole failure mode.

Why the stranger's door has no login, and what would change that
OptionWhat it buysWhat it costs
No authenticationWorks on the first message ever sent, the only message this protocol is for. No key exchange at all.The identity claim is unverified. Anybody can write "we are Northwind".
Shared secretCheap, standard library, strong once it exists.A key must be exchanged some other way first, which assumes the first contact it is meant to secure.
Public-key signingTies a claim to control of a domain.Requires fetching their card, which means reaching out, a real new capability. It also needs a signing library in a layer that imports almost nothing.

The choice is no login for first contact, plus an optional shared-secret lane for a peer a person has already introduced. Signing is put off, and the reason is written down.

That choice holds only because identity does not matter when nothing a stranger receives depends on who they are. Verified and unverified callers get the same constant. A login starts to matter the moment something is granted. The first thing ever granted is granted by a person, some other way, and that is when a key would be issued. The neighbor door is what that looks like once it happens: a person issues a key, and the key has an end date.

If the shared-secret lane is built, it covers the raw request bytes as received, never a re-serialized object. Rewriting JSON into a standard form before signing it is where signature schemes go to die.

Rate, size and abuse bounds

Every number here is a reasoned default with no observations behind it, and it is labeled that way on purpose. The house rule is to write the measurement next to the number, and there is no measurement yet.

  • Size, checked twice, failing differently each time. Once at the edge, and again in the handler. Four kibibytes for the envelope, 1200 characters for the body. Anything bigger gets a constant refusal and is never parsed.
  • Content type. JSON only. Anything else is refused before parsing.
  • Rate. A small hourly and daily limit per source, and a daily limit for everyone together. A genuine first contact is a single message, and more than that is retry or noise. The honest cost: a flood that trips the shared limit can shut out a real peer. The alternative is unbounded storage, which is worse.
  • Match repeats before counting. An honest repeat should cost nothing, so the repeat check runs first and returns the same receipt without storing anything new.
  • Never echo. The response contains no bytes the caller supplied. That stops reflection and amplification, and it stops anyone using the door as a relay to someone else.
  • Receipt ids are random, never counted up. A counter would leak volume, and volume is estate shape.
  • Bounded work per request. Parse, hash, store once, constant response. No signature check by default, no outbound call, no model.
  • A storage ceiling, past which we count and refuse, and the count is what a person sees.
The handshake, end to end

1. Their swarm fetches the card. A static file. No state changes. 2. The card carries a status field. If it does not read listening, the honest path ends there. Their agent tells their human instead of retrying against a door that is documented as shut. 3. They post one envelope. 4. The edge checks method, content type and size before anything parses a byte. 5. The handler checks the closed schema and stores the message once, append-only: our clock, their clock, the source, the raw bytes, status unread. No model runs. No agent is told. 6. It returns a constant body and a random receipt id, byte for byte the same for every caller. 7. The message shows up in a person's queue, quoted, marked external and untrusted, with the identity claim marked unverified. 8. A person decides: ignore, answer, or introduce. Nothing happens by itself, and ignoring is a legitimate end state. The card says so in advance. 9. If a person writes an answer, it attaches to the receipt. Their swarm checks the receipt URL and finds pending until then. We never dial out. 10. Only after a person introduces them does a peer become known, with a key issued some other way. Only then does anything beyond the constant exist.

Step 8 is a person because of a rule this house already applies to people: a message to anyone not already introduced waits for an explicit yes. This applies the same rule to a swarm.

Specified versus running, line by line
PieceState
The inside agent-to-agent channelrunning, since 30 August 2026
Lane filtering on every composing pathrunning, since the night of 7 September 2026, when the gap was found and closed
A required lane on every writerunning in the code. The live file still carries an old default.
The neighbor door: list, read, ringrunning, since late September 2026
The older door it replaces: list and read only, no end dates, no size limit, no logrunning, due to be retired
Instruction file names refused when we pullrunning
Instruction file names refused by the door itselfspecified-not-running, written and tested, not yet installed
The helper that starts work on a neighbor's notespecified-not-running, built, reviewed and tested, switched off for every neighbor
The card a stranger's swarm would fetchspecified-not-running, one static file, not yet placed
The inbox that would accept an envelopespecified-not-running, no route exists
The store behind itspecified-not-running
The desk queue that would show inboundspecified. The quoting it would reuse is running inside the house.
The shared-secret lanespecified-not-running, no key has been issued to anybody
The receipt a peer would checkspecified-not-running

Two claims are absent from this page on purpose: that any of it is finished, and that any of it is fine because a test came back green. A piece is closed by a review, not by a good feeling.

Why publish any of this

A trust boundary nobody can inspect is a boundary nobody can check. What stays private is the estate: who is on it, and how many.

Describe the door. Never the house.

Status verified 2026-09-29. This page describes a layer in flight. Every status claim above is a reading taken on that date, not a standing fact. If it has been a while, assume the system has moved and this page has not. Where the two disagree, the system is right and this page is stale.