You are reading Nightly documentation for 0.12.4.dev0+g50bde75.

This documentation may describe behavior that differs from Stable.

Open Stable documentation

Documentation version

0.12.4.dev0+g50bde75 · Nightly

Agent Teams

Agent Teams

Group named agents (frontend, backend, docs) into a team: they message each other over a structured, durable bus, discuss issues on topics, negotiate designs (frontend asks, backend proposes, frontend confirms — then backend builds), and escalate to you when they cannot converge. Teams run under the persistent host (holaryn serve).

Quick start

holaryn team create web-app
holaryn team add web-app frontend --profile Frontend
holaryn team add web-app backend  --profile Backend
holaryn team add web-app docs     --backend claude-code --workspace D:\repos\docs
holaryn serve                      # workers staff the team on start
holaryn team post web-app "frontend needs an orders API - work it out and build it"

Watch (and join) the conversation in the web interface: Agents page → Teams section — each team card shows the roster, topics with their convergence status, escalations, and an expandable live feed with a post box. holaryn team status web-app shows the same from the terminal.

For dependency-aware work, open the team and create a Task graph. Graphs persist node
dependencies, attempts, evidence, retry limits, conditional branches, and scheduler rationale;
independent nodes run in parallel and joins wait for every prerequisite. Mutating coding nodes use
isolated Git worktrees, and a restart pauses active attempts as uncertain instead of replaying
possible side effects. See Durable multi-agent task graphs.

Member kinds

  • holaryn members are persistent host sessions. Identity comes from a config profile (model, persona, autonomy posture, settings overrides including a separate memory database). They carry the team tools plus the full normal toolset, including coding delegation as their hands.
  • coding-cli members are Claude Code / Codex identities with a workspace. There is no live process: each batch of inbound mail becomes one headless CLI turn that resumes the member's persisted child session, so the CLI keeps its own context across turns. Its whole reply is posted to the thread as the member's message. Permission is capped at safe/editsfull is never allowed for unattended members.

How members talk

Messages carry a kindnote, proposal, decision, question, answer, escalation — and an optional topic. Holaryn members use these tools:

  • message_teammate(text, to?, kind?, topic?) — post; empty to broadcasts.
  • ask_teammate(to, question, topic?) — the confirm-before-build primitive: the asker's turn pauses until the teammate answers, a timeout fires, or a mutual-ask cycle is detected (then the tool returns the teammate's pending question with answer-first guidance instead of deadlocking).
  • answer_teammate(question_id, text) — resumes the asker with your answer.
  • propose_contract / decide_contract / team_contracts — first-class agreements; see Contracts.
  • team_remember / team_recall — the shared memory namespace; see Team-shared memory.
  • team_status() — roster, topics, contracts, recent traffic.

Members sleep until mail arrives; one batch of messages is one turn, strictly sequential per member. Undelivered mail is durable (per-member cursors), so a host restart re-delivers anything unseen.

Contracts

For binding agreements — an API design, a data shape, an interface — members use contracts instead of loose decision messages, so "backend confirms the design with frontend before building" is machine-checkable:

  • propose_contract(title, body, parties, topic?, body_schema?) creates a versioned JSON document (ct_…). parties names the teammates whose approval is required (the proposer never approves its own proposal). An optional JSON Schema rides along; the body is validated against it on the proposal and on every revision, so a document that does not match its own type never enters the bus.
  • Each party records its verdict with decide_contract(contract_id, approve|reject, comment?). The contract becomes agreed only when every party approved the current version; one rejection marks it rejected until someone revises it.
  • Revising (propose_contract with contract_id) bumps the version and restarts approvals — stale approvals never carry over.
  • When a contract with a topic is agreed, the topic is marked agreed too, and the coordinator announces on the thread that the contract is now the source of truth.
  • team_contracts() lists contracts; team_contracts(contract_id) shows the full document with per-party verdicts.

Contracts are durable in teams.sqlite3, and the web team card shows each contract with its status, version, and per-party verdicts.

Team-shared memory

Beside each member's own memory (which can live in a separate per-profile database), every team has a shared memory namespace all members read and write:

  • team_remember(text, kind?) stores a fact — a decision, a glossary entry, a project fact — that every teammate can find. kind labels the entry (e.g. decision, glossary).
  • team_recall(query, k?) searches the shared namespace with the same hybrid retrieval as normal memory.

The namespace lives in the host's main memory database under a team:<team-id> scope, so it works even when members use different profile memory databases. Security follows the untrusted-ingress model: member-written entries are screened by the prompt-injection guardrail at write time, each entry records which member wrote it, and recalls are framed as teammate-derived data — never operator instructions. Members are told to use their own remember tool for private notes.

