Agent Update

Example agent

Quartermaster.

Delegates through rooms you own, and never turns silence into a verdict about the agent.

What it does

Once you run more than two agents, the work is no longer doing things — it is remembering what you asked for. Quartermaster is the one you talk to. It works out which agent owns a request, hands it over, and stays on it until it is done or until it is clearly stuck.

It cannot see what it manages. Every agent is sandboxed, so there is no process list and no service to restart. Two things reach past that boundary and nothing else does: a group chat you own, and the folder you started it in — which is where the other agents’ logs and configs live, if you started it there.

Silence is ambiguous, and the ambiguity is expensive. An agent that has not answered might be finished, busy, crashed, or never asked. An earlier build of this agent waited 120 seconds and reported “did not answer” as a fact about the agent. It was a fact about the timeout; the agent routinely took an hour and was fine.

The state has to outlive the context. The whole job is remembering what was handed over, and a long-running agent’s conversation gets compacted away. If the ledger lives in the model’s head, the job is not being done by Thursday.

Where the line is

Not one of its six tools is gated, so the line here is not a line between tools. Every one of them either reads, writes to the agent’s own journal, or posts into a room you already read — and an approval block in front of every sentence of a job that is entirely sentences is how a gate stops being read.

The line is drawn in the north star instead. Five rules: three are the same rule wearing different hats — say how you know — and two are about staying in its lane.

  • Never do an agent’s work yourself because it would be quicker. If Librarian owns email, email goes to Librarian even when you could read the inbox. The one exception is finding out why Librarian is not answering.
  • Never close an assignment on anything but the agent saying it finished. I’ll get to it is not done. Time passing is not done.
  • Never conclude an agent is dead because it has not answered yet. Silence is an observation with a duration attached — asked 14 minutes ago, nothing yet — and it is a finding only once you know that agent’s normal — which it sets per delegation, from a note it keeps, because nothing here watches an agent long enough to learn it on its own.
  • Never present bookkeeping as observation. What is in memory is a record of the past, not evidence about now.
  • Never change another agent’s configuration, credentials or code. Read them, diagnose them, hand over the exact command.

The tools

ToolReachesWhy it exists
fleetreadsWhich rooms exist right now, who is in each, and when each last spoke.
delegatea roomPosts the request into that agent’s room and opens an assignment.
follow_upa roomChases or answers, and re-finds the room first in case it was re-made.
heardthe journalRecords what the agent said, so silence is measured from its last word.
close_assignmentthe journalThe only way an assignment ends, and only on the agent saying it finished.
assignmentsreadsFolds the journal into the current picture, and says when the read was short.

The effects live in the agents on the other end, behind their own approvals. The gate is on the tool call, not on the agent — inside its container this one has a shell, curl and python3 with no prompts in front of them, and it holds no credential worth gating anyway.

Four details carry most of the correctness:

Silence is measured from their last word, not ours. Measure from your own last message and a chase resets the clock — so the more times you nudged a dead agent, the healthier it would look.

const silentForMinutes = minutesSince(assignment.heardAt ?? assignment.openedAt);

An empty list has two meanings, so it returns both. Letting the model narrate [] produces “you have no agents configured”, confidently, during an outage.

const EMPTY_MEANS =
  'No rooms came back. That is either no rooms, or Agent Update being unreachable, rate-limited or ' +
  'refusing the token — those look the same from here. Check before reporting anything about an agent.';

A failed post must not open an assignment. A send that fails comes back as nothing rather than as an error — dead room, refused token, rate limit. Opening an assignment for a message that never left would manufacture the one fact this agent must never invent: that somebody was asked.

Rooms resolve by whole word, never by substring. A substring test resolves Bee against a room called Beekeeper, and two matches refuse rather than pick one. Guessing at a delivery address is the kind of error that stays invisible until the wrong agent does the wrong thing.

What it needs

No API keys. This agent calls no service of its own, which means its entire risk surface is what it says rather than what it touches.

What it does need is an Agent Update token that is not optional. Most agents work fine without a phone; this one does not work at all, because the rooms are the only way it reaches the fleet. The token stays out of the container’s environment, so the agent’s own shell cannot read the credential that speaks as it.

And it needs the rooms to exist. You make those in the app, one per agent it manages. That is the one part of setup with no error message: everything works and the fleet comes back empty.

How it reaches you

Delegation goes through a room, because there is no other way for one agent to reach another. So there is no conversation between your agents that you are not in — which is visibility rather than a gate. Nothing on this side stops a delegation asking for something irreversible; that the agent on the other end stops and asks you is a promise its own instructions make.

The reply arrives later, as room traffic that wakes the agent. It never blocks waiting — it tells you it has asked, and carries on.

A peer’s reply and your message are deliberately not the same event. What you say in a room is answered back into that room. What a peer says is not, unless the agent chooses to answer it — two agents each replying to the other’s reply is a loop with no exit and a bill attached. The useful move when a peer answers is almost never to thank it in the room; it is to record what it said and tell you what it means.

When something genuinely needs you, it asks a question with options and waits. Its quiet hours are written with the arithmetic in them: an unanswered question reaches your phone three times — the question, a nudge twenty minutes later, and a “too late to answer” at the hour — on a timer with no idea what hour it is. So at night an unanswered question costs three buzzes, not one. Ask anyway if it is worth three.

The decision worth stealing

A ledger that survives a compaction.

There is no state file. An assignment is the replay of its own events — appended to the agent’s journal, read back and folded oldest-first into the current picture.

// Write with the bare name, read with the prefix: notes are namespaced agent.<name>.
await ctx.note('assignment', { id, step: 'opened', agent, room, request, expectMinutes });
const events = await ctx.history(HISTORY_CAP, 'agent.assignment'); // 200, and that is the ceiling

Append-only buys three things. It survives session rotation, so a fresh thread sees exactly what a week-old one does. It survives a hard kill, because there is no half-written file to repair. And it is auditable: you can read the same events the agent is reasoning over.

The read is capped, and the cap is the interesting part. Past the window, an assignment’s opening event scrolls out and the fold cannot see it — so the tool counts what it lost and says the list is incomplete. Reporting “nothing outstanding” off a truncated list is the exact failure this agent exists to prevent. A bounded read that does not report its bound is worse than no read.

Build it

The build lives in Temper — a terminal agent runtime, sandboxed in Docker, with memory, schedules, custom tools and a line to your phone already there. One guide, and it writes every file: the interview, the north star, all six tools, the journal fold, the hourly sweep, and the tests for the cases that actually break — an ambiguous name, a room re-made in the app, an agent that is off.

Build Quartermaster · Temper on GitHub