OS tools

The os.* tools a run can call: execution labels, gate summaries, questions for the CEO, self-improvement, mail, company state, shared context, unblocking runs, planning, the CEO’s brief and chat threads.

Generated from the built-in tools registry. Do not edit by hand.

os.* tools are answered by the OS itself. They need no vendor binding, and none of them calls a vendor. A task still declares each one it calls in its tools, like any other tool.

Shared context (os.set_context, os.get_context, os.delete_context) is a JSON value one run stores and a later run of any task can read. A task version names the key prefixes it wants, and those keys arrive in the run input as context before the model is called. It is bookkeeping between runs. It is not memory, and it is not a place for a secret.

Everything the OS does not have to do itself (send mail, post a message, file an issue) is a vendor tool the enterprise binds, never an os.* tool. The CEO chat has a few os.* tools of its own: see Chat tools.

The company-state tools (os.inspect_enterprise, os.directory, os.list_blockers, os.list_open_gates) only read. None of them approves, rejects, retries or clears anything, and calling one never opens or resolves a gate.

The unblocking tools (os.retry_run, os.route_run, os.clear_run, os.escalate_run) move the runs on Blockers: start one again, send its work to another stage of its pipeline, take one off, or hand one to its team lead or the CEO. None of them decides a gate.

The brief tool (os.file_daily_brief) files the CEO’s brief in the member’s chat thread: what is waiting on the CEO, composed by the OS from its own records. It sends no mail and opens no gate.

The chat thread tools (os.chat_list, os.chat_read, os.chat_write, os.chat_open) are a member’s conversations with the enterprise’s people, as the mail tools are their mailbox. Every caller reads and writes as who it is. A run is its assigned member: it sees that member’s threads only, and what it writes is the member speaking, left for the people to read, so the run never waits for a reply. An API token or the MCP server is the person’s side: what it writes is a message to the member, who replies. A message a run writes is not something the member remembers when a person later talks to them in that thread.

Tool What it does Effect
os.set_execution_name Replace the execution's default title with a human-readable name. write
os.add_execution_link Attach an http(s) link to the execution: an issue, a pull request, a preview. write
os.set_gate_summary Write what the person approving this run's next gate is deciding on. write
os.ask_ceo Ask the CEO questions, each with a recommended answer, and end the run on their decision. write
os.list_recommendations List self-improvement recommendations: pending, decided, task-record, or the open issues. read
os.get_recommendation One recommendation with its evidence. read
os.decide_recommendation Decide one pending recommendation: decline, actioned, or implement. write
os.mail_list List the assigned member's inbox, newest first: metadata and a snippet, no bodies. read
os.mail_read Read one message from the assigned member's inbox. read
os.mail_reply Reply to a stored mail from the address it arrived at, once the CEO approves. write
os.mail_send Send a new mail from the assigned member’s mailbox, once the CEO approves. write
os.mail_set_label Set the labels on a mail in the assigned member’s inbox. write
os.mail_set_handled Record whether a mail in the assigned member’s inbox has been handled. write
os.mail_set_many Set the labels, the handled state, or both on many mails in the assigned member’s inbox at once. write
os.mail_quiet_threads The threads the assigned member replied to that have gone quiet, oldest first, at most ten. read
os.mail_mark_chased Record that a quiet thread was answered today, so no second run today raises it again. write
os.mail_snooze Leave a quiet thread alone until a date, instead of drafting a follow-up. write
os.escalate_mail The gate a mail that needs the CEO opens: the mail, why it matters, and what is suggested. write
os.mail_reply_draft The gate a drafted reply opens, so no reply leaves without a decision on it. write
os.list_kpis List the signed-off KPIs the run's own team owns and those its task is the source of, with their lights. read
os.record_kpi_reading Record a reading for a KPI whose source is this run's task. write
os.get_enterprise_kpis The company's KPI targets, bands, cadence and source. read
os.set_enterprise_kpis Replace the company's KPI target list. write
os.get_team_kpis A team's KPI targets, including its key KPI. read
os.set_team_kpis Replace a team's KPI target list and key KPI. write
os.set_context Store a JSON value for the enterprise under a key, so a later run can read it. write
os.get_context Read one enterprise context value, or null when the key has never been set. read
os.delete_context Remove one enterprise context key. A missing key is a success. write
kpi.record_reading Record one reading of a KPI by hand: the value, when it was taken, and who or what took it. write
kpi.list_readings One KPI's readings, newest first, corrected ones included. read
role.list_kpis A role's KPIs, oldest first, with the team KPI each serves, its sign-off, light, trend and readings. read
role.define_kpi Propose a KPI on a role, waiting for a person to sign it off. write
role.edit_kpi Change a role KPI's fields; a signed-off one only a person changes. write
os.inspect_enterprise A snapshot of the company: teams, members, roles, and the tasks of the roles each member holds. read
os.directory Teams, members, roles, and the tasks of the roles each member holds: who to offload work to. read
os.list_blockers What has stopped: failed, stalled, blocked and spend-paused runs, and stopped pipelines, with who owns each. read
os.list_open_gates Every gate waiting on a decision, oldest first: the CEO inbox's own rows. read
os.retry_run Start a stopped run again, with the same input, so its work moves. write
os.route_run Send a stopped run's work to another task of its pipeline, instead of running the same task again. write
os.clear_run Take a stopped run off Blockers without running it again. write
os.escalate_run Hand a stopped run to its team's lead, or to the CEO, and record that you did. write
os.read_enterprise The whole enterprise: teams, charters, roles, members and every task in full. read
os.search_catalog Published catalog tasks that match a plain-English intent, best first. read
os.list_tool_bindings The tool types this enterprise has bound, at which layer, and the tools each exposes. read
os.list_built_in_tools Every built-in tool a run can call, with its summary and whether it reads or writes. read
os.read_task One task in full, its pending recommendations, and the tasks that start it. read
os.assess_impact What points at a role, team, task or member a plan would delete. read
os.write_plan Write or replace this chat session's plan, checked before the CEO sees it. write
os.read_plan This chat session's plan as it stands, with its version. read
os.file_daily_brief File the CEO's brief in your chat thread: what is waiting on them, and the time on their clock. write
os.chat_list List chat threads, most recently active first: who each is with, its state and its last message. read
os.chat_read Read one chat thread: its messages, oldest first. read
os.chat_write Write one message in a chat thread, as yourself. write
os.chat_open Open a new chat thread with a member. write

os.set_execution_name

Replace the execution's default title with a human-readable name.

What the model reads:

Set a human-readable name for this execution, shown on Executions and run detail instead of the first task name. Later calls replace it. Pass name, e.g. "GitHub Issue #105 - Work keeps moving while decisions wait on the CEO".
Field Type Required Description
name string yes Shown on Executions instead of the first task name

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. The last successful call in the execution wins, so a repeat leaves the same name.
  • Called by: a run, the API.
  • Returns: The name as stored: whitespace collapsed and trimmed.
  • API: POST /v1/runs/self/execution-name (run:self)
Error When What happens
name_required name is missing, or blank once whitespace is trimmed. Tool error
name_too_long:200 name is over 200 characters. Tool error

Example:

{
  "name": "Issue #12 - Export the monthly report as CSV"
}

Attach an http(s) link to the execution: an issue, a pull request, a preview.

What the model reads:

Attach a helpful http(s) link to this execution (issue, PR, preview, CRM profile). Shown on Executions and run detail. The same URL later updates the label. Pass name and url.
Field Type Required Description
name string yes Label, e.g. GitHub issue, Pull request, Preview
url string yes http(s) URL

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. The same URL updates its label rather than adding a second link.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/execution-links (run:self)
Error When What happens
name_required name is missing, or blank once whitespace is trimmed. Tool error
name_too_long:200 name is over 200 characters. Tool error
url_required url is missing or blank. Tool error
url_too_long:2000 url is over 2000 characters. Tool error
url_invalid url is not an http: or https: URL. Tool error

Example:

{
  "name": "Pull request",
  "url": "https://github.com/acme/app/pull/34"
}

os.set_gate_summary

Write what the person approving this run's next gate is deciding on.

What the model reads:

Write, for whoever approves this run’s next gate, what they are deciding on. Markdown bullets: what the action does, and anything to watch out for. Shown on the approval card in Gates and the run page. Required before github.merge_pull_request and os.mail_reply — either call is handed back until you write one. A later call replaces it, but never rewrites a gate already waiting. Pass summary.
Field Type Required Description
summary string yes Markdown. Short headed sections of "- " bullets, e.g. "## What changed" then "## Watch out for". No preamble.

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. A later call replaces the summary, so a repeat leaves the same one.
  • Called by: a run, the API.
  • Needs:
    • Called before github.merge_pull_request, os.mail_reply, which is handed back until it is.
  • Returns: The summary as stored. The gate copies it when it opens, so a later call never rewrites a gate already waiting or decided.
  • API: POST /v1/runs/self/gate-summary (run:self)