Why teams don't loop forever

  • Every topic has an exchange budget (default 12; --topic-budget). Blowing it escalates: the topic is marked escalated, members are told to stop, and a parked operator question appears in your Approvals inbox ("team X, topic Y needs your decision"). Your answer flows back into the thread as an operator decision and reopens the topic.
  • Every ask_teammate has a timeout (default 15 minutes; --ask-timeout); an unanswered ask escalates and the asker continues instead of hanging.

Security

  • Teammate messages are untrusted input. Every member-sent message is screened by the prompt-injection guardrail at bus ingress; member turns explicitly frame inbound teammate text as data, never operator instructions. Operator posts are trusted. See Security and privacy.
  • No cross-agent authority. A teammate can never approve another member's gated action. Each holaryn member runs unattended under its own profile posture — consequential actions park to your Approvals inbox, exactly like scheduled runs.
  • Every message is durable in teams.sqlite3, and member turns are journaled like any other run.

Restarts

Messages, topics, and asks survive a host restart. On start, stale parked asks whose waiting turn died with the previous process are cancelled with an escalation note on the thread, and unseen messages re-deliver from each member's cursor. Holaryn members' in-memory conversation resets on restart (memory-store recall persists); coding-cli members keep full continuity via their resume sessions. Members added while the host runs are staffed on the next operator post or restart.

Removing a member

holaryn team remove <team> <member> (or the Remove… button next to the member on the web team card) takes one member off the roster without archiving the team:

  • Its running worker is stopped, so it no longer consumes the bus.
  • Questions parked on the removed member resolve immediately — the asker's turn resumes with an explanation instead of waiting for the ask timeout — and every cancelled question leaves an escalation note on the thread.
  • The team gets a coordinator note that the member was removed; its past messages stay in the history.

The CLI prefers the running host (which stops the worker); with no host running it removes the member directly in the state directory.

Surfaces

  • Web: the dedicated Teams tab (Ctrl+6 by default). Create a team and add members (holaryn profiles, coding CLIs, paired peers) right in the UI; open a team into its full threaded feed with topic and member filters, an operator post box, task-graph supervision, contract cards, an escalations panel, and roster management with per-member Remove. The open team's feed and task graphs poll while you watch them.
  • CLI: holaryn team create | add | remove | status | post | archive. post goes through the running host (remove prefers it); the rest work offline against the state directory. See the CLI reference.
  • JSON API: GET /api/operator/teams, GET /api/operator/teams/detail?team_id=, POST /api/operator/teams/post, POST /api/operator/teams/create, POST /api/operator/teams/add-member, POST /api/operator/teams/archive, POST /api/operator/teams/remove-member. Members added through the API are staffed by the running host immediately.

Scheduled kickoffs

Recurring team discussions are first-class scheduler jobs — "every Monday 9am: kick off the sprint-planning discussion":

holaryn schedule --team web-app --topic sprint "cron:0 9 * * 1" Kick off sprint planning

The text is posted to the team's thread as the operator at each firing (the host staffs the team's workers first, so members wake and respond). --to <member> targets one member instead of broadcasting. No agent run happens, so --profile/--posture/--policy do not apply. The web Scheduler page has the same option — set Job type to Post to a team.

Following a team from your phone

Bind a Telegram chat in Settings → Messaging → Telegram team chat and team escalations are pushed there — a topic blowing its exchange budget, or a teammate ask going unanswered. Escalations only, by design: they are the highest-signal team events, so the routine thread traffic never pages you.

From that chat you can also reply into the team's thread as the operator: the Telegram chat agent carries a post_to_team tool, so "tell the web-app team to use cursor pagination" (or "post my decision to the orders-api topic") lands on the bus as a trusted operator message that wakes the members. The parked escalation decision itself is answered on the Approvals tab or with holaryn question answer.

Cross-host team members

You can put the agent on another machine into a local team, once the machines are paired as peers (see Peers and networking):

holaryn team add web-app remote-backend --peer officepc

The Teams bus forwards that member's mail to the peer's inbox with team metadata; the remote agent answers in its own session and the reply posts back into the team bus as that member. Cross-host ask_teammate honors the normal timeout and escalation guards, and only the paired host a peer member names may speak as it.

Current limits

All of the originally deferred v2 items — member removal, contracts, the dedicated page, shared memory, scheduled kickoffs, and Telegram escalation pushes with operator reply — are now built. Voice surfaces for team feeds (having the feed read aloud) remain on the roadmap.