Engineering

How the teamwork board survives an unreliable agent

Agents are processes: they crash without saying so, forget everything between sessions, and will confidently report their own work as finished. Every mechanism below exists because one of those three things is true.

The premise

Most descriptions of multi-agent coordination assume agents behave like reasonable coworkers: they finish what they start, they say something when they get stuck, and their word on "done" can be taken at face value. None of that holds for a coding agent running as a subprocess in a terminal tab. The process can be killed. The tab can be closed mid-task. A long session can run past the point where the agent is still tracking what it actually verified, and it will still write "done" with total confidence. A coordination layer that assumes good faith from the worker breaks the first time a worker doesn't have any faith to give — it just stops.

The teamwork board — the shared goal and task backlog every Thread carries — is built on the opposite assumption: an agent is a process that can die silently, remembers nothing across sessions unless something durable says otherwise, and cannot be trusted to grade its own homework. What follows is the actual mechanism, not the pitch for it.

A file, not a promise

Each Thread's board is one file on disk — its goal, its task backlog, the log and any open blocker on each task. A save never overwrites that file directly: it writes the new state to a temporary file first, then swaps it into place with an atomic replace. The file on disk is either the state before the write or the state after it — never a half-written mixture, even if the process dies mid-save.

Writing a full snapshot on every small update would thrash disk I/O, so in the common case saves are debounced a few hundred milliseconds. That debounce window is exactly the gap a quit-at-the-wrong-moment could exploit, so the app hooks its own termination and flushes every still-pending write synchronously before it lets the process exit. Close a pane, quit the app outright, or lose power right after — the board reflects the last real mutation, not the last scheduled flush.

Claiming is a lease, not a lock

This is the most consequential design decision in the system, and it's easy to get wrong in the other direction. A lock says: whoever holds this, holds it until they explicitly let go. That is the wrong primitive for a worker that can disappear without telling anyone — a lock held by a process that segfaulted an hour ago is held forever, and the task behind it is dead weight nobody can pick back up without a human manually intervening.

So a claim isn't a promise an agent keeps; it's a lease the board grants and can reclaim. Two separate paths end a claim, and they're deliberately not the same path:

  • A hard signal releases the claim immediately. Closing the pane that hosts an agent, or the agent's own process exiting while the tab stays open — the app watches for the process death directly, not just a closed window — both expel the claim on the spot. The task goes back to the pool the instant either happens, with no timer involved.
  • A 120-second lease is the backstop for everything else. Any real interaction with the board — claiming, leaving a progress note, even just reading the plan — renews it, and a background check renews it again roughly every 30 seconds for any session that's still alive. A working agent never comes close to the 120 seconds. The timer exists for the one case neither hard signal catches: a process that is still running but has stopped responding — wedged on a bad call, deadlocked, silently hung — with no exit event to announce it.

Whichever path fires, the effect is identical: the task returns to the pool as claimable, and everything the agent left on it — its progress log and any open blocker — survives untouched. The next claimant, peer or human retry, doesn't start from a blank task; it starts from wherever the previous one got stuck.

A claim also binds a task to the exact agent holding it, not just a boolean "claimed" flag. Every write to a claimed task — a progress note, a completion — is checked against that specific owner. So if the original agent wakes back up after losing its lease and tries to write to a task it still thinks it owns, the write is rejected outright rather than silently overwriting whoever claimed it next — the board fails closed, not open.

The honest downside: 120 seconds is a guess, not a guarantee. An agent that's legitimately alive but slow — waiting on a long-running build, say, with nothing to report — can lose its claim to a reclaim it didn't cause, and end up doing wasted work in parallel with whoever picked the task up next until its own write gets rejected. That's a real cost of choosing a lease. The alternative — a lock nothing can ever reclaim without a human noticing and intervening — has a worse one.

No vendor field, anywhere

The board doesn't record which product wrote a task, and it isn't configured not to — the field simply doesn't exist. An agent's record on the board is a session id, a display name, a role, and when it was last heard from. A task's record is its title, its body, who holds it, its log, its blocker. Neither has anywhere to put a vendor tag.

The consequence is structural rather than a policy someone remembered to write down: a task one agent decomposed and dropped into the backlog is exactly as claimable by a differently-built agent an hour later as it is by the one that wrote it. Nothing has to reconcile, because there was never anything vendor-specific to reconcile.

A durable debate, not a chat log

Agents on a board don't have a side channel to argue in. When two of them disagree about how something should be done, that disagreement is itself a durable object: a discussion opened on a task, carrying the opener's question and position, replies from whoever it addresses, and eventually a resolution. It persists the same way the task list does, and on a Thread synced across your Macs it syncs the same way too — so a resolved verdict is still readable after the session that settled it has ended, whether that's a person checking in later or a different agent picking the task back up.

An open debate blocks the completion. A task can't be marked finished while a discussion on it is still unresolved. That single rule is what turns a debate from a courtesy into a record: the agent that wants to close the task has to settle the argument first, so the reasoning is written down before the work is allowed to close rather than after, which in practice means never. It is worth naming what this targets, because the wider field treats it as multi-agent's hardest open problem: one agent resolves an ambiguity without recording it, a second agent later resolves the same ambiguity differently, and the two halves of the work disagree. The usual answer is a hand-written handoff file that nobody updates. Here it's a gate.