Error When What happens
summary_required summary is missing or blank. Tool error
summary_too_short:40 summary is under 40 characters. Tool error
summary_too_long:6000 summary is over 6000 characters. Tool error
summary_needs_bullets summary has no markdown bullet (- , * or 1. ). Tool error
gate_summary_required A merge or reply call arrives before this run has written a summary. Handed back

Example:

{
  "summary": "## What changed\n- Adds CSV export to the monthly report\n\n## Watch out for\n- A migration runs on release"
}

os.ask_ceo

Ask the CEO questions, each with a recommended answer, and end the run on their decision.

What the model reads:

Ask the CEO questions and stop. This run waits for their decision, then ends. Pass questions: one decision each, numbered from 1, each answerable on its own, each with context (why it blocks, the options you see) and ctoRecommendation (the answer you recommend and why; required). The CEO answers each one: accepts your recommendation, answers it themselves, or asks you to explain further. Submit → outcome ceo_approved, Reject → ceo_rejected; the next task gets their answers as ceoAnswers and any note as ceoNote. Call it last.
Field Type Required Description
questions array of object, at least 1 yes
questions[].number integer, at least 1 yes
questions[].question string yes One decision, answerable on its own
questions[].context string no Why it blocks, and the options you see
questions[].ctoRecommendation string yes The answer you recommend, and a one-line reason. Never empty.
context string no One line on what is blocked, and the issue URL

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. The gate is found again by run and tool call, so a repeat never asks twice.
  • Called by: a run, the API.
  • Needs:
    • A gate: the CEO decides every call, whatever the task declares.
  • Returns: The CEO's decision, as the run's outcome: ceo_approved with ceoAnswers (one per question) and ceoNote, or ceo_rejected with ceoNote. The next task receives them.
  • API: POST /v1/runs/self/ask-ceo (run:self)
Error When What happens
ceo_questions_invalid No questions, or a question with a repeated or invalid number, no text or no ctoRecommendation. Recorded as ceo_questions_invalid:<why>, before any gate opens. Handed back

Example:

{
  "questions": [
    {
      "number": 1,
      "question": "Should the export include archived reports?",
      "context": "Customers asked for both; including them doubles the file size.",
      "ctoRecommendation": "No: archived reports are rarely opened, and a filter can add them later."
    }
  ],
  "context": "Scoping https://github.com/acme/app/issues/12"
}

os.list_recommendations

List self-improvement recommendations: pending, decided, task-record, or the open issues.

What the model reads:

List self-improvement recommendations. view: pending (default, oldest first, to decide), decided (the last 60 days across every task, to spot recurrence), task-record (actioned for implementation in the last 14 days, to follow up their issues), or issues (every open self-improvement issue in the repo, read by the harness: look here before filing, never rely on a search). Optional limit.
Field Type Required Description
view string, one of pending, decided, task-record, issues no
limit integer, at least 1 no

No other fields are accepted.

  • Effect: read.
  • Side effects: View issues refreshes issueOutcome on recommendations whose issue was still open, and returns what ended since the last call as settled.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • A repo input, for view issues only.
    • A GitHub credential in the run's cascade, for view issues only: the open issues are read with it.
  • API: POST /v1/runs/self/tools/os.list_recommendations (run:self)
Error When What happens
view_invalid view is not one of the four. Tool error
repo_required View issues on a run with no repo input. Tool error
issues_unavailable View issues could not read the repository. It fails rather than report an empty backlog. Tool error

Example:

{
  "view": "pending",
  "limit": 10
}

Example:

{
  "view": "issues"
}

os.get_recommendation

One recommendation with its evidence.

What the model reads:

One recommendation with its evidence: the source run (status, recent events, failed tool calls), task versions newer than the one the source run ran, and every other recommendation for the same task. Pass id.
Field Type Required Description
id string yes

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.get_recommendation (run:self)
Error When What happens
id_required id is missing. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000001"
}

os.decide_recommendation

Decide one pending recommendation: decline, actioned, or implement.

What the model reads:

Decide one pending recommendation. decision: decline (reason required), actioned (issueUrl of the GitHub issue or PR that takes the work, and lane: os, repo-commands or local-stack), or implement (the task-record lane: issueUrl of the issue that tracks it; starts the implementer). issueUrl must be in this repo and must not be a closed issue or an unmerged closed pull request. Pass id, decision, reason, issueUrl, lane.
Field Type Required Description
id string yes
decision string, one of decline, actioned, implement yes
reason string yes
issueUrl string no https://github.com/&lt;owner>/<repo>/issues/<n> or /pull/<n>
lane string, one of os, repo-commands, local-stack, task-record no

No other fields are accepted.

  • Effect: write.
  • Side effects: implement starts the implementing task.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call: the same call sent twice decides once and returns the first answer. A different call on a decided recommendation is refused.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.decide_recommendation (run:self)
Error When What happens
id_required id is missing. Tool error
recommendation_not_pending:<status> Someone else already decided it. Only a pending recommendation can be decided. Tool error
decision_invalid decision is not one of the three. Tool error
lane_invalid actioned without a valid lane. Tool error
task_record_uses_implement actioned with lane task-record: decide implement instead. Tool error
issue_url_invalid implement without a GitHub issue URL. Tool error
issue_url_wrong_repo issueUrl is not in the run's repo. Tool error
issue_closed issueUrl is a closed issue. Tool error
pull_request_closed_unmerged issueUrl is a pull request closed without merging. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000001",
  "decision": "decline",
  "reason": "Not reproduced on the source run"
}

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000002",
  "decision": "actioned",
  "reason": "Covered by the retry change",
  "issueUrl": "https://github.com/acme/app/issues/12",
  "lane": "os"
}

os.mail_list

List the assigned member's inbox, newest first: metadata and a snippet, no bodies.

What the model reads:

List mail in the inbox of the member doing this run, newest first. Metadata and a snippet only, no bodies: read one with os.mail_read. Optional unreadOnly (only messages not yet opened), label (only mail carrying that label), handledState (only mail in that state), threadId (every mail in one conversation), order (desc, the default, or asc), page and limit (default 25, max 200). Filters combine: label VIP with handledState unhandled lists only unhandled VIP mail. A full row is large: about 30 fit in one result. To work through a long backlog pass compact true, which returns only id, fromAddress, subject, receivedAt, labels and handledState, so about 100 rows fit.
Field Type Required Description
unreadOnly boolean no Only messages nobody has opened yet
label string no Only mail carrying this label, e.g. VIP
handledState string, one of unhandled, replied, skipped, snoozed, escalated no Only mail in this handled state
threadId string no Only mail in this conversation, from a row’s threadId
order string, one of desc, asc no By receivedAt. Default desc
page integer, at least 1 no
limit integer, 1 to 200 no
compact boolean no Only id, fromAddress, subject, receivedAt, labels and handledState on each row

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { data, total, page, limit, pages }. Each row has id, fromAddress, fromName, subject, snippet, receivedAt, readAt, hasAttachments, threadKey (the same on every message in one conversation), direction (inbound when it arrived, outbound when the member sent it), to, cc, threadId, labels (free text, [] when none), handledState (unhandled, replied, skipped, snoozed or escalated) and snoozedUntil (only when snoozed). With compact, each row has only id, fromAddress, subject, receivedAt, labels and handledState.
  • API: GET /v1/members/:id/emails (members:read)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
handled_state_invalid handledState is not one of the handled states. Tool error

Example:

{
  "unreadOnly": true,
  "limit": 10
}

Example:

{
  "handledState": "unhandled",
  "order": "asc",
  "compact": true,
  "limit": 100
}

os.mail_read

Read one message from the assigned member's inbox.

What the model reads:

Read one message from the assigned member’s inbox: sender, subject, text and sanitised HTML bodies, and attachment names (not their contents). Reading marks it read for the member too. Pass id from os.mail_list.
Field Type Required Description
id string yes A message id from os.mail_list

No other fields are accepted.

  • Effect: read.
  • Side effects: Marks the message read for the member, the same as opening it in the portal.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: The message: sender, subject, textBody, sanitised htmlBody, receivedAt, readAt, attachments (names and sizes, not contents), to, cc, threadId, labels (free text, [] when none), handledState (unhandled, replied, skipped, snoozed or escalated) and snoozedUntil (only when snoozed).
  • API: GET /v1/members/:id/emails/:emailId (members:read)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
email_not_found The id is not in this member's mailbox, including another member's message. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000003"
}

os.mail_reply

Reply to a stored mail from the address it arrived at, once the CEO approves.

What the model reads:

Reply to one message in the assigned member’s inbox. Pass id from os.mail_list and the reply as text (optional html for a formatted body); the subject, the recipient and the sending address are taken from the message being answered, so a mail sent to the member’s Zero Human address is answered from that address. The reply threads under the original in the recipient’s mail client. Every send waits for the CEO to approve this exact payload, and approval sends that and nothing else. The sent reply is filed in the member’s mailbox in the original’s thread, and the original is marked read and moves to handled state replied.
Field Type Required Description
id string yes A message id from os.mail_list: the mail this answers
text string yes The reply, as plain text
html string no Optional HTML body. Plain text is sent either way

