Several agents, one shared record of the work
Two agents polling the same queue will do the same job twice. An agent with no memory of the last run will start again from nothing. This is the coordination layer — an append-only bus, a dependency graph with leases, and a background runtime that is not allowed to act on its own conclusions.
- Append-only message bus
- Leases from 30s to 24h
- 13 operations
- Writes candidates, never actions
The bus is a log, not a queue
POST /v1/agent-workflows/messages appends. Nothing
consumes a message and makes it disappear, so what one agent told
another six weeks ago is still there to read. A message names its
sender, optionally its recipient — omit to_agent and it
is a broadcast — a message type of your choosing, a human-readable
body and a structured payload.
Messages are written with authority raw,
truth_state: fact and confidence 1.0, because the fact
being recorded is that the message was sent, not that its contents
are true. Both agent names go into the record's tags, so filtering
by agent returns everything that agent said and
everything said to it.
The reason this is a log is that debugging multi-agent work is archaeology. When a run goes wrong at three in the morning, the question is always what the agents told each other and in what order — and a queue that deletes on read cannot answer it.
"from_agent": "planner"
"to_agent": "reviewer"
"message_type": "handoff"
"body": "schema draft ready, 3 tables"
"dependency_ids": ["tk_4c1a"]
// later, from anywhere
GET /v1/agent-workflows/messages?agent=reviewer
→ everything sent to it, and everything
it sent, oldest exchange still intact
Ready means ready
A task graph is only useful if asking it what to do next gives an answer you can act on without checking anything else.
publish-digest is blocked because one of its prerequisites is
still running. write-adr is excluded because agent-2 holds a
live lease on it. Only doctor-pass has every dependency
satisfied and no claim against it, so it is the only task returned.
Tasks are upserted by key, so a supervisor that re-declares its
plan on every run converges on the same graph instead of
duplicating it. depends_on holds task keys, not
record ids, which means you can declare a dependency on work that
does not exist yet and have it resolve when that task appears.
GET /tasks/ready applies two filters at once. Every
key in depends_on must belong to a task that is done
or completed, and no unexpired lease may be held. A task blocked
on either count is simply absent from the response — there is no
status field for the caller to interpret, and no way to
accidentally start blocked work by ignoring one.
GET /graph returns the whole thing as nodes and
precedes edges. Dependencies declared by key are
resolved to record ids where the task exists, so the graph is
drawable as it stands.
Leases, and why they expire
POST /tasks/{task_id}/lease takes an agent
name and a duration — 900 seconds by default, 30 at the shortest,
86,400 at the longest. The task moves to
processing and records who holds it and when the
claim runs out.
Because the claim carries an expiry rather than a flag, a crashed agent unblocks its own work. Nothing has to notice the crash, no reaper process has to run, and no operator has to clear a stuck row: the lease simply stops being live and the task is ready again the next time anybody asks. Long jobs re-lease to extend.
Tasks also carry a checkpoint object, merged rather
than replaced on each write, so an agent that picks up work after
an expired lease can resume from where the last one got to instead
of starting over.
The Default Mode runtime, and its hard boundary
A background process that reflects on your work and updates your memory is a good idea right up until it is wrong about something. The boundary here is structural rather than a matter of prompting.
It writes candidates
Every output of this runtime is a
default_mode_candidate — a record in a holding area,
not in memory. Nothing it produces is retrievable as an ordinary
memory until a separate, explicit promote call moves it.
It takes no external action
There is no branch in this runtime that sends an email, calls a webhook, writes a file or hits a third-party API. It reads records and writes records. That is the whole surface, and it is the reason it is safe to leave running.
The critic sits in front of promotion
Anything mentioning risk, privacy, legal, medical, financial or
failure — and anything with no evidence references at all — is
forced to needs_review and
truth_state: hypothesis, regardless of how confident the
candidate was.
-
Reflect over a session
Send a transcript, or a session id to pull one. Each line over twenty characters is scored for salience — action-word density, a risk signal, novelty, repetition, whether the user said “remember” or “important”, and supplied goal-relevance and evidence-strength weights. Anything under 0.35 is dropped on the floor.
-
Classify what survived
A line ending in a question mark becomes an open_question. Decided, choose or selected make it a decision; todo, next or blocker make it an open_loop; prefer, likes or style make it a preference; a risk word makes it a risk. Everything else is a reflection.
-
Run the critic
Risky or unevidenced candidates drop to needs_review as hypotheses. Synthetic candidates are approved but keep truth_state simulation, so approval never launders them into facts. The decision, the risk flag and whether evidence was present are all written onto the record.
-
Promote, deliberately
POST /promote/{candidate_id} creates the real record. It carries authority derived and evidence_refs that begin with the candidate it came from, and the candidate is marked promoted with a pointer to what it became. A candidate that is neither approved nor needs_review is refused with a 400.
Three endpoints that are checklists, not thinking
We would rather tell you this than have you discover it.
/simulate-future, /stakeholder-simulation and
/incubate return fixed templates with your input
substituted in. They are the same every time.
/simulate-future returns five named paths — best
case, most likely, failure path, hidden risk and low effort — each
a fixed list of considerations. /stakeholder-simulation
returns a fixed set of concerns and message adjustments under the
label speculative_general_pattern.
/incubate crosses your problem with six fixed
reframing seeds. No model is called, and none of these three is
generation, imagination or reasoning.
What is genuinely working is the governance around them, and that
part is not cosmetic. The two simulation endpoints stamp
truth_state: simulation and
synthetic: true, which is what keeps their output out
of ordinary Librarian answers, where
include_synthetic defaults to off. Incubation lands as
a needs_review hypothesis. All three are candidates
until somebody promotes them.
A fixed prompt list is a legitimately useful thing — it is what a good checklist is — and it costs nothing, needs no key and cannot hallucinate. It is simply not what the word “simulation” usually implies, so the page says which it is.
"goal": "ship the migration by Friday"
best_case clear owner, small next action…
most_likely partial progress, unclear handoff…
failure_path scope too broad, no acceptance…
hidden_risk privacy/permission mismatch…
low_effort draft the smallest reusable asset…
truth_state simulation
synthetic true
// fixed paths — the goal is the variable
// excluded from librarian answers by default
Thirteen operations
Twelve paths across /v1/agent-workflows and
/v1/default-mode.
| Endpoint | What it does |
|---|---|
POST /v1/agent-workflows/messages | Post to the bus: from_agent, an optional to_agent — leave it out to broadcast — a message type, a body, a structured payload and dependency ids. |
GET /v1/agent-workflows/messages | Read the bus, filtered by project or by agent. An agent filter matches messages at either end of the exchange. |
POST /v1/agent-workflows/tasks | Create or update a task by key: title, depends_on, status and a checkpoint object for resumable work. |
GET /v1/agent-workflows/tasks/ready | The only tasks that can be started right now: every dependency done, no live lease held. |
POST /v1/agent-workflows/tasks/{task_id}/lease | Claim a task for an agent for a bounded number of seconds — 30 minimum, 86,400 maximum, 900 by default. |
GET /v1/agent-workflows/graph | The whole task graph as nodes and precedes edges, ready to draw. |
POST /v1/default-mode/reflect-session | Score a transcript line by line for salience, write the survivors as candidates, then run the critic over them. |
POST /v1/default-mode/consolidate | Summarise the distribution of recent records into one candidate carrying every record it looked at as evidence. |
POST /v1/default-mode/simulate-future | Stamp a goal against a fixed five-path checklist and store it as a simulation. |
POST /v1/default-mode/stakeholder-simulation | Store a fixed concerns-and-adjustments checklist for a named stakeholder, labelled speculative. |
POST /v1/default-mode/incubate | Combine a problem with six fixed reframing seeds and store the result as a hypothesis for review. |
POST /v1/default-mode/critic | Review candidates: anything risky or unevidenced is forced to needs_review. |
POST /v1/default-mode/promote/{candidate_id} | Move one reviewed candidate into real memory, carrying its provenance with it. |
Give your agents a memory you can audit
Run the whole system on your own hardware under the MIT licence, or ask us about hosted access. Both start from the same place.