[{"data":1,"prerenderedAt":175},["ShallowReactive",2],{"$fi7p7tvr5k3zp":3},{"href":4,"title":5,"description":6,"kind":7,"mark":7,"planned":8,"contributors":9,"provenance":7,"html":10,"headings":11},"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos","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.",null,false,[],"\u003Cp>\u003Cem>Generated from the built-in tools registry. Do not edit by hand.\u003C\u002Fem>\u003C\u002Fp>\n\u003Cp>\u003Ccode>os.*\u003C\u002Fcode> 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 \u003Ccode>tools\u003C\u002Fcode>, like any other tool.\u003C\u002Fp>\n\u003Cp>Shared context (\u003Ccode>os.set_context\u003C\u002Fcode>, \u003Ccode>os.get_context\u003C\u002Fcode>, \u003Ccode>os.delete_context\u003C\u002Fcode>) 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 \u003Ccode>context\u003C\u002Fcode> before the model is called. It is bookkeeping between runs. It is not memory, and it is not a place for a secret.\u003C\u002Fp>\n\u003Cp>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 \u003Ccode>os.*\u003C\u002Fcode> tool. The CEO chat has a few \u003Ccode>os.*\u003C\u002Fcode> tools of its own: see \u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fchat\">Chat tools\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>The company-state tools (\u003Ccode>os.inspect_enterprise\u003C\u002Fcode>, \u003Ccode>os.directory\u003C\u002Fcode>, \u003Ccode>os.list_blockers\u003C\u002Fcode>, \u003Ccode>os.list_open_gates\u003C\u002Fcode>) only read. None of them approves, rejects, retries or clears anything, and calling one never opens or resolves a gate.\u003C\u002Fp>\n\u003Cp>The unblocking tools (\u003Ccode>os.retry_run\u003C\u002Fcode>, \u003Ccode>os.route_run\u003C\u002Fcode>, \u003Ccode>os.clear_run\u003C\u002Fcode>, \u003Ccode>os.escalate_run\u003C\u002Fcode>) 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.\u003C\u002Fp>\n\u003Cp>The brief tool (\u003Ccode>os.file_daily_brief\u003C\u002Fcode>) 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.\u003C\u002Fp>\n\u003Cp>The chat thread tools (\u003Ccode>os.chat_list\u003C\u002Fcode>, \u003Ccode>os.chat_read\u003C\u002Fcode>, \u003Ccode>os.chat_write\u003C\u002Fcode>, \u003Ccode>os.chat_open\u003C\u002Fcode>) 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.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Tool\u003C\u002Fth>\n\u003Cth>What it does\u003C\u002Fth>\n\u003Cth>Effect\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-set-execution-name\">\u003Ccode>os.set_execution_name\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Replace the execution's default title with a human-readable name.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-add-execution-link\">\u003Ccode>os.add_execution_link\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Attach an http(s) link to the execution: an issue, a pull request, a preview.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-set-gate-summary\">\u003Ccode>os.set_gate_summary\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Write what the person approving this run's next gate is deciding on.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-ask-ceo\">\u003Ccode>os.ask_ceo\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Ask the CEO questions, each with a recommended answer, and end the run on their decision.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-recommendations\">\u003Ccode>os.list_recommendations\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>List self-improvement recommendations: pending, decided, task-record, or the open issues.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-get-recommendation\">\u003Ccode>os.get_recommendation\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One recommendation with its evidence.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-decide-recommendation\">\u003Ccode>os.decide_recommendation\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Decide one pending recommendation: decline, actioned, or implement.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-list\">\u003Ccode>os.mail_list\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>List the assigned member's inbox, newest first: metadata and a snippet, no bodies.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-read\">\u003Ccode>os.mail_read\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Read one message from the assigned member's inbox.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-reply\">\u003Ccode>os.mail_reply\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Reply to a stored mail from the address it arrived at, once the CEO approves.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-send\">\u003Ccode>os.mail_send\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Send a new mail from the assigned member’s mailbox, once the CEO approves.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-set-label\">\u003Ccode>os.mail_set_label\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Set the labels on a mail in the assigned member’s inbox.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-set-handled\">\u003Ccode>os.mail_set_handled\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Record whether a mail in the assigned member’s inbox has been handled.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-set-many\">\u003Ccode>os.mail_set_many\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Set the labels, the handled state, or both on many mails in the assigned member’s inbox at once.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-quiet-threads\">\u003Ccode>os.mail_quiet_threads\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The threads the assigned member replied to that have gone quiet, oldest first, at most ten.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-mark-chased\">\u003Ccode>os.mail_mark_chased\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Record that a quiet thread was answered today, so no second run today raises it again.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-snooze\">\u003Ccode>os.mail_snooze\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Leave a quiet thread alone until a date, instead of drafting a follow-up.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-escalate-mail\">\u003Ccode>os.escalate_mail\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The gate a mail that needs the CEO opens: the mail, why it matters, and what is suggested.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-mail-reply-draft\">\u003Ccode>os.mail_reply_draft\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The gate a drafted reply opens, so no reply leaves without a decision on it.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-kpis\">\u003Ccode>os.list_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>List the signed-off KPIs the run's own team owns and those its task is the source of, with their lights.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-record-kpi-reading\">\u003Ccode>os.record_kpi_reading\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Record a reading for a KPI whose source is this run's task.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-get-enterprise-kpis\">\u003Ccode>os.get_enterprise_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The company's KPI targets, bands, cadence and source.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-set-enterprise-kpis\">\u003Ccode>os.set_enterprise_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Replace the company's KPI target list.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-get-team-kpis\">\u003Ccode>os.get_team_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>A team's KPI targets, including its key KPI.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-set-team-kpis\">\u003Ccode>os.set_team_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Replace a team's KPI target list and key KPI.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-set-context\">\u003Ccode>os.set_context\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Store a JSON value for the enterprise under a key, so a later run can read it.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-get-context\">\u003Ccode>os.get_context\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Read one enterprise context value, or null when the key has never been set.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-delete-context\">\u003Ccode>os.delete_context\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Remove one enterprise context key. A missing key is a success.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#kpi-record-reading\">\u003Ccode>kpi.record_reading\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Record one reading of a KPI by hand: the value, when it was taken, and who or what took it.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#kpi-list-readings\">\u003Ccode>kpi.list_readings\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One KPI's readings, newest first, corrected ones included.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#role-list-kpis\">\u003Ccode>role.list_kpis\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>A role's KPIs, oldest first, with the team KPI each serves, its sign-off, light, trend and readings.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#role-define-kpi\">\u003Ccode>role.define_kpi\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Propose a KPI on a role, waiting for a person to sign it off.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#role-edit-kpi\">\u003Ccode>role.edit_kpi\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Change a role KPI's fields; a signed-off one only a person changes.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-inspect-enterprise\">\u003Ccode>os.inspect_enterprise\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>A snapshot of the company: teams, members, roles, and the tasks of the roles each member holds.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-directory\">\u003Ccode>os.directory\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Teams, members, roles, and the tasks of the roles each member holds: who to offload work to.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-blockers\">\u003Ccode>os.list_blockers\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>What has stopped: failed, stalled, blocked and spend-paused runs, and stopped pipelines, with who owns each.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-open-gates\">\u003Ccode>os.list_open_gates\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Every gate waiting on a decision, oldest first: the CEO inbox's own rows.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-retry-run\">\u003Ccode>os.retry_run\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Start a stopped run again, with the same input, so its work moves.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-route-run\">\u003Ccode>os.route_run\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Send a stopped run's work to another task of its pipeline, instead of running the same task again.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-clear-run\">\u003Ccode>os.clear_run\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Take a stopped run off Blockers without running it again.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-escalate-run\">\u003Ccode>os.escalate_run\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Hand a stopped run to its team's lead, or to the CEO, and record that you did.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-read-enterprise\">\u003Ccode>os.read_enterprise\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The whole enterprise: teams, charters, roles, members and every task in full.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-search-catalog\">\u003Ccode>os.search_catalog\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Published catalog tasks that match a plain-English intent, best first.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-tool-bindings\">\u003Ccode>os.list_tool_bindings\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>The tool types this enterprise has bound, at which layer, and the tools each exposes.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-list-built-in-tools\">\u003Ccode>os.list_built_in_tools\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Every built-in tool a run can call, with its summary and whether it reads or writes.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-read-task\">\u003Ccode>os.read_task\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>One task in full, its pending recommendations, and the tasks that start it.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-assess-impact\">\u003Ccode>os.assess_impact\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>What points at a role, team, task or member a plan would delete.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-write-plan\">\u003Ccode>os.write_plan\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Write or replace this chat session's plan, checked before the CEO sees it.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-read-plan\">\u003Ccode>os.read_plan\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>This chat session's plan as it stands, with its version.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-file-daily-brief\">\u003Ccode>os.file_daily_brief\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>File the CEO's brief in your chat thread: what is waiting on them, and the time on their clock.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-chat-list\">\u003Ccode>os.chat_list\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>List chat threads, most recently active first: who each is with, its state and its last message.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-chat-read\">\u003Ccode>os.chat_read\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Read one chat thread: its messages, oldest first.\u003C\u002Ftd>\n\u003Ctd>read\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-chat-write\">\u003Ccode>os.chat_write\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Write one message in a chat thread, as yourself.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fos#os-chat-open\">\u003Ccode>os.chat_open\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Ftd>\n\u003Ctd>Open a new chat thread with a member.\u003C\u002Ftd>\n\u003Ctd>write\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"os-set-execution-name\">\u003Ccode>os.set_execution_name\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Replace the execution's default title with a human-readable name.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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. &quot;GitHub Issue #105 - Work keeps moving while decisions wait on the CEO&quot;.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Shown on Executions instead of the first task name\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The name as stored: whitespace collapsed and trimmed.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Fexecution-name\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode> is missing, or blank once whitespace is trimmed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name_too_long:200\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode> is over 200 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;name&quot;: &quot;Issue #12 - Export the monthly report as CSV&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-add-execution-link\">\u003Ccode>os.add_execution_link\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Attach an http(s) link to the execution: an issue, a pull request, a preview.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Label, e.g. GitHub issue, Pull request, Preview\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>url\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>http(s) URL\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Fexecution-links\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode> is missing, or blank once whitespace is trimmed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name_too_long:200\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode> is over 200 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>url_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>url\u003C\u002Fcode> is missing or blank.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>url_too_long:2000\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>url\u003C\u002Fcode> is over 2000 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>url_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>url\u003C\u002Fcode> is not an \u003Ccode>http:\u003C\u002Fcode> or \u003Ccode>https:\u003C\u002Fcode> URL.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;name&quot;: &quot;Pull request&quot;,\n  &quot;url&quot;: &quot;https:\u002F\u002Fgithub.com\u002Facme\u002Fapp\u002Fpull\u002F34&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-set-gate-summary\">\u003Ccode>os.set_gate_summary\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Write what the person approving this run's next gate is deciding on.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Markdown. Short headed sections of &quot;- &quot; bullets, e.g. &quot;## What changed&quot; then &quot;## Watch out for&quot;. No preamble.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>Called before \u003Ccode>github.merge_pull_request\u003C\u002Fcode>, \u003Ccode>os.mail_reply\u003C\u002Fcode>, which is handed back until it is.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The summary as stored. The gate copies it when it opens, so a later call never rewrites a gate already waiting or decided.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Fgate-summary\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode> is missing or blank.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary_too_short:40\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode> is under 40 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary_too_long:6000\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode> is over 6000 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary_needs_bullets\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode> has no markdown bullet (\u003Ccode>- \u003C\u002Fcode>, \u003Ccode>* \u003C\u002Fcode> or \u003Ccode>1. \u003C\u002Fcode>).\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>gate_summary_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A merge or reply call arrives before this run has written a summary.\u003C\u002Ftd>\n\u003Ctd>Handed back\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;summary&quot;: &quot;## What changed\\n- Adds CSV export to the monthly report\\n\\n## Watch out for\\n- A migration runs on release&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-ask-ceo\">\u003Ccode>os.ask_ceo\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Ask the CEO questions, each with a recommended answer, and end the run on their decision.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>questions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of object, at least 1\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>questions[].number\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>questions[].question\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>One decision, answerable on its own\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>questions[].context\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Why it blocks, and the options you see\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>questions[].ctoRecommendation\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The answer you recommend, and a one-line reason. Never empty.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>context\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>One line on what is blocked, and the issue URL\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>A gate: the CEO decides every call, whatever the task declares.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The CEO's decision, as the run's outcome: \u003Ccode>ceo_approved\u003C\u002Fcode> with \u003Ccode>ceoAnswers\u003C\u002Fcode> (one per question) and \u003Ccode>ceoNote\u003C\u002Fcode>, or \u003Ccode>ceo_rejected\u003C\u002Fcode> with \u003Ccode>ceoNote\u003C\u002Fcode>. The next task receives them.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Fask-ceo\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>ceo_questions_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No \u003Ccode>questions\u003C\u002Fcode>, or a question with a repeated or invalid number, no text or no \u003Ccode>ctoRecommendation\u003C\u002Fcode>. Recorded as \u003Ccode>ceo_questions_invalid:&lt;why&gt;\u003C\u002Fcode>, before any gate opens.\u003C\u002Ftd>\n\u003Ctd>Handed back\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;questions&quot;: [\n    {\n      &quot;number&quot;: 1,\n      &quot;question&quot;: &quot;Should the export include archived reports?&quot;,\n      &quot;context&quot;: &quot;Customers asked for both; including them doubles the file size.&quot;,\n      &quot;ctoRecommendation&quot;: &quot;No: archived reports are rarely opened, and a filter can add them later.&quot;\n    }\n  ],\n  &quot;context&quot;: &quot;Scoping https:\u002F\u002Fgithub.com\u002Facme\u002Fapp\u002Fissues\u002F12&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-list-recommendations\">\u003Ccode>os.list_recommendations\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>List self-improvement recommendations: pending, decided, task-record, or the open issues.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>view\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>pending\u003C\u002Fcode>, \u003Ccode>decided\u003C\u002Fcode>, \u003Ccode>task-record\u003C\u002Fcode>, \u003Ccode>issues\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>limit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> View \u003Ccode>issues\u003C\u002Fcode> refreshes \u003Ccode>issueOutcome\u003C\u002Fcode> on recommendations whose issue was still open, and returns what ended since the last call as \u003Ccode>settled\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>A \u003Ccode>repo\u003C\u002Fcode> input, for view \u003Ccode>issues\u003C\u002Fcode> only.\u003C\u002Fli>\n\u003Cli>A GitHub credential in the run's cascade, for view \u003Ccode>issues\u003C\u002Fcode> only: the open issues are read with it.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.list_recommendations\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>view_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>view\u003C\u002Fcode> is not one of the four.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>repo_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>View \u003Ccode>issues\u003C\u002Fcode> on a run with no \u003Ccode>repo\u003C\u002Fcode> input.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>issues_unavailable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>View \u003Ccode>issues\u003C\u002Fcode> could not read the repository. It fails rather than report an empty backlog.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;view&quot;: &quot;pending&quot;,\n  &quot;limit&quot;: 10\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;view&quot;: &quot;issues&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-get-recommendation\">\u003Ccode>os.get_recommendation\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>One recommendation with its evidence.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.get_recommendation\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000001&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-decide-recommendation\">\u003Ccode>os.decide_recommendation\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Decide one pending recommendation: decline, actioned, or implement.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>decision\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>decline\u003C\u002Fcode>, \u003Ccode>actioned\u003C\u002Fcode>, \u003Ccode>implement\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>reason\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>issueUrl\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002F&amp;lt;owner\">https:\u002F\u002Fgithub.com\u002F&amp;lt;owner\u003C\u002Fa>&gt;\u002F&lt;repo&gt;\u002Fissues\u002F&lt;n&gt; or \u002Fpull\u002F&lt;n&gt;\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>lane\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>os\u003C\u002Fcode>, \u003Ccode>repo-commands\u003C\u002Fcode>, \u003Ccode>local-stack\u003C\u002Fcode>, \u003Ccode>task-record\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> \u003Ccode>implement\u003C\u002Fcode> starts the implementing task.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.decide_recommendation\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>recommendation_not_pending:&lt;status&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Someone else already decided it. Only a \u003Ccode>pending\u003C\u002Fcode> recommendation can be decided.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>decision_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>decision\u003C\u002Fcode> is not one of the three.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>lane_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>actioned\u003C\u002Fcode> without a valid \u003Ccode>lane\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_record_uses_implement\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>actioned\u003C\u002Fcode> with lane \u003Ccode>task-record\u003C\u002Fcode>: decide \u003Ccode>implement\u003C\u002Fcode> instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>issue_url_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>implement\u003C\u002Fcode> without a GitHub issue URL.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>issue_url_wrong_repo\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>issueUrl\u003C\u002Fcode> is not in the run's \u003Ccode>repo\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>issue_closed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>issueUrl\u003C\u002Fcode> is a closed issue.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pull_request_closed_unmerged\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>issueUrl\u003C\u002Fcode> is a pull request closed without merging.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000001&quot;,\n  &quot;decision&quot;: &quot;decline&quot;,\n  &quot;reason&quot;: &quot;Not reproduced on the source run&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000002&quot;,\n  &quot;decision&quot;: &quot;actioned&quot;,\n  &quot;reason&quot;: &quot;Covered by the retry change&quot;,\n  &quot;issueUrl&quot;: &quot;https:\u002F\u002Fgithub.com\u002Facme\u002Fapp\u002Fissues\u002F12&quot;,\n  &quot;lane&quot;: &quot;os&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-list\">\u003Ccode>os.mail_list\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>List the assigned member's inbox, newest first: metadata and a snippet, no bodies.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>unreadOnly\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only messages nobody has opened yet\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>label\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only mail carrying this label, e.g. VIP\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handledState\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>unhandled\u003C\u002Fcode>, \u003Ccode>replied\u003C\u002Fcode>, \u003Ccode>skipped\u003C\u002Fcode>, \u003Ccode>snoozed\u003C\u002Fcode>, \u003Ccode>escalated\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only mail in this handled state\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>threadId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only mail in this conversation, from a row’s threadId\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>order\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>desc\u003C\u002Fcode>, \u003Ccode>asc\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>By receivedAt. Default desc\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>page\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>limit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, 1 to 200\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>compact\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only id, fromAddress, subject, receivedAt, labels and handledState on each row\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ data, total, page, limit, pages }\u003C\u002Fcode>. Each row has \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>fromAddress\u003C\u002Fcode>, \u003Ccode>fromName\u003C\u002Fcode>, \u003Ccode>subject\u003C\u002Fcode>, \u003Ccode>snippet\u003C\u002Fcode>, \u003Ccode>receivedAt\u003C\u002Fcode>, \u003Ccode>readAt\u003C\u002Fcode>, \u003Ccode>hasAttachments\u003C\u002Fcode>, \u003Ccode>threadKey\u003C\u002Fcode> (the same on every message in one conversation), \u003Ccode>direction\u003C\u002Fcode> (\u003Ccode>inbound\u003C\u002Fcode> when it arrived, \u003Ccode>outbound\u003C\u002Fcode> when the member sent it), \u003Ccode>to\u003C\u002Fcode>, \u003Ccode>cc\u003C\u002Fcode>, \u003Ccode>threadId\u003C\u002Fcode>, \u003Ccode>labels\u003C\u002Fcode> (free text, \u003Ccode>[]\u003C\u002Fcode> when none), \u003Ccode>handledState\u003C\u002Fcode> (\u003Ccode>unhandled\u003C\u002Fcode>, \u003Ccode>replied\u003C\u002Fcode>, \u003Ccode>skipped\u003C\u002Fcode>, \u003Ccode>snoozed\u003C\u002Fcode> or \u003Ccode>escalated\u003C\u002Fcode>) and \u003Ccode>snoozedUntil\u003C\u002Fcode> (only when \u003Ccode>snoozed\u003C\u002Fcode>). With \u003Ccode>compact\u003C\u002Fcode>, each row has only \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>fromAddress\u003C\u002Fcode>, \u003Ccode>subject\u003C\u002Fcode>, \u003Ccode>receivedAt\u003C\u002Fcode>, \u003Ccode>labels\u003C\u002Fcode> and \u003Ccode>handledState\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fmembers\u002F:id\u002Femails\u003C\u002Fcode> (\u003Ccode>members:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handled_state_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>handledState is not one of the handled states.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;unreadOnly&quot;: true,\n  &quot;limit&quot;: 10\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;handledState&quot;: &quot;unhandled&quot;,\n  &quot;order&quot;: &quot;asc&quot;,\n  &quot;compact&quot;: true,\n  &quot;limit&quot;: 100\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-read\">\u003Ccode>os.mail_read\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Read one message from the assigned member's inbox.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A message id from os.mail_list\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Marks the message read for the member, the same as opening it in the portal.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The message: sender, subject, \u003Ccode>textBody\u003C\u002Fcode>, sanitised \u003Ccode>htmlBody\u003C\u002Fcode>, \u003Ccode>receivedAt\u003C\u002Fcode>, \u003Ccode>readAt\u003C\u002Fcode>, \u003Ccode>attachments\u003C\u002Fcode> (names and sizes, not contents), \u003Ccode>to\u003C\u002Fcode>, \u003Ccode>cc\u003C\u002Fcode>, \u003Ccode>threadId\u003C\u002Fcode>, \u003Ccode>labels\u003C\u002Fcode> (free text, \u003Ccode>[]\u003C\u002Fcode> when none), \u003Ccode>handledState\u003C\u002Fcode> (\u003Ccode>unhandled\u003C\u002Fcode>, \u003Ccode>replied\u003C\u002Fcode>, \u003Ccode>skipped\u003C\u002Fcode>, \u003Ccode>snoozed\u003C\u002Fcode> or \u003Ccode>escalated\u003C\u002Fcode>) and \u003Ccode>snoozedUntil\u003C\u002Fcode> (only when \u003Ccode>snoozed\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fmembers\u002F:id\u002Femails\u002F:emailId\u003C\u002Fcode> (\u003Ccode>members:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The id is not in this member's mailbox, including another member's message.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-reply\">\u003Ccode>os.mail_reply\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Reply to a stored mail from the address it arrived at, once the CEO approves.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A message id from os.mail_list: the mail this answers\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>text\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The reply, as plain text\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>html\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Optional HTML body. Plain text is sent either way\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> 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 \u003Ccode>replied\u003C\u002Fcode> (clearing any snooze).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. A repeat sends a second reply. Call it again only if the first reported a refusal.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>A gate: the CEO decides every call, whatever the task declares.\u003C\u002Fli>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ status, id }\u003C\u002Fcode>: \u003Ccode>sent\u003C\u002Fcode>, or \u003Ccode>send_failed\u003C\u002Fcode> with the provider’s \u003Ccode>error\u003C\u002Fcode> when it did not go out. \u003Ccode>id\u003C\u002Fcode> is the copy filed in the mailbox either way.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_reply\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The id is not in this member's mailbox, including another member's message.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>recipient_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The stored message names no address that can be replied to.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sender_not_own_mailbox\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The address the original arrived at does not deliver to this member's mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>gate_summary_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has not written the recipient and gist the CEO should read on the approval card.\u003C\u002Ftd>\n\u003Ctd>Handed back\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;text&quot;: &quot;Thursday at 10:00 works.&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-send\">\u003Ccode>os.mail_send\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Send a new mail from the assigned member’s mailbox, once the CEO approves.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>to\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The recipient, one email address\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>subject\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>text\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The message, as plain text\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>html\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Optional HTML body. Plain text is sent either way\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>from\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Another address that delivers to this member’s mailbox. Defaults to their own\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Files the sent message in the member’s mailbox.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. A repeat sends a second message. Call it again only if the first reported a refusal.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>A gate: the CEO decides every call, whatever the task declares.\u003C\u002Fli>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ status, id }\u003C\u002Fcode>: \u003Ccode>sent\u003C\u002Fcode>, or \u003Ccode>send_failed\u003C\u002Fcode> with the provider’s \u003Ccode>error\u003C\u002Fcode> when it did not go out. \u003Ccode>id\u003C\u002Fcode> is the copy filed in the mailbox either way.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_send\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>recipient_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>to\u003C\u002Fcode> is not an email address.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sender_not_own_mailbox\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>from\u003C\u002Fcode> names an address that does not deliver to this member's mailbox. Leave \u003Ccode>from\u003C\u002Fcode> out to send from the member's own address.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;to&quot;: &quot;vip@acme.com&quot;,\n  &quot;subject&quot;: &quot;Thursday&quot;,\n  &quot;text&quot;: &quot;Thursday at 10:00 works for the CEO.&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-set-label\">\u003Ccode>os.mail_set_label\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Set the labels on a mail in the assigned member’s inbox.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A message id from os.mail_list\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>labels\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Every label the mail should carry. Replaces the current set\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Idempotent: a repeat of the same call acts once, or leaves the same state.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ id, labels }\u003C\u002Fcode>: the mail and the labels it now carries.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_set_label\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The id is not in this member's mailbox, including another member's message.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>labels_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>labels is not a list of non-empty strings of at most 64 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;labels&quot;: [\n    &quot;VIP&quot;\n  ]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-set-handled\">\u003Ccode>os.mail_set_handled\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Record whether a mail in the assigned member’s inbox has been handled.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A message id from os.mail_list\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handledState\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>unhandled\u003C\u002Fcode>, \u003Ccode>replied\u003C\u002Fcode>, \u003Ccode>skipped\u003C\u002Fcode>, \u003Ccode>snoozed\u003C\u002Fcode>, \u003Ccode>escalated\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozedUntil\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>ISO 8601 date and time the mail comes back. Only with handledState snoozed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Idempotent: a repeat of the same call acts once, or leaves the same state.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ id, handledState, snoozedUntil }\u003C\u002Fcode>: the state the mail is now in.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_set_handled\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The id is not in this member's mailbox, including another member's message.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handled_state_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>handledState is not one of the handled states.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozed_until_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>handledState is snoozed and snoozedUntil is missing or not a date.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozed_until_not_allowed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>snoozedUntil was given with a handledState other than snoozed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;handledState&quot;: &quot;snoozed&quot;,\n  &quot;snoozedUntil&quot;: &quot;2026-10-01T09:00:00Z&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-set-many\">\u003Ccode>os.mail_set_many\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Set the labels, the handled state, or both on many mails in the assigned member’s inbox at once.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>ids\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of string, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Up to 200 message ids from os.mail_list. Not together with fromAddress\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>fromAddress\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>A sender address, matched exactly: every unhandled mail that arrived from it. Not together with ids\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>labels\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Every label each mail should carry. Replaces the current set\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handledState\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>unhandled\u003C\u002Fcode>, \u003Ccode>replied\u003C\u002Fcode>, \u003Ccode>skipped\u003C\u002Fcode>, \u003Ccode>snoozed\u003C\u002Fcode>, \u003Ccode>escalated\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozedUntil\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>ISO 8601 date and time the mail comes back. Only with handledState snoozed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Idempotent: a repeat of the same call acts once, or leaves the same state.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ updated, notFound }\u003C\u002Fcode>: how many mails were changed, and the ids given that are not in this mailbox (always \u003Ccode>[]\u003C\u002Fcode> when selecting by \u003Ccode>fromAddress\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_set_many\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's member no longer exists.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mail_selector_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Neither ids nor fromAddress was given, or both were.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mail_ids_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>ids is not a list of 1 to 200 non-empty message ids.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mail_change_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Neither labels nor handledState was given, so there is nothing to record.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>labels_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>labels is not a list of non-empty strings of at most 64 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>handled_state_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>handledState is not one of the handled states.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozed_until_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>handledState is snoozed and snoozedUntil is missing or not a date.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snoozed_until_not_allowed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>snoozedUntil was given with a handledState other than snoozed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;fromAddress&quot;: &quot;updates@example.com&quot;,\n  &quot;labels&quot;: [\n    &quot;Newsletter&quot;\n  ],\n  &quot;handledState&quot;: &quot;skipped&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;ids&quot;: [\n    &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n    &quot;5f0c1d2e-0000-4000-8000-000000000004&quot;\n  ],\n  &quot;handledState&quot;: &quot;skipped&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-quiet-threads\">\u003Ccode>os.mail_quiet_threads\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The threads the assigned member replied to that have gone quiet, oldest first, at most ten.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ data }\u003C\u002Fcode>: each row has \u003Ccode>threadKey\u003C\u002Fcode>, \u003Ccode>replyToEmailId\u003C\u002Fcode> (the newest inbound message: pass it to os.mail_reply as \u003Ccode>id\u003C\u002Fcode>), \u003Ccode>correspondent\u003C\u002Fcode>, \u003Ccode>subject\u003C\u002Fcode> and \u003Ccode>lastOutboundAt\u003C\u002Fcode>. An empty list when nothing is quiet or the member has no mailbox.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_quiet_threads\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-mark-chased\">\u003Ccode>os.mail_mark_chased\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Record that a quiet thread was answered today, so no second run today raises it again.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>threadKey\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A threadKey from os.mail_quiet_threads\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Stores when the thread was last chased on the member's chase record for it. Nothing is sent.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ threadKey, chasedAt }\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_mark_chased\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>thread_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The threadKey names no conversation in this member's mailbox, including another member's.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;threadKey&quot;: &quot;&lt;0f1e2d3c@mail.example.com&gt;&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-snooze\">\u003Ccode>os.mail_snooze\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Leave a quiet thread alone until a date, instead of drafting a follow-up.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>threadKey\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A threadKey from os.mail_quiet_threads\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>until\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>When to raise it again: a date or timestamp after now\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Stores the snooze on the member's chase record for the thread. Nothing is sent.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>The run's assigned member. The mailbox is always theirs: a \u003Ccode>memberId\u003C\u002Fcode> in the payload is ignored, and an OS-owned task has no mailbox.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ threadKey, snoozedUntil }\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.mail_snooze\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no assigned member, so no mailbox.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>thread_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The threadKey names no conversation in this member's mailbox, including another member's.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>snooze_until_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>until\u003C\u002Fcode> is not a date, or is not after now.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;threadKey&quot;: &quot;&lt;0f1e2d3c@mail.example.com&gt;&quot;,\n  &quot;until&quot;: &quot;2026-10-12&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-escalate-mail\">\u003Ccode>os.escalate_mail\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The gate a mail that needs the CEO opens: the mail, why it matters, and what is suggested.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The mail the escalation is about\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mailId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The same id, under the key both mail gates use\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>subject\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>fromAddress\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Why this mail needs the CEO\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>suggestion\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Which choice you suggest (Reply, Snooze or Leave it) and why, in one or two sentences\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Opens one CEO gate for this run and parks the run until it is decided.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the OS itself, on nobody’s call.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>A gate: the CEO decides every call, whatever the task declares.\u003C\u002Fli>\n\u003Cli>The gate offers these choices in place of Approve and Reject. Reply (\u003Ccode>reply\u003C\u002Fcode>): A reply is drafted and comes back to you to approve before anything is sent. Snooze (\u003Ccode>snooze\u003C\u002Fcode>): Nothing is sent. The mail is set aside until the time you pick. Leave it (\u003Ccode>leave\u003C\u002Fcode>): No reply is sent and the mail is archived.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The CEO’s choice, to the task that runs next: \u003Ccode>ceoChoice\u003C\u002Fcode> (\u003Ccode>reply\u003C\u002Fcode> or \u003Ccode>snooze\u003C\u002Fcode>), \u003Ccode>ceoNote\u003C\u002Fcode> and \u003Ccode>mailId\u003C\u002Fcode>. Leave it hands nothing on: the run is cancelled and the mail archived.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;subject&quot;: &quot;Renewal before Friday&quot;,\n  &quot;fromAddress&quot;: &quot;vip@acme.example&quot;,\n  &quot;summary&quot;: &quot;A VIP contact wants to renew before Friday and has asked for a call.&quot;,\n  &quot;suggestion&quot;: &quot;Reply: confirm the renewal terms and offer Thursday for the call.&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-mail-reply-draft\">\u003Ccode>os.mail_reply_draft\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The gate a drafted reply opens, so no reply leaves without a decision on it.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mailId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The mail this reply answers\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>body\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The drafted reply, as it would be sent\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the OS itself, on nobody’s call.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The decision on the draft: approved and it may be sent, or not, with the decider's note.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;mailId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;body&quot;: &quot;Thursday works — I will send an invitation shortly.&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-list-kpis\">\u003Ccode>os.list_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>List the signed-off KPIs the run's own team owns and those its task is the source of, with their lights.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ data }\u003C\u002Fcode>: one row per KPI, each with \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>ownerLayer\u003C\u002Fcode> (\u003Ccode>company\u003C\u002Fcode>, \u003Ccode>team\u003C\u002Fcode>, \u003Ccode>role\u003C\u002Fcode>, \u003Ccode>task\u003C\u002Fcode>, or null for a company KPI set before layers), \u003Ccode>ownerId\u003C\u002Fcode>, \u003Ccode>sourceKind\u003C\u002Fcode> (\u003Ccode>task\u003C\u002Fcode> or \u003Ccode>person\u003C\u002Fcode>), \u003Ccode>sourceRef\u003C\u002Fcode> (the task or person id), \u003Ccode>target\u003C\u002Fcode>, \u003Ccode>cadenceMinutes\u003C\u002Fcode>, \u003Ccode>light\u003C\u002Fcode>, \u003Ccode>overdue\u003C\u002Fcode> and \u003Ccode>lastReading\u003C\u002Fcode> (\u003Ccode>id\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, \u003Ccode>takenAt\u003C\u002Fcode>, \u003Ccode>recordedBy\u003C\u002Fcode>, \u003Ccode>runId\u003C\u002Fcode>), or null before the first reading.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.list_kpis\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-record-kpi-reading\">\u003Ccode>os.record_kpi_reading\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Record a reading for a KPI whose source is this run's task.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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:&lt;runId&gt;`); 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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A KPI id from os.list_kpis\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>takenAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>ISO 8601. Defaults to now.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The recorded reading and the KPI it landed on: \u003Ccode>kpiId\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>readingId\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, \u003Ccode>takenAt\u003C\u002Fcode>, \u003Ccode>recordedBy\u003C\u002Fcode> and \u003Ccode>runId\u003C\u002Fcode> (this run), \u003Ccode>target\u003C\u002Fcode>, and the KPI’s \u003Ccode>light\u003C\u002Fcode> as it now stands.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.record_kpi_reading\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode> is missing or not a number.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>taken_at_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>takenAt\u003C\u002Fcode> is not a timestamp anything can read.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode> 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.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;kpiId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000009&quot;,\n  &quot;value&quot;: 42\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-get-enterprise-kpis\">\u003Ccode>os.get_enterprise_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The company's KPI targets, bands, cadence and source.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fenterprise\u002Fkpi-targets\u003C\u002Fcode> (\u003Ccode>enterprise:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"os-set-enterprise-kpis\">\u003Ccode>os.set_enterprise_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Replace the company's KPI target list.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of object\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The complete KPI target list. Every item is validated by the API.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].unit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].targetValue\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].targetDirection\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>at_least\u003C\u002Fcode>, \u003Ccode>at_most\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].amberBand\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number, at least 0\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].amberBandType\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>absolute\u003C\u002Fcode>, \u003Ccode>percentage\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].onTargetWorseningRule\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].cadenceMinutes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].sourceKind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>person\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].sourceRef\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].isKey\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Idempotent: a repeat of the same call acts once, or leaves the same state.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>PATCH \u002Fv1\u002Fenterprise\u002Fkpi-targets\u003C\u002Fcode> (\u003Ccode>enterprise:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>invalid_kpis\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A KPI is incomplete or malformed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"os-get-team-kpis\">\u003Ccode>os.get_team_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>A team's KPI targets, including its key KPI.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>teamId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The team whose KPI targets are read.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fteams\u002F:teamId\u002Fkpi-targets\u003C\u002Fcode> (\u003Ccode>teams:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>team_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The team is not in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"os-set-team-kpis\">\u003Ccode>os.set_team_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Replace a team's KPI target list and key KPI.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>teamId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The team whose KPI targets are replaced.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of object\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The complete KPI target list. Every item is validated by the API.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].unit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].targetValue\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].targetDirection\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>at_least\u003C\u002Fcode>, \u003Ccode>at_most\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].amberBand\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number, at least 0\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].amberBandType\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>absolute\u003C\u002Fcode>, \u003Ccode>percentage\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].onTargetWorseningRule\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].cadenceMinutes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].sourceKind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>person\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].sourceRef\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpis[].isKey\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Idempotent: a repeat of the same call acts once, or leaves the same state.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>PATCH \u002Fv1\u002Fteams\u002F:teamId\u002Fkpi-targets\u003C\u002Fcode> (\u003Ccode>teams:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>team_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The team is not in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>invalid_kpis\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A KPI is incomplete or two KPIs are marked key.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"os-set-context\">\u003Ccode>os.set_context\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Store a JSON value for the enterprise under a key, so a later run can read it.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A short key, such as sitemap:\u003Ca href=\"https:\u002F\u002Fexample.com\u002Fpricing\">https:\u002F\u002Fexample.com\u002Fpricing\u003C\u002Fa>. No spaces. Not a secret.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>any\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Any JSON value, at most 16KB. Not a secret.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ key, value }\u003C\u002Fcode>: the key and the JSON value now stored for it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.set_context\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is empty, longer than 512 characters, or contains whitespace or a control character.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value_not_json\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode> is not JSON.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value_too_large\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode> is over 16KB.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;key&quot;: &quot;sitemap:https:\u002F\u002Fexample.com\u002Fpricing&quot;,\n  &quot;value&quot;: {\n    &quot;status&quot;: &quot;absorbed&quot;\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-get-context\">\u003Ccode>os.get_context\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Read one enterprise context value, or null when the key has never been set.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A short key, such as sitemap:\u003Ca href=\"https:\u002F\u002Fexample.com\u002Fpricing\">https:\u002F\u002Fexample.com\u002Fpricing\u003C\u002Fa>. No spaces. Not a secret.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ key, value }\u003C\u002Fcode>. \u003Ccode>value\u003C\u002Fcode> is null when the key has never been set.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.get_context\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is empty, longer than 512 characters, or contains whitespace or a control character.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;key&quot;: &quot;sitemap:https:\u002F\u002Fexample.com\u002Fpricing&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-delete-context\">\u003Ccode>os.delete_context\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Remove one enterprise context key. A missing key is a success.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A short key, such as sitemap:\u003Ca href=\"https:\u002F\u002Fexample.com\u002Fpricing\">https:\u002F\u002Fexample.com\u002Fpricing\u003C\u002Fa>. No spaces. Not a secret.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ key, deleted }\u003C\u002Fcode>. \u003Ccode>deleted\u003C\u002Fcode> is false when the key was not there.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.delete_context\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>key_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>key\u003C\u002Fcode> is empty, longer than 512 characters, or contains whitespace or a control character.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;key&quot;: &quot;sitemap:https:\u002F\u002Fexample.com\u002Fpricing&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"kpi-record-reading\">\u003Ccode>kpi.record_reading\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Record one reading of a KPI by hand: the value, when it was taken, and who or what took it.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The KPI the reading belongs to.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>value\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The number read, in the KPI's own unit.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>takenAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>When the reading was taken, as an ISO 8601 timestamp, e.g. 2026-01-08T00:00:00Z.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>recordedBy\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Who or what took the reading: a member id, or the agent or tool that read it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. Every call appends a reading, so a repeat records a second one.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The reading as recorded: \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>kpiId\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, \u003Ccode>takenAt\u003C\u002Fcode> (ISO 8601) and \u003Ccode>recordedBy\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fkpis\u002F:kpiId\u002Freadings\u003C\u002Fcode> (\u003Ccode>kpis:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode> names no KPI in this enterprise, another enterprise's included.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_not_signed_off\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Nobody has signed the KPI off yet, so it takes no reading.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;kpiId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000009&quot;,\n  &quot;value&quot;: 3.5,\n  &quot;takenAt&quot;: &quot;2026-01-08T00:00:00Z&quot;,\n  &quot;recordedBy&quot;: &quot;weekly-metrics-export&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"kpi-list-readings\">\u003Ccode>kpi.list_readings\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>One KPI's readings, newest first, corrected ones included.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The KPI whose readings to list.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> Every reading of the KPI, newest by \u003Ccode>takenAt\u003C\u002Fcode> first, each with \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>kpiId\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, \u003Ccode>takenAt\u003C\u002Fcode> and \u003Ccode>recordedBy\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fkpis\u002F:kpiId\u002Freadings\u003C\u002Fcode> (\u003Ccode>kpis:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode> names no KPI in this enterprise, another enterprise's included.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;kpiId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000009&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"role-list-kpis\">\u003Ccode>role.list_kpis\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>A role's KPIs, oldest first, with the team KPI each serves, its sign-off, light, trend and readings.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The role whose KPIs to list.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Froles\u002F:roleId\u002Fkpis\u003C\u002Fcode> (\u003Ccode>roles:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode> names no role in this enterprise, another enterprise's included.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"role-define-kpi\">\u003Ccode>role.define_kpi\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Propose a KPI on a role, waiting for a person to sign it off.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The role the KPI belongs to.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>What is measured, up to 200 characters.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>targetValue\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The target, in the KPI’s own unit. Set it with targetDirection, or leave both out for no target.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>targetDirection\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>at_least\u003C\u002Fcode>, \u003Ccode>at_most\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>at_least when higher is better, at_most when lower is better.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>amberBand\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number, at least 0\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>How far past the target still reads AMBER rather than RED.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>onTargetWorseningRule\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>true to read AMBER when on target but worse than the reading before.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cadenceMinutes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>How often it is read, in minutes; a reading older than this is overdue.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>servesKpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The KPI of the role’s own team this one serves.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sourceKind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>person\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Where readings come from: task (a task records them) or person (entered by hand).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sourceRef\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The task id, or the person’s human-user id, sourceKind names.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. Every call adds a KPI to the role, so a repeat proposes a second one.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Froles\u002F:roleId\u002Fkpis\u003C\u002Fcode> (\u003Ccode>roles:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode> names no role in this enterprise, another enterprise's included.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_kpi_serves_other_team\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>servesKpiId\u003C\u002Fcode> names a KPI that is not one of the role’s own team.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"role-edit-kpi\">\u003Ccode>role.edit_kpi\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Change a role KPI's fields; a signed-off one only a person changes.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The role the KPI belongs to.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>What is measured, up to 200 characters.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>targetValue\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The target, in the KPI’s own unit. Set it with targetDirection, or leave both out for no target.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>targetDirection\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>at_least\u003C\u002Fcode>, \u003Ccode>at_most\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>at_least when higher is better, at_most when lower is better.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>amberBand\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>number, at least 0\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>How far past the target still reads AMBER rather than RED.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>onTargetWorseningRule\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>true to read AMBER when on target but worse than the reading before.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cadenceMinutes\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>How often it is read, in minutes; a reading older than this is overdue.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>servesKpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The KPI of the role’s own team this one serves.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sourceKind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>person\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Where readings come from: task (a task records them) or person (entered by hand).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sourceRef\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The task id, or the person’s human-user id, sourceKind names.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The KPI to change.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>PATCH \u002Fv1\u002Froles\u002F:roleId\u002Fkpis\u002F:kpiId\u003C\u002Fcode> (\u003Ccode>roles:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>roleId\u003C\u002Fcode> names no role in this enterprise, another enterprise's included.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_kpi_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kpiId\u003C\u002Fcode> names no KPI still on this role.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_kpi_serves_other_team\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>servesKpiId\u003C\u002Fcode> names a KPI that is not one of the role’s own team.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kpi_signed_off_person_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A person has signed the KPI off, so only a person may change it.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"os-inspect-enterprise\">\u003Ccode>os.inspect_enterprise\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>A snapshot of the company: teams, members, roles, and the tasks of the roles each member holds.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>No declared fields: the schema accepts any object.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ teams, members }\u003C\u002Fcode>: 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).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.inspect_enterprise\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"os-directory\">\u003Ccode>os.directory\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Teams, members, roles, and the tasks of the roles each member holds: who to offload work to.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>No declared fields: the schema accepts any object.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the CEO chat, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ teams, members }\u003C\u002Fcode>: 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 \u003Ccode>os.inspect_enterprise\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.directory\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"os-list-blockers\">\u003Ccode>os.list_blockers\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>What has stopped: failed, stalled, blocked and spend-paused runs, and stopped pipelines, with who owns each.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>failedPage\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The page of failed runs to read, from 1. Defaults to 1. A page past the last reads the last.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pageSize\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, 1 to 100\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Failed runs per page, up to 100. Defaults to 100 in a run, and to 20 over the API.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> What the portal shows as blockers. To a run: \u003Ccode>runs\u003C\u002Fcode>, each stopped run once, oldest first, with \u003Ccode>runId\u003C\u002Fcode>, the \u003Ccode>sections\u003C\u002Fcode> it is listed in (failed, stopped, blocked, stalled, paused), \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>reason\u003C\u002Fcode>, \u003Ccode>blockClass\u003C\u002Fcode>, its \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>team\u003C\u002Fcode>, team \u003Ccode>lead\u003C\u002Fcode> and \u003Ccode>assignee\u003C\u002Fcode>, the \u003Ccode>repo\u003C\u002Fcode>, \u003Ccode>issue\u003C\u002Fcode> and \u003Ccode>pr\u003C\u002Fcode> from its input, \u003Ccode>since\u003C\u002Fcode>, \u003Ccode>taskRetries\u003C\u002Fcode> (how many times tasks have retried its chain) and \u003Ccode>escalation\u003C\u002Fcode> (to whom, \u003Ccode>toYou\u003C\u002Fcode>, the note, when) once someone escalated it; then \u003Ccode>failedTotal\u003C\u002Fcode> (every uncleared failed run), the \u003Ccode>failedPage\u003C\u002Fcode> read and its \u003Ccode>pageSize\u003C\u002Fcode>, the team-health rows (\u003Ccode>missingLead\u003C\u002Fcode>, \u003Ccode>overdueRetro\u003C\u002Fcode>, and \u003Ccode>kpiHealth\u003C\u002Fcode>: a KPI that needs attention, with its \u003Ccode>condition\u003C\u002Fcode> (\u003Ccode>no_key_kpi\u003C\u002Fcode>, \u003Ccode>no_target\u003C\u002Fcode>, \u003Ccode>overdue\u003C\u002Fcode> or \u003Ccode>light\u003C\u002Fcode>), \u003Ccode>team\u003C\u002Fcode>, \u003Ccode>kpi\u003C\u002Fcode>, \u003Ccode>layer\u003C\u002Fcode>, \u003Ccode>light\u003C\u002Fcode>, \u003Ccode>trend\u003C\u002Fcode> and \u003Ccode>owner\u003C\u002Fcode>), \u003Ccode>spendEnabled\u003C\u002Fcode> and \u003Ccode>draining\u003C\u002Fcode>. Over the API: the portal rows as they are. Waiting gates are not here: read them with \u003Ccode>os.list_open_gates\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fblockers\u003C\u002Fcode> (\u003Ccode>blockers:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"os-list-open-gates\">\u003Ccode>os.list_open_gates\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Every gate waiting on a decision, oldest first: the CEO inbox's own rows.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> Each waiting gate: \u003Ccode>runId\u003C\u002Fcode>, \u003Ccode>taskSlug\u003C\u002Fcode>, \u003Ccode>toolName\u003C\u002Fcode>, \u003Ccode>owner\u003C\u002Fcode>, \u003Ccode>payload\u003C\u002Fcode>, \u003Ccode>summary\u003C\u002Fcode> (when the run wrote one), \u003Ccode>createdAt\u003C\u002Fcode> and its age.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fgates?status=waiting\u003C\u002Fcode> (\u003Ccode>gates:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"os-retry-run\">\u003Ccode>os.retry_run\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Start a stopped run again, with the same input, so its work moves.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The run id, as \u003Ccode>os.list_blockers\u003C\u002Fcode> names it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Why another run should get through, in one line\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ runId, retryRunId, status, taskRetries }\u003C\u002Fcode>: the new run, \u003Ccode>queued\u003C\u002Fcode>, or \u003Ccode>pending\u003C\u002Fcode> while its task or member is busy, and how many times tasks have now retried the chain.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.retry_run\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode> is missing or empty.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No run with that id in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>own_run\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run named is the one calling.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>not_stopped:&lt;status&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>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.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_retried\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A later run already retried or routed it. Act on that run instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_cleared\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Someone already cleared it.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>retry_limit_reached\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Tasks have already retried this chain 3 times.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_not_runnable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is deprecated, has no current version, or has nobody to run it. Escalate it instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;runId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000001&quot;,\n  &quot;note&quot;: &quot;Ran out of steps after pushing most of the change; the next run continues from the branch&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-route-run\">\u003Ccode>os.route_run\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Send a stopped run's work to another task of its pipeline, instead of running the same task again.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The run id, as \u003Ccode>os.list_blockers\u003C\u002Fcode> names it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The slug of the task to run next\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>What that task should do with it, and why, in a line or two\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ runId, routedRunId, task, status, taskRetries }\u003C\u002Fcode>: the new run, \u003Ccode>queued\u003C\u002Fcode>, or \u003Ccode>pending\u003C\u002Fcode> while its task or member is busy, and how many times tasks have now retried or routed the chain.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.route_run\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode> is missing or empty.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No run with that id in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>own_run\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run named is the one calling.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>not_stopped:&lt;status&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>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.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_retried\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A later run already retried or routed it. Act on that run instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_cleared\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Someone already cleared it.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>task\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>same_task\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>task\u003C\u002Fcode> is the stopped run's own task.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_not_in_pipeline\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No run of that task is on the run's issue or pull request, and its task names no such successor.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>retry_limit_reached\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Tasks have already retried or routed this chain 3 times.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_not_runnable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The task named is deprecated, has no current version, or has nobody to run it. Escalate it instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;runId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000004&quot;,\n  &quot;task&quot;: &quot;write-red-suite&quot;,\n  &quot;note&quot;: &quot;The locked tests never cover the last acceptance criterion; add them, then implement runs on&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-clear-run\">\u003Ccode>os.clear_run\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Take a stopped run off Blockers without running it again.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The run id, as \u003Ccode>os.list_blockers\u003C\u002Fcode> names it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>Why nothing more is needed, in one line\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Removes the blocked label from the run's issue when no later run on it exists.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ runId, status, cleared }\u003C\u002Fcode>: the run as it now stands.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.clear_run\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode> is missing or empty.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No run with that id in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>own_run\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run named is the one calling.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>not_stopped:&lt;status&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>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.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_retried\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A later run already retried or routed it. Act on that run instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_cleared\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Someone already cleared it.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;runId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000002&quot;,\n  &quot;note&quot;: &quot;The pull request merged by hand on 28 Sep&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-escalate-run\">\u003Ccode>os.escalate_run\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Hand a stopped run to its team's lead, or to the CEO, and record that you did.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The run id, as \u003Ccode>os.list_blockers\u003C\u002Fcode> names it\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>to\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>lead\u003C\u002Fcode>, \u003Ccode>ceo\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>What is stuck, what was tried, and the move you recommend\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ runId, to, memberId, memberName, alreadyEscalated }\u003C\u002Fcode>: who now owns the decision. \u003Ccode>alreadyEscalated\u003C\u002Fcode> is true when the run was already escalated to them, and nothing new is recorded.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.escalate_run\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>runId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>note_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>note\u003C\u002Fcode> is missing or empty.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>run_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No run with that id in this enterprise.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>own_run\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run named is the one calling.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>not_stopped:&lt;status&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>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.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_retried\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A later run already retried or routed it. Act on that run instead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>already_cleared\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Someone already cleared it.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>to_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>to\u003C\u002Fcode> is not \u003Ccode>lead\u003C\u002Fcode> or \u003Ccode>ceo\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_lead\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Nobody leads the run's team (or it has none), and nobody holds the COO role to act as lead.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;runId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000003&quot;,\n  &quot;to&quot;: &quot;lead&quot;,\n  &quot;note&quot;: &quot;Loop cap on recover-red-suite (2\u002F2): each lap found the same failing test outside the issue. Recommend closing the loop and filing the test separately.&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-read-enterprise\">\u003Ccode>os.read_enterprise\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The whole enterprise: teams, charters, roles, members and every task in full.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>team\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>A team id or slug: that team, its roles, members and tasks.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>noTeam\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>true: the members and tasks on no team.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>With team only. A role id or slug from allRoles: that team's tasks that run as it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>noRole\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>boolean\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>With team only. true: that team's tasks that run as no role.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.read_enterprise\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>team_not_found:&lt;team&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No team in this enterprise has that id or slug. Read allTeams with noTeam: true.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>team_selector_conflict\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Both \u003Ccode>team\u003C\u002Fcode> and \u003Ccode>noTeam\u003C\u002Fcode> are set.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_requires_team\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>role\u003C\u002Fcode> or \u003Ccode>noRole\u003C\u002Fcode> is set without \u003Ccode>team\u003C\u002Fcode>: they narrow one team.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_not_found:&lt;role&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No task on that team can run as a role with that id or slug. The message lists the team's allRoles.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>role_selector_conflict\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Both \u003Ccode>role\u003C\u002Fcode> and \u003Ccode>noRole\u003C\u002Fcode> are set.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;noTeam&quot;: true\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;team&quot;: &quot;engineering&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;team&quot;: &quot;engineering&quot;,\n  &quot;role&quot;: &quot;cto&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;team&quot;: &quot;engineering&quot;,\n  &quot;noRole&quot;: true\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-search-catalog\">\u003Ccode>os.search_catalog\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Published catalog tasks that match a plain-English intent, best first.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>intent\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.search_catalog\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;intent&quot;: &quot;Triage a new GitHub issue&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-list-tool-bindings\">\u003Ccode>os.list_tool_bindings\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>The tool types this enterprise has bound, at which layer, and the tools each exposes.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>Each binding's own credential, to ask its server which tools it exposes.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.list_tool_bindings\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-list-built-in-tools\">\u003Ccode>os.list_built_in_tools\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Every built-in tool a run can call, with its summary and whether it reads or writes.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Takes no arguments: no fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>[{ name, summary, effect, gate? }]\u003C\u002Fcode>: one row per built-in tool a run can call, in reference order. \u003Ccode>gate\u003C\u002Fcode> is only on a tool a task may hold only with a CEO gate, and names the tool that gate is declared on.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Ftools\u002Fbuilt-in?callable=run\u003C\u002Fcode> (\u003Ccode>tools:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-read-task\">\u003Ccode>os.read_task\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>One task in full, its pending recommendations, and the tasks that start it.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>taskId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.read_task\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>taskId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>task_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No task in this enterprise has that id.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;taskId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000001&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-assess-impact\">\u003Ccode>os.assess_impact\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>What points at a role, team, task or member a plan would delete.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>role\u003C\u002Fcode>, \u003Ccode>team\u003C\u002Fcode>, \u003Ccode>task\u003C\u002Fcode>, \u003Ccode>member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.assess_impact\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kind_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>kind\u003C\u002Fcode> is not role, team, task or member.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;kind&quot;: &quot;role&quot;,\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000002&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-write-plan\">\u003Ccode>os.write_plan\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Write or replace this chat session's plan, checked before the CEO sees it.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chatSessionId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>ceoIntent\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>kind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>items\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>array of object\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>version\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, at least 1\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Moves the plan to \u003Ccode>awaiting_ceo\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> 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 \u003Ccode>plan_version_conflict\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.write_plan\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chat_session_id_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>chatSessionId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>summary_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>summary\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>plan_payload_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>An item is not one the plan can apply (a missing handle, an unknown type).\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>tool_type_not_bound:&lt;type&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>An item names a tool whose type this enterprise has not bound.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>slug_collision:&lt;slug&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>An item creates a task, role or team with a slug the enterprise already has.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>assignee_member_removed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A \u003Ccode>create_task\u003C\u002Fcode> item names \u003Ccode>assigneeMemberId\u003C\u002Fcode>. A task is assigned to a role: set \u003Ccode>assigneeRole\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>definition_issue:&lt;code&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A proposed task fails the check a saved task must pass (an unknown successor, a deterministic task with tools).\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>plan_version_conflict\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>version\u003C\u002Fcode> is not the version the plan is at now. Read it back with os.read_plan and write again.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;chatSessionId&quot;: &quot;chat-42&quot;,\n  &quot;ceoIntent&quot;: &quot;Hire a support lead&quot;,\n  &quot;summary&quot;: &quot;Creates a Support Lead role&quot;,\n  &quot;kind&quot;: &quot;os_change&quot;,\n  &quot;items&quot;: [\n    {\n      &quot;type&quot;: &quot;create_role&quot;,\n      &quot;handle&quot;: &quot;r1&quot;,\n      &quot;slug&quot;: &quot;support-lead&quot;,\n      &quot;name&quot;: &quot;Support Lead&quot;\n    }\n  ]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-read-plan\">\u003Ccode>os.read_plan\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>This chat session's plan as it stands, with its version.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chatSessionId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.read_plan\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os_owned_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run's task is not OS-owned. These tools read every team's work, so only an OS task may call them.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;chatSessionId&quot;: &quot;chat-42&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-file-daily-brief\">\u003Ccode>os.file_daily_brief\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>File the CEO's brief in your chat thread: what is waiting on them, and the time on their clock.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>edition\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string, one of \u003Ccode>morning\u003C\u002Fcode>, \u003Ccode>evening\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Which sitting the brief is for. Defaults to the edition in your run input.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> One assistant message in the member's open chat thread with the CEO.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. A repeat files a second brief in the thread: call it once per run.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> \u003Ccode>{ filed: true, edition, messageId, waiting }\u003C\u002Fcode>: \u003Ccode>waiting\u003C\u002Fcode> is how many things the brief named.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fruns\u002Fself\u002Ftools\u002Fos.file_daily_brief\u003C\u002Fcode> (\u003Ccode>run:self\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>daily_brief_edition_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Neither the call nor the run input names morning or evening.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>daily_brief_timezone_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The enterprise's timezone is not one the OS knows.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>daily_brief_no_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run has no member whose thread the brief could be filed in.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;edition&quot;: &quot;morning&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-chat-list\">\u003Ccode>os.chat_list\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>List chat threads, most recently active first: who each is with, its state and its last message.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>memberId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Over the API: only threads with this member. A run always lists its own member’s.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>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.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> To a run: \u003Ccode>{ threads }\u003C\u002Fcode>, each with \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode> (\u003Ccode>idle\u003C\u002Fcode>, \u003Ccode>replying\u003C\u002Fcode> or \u003Ccode>closed\u003C\u002Fcode>), \u003Ccode>lastMessage\u003C\u002Fcode> (\u003Ccode>role\u003C\u002Fcode>, \u003Ccode>content\u003C\u002Fcode>, \u003Ccode>createdAt\u003C\u002Fcode>, or null in an empty thread), \u003Ccode>lastReadAt\u003C\u002Fcode> (when a person last opened it, or null), \u003Ccode>createdAt\u003C\u002Fcode> and \u003Ccode>updatedAt\u003C\u002Fcode>. Over the API: the Chat page’s rows as they are, which add the \u003Ccode>member\u003C\u002Fcode> and the \u003Ccode>unread\u003C\u002Fcode> and \u003Ccode>unreadCount\u003C\u002Fcode> of the person’s side.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fchats\u003C\u002Fcode> (\u003Ccode>chats:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A run with no assigned member called it, so there is no thread.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-chat-read\">\u003Ccode>os.chat_read\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Read one chat thread: its messages, oldest first.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>A thread id from os.chat_list\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>limit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>integer, 1 to 200\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>Only the newest messages, up to 200. In a run it defaults to 50; over the API, to all of them.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> Over the API and the MCP server, marks the thread read, the same as opening it in the portal. In a run, nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Safe: repeating the call changes nothing.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>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.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> To a run: \u003Ccode>{ id, status, total, messages }\u003C\u002Fcode>, where \u003Ccode>total\u003C\u002Fcode> counts every message said in the thread and each message has \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>role\u003C\u002Fcode> (\u003Ccode>user\u003C\u002Fcode> for a person or an API token, \u003Ccode>assistant\u003C\u002Fcode> for the member), \u003Ccode>content\u003C\u002Fcode>, \u003Ccode>sender\u003C\u002Fcode> (the name of whoever wrote a \u003Ccode>user\u003C\u002Fcode> message, else null) and \u003Ccode>createdAt\u003C\u002Fcode>. Over the API: the thread as the portal reads it, with its \u003Ccode>member\u003C\u002Fcode> and every row, each with its \u003Ccode>sender\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>GET \u002Fv1\u002Fchats\u002F:id\u003C\u002Fcode> (\u003Ccode>chats:read\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A run with no assigned member called it, so there is no thread.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chat_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No thread has that id: in a run, including another member's thread and a conversation opened for one job.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000021&quot;,\n  &quot;limit&quot;: 20\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-chat-write\">\u003Ccode>os.chat_write\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Write one message in a chat thread, as yourself.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>id\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>The thread, from os.chat_list. A run may leave it out to write in its member’s open thread. Required over the API.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>content\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The message, as plain text\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Side effects:\u003C\u002Fstrong> 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.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. A repeat writes the message again: call it once for each thing you have to say.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> a run, the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Needs:\u003C\u002Fstrong>\n\u003Cul>\n\u003Cli>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.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> To a run: \u003Ccode>{ written: true, id, messageId }\u003C\u002Fcode>, the thread it went in and the message. Over the API: the thread as os.chat_read returns it, with the member’s reply.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fchats\u002F:id\u002Fmessages\u003C\u002Fcode> (\u003Ccode>chats:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>no_assigned_member\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A run with no assigned member called it, so there is no thread.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chat_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No thread has that id: in a run, including another member's thread and a conversation opened for one job.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>empty_message\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>content\u003C\u002Fcode> is missing, not text, or blank.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>message_too_long\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>In a run, \u003Ccode>content\u003C\u002Fcode> is longer than 4000 characters.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chat_closed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The thread is closed.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>chat_replying\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Over the API, the member is still replying to an earlier message in the thread.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;content&quot;: &quot;New enquiry\\nFrom: jo@example.com\\nAsking about: the Team plan&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Example, Over the API: a person’s message to the member, answered in the response.:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;id&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000021&quot;,\n  &quot;content&quot;: &quot;Where did last week’s report go?&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"os-chat-open\">\u003Ccode>os.chat_open\u003C\u002Fcode>\u003C\u002Fh2>\n\u003Cp>Open a new chat thread with a member.\u003C\u002Fp>\n\u003Cp>What the model reads:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">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.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Required\u003C\u002Fth>\n\u003Cth>Description\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>memberId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>string\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003Ctd>The member to talk to\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>No other fields are accepted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Effect:\u003C\u002Fstrong> write.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retry:\u003C\u002Fstrong> Not retry-safe: a repeat acts again. A repeat opens another thread with the same member.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Called by:\u003C\u002Fstrong> the API, the OS MCP server.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Returns:\u003C\u002Fstrong> The new thread: its \u003Ccode>id\u003C\u002Fcode>, \u003Ccode>memberId\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode> (\u003Ccode>idle\u003C\u002Fcode>) and times.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>API:\u003C\u002Fstrong> \u003Ccode>POST \u002Fv1\u002Fchats\u003C\u002Fcode> (\u003Ccode>chats:write\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Error\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>memberId_required\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>memberId\u003C\u002Fcode> is missing.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>member_not_found\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No member of this enterprise has that id.\u003C\u002Ftd>\n\u003Ctd>Tool error\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;memberId&quot;: &quot;5f0c1d2e-0000-4000-8000-000000000007&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n",[12,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136,139,142,145,148,151,154,157,160,163,166,169,172],{"id":13,"text":14,"level":15,"planned":8},"os-set-execution-name","os.set_execution_name",2,{"id":17,"text":18,"level":15,"planned":8},"os-add-execution-link","os.add_execution_link",{"id":20,"text":21,"level":15,"planned":8},"os-set-gate-summary","os.set_gate_summary",{"id":23,"text":24,"level":15,"planned":8},"os-ask-ceo","os.ask_ceo",{"id":26,"text":27,"level":15,"planned":8},"os-list-recommendations","os.list_recommendations",{"id":29,"text":30,"level":15,"planned":8},"os-get-recommendation","os.get_recommendation",{"id":32,"text":33,"level":15,"planned":8},"os-decide-recommendation","os.decide_recommendation",{"id":35,"text":36,"level":15,"planned":8},"os-mail-list","os.mail_list",{"id":38,"text":39,"level":15,"planned":8},"os-mail-read","os.mail_read",{"id":41,"text":42,"level":15,"planned":8},"os-mail-reply","os.mail_reply",{"id":44,"text":45,"level":15,"planned":8},"os-mail-send","os.mail_send",{"id":47,"text":48,"level":15,"planned":8},"os-mail-set-label","os.mail_set_label",{"id":50,"text":51,"level":15,"planned":8},"os-mail-set-handled","os.mail_set_handled",{"id":53,"text":54,"level":15,"planned":8},"os-mail-set-many","os.mail_set_many",{"id":56,"text":57,"level":15,"planned":8},"os-mail-quiet-threads","os.mail_quiet_threads",{"id":59,"text":60,"level":15,"planned":8},"os-mail-mark-chased","os.mail_mark_chased",{"id":62,"text":63,"level":15,"planned":8},"os-mail-snooze","os.mail_snooze",{"id":65,"text":66,"level":15,"planned":8},"os-escalate-mail","os.escalate_mail",{"id":68,"text":69,"level":15,"planned":8},"os-mail-reply-draft","os.mail_reply_draft",{"id":71,"text":72,"level":15,"planned":8},"os-list-kpis","os.list_kpis",{"id":74,"text":75,"level":15,"planned":8},"os-record-kpi-reading","os.record_kpi_reading",{"id":77,"text":78,"level":15,"planned":8},"os-get-enterprise-kpis","os.get_enterprise_kpis",{"id":80,"text":81,"level":15,"planned":8},"os-set-enterprise-kpis","os.set_enterprise_kpis",{"id":83,"text":84,"level":15,"planned":8},"os-get-team-kpis","os.get_team_kpis",{"id":86,"text":87,"level":15,"planned":8},"os-set-team-kpis","os.set_team_kpis",{"id":89,"text":90,"level":15,"planned":8},"os-set-context","os.set_context",{"id":92,"text":93,"level":15,"planned":8},"os-get-context","os.get_context",{"id":95,"text":96,"level":15,"planned":8},"os-delete-context","os.delete_context",{"id":98,"text":99,"level":15,"planned":8},"kpi-record-reading","kpi.record_reading",{"id":101,"text":102,"level":15,"planned":8},"kpi-list-readings","kpi.list_readings",{"id":104,"text":105,"level":15,"planned":8},"role-list-kpis","role.list_kpis",{"id":107,"text":108,"level":15,"planned":8},"role-define-kpi","role.define_kpi",{"id":110,"text":111,"level":15,"planned":8},"role-edit-kpi","role.edit_kpi",{"id":113,"text":114,"level":15,"planned":8},"os-inspect-enterprise","os.inspect_enterprise",{"id":116,"text":117,"level":15,"planned":8},"os-directory","os.directory",{"id":119,"text":120,"level":15,"planned":8},"os-list-blockers","os.list_blockers",{"id":122,"text":123,"level":15,"planned":8},"os-list-open-gates","os.list_open_gates",{"id":125,"text":126,"level":15,"planned":8},"os-retry-run","os.retry_run",{"id":128,"text":129,"level":15,"planned":8},"os-route-run","os.route_run",{"id":131,"text":132,"level":15,"planned":8},"os-clear-run","os.clear_run",{"id":134,"text":135,"level":15,"planned":8},"os-escalate-run","os.escalate_run",{"id":137,"text":138,"level":15,"planned":8},"os-read-enterprise","os.read_enterprise",{"id":140,"text":141,"level":15,"planned":8},"os-search-catalog","os.search_catalog",{"id":143,"text":144,"level":15,"planned":8},"os-list-tool-bindings","os.list_tool_bindings",{"id":146,"text":147,"level":15,"planned":8},"os-list-built-in-tools","os.list_built_in_tools",{"id":149,"text":150,"level":15,"planned":8},"os-read-task","os.read_task",{"id":152,"text":153,"level":15,"planned":8},"os-assess-impact","os.assess_impact",{"id":155,"text":156,"level":15,"planned":8},"os-write-plan","os.write_plan",{"id":158,"text":159,"level":15,"planned":8},"os-read-plan","os.read_plan",{"id":161,"text":162,"level":15,"planned":8},"os-file-daily-brief","os.file_daily_brief",{"id":164,"text":165,"level":15,"planned":8},"os-chat-list","os.chat_list",{"id":167,"text":168,"level":15,"planned":8},"os-chat-read","os.chat_read",{"id":170,"text":171,"level":15,"planned":8},"os-chat-write","os.chat_write",{"id":173,"text":174,"level":15,"planned":8},"os-chat-open","os.chat_open",1791124519750]