No other fields are accepted.

  • Effect: write.
  • Side effects: Files the sent reply in the member’s mailbox, in the original’s thread, marks the original read and, once sent, moves it to handled state replied (clearing any snooze).
  • Retry: Not retry-safe: a repeat acts again. A repeat sends a second reply. Call it again only if the first reported a refusal.
  • Called by: a run, the API.
  • Needs:
    • A gate: the CEO decides every call, whatever the task declares.
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { status, id }: sent, or send_failed with the provider’s error when it did not go out. id is the copy filed in the mailbox either way.
  • API: POST /v1/runs/self/tools/os.mail_reply (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
email_not_found The id is not in this member's mailbox, including another member's message. Tool error
recipient_invalid The stored message names no address that can be replied to. Tool error
sender_not_own_mailbox The address the original arrived at does not deliver to this member's mailbox. Tool error
gate_summary_required The run has not written the recipient and gist the CEO should read on the approval card. Handed back

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000003",
  "text": "Thursday at 10:00 works."
}

os.mail_send

Send a new mail from the assigned member’s mailbox, once the CEO approves.

What the model reads:

Send a new message from the assigned member’s mailbox: to, subject and text (optional html). It goes out from the member’s own address unless from names another address that delivers to the same mailbox — any other sender is refused, whatever the payload says. Every send waits for the CEO to approve this exact payload, and approval sends that and nothing else. The sent message is filed in the member’s mailbox. To answer a message already in the inbox, use os.mail_reply instead, so it threads.
Field Type Required Description
to string yes The recipient, one email address
subject string yes
text string yes The message, as plain text
html string no Optional HTML body. Plain text is sent either way
from string no Another address that delivers to this member’s mailbox. Defaults to their own

