agents #
a subagent is a session with a parent. a message is mail. both are daemon primitives; the mail and agents extensions (on by default) give the model its python surface.
python #
m = await agents.models() # required once before spawning
kid = await agents.self.spawn("map every wake path", name="scout", model=m[0],
deliverable="notes/wakes.md") # returns at once
await mail.submit(kid, "also cover schedules") # or "parent", a name, a session id
kids = await agents.self.children() # snapshots: running, closed
await kid.cancel() # stop its turn, keep everything
await kid.close() # done: keep transcript + files, free the kernel
await agents.progress("on turn.gleam now") # shows in /agents, starts no turn
- spawn never returns the child's answer: the answer arrives later as
<mail>and starts the parent's next turn. a child whose turn ends without answering forwards its last message, markedunreviewed. - names resolve inside the family (children, siblings, parent); anything else takes a session id.
- cancel and close work on your own children only. the model cannot delete an agent; it asks the user.
- limits: 3 deep, 12 open children per parent, 1 MiB per letter, 1000 undelivered letters per session.
mail delivery #
a letter is stored first and marked delivered in the same transaction that writes it into the recipient's transcript. an idle recipient starts a turn; a running one reads it at its next step. undelivered letters retry every 15 seconds, so a restart or a session that is not running yet loses nothing. webhook deliveries are letters from outside, and wait for an idle session.
the orchestrator view #
ctrl+o or /agents in a session shows its whole tree live: running agents pulse with their token rate, mail travels the edges as blocks. tab picks an agent, enter opens it (typing there is you, as the user), text + enter sends to it, /spawn <name> <task> starts a child under it.
routes: GET /agents?session=<id> (the tree), GET /agents/stream (batched events), POST /sessions/:id/children, POST /sessions/:id/mail.
swarm overhead #
kernel boots queue behind four slots. each boot owns only its session's composition, and each kernel's host callback captures only its routes, store handle, and session id—not a snapshot of the other agents. bash waits on process-exit notifications, not periodic exit checks; descendant cleanup and deadlines still apply.
session replay retains at most 256 events or 4 mib, evicting incrementally rather than copying the full window per token. missing events require a transcript reset. the orchestrator feed batches every 100 ms and skips activity serialization when nobody is watching.
bash jobs start at once, at low priority (nice, and utility QoS on macOS), so a
busy swarm yields to the person at the machine. a job still running after the grace
window (5 s) is heavy and needs one of a few daemon-wide slots; without one it is
paused (SIGSTOP, job.queued is true) and resumed when one frees, so waiting
costs no cpu and loses no work. quick commands never wait. the timeout counts only
time the command ran. await job.stop() resumes a paused job so it can exit.
slots default to the machine's cores and shrink while the one-minute load average runs past 1.25× the cores, which catches heavy jobs that fan out workers of their own. a freed slot goes to the kernel holding the fewest, oldest request first, so one busy agent cannot starve the rest. a slot is released only after process-group cleanup is confirmed; failed cleanup keeps it held.
settings, read when the daemon starts: ALBEDO_MAX_LOCAL_JOBS fixes the slot count
(1–256), ALBEDO_JOB_GRACE_SECONDS sets the grace window, ALBEDO_JOB_LOAD=0
turns off the load adjustment. this bounds sustained shell work, not agent/model
concurrency, remote kernels, or python computed directly in a cell.