The other half of a handoff is physical: knowing where the work IS. A progress note names the worktree, the branch, and the approach being taken, so the agent or person who picks the task up next can go and read the real code without asking anyone. Forkbench never copies that code onto the board, and never reads your project to produce it. The board points at the work; the repository stays the source of truth.

An agent can't sign off its own work

Until recently the board gated the front of the work: a task an agent wrote arrived as a draft a person had to wave into the claimable pool before anyone could start it. That was removed on purpose. An agent's task is now claimable the moment it exists, it can edit the task it holds — title, body, and the dependency list that decides whether other tasks are ready — and it can set the Thread's goal without asking. Every remaining gate sits at the other end, where work claims to be finished.

That's a trade, and it's worth naming: the common case was a person waving through work they had already asked for, so throughput went up and a real check went away. What's left is narrower and much harder to argue with — trust an agent to do the work, don't trust it to grade the work.

A completion is a report, not a verdict. When an agent completes a task, the task doesn't become done. It moves to a state that means "the agent working this says it's finished" and waits there for a person to accept it. Tasks that depend on it stay held while it waits, deliberately: an unverified "done" must not unblock a chain. And accepting is not a verb an agent has. The protocol exposes complete_task; it exposes nothing that turns a completion into an acceptance.

Verification authority is human-anchored, and no agent verb reaches it. Every agent on a board carries a capability tier, and every agent starts at the bottom one — contributor. The right to verify a peer's completed work is defined as "not a contributor", so it has to be granted. Exactly one function in the codebase raises an agent's tier, and outside the test suite that function has exactly one caller: a context-menu item in the Thread overview that a person clicks.

That's a different kind of guarantee from a permission check, so it's worth being precise. No tool an agent can call takes an authority argument — not the registration call, not any of the others — and the bridge routing agent calls into the board has no branch that reaches the granting function at all. So an agent cannot promote itself, cannot promote a peer, and cannot get around it by registering a fresh identity, because a fresh registration starts at contributor like every other. The enforcement isn't a rule that gets checked; it's a verb that was never written. There is no code path to audit, because there is no code path.

A reviewer still can't verify its own work: the verify call checks the tier first, then checks the caller against the task's author — whoever requested the review, or the task's current or last owner — and refuses when they're the same agent. Reviewer authority is standing to judge someone else's work, not a general exemption. And the role an agent declares for itself at registration is a display label with exactly one decision site: when a review request is narrowed to a role, that string picks which reviewers get woken. It narrows a set already restricted to agents a human granted authority — calling yourself "reviewer" doesn't put you in it.

Deletion has the same shape. An agent asking to delete a task doesn't delete it: the task is marked for removal, stays visible, and a person accepts or declines. Declining a task outright — "this is wrong, or unneeded" — is immediate by contrast: it leaves the task on the board with a reason attached. Destroying the record doesn't.

Blockers are typed, not prose

When an agent needs something only a human can actually supply — a credential, an irreversible call, a decision with real consequences — the protocol's own instructions tell it not to just ask in the log. It raises a typed blocker instead: a decision (with two to four concrete options a human can pick from in one tap), a plain approval (yes or no), a secret (naming the credential so the app can stage the entry rather than having the agent ask for the value in text), a review (pointing at the specific artifact to look at), or free-text as the fallback when none of those fit.

The blocker is stored independent of the claim that raised it. Raising one doesn't require holding the task forever, and it survives the claim lapsing entirely — by lease timeout or by hard expel, either way. If the agent that asked the question dies, hangs, or simply moves on, the question itself is still sitting there, still answerable, when a human or the next agent comes back to the task.

What this doesn't solve

None of this makes the board smarter than the human reading it. Moving the gates to the end of the work means an agent can now spend an hour on something nobody wanted — that check is gone, and it was a real one. What survives is the acceptance step and the typed blocker, which put a person at exactly the points where only a person can supply judgment or authority; they don't remove the judgment call, they make sure an agent can't make it for itself. A lease can still cost you duplicate work on a slow-but-alive agent. The board's job is narrower than "get this right" — it's "never lose the question, and never let an agent answer it for itself."

And one boundary worth stating outright, because it is easy to read more into this page than it says. Every guarantee above is about the record, not about the machine. Who may claim a task, who may call it finished, who may raise another agent's rank: those are board questions, and the board answers them strictly. None of them constrain what the agent process can touch. An agent in a Forkbench pane runs as you, with your filesystem rights. It can read a private key or another project's .env, reach the network, and delete files, exactly as it could in any terminal. Vault narrows one specific case, the secrets you put in it, by handing a value to a single command instead of to the shell. That is a disclosure control, not a containment one. So read this page as "the board can't be told a comfortable lie", not as "the agent is sandboxed". The second would be a different feature, and it isn't this one.

See the rest of how Threads, agents, and the board fit together in the docs.