No other fields are accepted.

  • Effect: write.
  • Side effects: Files the sent message in the member’s mailbox.
  • Retry: Not retry-safe: a repeat acts again. A repeat sends a second message. Call it again only if the first reported a refusal.
  • Called by: a run, the API.
  • Needs:
    • A gate: the CEO decides every call, whatever the task declares.
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { status, id }: sent, or send_failed with the provider’s error when it did not go out. id is the copy filed in the mailbox either way.
  • API: POST /v1/runs/self/tools/os.mail_send (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
recipient_invalid to is not an email address. Tool error
sender_not_own_mailbox from names an address that does not deliver to this member's mailbox. Leave from out to send from the member's own address. Tool error

Example:

{
  "to": "[email protected]",
  "subject": "Thursday",
  "text": "Thursday at 10:00 works for the CEO."
}

os.mail_set_label

Set the labels on a mail in the assigned member’s inbox.

What the model reads:

Set the labels triage gives one message in the assigned member’s inbox. Pass id from os.mail_list and labels, the whole set the mail should carry: it replaces whatever labels it had, and an empty list clears them. Labels are free text (e.g. VIP, Needs reply); there is no fixed list to choose from. List mail by label with os.mail_list.
Field Type Required Description
id string yes A message id from os.mail_list
labels array of string yes Every label the mail should carry. Replaces the current set

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { id, labels }: the mail and the labels it now carries.
  • API: POST /v1/runs/self/tools/os.mail_set_label (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
email_not_found The id is not in this member's mailbox, including another member's message. Tool error
labels_invalid labels is not a list of non-empty strings of at most 64 characters. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000003",
  "labels": [
    "VIP"
  ]
}

os.mail_set_handled

Record whether a mail in the assigned member’s inbox has been handled.

What the model reads:

Record where one message in the assigned member’s inbox stands. Pass id from os.mail_list and handledState: unhandled, replied, skipped, snoozed or escalated. A mail is in one state at a time. snoozed needs snoozedUntil, the date and time it comes back (ISO 8601); every other state takes no date and clears any snooze. os.mail_reply moves the mail it answers to replied on its own.
Field Type Required Description
id string yes A message id from os.mail_list
handledState string, one of unhandled, replied, skipped, snoozed, escalated yes
snoozedUntil string no ISO 8601 date and time the mail comes back. Only with handledState snoozed

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { id, handledState, snoozedUntil }: the state the mail is now in.
  • API: POST /v1/runs/self/tools/os.mail_set_handled (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
email_not_found The id is not in this member's mailbox, including another member's message. Tool error
handled_state_invalid handledState is not one of the handled states. Tool error
snoozed_until_required handledState is snoozed and snoozedUntil is missing or not a date. Tool error
snoozed_until_not_allowed snoozedUntil was given with a handledState other than snoozed. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000003",
  "handledState": "snoozed",
  "snoozedUntil": "2026-10-01T09:00:00Z"
}

os.mail_set_many

Set the labels, the handled state, or both on many mails in the assigned member’s inbox at once.

What the model reads:

Record the same triage decision on many messages in the assigned member’s inbox in one call. Name the mail one way: ids (up to 200 message ids from os.mail_list), or fromAddress (every unhandled mail that arrived from that exact sender address; mail already handled and mail the member sent are never touched). Then pass labels (the whole set each mail should carry, replacing what it had), handledState (unhandled, replied, skipped, snoozed or escalated; snoozed needs snoozedUntil), or both. Use it to close a run of look-alike mail without reading each one. An id that is not in this mailbox changes nothing and comes back in notFound.
Field Type Required Description
ids array of string, at least 1 no Up to 200 message ids from os.mail_list. Not together with fromAddress
fromAddress string no A sender address, matched exactly: every unhandled mail that arrived from it. Not together with ids
labels array of string no Every label each mail should carry. Replaces the current set
handledState string, one of unhandled, replied, skipped, snoozed, escalated no
snoozedUntil string no ISO 8601 date and time the mail comes back. Only with handledState snoozed

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { updated, notFound }: how many mails were changed, and the ids given that are not in this mailbox (always [] when selecting by fromAddress).
  • API: POST /v1/runs/self/tools/os.mail_set_many (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
member_not_found The run's member no longer exists. Tool error
mail_selector_invalid Neither ids nor fromAddress was given, or both were. Tool error
mail_ids_invalid ids is not a list of 1 to 200 non-empty message ids. Tool error
mail_change_required Neither labels nor handledState was given, so there is nothing to record. Tool error
labels_invalid labels is not a list of non-empty strings of at most 64 characters. Tool error
handled_state_invalid handledState is not one of the handled states. Tool error
snoozed_until_required handledState is snoozed and snoozedUntil is missing or not a date. Tool error
snoozed_until_not_allowed snoozedUntil was given with a handledState other than snoozed. Tool error

Example:

{
  "fromAddress": "[email protected]",
  "labels": [
    "Newsletter"
  ],
  "handledState": "skipped"
}

Example:

{
  "ids": [
    "5f0c1d2e-0000-4000-8000-000000000003",
    "5f0c1d2e-0000-4000-8000-000000000004"
  ],
  "handledState": "skipped"
}

os.mail_quiet_threads

The threads the assigned member replied to that have gone quiet, oldest first, at most ten.

What the model reads:

List the conversations in the assigned member’s mailbox where the member had the last word and the correspondent has not answered for three clear weekdays, counted in the enterprise’s timezone. A thread already chased or snoozed today, one snoozed until a later date, and one whose original names no address a reply could go to are left out, so every row is one to act on today. At most the ten oldest.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { data }: each row has threadKey, replyToEmailId (the newest inbound message: pass it to os.mail_reply as id), correspondent, subject and lastOutboundAt. An empty list when nothing is quiet or the member has no mailbox.
  • API: POST /v1/runs/self/tools/os.mail_quiet_threads (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error

Example:

{}

os.mail_mark_chased

Record that a quiet thread was answered today, so no second run today raises it again.

What the model reads:

Record that one thread from os.mail_quiet_threads has been dealt with today. Call it before os.mail_reply on that thread: the reply waits for the CEO, and a second run the same day must not propose a second follow-up while the first is still waiting. The thread is offered again on a later weekday if it is still quiet.
Field Type Required Description
threadKey string yes A threadKey from os.mail_quiet_threads

No other fields are accepted.

  • Effect: write.
  • Side effects: Stores when the thread was last chased on the member's chase record for it. Nothing is sent.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { threadKey, chasedAt }.
  • API: POST /v1/runs/self/tools/os.mail_mark_chased (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
thread_not_found The threadKey names no conversation in this member's mailbox, including another member's. Tool error

Example:

{
  "threadKey": "<[email protected]>"
}

os.mail_snooze

Leave a quiet thread alone until a date, instead of drafting a follow-up.

What the model reads:

Snooze one thread from os.mail_quiet_threads until a later date (YYYY-MM-DD or an ISO timestamp): it is not offered again until that date has passed or the member writes in the thread again, and it counts as dealt with today. Use it instead of a follow-up when chasing now would be premature. Nothing is sent.
Field Type Required Description
threadKey string yes A threadKey from os.mail_quiet_threads
until string yes When to raise it again: a date or timestamp after now

No other fields are accepted.

  • Effect: write.
  • Side effects: Stores the snooze on the member's chase record for the thread. Nothing is sent.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • The run's assigned member. The mailbox is always theirs: a memberId in the payload is ignored, and an OS-owned task has no mailbox.
  • Returns: { threadKey, snoozedUntil }.
  • API: POST /v1/runs/self/tools/os.mail_snooze (run:self)
Error When What happens
no_assigned_member The run has no assigned member, so no mailbox. Tool error
thread_not_found The threadKey names no conversation in this member's mailbox, including another member's. Tool error
snooze_until_invalid until is not a date, or is not after now. Tool error

Example:

{
  "threadKey": "<[email protected]>",
  "until": "2026-10-12"
}

os.escalate_mail

The gate a mail that needs the CEO opens: the mail, why it matters, and what is suggested.

What the model reads:

Escalate a mail the CEO has to see (a VIP, something sensitive, or a question only they can answer), then stop. The gate carries the mail, why it matters and which of its three choices you suggest. It appears on the gates page and in the assigned member’s chat thread, where the CEO chooses Reply, Snooze or Leave it. Reply and Snooze end this run with outcome ceo_approved, the choice as ceoChoice (reply or snooze), this mail as mailId, and ceoNote: what the reply should say, or the ISO 8601 time the snooze ends. Leave it cancels this run and archives the mail. Call it last.
Field Type Required Description
id string yes The mail the escalation is about
mailId string no The same id, under the key both mail gates use
subject string yes
fromAddress string yes
summary string yes Why this mail needs the CEO
suggestion string yes Which choice you suggest (Reply, Snooze or Leave it) and why, in one or two sentences

No other fields are accepted.

  • Effect: write.
  • Side effects: Opens one CEO gate for this run and parks the run until it is decided.
  • Retry: Not retry-safe: a repeat acts again. Escalating the same mail again opens a second gate carrying its own summary, so the CEO is asked twice. Whether a mail has already been escalated is triage’s to track, not this gate’s.
  • Called by: a run, the OS itself, on nobody’s call.
  • Needs:
    • A gate: the CEO decides every call, whatever the task declares.
    • The gate offers these choices in place of Approve and Reject. Reply (reply): A reply is drafted and comes back to you to approve before anything is sent. Snooze (snooze): Nothing is sent. The mail is set aside until the time you pick. Leave it (leave): No reply is sent and the mail is archived.
  • Returns: The CEO’s choice, to the task that runs next: ceoChoice (reply or snooze), ceoNote and mailId. Leave it hands nothing on: the run is cancelled and the mail archived.

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000003",
  "subject": "Renewal before Friday",
  "fromAddress": "[email protected]",
  "summary": "A VIP contact wants to renew before Friday and has asked for a call.",
  "suggestion": "Reply: confirm the renewal terms and offer Thursday for the call."
}

os.mail_reply_draft

The gate a drafted reply opens, so no reply leaves without a decision on it.

What the model reads:

Nobody calls this: the OS opens this gate itself when the CEO’s instruction on an escalated mail is to reply. It carries the mail the reply answers and the drafted body, and waits for a decision before anything is sent.
Field Type Required Description
mailId string yes The mail this reply answers
body string yes The drafted reply, as it would be sent

No other fields are accepted.

  • Effect: write.
  • Retry: Not retry-safe: a repeat acts again. A second draft for the same mail opens a second gate, decided on its own: nothing folds it into the first.
  • Called by: the OS itself, on nobody’s call.
  • Returns: The decision on the draft: approved and it may be sent, or not, with the decider's note.

Example:

{
  "mailId": "5f0c1d2e-0000-4000-8000-000000000003",
  "body": "Thursday works — I will send an invitation shortly."
}

os.list_kpis

List the signed-off KPIs the run's own team owns and those its task is the source of, with their lights.

What the model reads:

List the KPIs this run may read: its task's team KPIs, and any KPI whose source is this run's task, whichever layer owns it. Only KPIs a person has signed off are listed. Each row carries its target (value, direction, AMBER band, on-target-worsening rule), cadence, owner (layer and id), source (kind and ref), latest reading, current light and whether it is overdue. The light is null when the KPI has no target or no reading yet. No arguments. Never another team's KPI unless its source is this run's task, and never another enterprise's.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Returns: { data }: one row per KPI, each with id, name, ownerLayer (company, team, role, task, or null for a company KPI set before layers), ownerId, sourceKind (task or person), sourceRef (the task or person id), target, cadenceMinutes, light, overdue and lastReading (id, value, takenAt, recordedBy, runId), or null before the first reading.
  • API: POST /v1/runs/self/tools/os.list_kpis (run:self)

Example:

{}

os.record_kpi_reading

Record a reading for a KPI whose source is this run's task.

What the model reads:

Record one reading for the KPI named by kpiId (from os.list_kpis). Only a run of the task that KPI names as its source (sourceKind task, sourceRef this run's task) may record one, and only once a person has signed the KPI off. Pass value and, optionally, takenAt (ISO 8601; defaults to now). No other field is accepted: no os.* tool lets a run change a target, band, cadence or source. The reading is recorded as taken by this run (`recordedBy: run:<runId>`); a run cannot record on another run's behalf. It lands in the same history HQ's board and the KPI readings API read, so the light they show moves with it.
Field Type Required Description
kpiId string yes A KPI id from os.list_kpis
value number yes
takenAt string no ISO 8601. Defaults to now.

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call, like every other os.* write tool: a repeat of the same call records once and returns the same reading.
  • Called by: a run, the API.
  • Returns: The recorded reading and the KPI it landed on: kpiId, name, readingId, value, takenAt, recordedBy and runId (this run), target, and the KPI’s light as it now stands.
  • API: POST /v1/runs/self/tools/os.record_kpi_reading (run:self)
Error When What happens
kpi_id_required kpiId is missing. Tool error
value_required value is missing or not a number. Tool error
taken_at_invalid takenAt is not a timestamp anything can read. Tool error
kpi_not_found kpiId does not name a KPI this run may record: another enterprise's, one whose source is not this run's task, or one nobody has signed off yet. Tool error

Example:

{
  "kpiId": "5f0c1d2e-0000-4000-8000-000000000009",
  "value": 42
}

os.get_enterprise_kpis

The company's KPI targets, bands, cadence and source.

What the model reads:

The company's KPI targets as set: unit, target and direction, AMBER band, cadence and source. A KPI waiting for a target has targetValue null.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: the API, the OS MCP server.
  • API: GET /v1/enterprise/kpi-targets (enterprise:read)

os.set_enterprise_kpis

Replace the company's KPI target list.

What the model reads:

Replace the company's complete KPI target list. A target needs its unit, direction, cadence and source; a no-target KPI is accepted. Uses the same validation as the portal.
Field Type Required Description
kpis array of object yes The complete KPI target list. Every item is validated by the API.
kpis[].id string no
kpis[].name string yes
kpis[].unit string no
kpis[].targetValue number no
kpis[].targetDirection string, one of at_least, at_most no
kpis[].amberBand number, at least 0 no
kpis[].amberBandType string, one of absolute, percentage no
kpis[].onTargetWorseningRule boolean no
kpis[].cadenceMinutes integer, at least 1 no
kpis[].sourceKind string, one of task, person no
kpis[].sourceRef string no
kpis[].isKey boolean no

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state.
  • Called by: the API, the OS MCP server.
  • API: PATCH /v1/enterprise/kpi-targets (enterprise:write)
Error When What happens
invalid_kpis A KPI is incomplete or malformed. Tool error

os.get_team_kpis

A team's KPI targets, including its key KPI.

What the model reads:

A team's own KPI targets as set, including which one is its key KPI. This is the target list, not the role and task KPI tree.
Field Type Required Description
teamId string yes The team whose KPI targets are read.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: the API, the OS MCP server.
  • API: GET /v1/teams/:teamId/kpi-targets (teams:read)
Error When What happens
team_not_found The team is not in this enterprise. Tool error

os.set_team_kpis

Replace a team's KPI target list and key KPI.

What the model reads:

Replace a team's complete KPI target list. Two key KPIs are refused; a list with none marked is stored with no key KPI. Uses the same validation as the portal.
Field Type Required Description
teamId string yes The team whose KPI targets are replaced.
kpis array of object yes The complete KPI target list. Every item is validated by the API.
kpis[].id string no
kpis[].name string yes
kpis[].unit string no
kpis[].targetValue number no
kpis[].targetDirection string, one of at_least, at_most no
kpis[].amberBand number, at least 0 no
kpis[].amberBandType string, one of absolute, percentage no
kpis[].onTargetWorseningRule boolean no
kpis[].cadenceMinutes integer, at least 1 no
kpis[].sourceKind string, one of task, person no
kpis[].sourceRef string no
kpis[].isKey boolean no

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state.
  • Called by: the API, the OS MCP server.
  • API: PATCH /v1/teams/:teamId/kpi-targets (teams:write)
Error When What happens
team_not_found The team is not in this enterprise. Tool error
invalid_kpis A KPI is incomplete or two KPIs are marked key. Tool error

os.set_context

Store a JSON value for the enterprise under a key, so a later run can read it.

What the model reads:

Store value at key for this enterprise. A later call with the same key replaces the value. Any later run of any task in the enterprise can read it, and a task version that declares a prefix of the key receives it in input.context when a run starts. value is JSON and at most 16KB. This is bookkeeping between runs, not memory, and not a place for a secret. A repeat of the same tool call returns the value that call stored and does not write again.
Field Type Required Description
key string yes A short key, such as sitemap:https://example.com/pricing. No spaces. Not a secret.
value any yes Any JSON value, at most 16KB. Not a secret.

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call, like every other os.* write tool: a repeat of the same call stores once and returns the value that call stored.
  • Called by: a run, the API.
  • Returns: { key, value }: the key and the JSON value now stored for it.
  • API: POST /v1/runs/self/tools/os.set_context (run:self)
Error When What happens
key_required key is missing. Tool error
key_invalid key is empty, longer than 512 characters, or contains whitespace or a control character. Tool error
value_required value is missing. Tool error
value_not_json value is not JSON. Tool error
value_too_large value is over 16KB. Tool error

Example:

{
  "key": "sitemap:https://example.com/pricing",
  "value": {
    "status": "absorbed"
  }
}

os.get_context

Read one enterprise context value, or null when the key has never been set.

What the model reads:

Read the JSON value stored at key for this enterprise. Returns value null when the key has never been set. A run whose task declares a prefix of the key already has it in input.context; this reads one key that was not loaded, or a key another run has changed since this run started.
Field Type Required Description
key string yes A short key, such as sitemap:https://example.com/pricing. No spaces. Not a secret.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Returns: { key, value }. value is null when the key has never been set.
  • API: POST /v1/runs/self/tools/os.get_context (run:self)
Error When What happens
key_required key is missing. Tool error
key_invalid key is empty, longer than 512 characters, or contains whitespace or a control character. Tool error

Example:

{
  "key": "sitemap:https://example.com/pricing"
}

os.delete_context

Remove one enterprise context key. A missing key is a success.

What the model reads:

Remove key from this enterprise. A key that is not there is a success. Used when the thing the key tracked has gone, such as a URL that left the sitemap. A repeat of the same tool call returns the result that call stored and does not delete a key a later call has set.
Field Type Required Description
key string yes A short key, such as sitemap:https://example.com/pricing. No spaces. Not a secret.

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call, like every other os.* write tool: a repeat of the same call removes the key once and returns that call’s result.
  • Called by: a run, the API.
  • Returns: { key, deleted }. deleted is false when the key was not there.
  • API: POST /v1/runs/self/tools/os.delete_context (run:self)
Error When What happens
key_required key is missing. Tool error
key_invalid key is empty, longer than 512 characters, or contains whitespace or a control character. Tool error

Example:

{
  "key": "sitemap:https://example.com/pricing"
}

kpi.record_reading

Record one reading of a KPI by hand: the value, when it was taken, and who or what took it.

What the model reads:

Record one reading of a KPI by hand: the value, when it was taken, and who or what took it. Every reading is kept — correcting a wrong one means recording a new reading, never editing or deleting the old one — so a KPI history shows how it got to where it is. The light and the trend are read off the readings, so recording one is what moves them. A KPI nobody has signed off takes no reading: it is refused with 409 kpi_not_signed_off.
Field Type Required Description
kpiId string yes The KPI the reading belongs to.
value number yes The number read, in the KPI's own unit.
takenAt string yes When the reading was taken, as an ISO 8601 timestamp, e.g. 2026-01-08T00:00:00Z.
recordedBy string yes Who or what took the reading: a member id, or the agent or tool that read it.

No other fields are accepted.

  • Effect: write.
  • Retry: Not retry-safe: a repeat acts again. Every call appends a reading, so a repeat records a second one.
  • Called by: the API, the OS MCP server.
  • Returns: The reading as recorded: id, kpiId, value, takenAt (ISO 8601) and recordedBy.
  • API: POST /v1/kpis/:kpiId/readings (kpis:write)
Error When What happens
kpi_not_found kpiId names no KPI in this enterprise, another enterprise's included. Tool error
kpi_not_signed_off Nobody has signed the KPI off yet, so it takes no reading. Tool error

Example:

{
  "kpiId": "5f0c1d2e-0000-4000-8000-000000000009",
  "value": 3.5,
  "takenAt": "2026-01-08T00:00:00Z",
  "recordedBy": "weekly-metrics-export"
}

kpi.list_readings

One KPI's readings, newest first, corrected ones included.

What the model reads:

One KPI's readings, newest first: the value, when it was taken and who or what took it. This is the whole history behind the KPI's light and its trend, including any reading a later one corrected.
Field Type Required Description
kpiId string yes The KPI whose readings to list.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: the API, the OS MCP server.
  • Returns: Every reading of the KPI, newest by takenAt first, each with id, kpiId, value, takenAt and recordedBy.
  • API: GET /v1/kpis/:kpiId/readings (kpis:read)
Error When What happens
kpi_not_found kpiId names no KPI in this enterprise, another enterprise's included. Tool error

Example:

{
  "kpiId": "5f0c1d2e-0000-4000-8000-000000000009"
}

role.list_kpis

A role's KPIs, oldest first, with the team KPI each serves, its sign-off, light, trend and readings.

What the model reads:

A role's KPIs, oldest first: each one's target, direction, AMBER band, cadence, source, the team KPI it serves (servesName names it; served is false for one that serves none), whether a person has signed it off, its light and trend (null until it is signed off) and its readings. Not found for a role in another enterprise.
Field Type Required Description
roleId string yes The role whose KPIs to list.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: the API, the OS MCP server.
  • API: GET /v1/roles/:roleId/kpis (roles:read)
Error When What happens
role_not_found roleId names no role in this enterprise, another enterprise's included. Tool error

role.define_kpi

Propose a KPI on a role, waiting for a person to sign it off.

What the model reads:

Propose a KPI on a role, with the same fields as a KPI on any other layer. It waits for a person to sign it off, and until then draws no light and takes no reading. A KPI may serve only a KPI of the role’s own team (400 role_kpi_serves_other_team otherwise); leave servesKpiId out for one that serves none. The response is the role's KPIs after the change.
Field Type Required Description
roleId string yes The role the KPI belongs to.
name string yes What is measured, up to 200 characters.
targetValue number no The target, in the KPI’s own unit. Set it with targetDirection, or leave both out for no target.
targetDirection string, one of at_least, at_most no at_least when higher is better, at_most when lower is better.
amberBand number, at least 0 no How far past the target still reads AMBER rather than RED.
onTargetWorseningRule boolean no true to read AMBER when on target but worse than the reading before.
cadenceMinutes integer, at least 1 no How often it is read, in minutes; a reading older than this is overdue.
servesKpiId string no The KPI of the role’s own team this one serves.
sourceKind string, one of task, person no Where readings come from: task (a task records them) or person (entered by hand).
sourceRef string no The task id, or the person’s human-user id, sourceKind names.

No other fields are accepted.

  • Effect: write.
  • Retry: Not retry-safe: a repeat acts again. Every call adds a KPI to the role, so a repeat proposes a second one.
  • Called by: the API, the OS MCP server.
  • API: POST /v1/roles/:roleId/kpis (roles:write)
Error When What happens
role_not_found roleId names no role in this enterprise, another enterprise's included. Tool error
role_kpi_serves_other_team servesKpiId names a KPI that is not one of the role’s own team. Tool error

role.edit_kpi

Change a role KPI's fields; a signed-off one only a person changes.

What the model reads:

Change a role KPI's fields; the ones left out keep their value. A KPI a person has signed off is changed only by a person, so this is refused with 403 kpi_signed_off_person_only for it. The response is the role's KPIs after the change.
Field Type Required Description
roleId string yes The role the KPI belongs to.
name string no What is measured, up to 200 characters.
targetValue number no The target, in the KPI’s own unit. Set it with targetDirection, or leave both out for no target.
targetDirection string, one of at_least, at_most no at_least when higher is better, at_most when lower is better.
amberBand number, at least 0 no How far past the target still reads AMBER rather than RED.
onTargetWorseningRule boolean no true to read AMBER when on target but worse than the reading before.
cadenceMinutes integer, at least 1 no How often it is read, in minutes; a reading older than this is overdue.
servesKpiId string no The KPI of the role’s own team this one serves.
sourceKind string, one of task, person no Where readings come from: task (a task records them) or person (entered by hand).
sourceRef string no The task id, or the person’s human-user id, sourceKind names.
kpiId string yes The KPI to change.

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. The same fields set twice leave the KPI as the first call did.
  • Called by: the API, the OS MCP server.
  • API: PATCH /v1/roles/:roleId/kpis/:kpiId (roles:write)
Error When What happens
role_not_found roleId names no role in this enterprise, another enterprise's included. Tool error
role_kpi_not_found kpiId names no KPI still on this role. Tool error
role_kpi_serves_other_team servesKpiId names a KPI that is not one of the role’s own team. Tool error
kpi_signed_off_person_only A person has signed the KPI off, so only a person may change it. Tool error

os.inspect_enterprise

A snapshot of the company: teams, members, roles, and the tasks of the roles each member holds.

What the model reads:

Read the company as it is now: every team (id, name, slug) and every member (id, name, email, persona, roles), with each member’s tasks: the tasks of the roles they hold on that task’s team (not OS tasks). The same snapshot as os.directory. Use this to name who owns what before you assign, route or escalate work. Read-only; pass no arguments.

No declared fields: the schema accepts any object.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Returns: { teams, members }: each team's id, name and slug, and each member's id, name, email, persona, roles, and the tasks of the roles they hold on that task's team (not OS tasks).
  • API: POST /v1/runs/self/tools/os.inspect_enterprise (run:self)

os.directory

Teams, members, roles, and the tasks of the roles each member holds: who to offload work to.

What the model reads:

List teams, members, roles, and each member’s tasks: the tasks of the roles they hold on that task’s team (not OS tasks). Use this to find someone to offload work to.

No declared fields: the schema accepts any object.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the CEO chat, the API.
  • Returns: { teams, members }: each team's id, name and slug, and each member's id, name, email, persona, roles, and the tasks of the roles they hold on that task's team (not OS tasks). The same snapshot as os.inspect_enterprise.
  • API: POST /v1/runs/self/tools/os.directory (run:self)

os.list_blockers

What has stopped: failed, stalled, blocked and spend-paused runs, and stopped pipelines, with who owns each.

What the model reads:

What is stopping work right now, as the Blockers page shows it: failed runs not yet cleared (one page, with failedTotal counting them all), stalled, blocked and spend-paused runs, stopped pipelines, missing model tiers, exhausted LLM capacity, task definition problems, and teams with no lead, no metric or an overdue retro. Read-only: nothing is retried or cleared. Waiting gates are not here; use os.list_open_gates.
Field Type Required Description
failedPage integer, at least 1 no The page of failed runs to read, from 1. Defaults to 1. A page past the last reads the last.
pageSize integer, 1 to 100 no Failed runs per page, up to 100. Defaults to 100 in a run, and to 20 over the API.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API, the OS MCP server.
  • Returns: What the portal shows as blockers. To a run: runs, each stopped run once, oldest first, with runId, the sections it is listed in (failed, stopped, blocked, stalled, paused), status, reason, blockClass, its task, team, team lead and assignee, the repo, issue and pr from its input, since, taskRetries (how many times tasks have retried its chain) and escalation (to whom, toYou, the note, when) once someone escalated it; then failedTotal (every uncleared failed run), the failedPage read and its pageSize, the team-health rows (missingLead, overdueRetro, and kpiHealth: a KPI that needs attention, with its condition (no_key_kpi, no_target, overdue or light), team, kpi, layer, light, trend and owner), spendEnabled and draining. Over the API: the portal rows as they are. Waiting gates are not here: read them with os.list_open_gates.
  • API: GET /v1/blockers (blockers:read)

os.list_open_gates

Every gate waiting on a decision, oldest first: the CEO inbox's own rows.

What the model reads:

Every gate waiting on a decision, oldest first: the run and task it belongs to, the tool call it holds (toolName, payload), its summary when the run wrote one, who owns the decision, when it times out, and whether the run can be sent back with changes requested. Read-only: deciding a gate is not a tool here.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API, the OS MCP server.
  • Returns: Each waiting gate: runId, taskSlug, toolName, owner, payload, summary (when the run wrote one), createdAt and its age.
  • API: GET /v1/gates?status=waiting (gates:read)

os.retry_run

Start a stopped run again, with the same input, so its work moves.

What the model reads:

Retry one stopped run: start a fresh run of the same task with the same input. Only a run on Blockers: failed, blocked, or finished on an outcome nothing follows, and not already cleared or retried. Pass runId and note (one line on why another run should get through, recorded on the run). Tasks may retry one chain 3 times; past that, escalate it.
Field Type Required Description
runId string yes The run id, as os.list_blockers names it
note string yes Why another run should get through, in one line

No other fields are accepted.

  • Effect: write.
  • Side effects: Starts a paid run of the stopped run's task. A blocked run is cancelled as it is retried, and the issue it works is marked in flight again.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call: the same call sent twice starts one run. A retried run is refused afterwards, so a later call starts nothing either.
  • Called by: a run, the API.
  • Returns: { runId, retryRunId, status, taskRetries }: the new run, queued, or pending while its task or member is busy, and how many times tasks have now retried the chain.
  • API: POST /v1/runs/self/tools/os.retry_run (run:self)
Error When What happens
run_id_required runId is missing. Tool error
note_required note is missing or empty. Tool error
run_not_found No run with that id in this enterprise. Tool error
own_run The run named is the one calling. Tool error
not_stopped:<status> The run is not on Blockers: it was cancelled, it finished on an outcome its task follows, or (to retry or clear) it is still active. A stalled or spend-paused run can only be escalated. Tool error
already_retried A later run already retried or routed it. Act on that run instead. Tool error
already_cleared Someone already cleared it. Tool error
retry_limit_reached Tasks have already retried this chain 3 times. Tool error
task_not_runnable The run's task is deprecated, has no current version, or has nobody to run it. Escalate it instead. Tool error

Example:

{
  "runId": "5f0c1d2e-0000-4000-8000-000000000001",
  "note": "Ran out of steps after pushing most of the change; the next run continues from the branch"
}

os.route_run

Send a stopped run's work to another task of its pipeline, instead of running the same task again.

What the model reads:

Route one stopped run: start a run of another task of its pipeline with the same input, in the same execution, when running the same task again would stop the same way. The task is one that already ran on the run's issue or pull request (send it back to an earlier stage), or one the stopped run's task names as a successor (move it on past this stage). Only a run on Blockers, as for os.retry_run. Pass runId, task (the slug to run next) and note (what that task should do, and why; the new run reads it as routedFrom in its input). Routes and retries share one limit: tasks may retry or route one chain 3 times; past that, escalate it.
Field Type Required Description
runId string yes The run id, as os.list_blockers names it
task string yes The slug of the task to run next
note string yes What that task should do with it, and why, in a line or two

No other fields are accepted.

  • Effect: write.
  • Side effects: Starts a paid run of the task named. A blocked run is cancelled as it is routed, and the issue it works is marked in flight again.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Keyed on the tool call: the same call sent twice starts one run. A routed run is refused afterwards, so a later call starts nothing either.
  • Called by: a run, the API.
  • Returns: { runId, routedRunId, task, status, taskRetries }: the new run, queued, or pending while its task or member is busy, and how many times tasks have now retried or routed the chain.
  • API: POST /v1/runs/self/tools/os.route_run (run:self)
Error When What happens
run_id_required runId is missing. Tool error
note_required note is missing or empty. Tool error
run_not_found No run with that id in this enterprise. Tool error
own_run The run named is the one calling. Tool error
not_stopped:<status> The run is not on Blockers: it was cancelled, it finished on an outcome its task follows, or (to retry or clear) it is still active. A stalled or spend-paused run can only be escalated. Tool error
already_retried A later run already retried or routed it. Act on that run instead. Tool error
already_cleared Someone already cleared it. Tool error
task_required task is missing. Tool error
same_task task is the stopped run's own task. Tool error
task_not_in_pipeline No run of that task is on the run's issue or pull request, and its task names no such successor. Tool error
retry_limit_reached Tasks have already retried or routed this chain 3 times. Tool error
task_not_runnable The task named is deprecated, has no current version, or has nobody to run it. Escalate it instead. Tool error

Example:

{
  "runId": "5f0c1d2e-0000-4000-8000-000000000004",
  "task": "write-red-suite",
  "note": "The locked tests never cover the last acceptance criterion; add them, then implement runs on"
}

os.clear_run

Take a stopped run off Blockers without running it again.

What the model reads:

Take one stopped run off Blockers without running it again, because nothing more is needed: its issue or pull request is closed or merged, or it is a leftover of a wait someone already ended. A failed run, or one that finished on an outcome nothing follows, is cleared; a blocked run is cancelled. Pass runId and note (why nothing more is needed, recorded on the run). Its issue stops being marked blocked once no later run exists.
Field Type Required Description
runId string yes The run id, as os.list_blockers names it
note string yes Why nothing more is needed, in one line

No other fields are accepted.

  • Effect: write.
  • Side effects: Removes the blocked label from the run's issue when no later run on it exists.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. The same call sent twice clears once. A cleared run is refused afterwards.
  • Called by: a run, the API.
  • Returns: { runId, status, cleared }: the run as it now stands.
  • API: POST /v1/runs/self/tools/os.clear_run (run:self)
Error When What happens
run_id_required runId is missing. Tool error
note_required note is missing or empty. Tool error
run_not_found No run with that id in this enterprise. Tool error
own_run The run named is the one calling. Tool error
not_stopped:<status> The run is not on Blockers: it was cancelled, it finished on an outcome its task follows, or (to retry or clear) it is still active. A stalled or spend-paused run can only be escalated. Tool error
already_retried A later run already retried or routed it. Act on that run instead. Tool error
already_cleared Someone already cleared it. Tool error

Example:

{
  "runId": "5f0c1d2e-0000-4000-8000-000000000002",
  "note": "The pull request merged by hand on 28 Sep"
}

os.escalate_run

Hand a stopped run to its team's lead, or to the CEO, and record that you did.

What the model reads:

Hand one stopped run to someone who can decide it, and record it on the run so nobody hands it on twice. to: lead (the lead of the run's team, or the COO where it has none; they own the decision from then on) or ceo (record it before you put the question to the CEO with os.ask_ceo). Pass runId, to and note (what is stuck, what was tried, and the move you recommend). os.list_blockers shows the escalation on the row from then on.
Field Type Required Description
runId string yes The run id, as os.list_blockers names it
to string, one of lead, ceo yes
note string yes What is stuck, what was tried, and the move you recommend

No other fields are accepted.

  • Effect: write.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. Escalating a run again to the same person records nothing new.
  • Called by: a run, the API.
  • Returns: { runId, to, memberId, memberName, alreadyEscalated }: who now owns the decision. alreadyEscalated is true when the run was already escalated to them, and nothing new is recorded.
  • API: POST /v1/runs/self/tools/os.escalate_run (run:self)
Error When What happens
run_id_required runId is missing. Tool error
note_required note is missing or empty. Tool error
run_not_found No run with that id in this enterprise. Tool error
own_run The run named is the one calling. Tool error
not_stopped:<status> The run is not on Blockers: it was cancelled, it finished on an outcome its task follows, or (to retry or clear) it is still active. A stalled or spend-paused run can only be escalated. Tool error
already_retried A later run already retried or routed it. Act on that run instead. Tool error
already_cleared Someone already cleared it. Tool error
to_invalid to is not lead or ceo. Tool error
no_lead Nobody leads the run's team (or it has none), and nobody holds the COO role to act as lead. Tool error

Example:

{
  "runId": "5f0c1d2e-0000-4000-8000-000000000003",
  "to": "lead",
  "note": "Loop cap on recover-red-suite (2/2): each lap found the same failing test outside the issue. Recommend closing the loop and filing the test separately."
}

os.read_enterprise

The whole enterprise: teams, charters, roles, members and every task in full.

What the model reads:

Read the whole enterprise a plan is proposed against: every team with its charter, roles and members (persona and roles), the members on no team, and every task, OS-owned ones included, in full: goal, metric, guardrails, tools, gates, spend, successors, its assigneeRole and the members holding it (roleHolderIds; assigneeMemberId when exactly one does), and lastRunStatus. A task is assigned to a role, never pinned to a member. With no arguments it returns everything. When that is too big to hand back (tool_result_truncated), read it in slices, each with its tasks in full: noTeam: true for the members and tasks on no team (OS-owned tasks among them), then team (a team id or slug) for each team, with its roles, members and tasks. When one team is too big, read it role by role: team with role (a role id or slug from allRoles in that team slice) for its tasks that run as that role, and team with noRole: true for its tasks that run as no role. Every slice lists allTeams, the id and slug of every team; every team slice also lists allRoles, every role its tasks can run as. Together the slices cover every member and task. OS-owned tasks only.
Field Type Required Description
team string no A team id or slug: that team, its roles, members and tasks.
noTeam boolean no true: the members and tasks on no team.
role string no With team only. A role id or slug from allRoles: that team's tasks that run as it.
noRole boolean no With team only. true: that team's tasks that run as no role.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.read_enterprise (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error
team_not_found:<team> No team in this enterprise has that id or slug. Read allTeams with noTeam: true. Tool error
team_selector_conflict Both team and noTeam are set. Tool error
role_requires_team role or noRole is set without team: they narrow one team. Tool error
role_not_found:<role> No task on that team can run as a role with that id or slug. The message lists the team's allRoles. Tool error
role_selector_conflict Both role and noRole are set. Tool error

Example:

{}

Example:

{
  "noTeam": true
}

Example:

{
  "team": "engineering"
}

Example:

{
  "team": "engineering",
  "role": "cto"
}

Example:

{
  "team": "engineering",
  "noRole": true
}

os.search_catalog

Published catalog tasks that match a plain-English intent, best first.

What the model reads:

Find published catalog tasks that match a plain-English intent, best match first (at most 10), so a plan proposes something proven instead of inventing it. Scored on the words of the intent found in a task slug or name; an intent of only filler words matches nothing. Pass intent.
Field Type Required Description
intent string yes

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.search_catalog (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error

Example:

{
  "intent": "Triage a new GitHub issue"
}

os.list_tool_bindings

The tool types this enterprise has bound, at which layer, and the tools each exposes.

What the model reads:

List the tool types this enterprise has bound, the layer each is bound at, and the individual tools each one exposes, asked of its MCP server. A proposal may only name tools of a bound type. A server that will not answer lists no tools, and its binding is still reported.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • Needs:
    • Each binding's own credential, to ask its server which tools it exposes.
  • API: POST /v1/runs/self/tools/os.list_tool_bindings (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error

Example:

{}

os.list_built_in_tools

Every built-in tool a run can call, with its summary and whether it reads or writes.

What the model reads:

List every built-in tool a run can call: its name, a one-line summary, and its effect (read or write). A task may declare a built-in tool (os.*, workspace.*, memory.*, web.*) only from this list: saving a task that names any other is refused, as a tool that does not exist (tool_unknown) or one only the CEO chat offers (tool_not_run_callable). A row with gate is a tool a task may hold only if it also declares a CEO gate on the tool gate names: saving it without that gate is refused (gate_required_for_tool). Vendor tools are not here: os.list_tool_bindings lists what the enterprise has bound. Takes no arguments.

Takes no arguments: no fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API, the OS MCP server.
  • Returns: [{ name, summary, effect, gate? }]: one row per built-in tool a run can call, in reference order. gate is only on a tool a task may hold only with a CEO gate, and names the tool that gate is declared on.
  • API: GET /v1/tools/built-in?callable=run (tools:read)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error

Example:

{}

os.read_task

One task in full, its pending recommendations, and the tasks that start it.

What the model reads:

One task in full (as os.read_enterprise reads a task), the improvement recommendations still pending against it, and incomingSuccessors: the tasks whose successors start it. Its own successors are what it starts. Pass taskId.
Field Type Required Description
taskId string yes

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.read_task (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error
task_id_required taskId is missing. Tool error
task_not_found No task in this enterprise has that id. Tool error

Example:

{
  "taskId": "5f0c1d2e-0000-4000-8000-000000000001"
}

os.assess_impact

What points at a role, team, task or member a plan would delete.

What the model reads:

What points at a role, team, task or member a plan would delete. role: the members holding it and the tasks assigned to it. team: the roles, members and tasks on it. task: the tasks whose successors name it and its runs in flight. member: the roles they hold, the teams they sit on and the tasks their roles run. Reported, never refused: the plan says what the deletion breaks. Pass kind and id.
Field Type Required Description
kind string, one of role, team, task, member yes
id string yes

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.assess_impact (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error
id_required id is missing. Tool error
kind_invalid kind is not role, team, task or member. Tool error

Example:

{
  "kind": "role",
  "id": "5f0c1d2e-0000-4000-8000-000000000002"
}

os.write_plan

Write or replace this chat session's plan, checked before the CEO sees it.

What the model reads:

Write this chat session's plan for the CEO, or replace it: one plan per chat session, and a replace carries the version os.read_plan returned. Every item is checked first, and the first problem comes back as the reason: a tool whose type this enterprise has not bound, a slug it already has, a task pinned to a member (set assigneeRole), or a task definition a saved task would fail. Fix it and write again. Pass chatSessionId, summary, ceoIntent, kind (default os_change), items and, to replace, version.
Field Type Required Description
chatSessionId string yes
summary string yes
ceoIntent string no
kind string no
items array of object yes
version integer, at least 1 no

No other fields are accepted.

  • Effect: write.
  • Side effects: Moves the plan to awaiting_ceo.
  • Retry: Idempotent: a repeat of the same call acts once, or leaves the same state. A repeat replaces the same plan with the same content rather than adding a second. A repeat carrying a version the plan has moved past is refused as plan_version_conflict.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.write_plan (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error
chat_session_id_required chatSessionId is missing. Tool error
summary_required summary is missing. Tool error
plan_payload_invalid An item is not one the plan can apply (a missing handle, an unknown type). Tool error
tool_type_not_bound:<type> An item names a tool whose type this enterprise has not bound. Tool error
slug_collision:<slug> An item creates a task, role or team with a slug the enterprise already has. Tool error
assignee_member_removed A create_task item names assigneeMemberId. A task is assigned to a role: set assigneeRole. Tool error
definition_issue:<code> A proposed task fails the check a saved task must pass (an unknown successor, a deterministic task with tools). Tool error
plan_version_conflict version is not the version the plan is at now. Read it back with os.read_plan and write again. Tool error

Example:

{
  "chatSessionId": "chat-42",
  "ceoIntent": "Hire a support lead",
  "summary": "Creates a Support Lead role",
  "kind": "os_change",
  "items": [
    {
      "type": "create_role",
      "handle": "r1",
      "slug": "support-lead",
      "name": "Support Lead"
    }
  ]
}

os.read_plan

This chat session's plan as it stands, with its version.

What the model reads:

Read this chat session's plan as it stands, with the version a replacing os.write_plan must carry, so a feedback round continues it rather than starting again. Null when the session has no plan. With no chatSessionId, the plan this run wrote.
Field Type Required Description
chatSessionId string no

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API.
  • API: POST /v1/runs/self/tools/os.read_plan (run:self)
Error When What happens
os_owned_only The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them. Tool error

Example:

{
  "chatSessionId": "chat-42"
}

os.file_daily_brief

File the CEO's brief in your chat thread: what is waiting on them, and the time on their clock.

What the model reads:

File the CEO's brief as one message in your chat thread with them. The OS composes it: every gate still waiting on the CEO on a run of one of your tasks (what it asks, answered on Gates), an escalations section, and the time on the CEO’s clock in the enterprise timezone. It is filed even when nothing is waiting, and then says nothing needs the CEO. Pass edition: morning or evening. Sends no mail and opens no gate. Call it once per run.
Field Type Required Description
edition string, one of morning, evening no Which sitting the brief is for. Defaults to the edition in your run input.

No other fields are accepted.

  • Effect: write.
  • Side effects: One assistant message in the member's open chat thread with the CEO.
  • Retry: Not retry-safe: a repeat acts again. A repeat files a second brief in the thread: call it once per run.
  • Called by: a run, the API.
  • Returns: { filed: true, edition, messageId, waiting }: waiting is how many things the brief named.
  • API: POST /v1/runs/self/tools/os.file_daily_brief (run:self)
Error When What happens
daily_brief_edition_invalid Neither the call nor the run input names morning or evening. Tool error
daily_brief_timezone_invalid The enterprise's timezone is not one the OS knows. Tool error
daily_brief_no_member The run has no member whose thread the brief could be filed in. Tool error

Example:

{
  "edition": "morning"
}

os.chat_list

List chat threads, most recently active first: who each is with, its state and its last message.

What the model reads:

List chat threads, most recently active first: each thread’s id, its state (idle, replying or closed), and its last message. In a run these are the threads of the member doing the run, and memberId is ignored. Over the API they are every thread of the enterprise, or only one member’s when you pass memberId. No message bodies beyond the last: read a thread with os.chat_read. A conversation opened for one job, such as designing a task, is never listed.
Field Type Required Description
memberId string no Over the API: only threads with this member. A run always lists its own member’s.

No other fields are accepted.

  • Effect: read.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API, the OS MCP server.
  • Needs:
    • In a run, the run's assigned member: the threads are always theirs, and an OS-owned task has none. Over the API and the MCP server, any thread of the enterprise the token can reach.
  • Returns: To a run: { threads }, each with id, status (idle, replying or closed), lastMessage (role, content, createdAt, or null in an empty thread), lastReadAt (when a person last opened it, or null), createdAt and updatedAt. Over the API: the Chat page’s rows as they are, which add the member and the unread and unreadCount of the person’s side.
  • API: GET /v1/chats (chats:read)
Error When What happens
no_assigned_member A run with no assigned member called it, so there is no thread. Tool error

Example:

{}

os.chat_read

Read one chat thread: its messages, oldest first.

What the model reads:

Read one chat thread: its messages, oldest first, each with who wrote it. Pass id from os.chat_list, and optionally limit to read only the newest messages (up to 200). In a run you get what was said (people’s messages and the member’s), the newest 50 unless you pass limit, and reading does not mark the thread read. Over the API you get every row, tool calls included, and reading marks the thread read, as opening it in the portal does.
Field Type Required Description
id string yes A thread id from os.chat_list
limit integer, 1 to 200 no Only the newest messages, up to 200. In a run it defaults to 50; over the API, to all of them.

No other fields are accepted.

  • Effect: read.
  • Side effects: Over the API and the MCP server, marks the thread read, the same as opening it in the portal. In a run, nothing.
  • Retry: Safe: repeating the call changes nothing.
  • Called by: a run, the API, the OS MCP server.
  • Needs:
    • In a run, the run's assigned member: the threads are always theirs, and an OS-owned task has none. Over the API and the MCP server, any thread of the enterprise the token can reach.
  • Returns: To a run: { id, status, total, messages }, where total counts every message said in the thread and each message has id, role (user for a person or an API token, assistant for the member), content, sender (the name of whoever wrote a user message, else null) and createdAt. Over the API: the thread as the portal reads it, with its member and every row, each with its sender.
  • API: GET /v1/chats/:id (chats:read)
Error When What happens
no_assigned_member A run with no assigned member called it, so there is no thread. Tool error
chat_not_found No thread has that id: in a run, including another member's thread and a conversation opened for one job. Tool error

Example:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000021",
  "limit": 20
}

os.chat_write

Write one message in a chat thread, as yourself.

What the model reads:

Write one message in a chat thread, as yourself. Pass content: plain text, shown as written with line breaks kept and no formatting. In a run you write as the member doing the run, for the enterprise’s people to read: it shows as unread until someone opens the thread, nobody replies to the run, and the run carries on. Leave id out to write in the member’s open thread (one is opened if there is none), or pass id from os.chat_list. At most 4000 characters in a run. For a question you need answered, use os.ask_ceo. Over the API, id is required, you write as the person or token calling, and the member replies: the response is the thread with that reply in it. Sends no mail and opens no gate. A repeat writes the message again.
Field Type Required Description
id string no The thread, from os.chat_list. A run may leave it out to write in its member’s open thread. Required over the API.
content string yes The message, as plain text

No other fields are accepted.

  • Effect: write.
  • Side effects: In a run, one message from the member in the thread. Over the API, one message from the caller and a reply turn by the member, which spends on the enterprise’s model keys.
  • Retry: Not retry-safe: a repeat acts again. A repeat writes the message again: call it once for each thing you have to say.
  • Called by: a run, the API, the OS MCP server.
  • Needs:
    • In a run, the run's assigned member: the threads are always theirs, and an OS-owned task has none. Over the API and the MCP server, any thread of the enterprise the token can reach.
  • Returns: To a run: { written: true, id, messageId }, the thread it went in and the message. Over the API: the thread as os.chat_read returns it, with the member’s reply.
  • API: POST /v1/chats/:id/messages (chats:write)
Error When What happens
no_assigned_member A run with no assigned member called it, so there is no thread. Tool error
chat_not_found No thread has that id: in a run, including another member's thread and a conversation opened for one job. Tool error
empty_message content is missing, not text, or blank. Tool error
message_too_long In a run, content is longer than 4000 characters. Tool error
chat_closed The thread is closed. Tool error
chat_replying Over the API, the member is still replying to an earlier message in the thread. Tool error

Example:

{
  "content": "New enquiry\nFrom: [email protected]\nAsking about: the Team plan"
}

Example, Over the API: a person’s message to the member, answered in the response.:

{
  "id": "5f0c1d2e-0000-4000-8000-000000000021",
  "content": "Where did last week’s report go?"
}

os.chat_open

Open a new chat thread with a member.

What the model reads:

Open a new, empty chat thread with a member, and get its id to write in with os.chat_write. Pass memberId. Nothing is said and nothing is spent until a message is written. A run cannot call it: a run’s os.chat_write opens its member’s thread when there is none.
Field Type Required Description
memberId string yes The member to talk to

No other fields are accepted.

  • Effect: write.
  • Retry: Not retry-safe: a repeat acts again. A repeat opens another thread with the same member.
  • Called by: the API, the OS MCP server.
  • Returns: The new thread: its id, memberId, status (idle) and times.
  • API: POST /v1/chats (chats:write)
Error When What happens
memberId_required memberId is missing. Tool error
member_not_found No member of this enterprise has that id. Tool error

Example:

{
  "memberId": "5f0c1d2e-0000-4000-8000-000000000007"
}