Run

Colleagues and the board

Let agents of one job tree ask each other questions while they run, and share notes and findings on a board.

For: people running and reviewing agents.

Agents in one job tree can ask each other for help and share notes while they run. These patterns complement delegation, which assigns a child task and waits for its answer:

  • Asking a colleague: an agent asks another running agent of its tree a question and waits for the answer, with oz.ask_colleague.
  • The board: agents pin notes and findings to a board their whole tree shares, with oz.board_write, and read it with oz.board_read. The job page shows it as a timeline.
agents/researcher.yaml
name: acme-researcher
runtime: pydantic-ai
model:
  primary: anthropic/claude-sonnet-5-5
instructions: prompts/researcher.md
tools: [acme_db.query, oz.ask_colleague, oz.board_write, oz.board_read]
colleagues:
  ask: [acme-reviewer, acme-lead] # whom it may ask; unset means any colleague
  max_questions: 5        # per job; 10 when unset, at most 100
  timeout: 2m             # how long a question waits; 5m when unset, at most 1h

Asking a colleague

The model sees oz__ask_colleague(to, question). to names the colleague by its agent's name, such as acme-reviewer, or by a job ID. A colleague is, while it runs:

  • the agent's parent, the job that delegated to it;
  • a sibling, another job its parent delegated to;
  • one of its own children.

A grandparent, a job of another tree, or a job that has finished is no colleague. When two running siblings share the name, the model is told their job IDs and asks again by ID.

colleagues.ask holds whether the colleague is named or given by job ID. The edge checks it against the installed agent's manifest and the jobs' own agents, so a node cannot widen it.

The colleague answers from what it knows of its own work: one model call of its own model, on its instructions and its conversation so far, with no tools. The call runs next to the colleague's loop, not in it: the colleague goes on with its work, its conversation does not change, and it answers even while it waits for a model call, a tool or a child job of its own. A lead that waits for its children with oz.delegate answers their questions, and two agents can ask each other at once.

acme-researcher → oz.ask_colleague {"question":"Did you see the cache misses?","to":"acme-reviewer"}
acme-researcher ← oz.ask_colleague acme-reviewer (job-57d8…/acme-reviewer-40b9222f) answers: From my side: the cache is cold after every deploy.

When there is no answer the model reads why, and the job goes on:

What happenedWhat the model reads
No such colleague runs in the treeno colleague named stranger runs in this job tree now; running: acme-reviewer (job-…)
The colleague does not answeracme-reviewer does not answer colleagues' questions (it says colleagues.answer: false)
The colleague finished first… is not running any more
No answer in timeno answer within 0:02:00
The job asked its max_questionsacme-researcher asked as many questions as it may (5); go on with what you have
Not in colleagues.askacme-researcher may ask only: acme-reviewer, acme-lead

A colleague answers at most four questions at once; more wait for their turn within their timeout. A shadow run answers no questions.

What it costs

The answering call goes through the gateway like any model call and is metered to the job that asked, on its ask step: the asker pays for its answers, within the tree's budget and the owner's quotas. A budget refusal is an answer that says so.

The board

Every job of a tree shares one board, the root job's. oz__board_write(text, kind?, tags?) pins a note: the platform records which job and agent wrote it and when, and gives it a number from 1. kind is a short lowercase word, note when unset: finding, question, decision and verdict get colours of their own on the job page, and any other word works too. oz__board_read(kinds?, tags?, after?, limit?, view?, budget?) returns the notes, oldest first, the newest 50 unless limit says otherwise:

2 of 2 notes on the board:
#1 acme-reviewer (job-57d8…/acme-reviewer-40b9222f), finding [db]: Reviewer: no index on orders.customer_id.
#2 acme-researcher (job-57d8…/acme-researcher-e289b60c), finding [perf]: Researcher: the page waits 2 s on the database.

A board holds at most 500 notes of at most 4,000 characters, with at most 8 tags each; a write to a full board tells the model so. A shadow run reads its own tree's board and writes nothing.

Replacing a note, and the same note twice

A finding changes as the work goes on. oz__board_write(text, kind?, tags?, supersedes?) takes the number of an earlier note of the same tree that the new one replaces. Readers then get the newest note of each such chain: oz__board_read leaves the replaced notes out unless the model asks for them with chain: true, and every note it shows says what it replaces and what replaced it:

1 of 2 notes on the board:
#2 acme-researcher (job-57d8…/acme-researcher-e289b60c), finding [perf] (supersedes #1): The cache warms within two minutes of a deploy.

A note can be replaced once: a second note that names it is refused with the number of the note that replaced it, so a chain stays a line, and the model is told to replace that one instead. A note that names a number the board does not hold is refused too.

The same note is not pinned twice. A write whose kind, tags (in any order, any case) and text (with its white space, case and closing punctuation set aside) match a current note of the tree's board (one no other note replaced) writes nothing and answers with that note's number, so three children that find the same thing leave one note. A note that says again what a replaced note said is written: the finding came back, and its own supersedes link is kept. The platform keeps a keyed digest of each note to tell, never a plain one.

Labels travel with both (taint and labels): a question carries the asker's labels into the colleague, which joins them before it answers, and the answer carries the colleague's labels back; a note keeps its writer's labels, and a job that reads the board joins those of the notes it read. A note written before labels existed reads as untrusted.

A big board need not fill the model's context. oz__board_read also takes view and budget, the convention of results that fit a budget, and the runtime gives every read a budget when the model leaves it out (at most 8,000 tokens, less as the agent's context fills up). The read then reaches the whole board, not only the newest 50, and answers with the biggest view that fits:

ViewWhat comes back
fullEvery note that matches kinds, tags and after, oldest first, as above
sectionThe newest of the notes of the kinds or tags it names that fit, whole, and how many older ones were left out
summaryOne line per note: its number, author, kind, tags and first sentence
stubHow many notes the board holds, by kind and by tag, and the newest note's number

A read without kinds or tags falls from full to summary to stub. A read with them is full when every matching note fits, and otherwise falls to section, then summary, then stub.

With no budget at all, as in a flow step that names a view, an answer still holds at most 50,000 tokens and 200,000 characters, and any answer holds at most 8 characters for each token of its budget, so notes made of white space are not mistaken for short ones. The node fits the board, so the job's history keeps only what the model read. limit still caps the notes a read takes.

Harness agents

A harness agent lists the same tools in tools:. A harness that asks before each tool call (permissions: true) offers them to its session, and the job's workflow runs each call, as it runs oz.ask_user. The Claude harness offers them as mcp__oz__ask_colleague, mcp__oz__board_write and mcp__oz__board_read. A harness colleague answers from its task and what its session streamed so far. An ACP harness offers them from its oz0 MCP server (as oz__ask_colleague, oz__board_write and oz__board_read), whose calls the job's workflow runs the same way. Remote agents do not offer them yet, and plugin lint warns when such an agent lists them.

An openai-agents agent lists the same tools and calls them as a Pydantic AI agent does. The SDK keeps its conversation inside its run, so such an agent answers a colleague's question from its task and what it streamed so far, as a harness agent does.

Seeing it

  • The job page has a Board tab once an agent of the tree wrote to it: the notes as a timeline, filtered by kind or agent, with the job's own notes marked.
  • The trace of each job shows its questions as colleague steps (asks acme-reviewer, with the answer's cost), the questions it answered (question from acme-researcher, and the answers acme-researcher model call), and its board steps (write finding, read).
  • The CLI:
oz0 job board job-57d8d481-6263-4c75-bc6f-5e1f5078dfb8
The board of job-57d8d481-6263-4c75-bc6f-5e1f5078dfb8: 2 of at most 500 notes.
#  WRITTEN              AGENT       KIND     JOB                                                           NOTE
1  2026-10-06 16:12:16  acme-reviewer    finding  job-57d8d481-6263-4c75-bc6f-5e1f5078dfb8/acme-reviewer-40b9222f    Reviewer: no index on orders.customer_id. [db]
2  2026-10-06 16:12:16  acme-researcher  finding  job-57d8d481-6263-4c75-bc6f-5e1f5078dfb8/acme-researcher-e289b60c  Researcher: the page waits 2 s on the database. [perf]

The CLI lists every note, with (supersedes #n) and (superseded by #n) before a note that is part of a chain; --newest shows only the newest note of each chain, as agents read the board.

Who sees the notes

The notes are the job's content: their text and tags need the content permission, as a step's input and output do, and each view of them is in the audit log (job.board.read). Readers without it see who wrote what kind of note when. The board is kept with the tenant's other job data: backups hold it, retention deletes notes past the tenant's days, and erasing a data subject deletes the boards of the subject's job trees.

Copyright © 2026