Coordination

Wait on agents in different ways, wait for a whole subtree to finish, and tell a running agent when another one finishes.

Beyond the plain waitFor of Fan-out and gather, alineod gives you three ways to coordinate agents. All of them are declared by you (the operator) or in a spawn request. An agent can't start them from inside its own sandbox yet.

NeedUse
Start a child on the first result, a quorum, or a deadlinewaitFor modes
Wait until everything under an agent has finishedSubtree completion
Tell a running agent when another one finishes, without blocking itnotifyOn

waitFor modes

waitFor accepts an object as well as a list of IDs:

{
  "parentAgentId": "a_7b2e44d1",
  "spec": { "name": "judge", "...": "..." },
  "prompt": "Pick the best of the drafts in /inputs.",
  "waitFor": {
    "agents": ["a_c01d9e3a", "a_5e8f2b71", "a_91aa0c4d"],
    "mode": "quorum",
    "k": 2,
    "deadlineSec": 600
  }
}
modeThe child starts whenInputs written
settled (default)every agent has finished, whatever the outcomeall of them
allevery agent succeededall of them
anythe first agent finishesthat one
quorumk agents succeededthe first k successes

A plain list is the same as mode: "settled".

  • onDepFailure (all, quorum): fail (the default) ends the child failed without forking it when a dependency fails, or when a quorum can no longer be reached. proceed starts it anyway with what there is.
  • deadlineSec: stop waiting after this long. With onDeadline: "proceed" (the default) the child starts with whatever has finished, and the wait is marked partial. With "fail" the child ends failed. The deadline is stored, so it survives a daemon restart.
  • Agents still running after the child starts, like the losers of an any or quorum, keep running. Stop them yourself if you don't need them, e.g. with a subtree stop.

For any wait other than a plain settled one that fully succeeded, /inputs.json also has a __wait entry, so the child can tell a partial or race result from a complete one:

{
  "a_c01d9e3a": { "path": "/inputs/a_c01d9e3a.txt", "outcome": "success" },
  "a_5e8f2b71": { "path": "/inputs/a_5e8f2b71.txt", "outcome": "success" },
  "__wait": { "mode": "quorum", "outcome": "satisfied", "pending": ["a_91aa0c4d"] }
}

Each resolved wait emits wait_resolved. While a dependency the wait still needs is paused, wait_blocked_on_paused is emitted once for it, so a stalled wait shows up instead of hanging silently.

Waiting for a subtree

A subtree is quiescent when every agent in it has finished and none is still being spawned. A paused agent counts as not finished, and is listed in blockedOnPaused.

curl -s 'localhost:4600/agents/a_7b2e44d1/await?scope=subtree&wait=120'
{
  "rootAgentId": "a_7b2e44d1",
  "quiescent": true,
  "asOf": 214,
  "members": [
    {
      "agentId": "a_7b2e44d1",
      "state": "done",
      "outcome": "success",
      "resultRef": "fs://a_7b2e44d1/result.md"
    },
    {
      "agentId": "a_c01d9e3a",
      "state": "done",
      "outcome": "success",
      "resultRef": "fs://a_c01d9e3a/result.md"
    }
  ],
  "blockedOnPaused": []
}

The request is held for up to wait seconds (maximum 240) and returns as soon as the subtree is quiescent. A finished agent that you prompt again makes the subtree active again, so quiescence can come and go. subtree_quiescent is emitted each time a subtree someone is waiting on becomes quiescent.

To wait on a set of agents without spawning anything, use the same modes as waitFor:

curl -s -X POST localhost:4600/runs/r_3f9a1c20/await \
  -H 'content-type: application/json' \
  -d '{"agents": ["a_c01d9e3a", "a_5e8f2b71"], "mode": "any", "wait": 60}'
{
  "outcome": "satisfied",
  "selected": ["a_5e8f2b71"],
  "settled": [{ "agentId": "a_5e8f2b71", "outcome": "success" }],
  "pending": ["a_c01d9e3a"]
}

If it's still undecided when wait runs out, outcome is pending. {"subtree": "<agentId>", "wait": N} in the same body waits for that subtree instead.

notifyOn

waitFor holds a child until its dependencies finish. notifyOn doesn't block anything: the agent keeps working and is told when an agent it watches finishes.

{
  "parentAgentId": "a_7b2e44d1",
  "spec": { "name": "integrator", "...": "..." },
  "prompt": "Start integrating; the reviewers' findings will arrive as they finish.",
  "notifyOn": ["a_c01d9e3a", "a_5e8f2b71"]
}

Or subscribe an agent that already exists:

curl -s -X POST localhost:4600/agents/a_2d4c9e10/notify-on \
  -H 'content-type: application/json' -d '{"agents": ["a_c01d9e3a"], "wake": true}'

When a watched agent finishes, however it finishes, the subscriber gets a line like this:

[alineo] Update from agents you're watching:
- a_c01d9e3a (reviewer-auth) finished: success. Result: fs://a_c01d9e3a/result.md — "Found two issues in …"

How it arrives depends on what the subscriber is doing:

Subscriber isDelivery
runningSteered into its current turn, after its running tool call.
pausedHeld; delivered as soon as it's resumed.
idleAdded to the start of its next prompt. With wake: true, or POST /agents/:id/inbox/deliver, it starts a new turn instead.
not forked yetHeld; added to its first prompt.
stopped or lostDropped.
  • Merged: everything pending when a delivery happens goes out as one message.
  • Already finished: subscribing to an agent that has already finished notifies at once.
  • Finishing again: an agent prompted again that finishes again notifies again, as "finished again".
  • Durable: notifications are stored in the ledger, so pending ones survive a daemon restart.

GET /agents/:id/inbox lists pending and delivered notifications. The events are notify_registered, inbox_queued, inbox_delivered and inbox_dropped.

A notification can't interrupt a running tool call

Delivery into a running turn uses steer, so it lands after the agent's current tool call. If a long build is running, the notification waits for it. Use waitFor when the agent can't do anything useful until the result arrives.