{"info":{"title":"intentic sandbox daemon","version":"1","description":"Every call an intentic sandbox daemon answers, generated from the wire contract the daemon and its browser client both import.\n\nThe daemon runs beside your code, on hardware you own. There is no shared server and no central API: the address below is your sandbox and nobody else's, which is also why the playground on this site answers from a simulation in your own tab rather than by calling anything.\n\nTwo conventions cover the whole surface. Input rides in the path and query for a `GET` and in a JSON body otherwise. A failure comes back as a JSON object with a `message`, never as an empty body — a refusal is a result, not a crash.\n\nOne route is open: `GET /health`. It is not in this document because it is not part of the contract — it exists so a script can tell a live sandbox from a dead port, and it deliberately checks nothing.","license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"{sandbox}","description":"Your own sandbox. In a browser signed in to the workspace this is the address in the bar.","variables":{"sandbox":{"default":"http://localhost:39247","description":"The sandbox's base URL: its platform hostname, or the loopback listener on your own machine."}}}],"security":[{"session":[]},{"control":[]}],"tags":[{"name":"One agent","description":"The surface most callers are here for. Starting a turn answers with a run id and nothing else: the work happens inside the sandbox whether or not anybody stays connected, and attaching is how you watch it, from the beginning or from wherever you had got to. The rest is the things a person does to a turn in flight — answer a question it asked, redirect it, stop it, or put the whole conversation back to an earlier point. Everything here addresses one conversation by id."},{"name":"The fleet","description":"The roster rather than the turn. Each conversation works in its own private copy of the repos, so it has a cumulative set of changes you can read and two ways for it to end: merge that work into the shared tree, or throw it away. The rest is the bookkeeping a board needs — renaming, marking read, archiving, purging."},{"name":"Past sessions","description":"Two reads. The list of past conversations, and one conversation's full record by id."},{"name":"Workflows","description":"A workflow is a design: run these conversations, in this order, each handing its result to the next. These routes hold the saved designs and the run history, start a run, stop one in flight, and archive the ones you are done reading."},{"name":"Loops","description":"A loop repeats a conversation towards a goal until it converges or gives up. One half is what is running right now; the other is the designs somebody authored once and can point at a different job each time."},{"name":"Automations","description":"Scheduled and triggered work: what can trigger one here, what is configured, and switching one on or off, deleting it or firing it by hand. The other half is the approval queue — an automation set to ask first lands there each time it would have run."},{"name":"Workspace","description":"Everything under the workspace root. The tree and one folder's contents, a window of a file's text, search that blends text, structure, meaning and history, and the ordinary changes: make a folder, delete, move, copy. It also holds what a workspace knows about itself — which repos it contains, how its packages depend on each other, which apps are in it, and starting or stopping one."},{"name":"Git","description":"The largest group here, and the shape is consistent: the repo rides in the address and the verb is the route. Reading is history, differences and status; writing is staging, committing, branching, tagging, stashing, cherry-picking, reverting and pushing. Two routes cover a repo caught mid-merge or mid-rebase, and one answers what undoing would actually do before you undo it."},{"name":"History","description":"The points the sandbox saves as work happens, what changed between one and the next, one file's two sides, and restoring from one."},{"name":"Chores","description":"Maintenance evidence: read the measurements, ask for one to be retaken, record what somebody concluded. There is deliberately no \"run this chore\" route, because a chore run is an ordinary conversation and so already has its own working copy, record and cost."},{"name":"Panels","description":"One entry per repo, with whether its preview server is up and what the app worked out about its contents. Starting and stopping are here; watching the output is the terminal's job."},{"name":"Ports","description":"The ports something is answering on, and giving one an address on the outside or taking that away."},{"name":"Personas","description":"A persona records a decision about accounts that already exist: which of them this character speaks for, what a conversation wearing it may do, and what it is told. The kit routes edit the files behind one — its own instructions, and the skills only its conversations reach."},{"name":"Skills","description":"What is available and whether each is on, the text of one, and writing or deleting one. The list joins four separate sources into a single answer."},{"name":"Extensions","description":"The runtime half of the extension format documented under Developers. These routes enumerate what is installed, read and write each one's settings, switch it on or off, check for updates and apply or undo one, and start or stop the long-running processes an extension declares."},{"name":"Settings","description":"Read and write the settings that govern how agents behave here. The other three routes are read-only reports on their effects: what the token-saving measures were actually worth, the text behind a built-in prompt, and when each rule last did something."},{"name":"Capabilities","description":"A capability is a system the agent can reach: a forge account, a chat server, a database, one of your own machines. These routes connect and disconnect them, carry the credential each needs, report whether a connection is live, and drive the interactive parts of a sign-in, including a one-time code."},{"name":"Secrets","description":"Write a secret, list which names exist, delete one. Revealing a value is the deliberate exception and the only route that hands one back; everywhere else the daemon substitutes a secret by reference at the moment a command runs."},{"name":"VPN","description":"What is configured, dialling and dropping one, and reading connections out of an exported client configuration. Link state is read back from the operating system, not from memory."},{"name":"Exit locations","description":"Which countries a provider offers, bringing an exit up, moving it, taking a fresh address in the same country, and checking where the world actually sees you. That last check is what the others are judged against: a switch that quietly left traffic where it was is the failure this exists to rule out."},{"name":"Inventory","description":"The entries in the workspace's deployment configuration. Adding or removing one rewrites that file and commits it, exactly as an agent editing it by hand would, and answers with the whole updated list so a caller redraws from one response."},{"name":"Platform CLI","description":"Runs the sandbox's own command-line tool and streams its output line by line. The reconcile is separated out because it takes minutes: it starts a background job and answers at once, and its event stream replays from the beginning and then follows live, so a page refresh does not lose the progress."},{"name":"Claude accounts","description":"Begin and finish a sign-in, list what is connected with how full each account's limits were, rename one, disconnect one. A sandbox can hold several side by side."},{"name":"Grok accounts","description":"Begin a sign-in with a code typed on another page, see what is connected, disconnect it. The sandbox waits for the sign-in to complete on its own, so nothing is pasted back."},{"name":"Cursor accounts","description":"Begin a sign-in, abandon one nobody finished, see what is connected, rename one, disconnect one. A sandbox can hold several side by side. Nothing is pasted back and there is no code to type: the page that opens is already addressed to the attempt, and the sandbox completes the exchange itself, so the way to learn it worked is to watch the account list."},{"name":"Routed providers","description":"The bundled translator runs a non-Claude model on the user's own subscription, so each provider connects by signing in rather than with an API key, and one provider can hold several accounts at once. Two sign-in shapes ride these routes: a code typed on a device page, which finishes by itself, and a redirect whose landing address is handed back."},{"name":"Endpoints","description":"Every built-in provider's catalogue is one fixed route, because there is one of each. Endpoints are user-created and unbounded, so the id rides in the address and the answer is whatever the configured server says about itself. The trial belongs here because the trial is an endpoint — the one the daemon provisions rather than you."},{"name":"Providers","description":"One read, for the providers the sandbox ships with. Never empty, and left in the provider's own preference order rather than rearranged."},{"name":"Usage","description":"One read: the spending record over a range of days, grouped finely enough that every cost screen is a rearrangement of it rather than a second call."},{"name":"Pipelines","description":"Read the runs and the jobs inside them, re-run or cancel one, and mark the board read. The interesting one hands a failing run to an agent rather than to you."},{"name":"Pre-push check","description":"Three verbs about one run, because there is one main working tree and so exactly one check. Starting it answers immediately: a suite takes minutes, and a request held open that long dies at the first proxy. Poll for the verdict; the answer names the terminal where it is really happening."},{"name":"Outbox","description":"What the outbox holds and its address. Publishing copies a workspace file or folder in; withdrawing the last one removes the outbox, so its existing always means exactly \"something is published\". There is no route to read a published file back, because that is what the open address is for."},{"name":"Sharing","description":"What is currently shared, publishing a conversation, re-rendering one from how it stands now, and taking it down. As with the outbox, the page itself is the read."},{"name":"Post drafts","description":"The owner's side of the queue. The agent writes drafts directly; this is the inbox, the one call that covers approving, editing and retrying, and the deletion that is a rejection."},{"name":"System","description":"The group with the widest job. The identity read is what the daemon says it is, including the routes it implements, which is the one call that tells a newer client what this sandbox can do. The event stream is the sandbox-wide live feed. The rest is the machinery an agent leaves running: terminals and their history, browsers, and the records of helpers it delegated to."},{"name":"Activity","description":"Read-only by design. Entries are written by the sandbox alone and never by a caller, which is the whole reason the record can be trusted."},{"name":"Logs","description":"What log files exist, and a window of one. Captured terminal output, command runs, and the daemon's own log."},{"name":"Push notifications","description":"What a device needs in order to subscribe, subscribing and unsubscribing, and a test. The test earns its place because there are four separate places a notification can be lost that nobody can inspect from outside."}],"openapi":"3.1.1","paths":{"/agent":{"post":{"operationId":"agent.run","summary":"Say something to an agent","description":"Starts a turn and answers immediately with its id; the work runs inside the sandbox whether or not anybody stays connected. Watch it by attaching. Naming a conversation that does not exist yet opens it.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","description":"What to say to the agent. May be empty if you are only attaching files."},"title":{"description":"A title for a conversation this turn is opening. Ignored for a conversation that already has one.","type":"string","maxLength":80},"attachments":{"description":"Files to hand the agent along with the prompt, as workspace paths. Upload them first.","maxItems":20,"type":"array","items":{"type":"string","minLength":1}},"agent":{"description":"Which model provider serves this turn. Leave it out for Claude.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs the turn. Leave it out to use each provider's own.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which of that provider's connected accounts pays for the turn. Leave it out for the first one.","type":"string"},"actsAs":{"description":"Which persona the turn speaks as out in the world. Not the same as which account pays for it.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"sessionId":{"description":"Resume this provider session instead of starting a fresh one.","type":"string"},"conversationId":{"description":"The conversation this turn belongs to. You choose it, it survives model switches, and it is how you address the conversation later. Naming one that does not exist opens it.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"isolated":{"description":"Work in this conversation's own private copy of the repos rather than the shared tree, so several agents can work at once. Needs a conversation id.","type":"boolean"},"worktreeBase":{"description":"Pin a new private copy to these exact commits instead of today's workspace. Used when several agents must start from identical files.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"]}},"autoLand":{"description":"Whether this turn's work merges into the workspace when it finishes. Overrides the conversation's own setting for this turn only.","type":"boolean"},"origin":{"description":"Set by the sandbox alone: this turn opened a conversation on behalf of a message from outside rather than a person.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"]},"forkOf":{"description":"Where this conversation was cut from, on its first turn only. Only the client knows this, so only the client can say it.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"The conversation this one was cut from."},"keep":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many of that conversation's messages to copy in before this turn runs."},"files":{"type":"string","enum":["then","now"],"description":"Which files the fork opens on: \"now\" is the workspace as it stands, \"then\" is the files as they were at the cut, which needs a private copy."}},"required":["conversationId","keep","files"]},"model":{"description":"Which model to use. Leave it out for the provider's default.","type":"string"},"unattended":{"description":"Nobody chose a model for this turn because a screen started it rather than a person. The sandbox then fills in the model its owner picked for unwatched work.","type":"boolean"},"outsideWake":{"description":"Content from outside caused this turn, and what to call the source. It is what makes the sandbox treat the turn as carrying somebody else's words.","type":"string","minLength":1},"permissionMode":{"description":"How tool calls are gated: ask each time, accept file edits, propose a plan first, or run everything. The agent can move itself between these mid-turn.","type":"string","enum":["default","acceptEdits","plan","bypassPermissions"]},"allowedTools":{"description":"Narrow the turn to these tools. Leave it out for everything the runtime has. For a turn driven by an outside message this list is the real boundary, because prompt wording is only advice.","type":"array","items":{"type":"string","minLength":1}},"effort":{"description":"How hard the model should think, where the provider offers a choice.","type":"string"},"thinking":{"description":"Whether to show the model's reasoning as it works.","type":"boolean"},"fast":{"description":"Ask for the same work at a higher rate for a higher price. A request rather than a promise: the answer says what actually happened.","type":"boolean"},"tierHold":{"description":"Run exactly the model that was picked, even when the turn looks simple enough for a cheaper one. The judgement is still recorded; nothing is substituted.","type":"boolean"},"editorContext":{"description":"What the user has open in their editor, folded into the prompt so that pointing words like \"this\" resolve.","type":"object","properties":{"file":{"type":"string","minLength":1,"description":"The file open in the editor, as a workspace path."},"startLine":{"description":"First line of the selection, counting from one. Leave both out when the whole file is the context.","type":"integer","minimum":1,"maximum":9007199254740991},"endLine":{"description":"Last line of the selection, counting from one.","type":"integer","minimum":1,"maximum":9007199254740991},"selection":{"description":"The selected text itself. Cut it down before sending if it is long: this is context, not an upload.","type":"string","maxLength":20000}},"required":["file"]}},"required":["prompt"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"type":"string","description":"The id of the run that just started. Hand it back when you attach, so the stream resumes rather than replaying."}},"required":["run"],"additionalProperties":false}}}}}}},"/agent/attach":{"post":{"operationId":"agent.attach","summary":"Watch a turn happen","description":"Streams everything the agent does: its words, the tools it reaches for, and the answers it gets. Give it the point you have already seen and it replays from there before going live, so a reload loses nothing. The window that started the turn holds no special claim, and any number of watchers on any number of devices see the same thing.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"Which conversation to watch."},"run":{"description":"The run you were watching. If a newer turn has started since, the stream replays that one from its beginning instead.","type":"string"},"after":{"description":"The last frame you already have. Everything after it replays, then the stream goes live. Leave it out to start from the beginning.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["conversationId"]}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"attached","description":"The first frame, identifying the run you have joined."},"run":{"type":"string","description":"The run's id."},"prompt":{"type":"string","description":"What was said to start it, so a window that did not start the turn can still draw the message."},"startedAt":{"type":"number","description":"When it started, in milliseconds, so a window joining late can show how long it has been going."},"seq":{"type":"number","description":"How many frames already exist. Everything at or below this number is replay; everything above it is live."}},"required":["kind","run","prompt","startedAt","seq"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"frame","description":"One thing that happened."},"seq":{"type":"number","description":"Its position in the run, counting from one. Keep the last one you saw and hand it back to resume rather than replay."},"event":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"session"},"sessionId":{"type":"string"}},"required":["kind","sessionId"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"worktree"},"branch":{"type":"string"},"base":{"type":"string"},"unenforced":{"type":"boolean"},"sync":{"type":"object","properties":{"commits":{"type":"number"},"blocked":{"type":"array","items":{"type":"string"}}},"required":["commits","blocked"],"additionalProperties":false}},"required":["kind","branch","base"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"landed"},"landed":{"type":"boolean"},"conflicts":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"paths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Which file."},"reason":{"type":"string","enum":["workspace","diverged","binary"],"description":"Why it would not merge, and the three have nothing in common but the symptom. Your own uncommitted edits on that path, where yours is the copy at risk. The shared tree having moved under the conversation since it started, where nothing of yours is at risk. Or a file git cannot merge at all, where no automatic answer exists."}},"required":["path","reason"],"additionalProperties":false},"description":"The files that genuinely would not apply. Not the whole change: reporting everything whenever the cause could not be pinned down turned four real conflicts into a wall of fourteen."},"clean":{"type":"number","description":"How many files would apply regardless, so a screen can say how much is being held back by how little and offer to take it. Zero alongside an empty list means the repository could not be reached at all."},"mainBranch":{"description":"The branch your own checkout is on, which is what the conversation has to rebase onto. Carried because only the sandbox can see it. Absent where there is no name to give.","type":"string"}},"required":["repo","paths","clean"],"additionalProperties":false}},"held":{"type":"boolean"},"deps":{"type":"object","properties":{"missing":{"type":"number"},"started":{"type":"array","items":{"type":"string"}},"deferred":{"type":"boolean"}},"required":["missing","started","deferred"],"additionalProperties":false}},"required":["kind","landed"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"preamble"},"notes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The one line a reader sees, on a row that opens to the text below."},"text":{"type":"string","description":"The note itself, which is also exactly what the model was told."}},"required":["title","text"],"additionalProperties":false}}},"required":["kind","notes"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"init"},"model":{"type":"string"}},"required":["kind","model"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"checkpoint"},"id":{"type":"string"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["kind","id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"steer"},"text":{"type":"string"},"sentAt":{"type":"number"},"attachments":{"type":"array","items":{"type":"string"}}},"required":["kind","text","sentAt"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"delta"},"text":{"type":"string"},"parentToolUseId":{"type":"string"}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"text_end"},"parentToolUseId":{"type":"string"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"thinking"},"text":{"type":"string"},"parentToolUseId":{"type":"string"}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"tool_call"},"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","enum":["read","edit","delete","move","search","execute","think","fetch","other"]},"status":{"type":"string","enum":["pending","in_progress","completed","failed"]},"target":{"type":"string"},"locations":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path, whatever directory the tool was run from."},"line":{"description":"Which line, counting from one.","type":"number"}},"required":["path"],"additionalProperties":false}},"content":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Plain output."},"text":{"type":"string","description":"What the tool said."}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"diff","description":"A change to a file."},"path":{"type":"string","description":"Which file, as a workspace path."},"oldText":{"description":"What was there. Absent for a new file, or where the previous contents are not known.","type":"string"},"newText":{"type":"string","description":"What is there now."},"truncated":{"description":"One of the two sides was too large to send whole.","type":"boolean"}},"required":["type","path","newText"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image","description":"A picture the tool produced."},"path":{"type":"string","description":"Where it is, as a workspace path. A path rather than the bytes, because the workspace already serves it, sending it inline would bloat every stored record, and this way the picture stays openable afterwards."}},"required":["type","path"],"additionalProperties":false}]}},"parentToolUseId":{"type":"string"}},"required":["kind","id","name","category","status"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"tool_call_update"},"id":{"type":"string"},"status":{"type":"string","enum":["pending","in_progress","completed","failed"]},"content":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Plain output."},"text":{"type":"string","description":"What the tool said."}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"diff","description":"A change to a file."},"path":{"type":"string","description":"Which file, as a workspace path."},"oldText":{"description":"What was there. Absent for a new file, or where the previous contents are not known.","type":"string"},"newText":{"type":"string","description":"What is there now."},"truncated":{"description":"One of the two sides was too large to send whole.","type":"boolean"}},"required":["type","path","newText"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image","description":"A picture the tool produced."},"path":{"type":"string","description":"Where it is, as a workspace path. A path rather than the bytes, because the workspace already serves it, sending it inline would bloat every stored record, and this way the picture stays openable afterwards."}},"required":["type","path"],"additionalProperties":false}]}},"locations":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path, whatever directory the tool was run from."},"line":{"description":"Which line, counting from one.","type":"number"}},"required":["path"],"additionalProperties":false}}},"required":["kind","id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"terminal"},"session":{"type":"string"}},"required":["kind","session"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"browser"},"session":{"type":"string"}},"required":["kind","session"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"subagent"},"id":{"type":"string"},"subagentKind":{"type":"string","enum":["subagent","codex","grok"]},"agentType":{"type":"string"},"description":{"type":"string"},"model":{"type":"string"},"background":{"type":"boolean"},"terminal":{"type":"string"}},"required":["kind","id","subagentKind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"subagent_update"},"id":{"type":"string"},"status":{"type":"string","enum":["pending","running","blocked","completed","failed","killed","paused"]},"tokens":{"type":"number"},"toolUses":{"type":"number"},"lastTool":{"type":"string"},"summary":{"type":"string"},"error":{"type":"string"}},"required":["kind","id"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"todos"},"items":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string","description":"The item, as the agent wrote it."},"status":{"type":"string","enum":["pending","in_progress","completed"],"description":"Where it is."},"activeForm":{"description":"How to phrase it while it is happening, so a screen can say what the agent is doing rather than what it plans to do.","type":"string"}},"required":["content","status"],"additionalProperties":false}}},"required":["kind","items"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"commands"},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"What to type, without the leading slash."},"description":{"type":"string","description":"What it does."},"hint":{"description":"What its argument should look like, shown after the name.","type":"string"}},"required":["name","description"],"additionalProperties":false}}},"required":["kind","items"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"usage"},"account":{"type":"string"},"costUsd":{"type":"number"},"inputTokens":{"type":"number"},"outputTokens":{"type":"number"},"cacheReadTokens":{"type":"number"},"cacheCreationTokens":{"type":"number"},"durationMs":{"type":"number"},"numTurns":{"type":"number"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","enum":["allowed","allowed_warning","rejected"]},"resetsAt":{"type":"number"},"rateLimitType":{"type":"string"},"utilization":{"type":"number"},"kind":{"type":"string","const":"rate_limit_info"},"account":{"type":"string"}},"required":["status","kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"fast_mode"},"state":{"type":"string","enum":["off","cooldown","on"]},"reason":{"type":"string"}},"required":["kind","state"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"tier"},"tier":{"type":"string","enum":["fast","standard"]},"score":{"type":"number"},"rules":{"type":"array","items":{"type":"string"}},"model":{"type":"string"},"routed":{"type":"boolean"},"held":{"type":"boolean"}},"required":["kind","tier","score","rules","routed"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"provider_retry"},"attempt":{"type":"number"},"maxAttempts":{"type":"number"},"nextAttemptAt":{"type":"number"},"status":{"type":"number"}},"required":["kind","attempt"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"account_usage"},"account":{"type":"string"},"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}}},"required":["kind","windows"],"additionalProperties":false},{"type":"object","properties":{"tokens":{"type":"number","description":"How much the latest request sent, all told."},"contextWindow":{"type":"number","description":"How much the model can hold. The gap between these two is how close the conversation is to being compacted."},"kind":{"type":"string","const":"context_usage"}},"required":["tokens","contextWindow","kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"compact"},"trigger":{"type":"string"},"preTokens":{"type":"number"},"postTokens":{"type":"number"}},"required":["kind","trigger"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"plan","description":"The agent has written a plan and is waiting for a yes."},"requestId":{"type":"string","description":"What to send back when you answer."},"text":{"type":"string","description":"The plan itself."}},"required":["kind","requestId","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"question","description":"The agent has asked you something and is waiting."},"requestId":{"type":"string","description":"What to send back when you answer."},"questions":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string","description":"What the agent is asking."},"header":{"type":"string","description":"A short label for the question."},"multiSelect":{"type":"boolean","description":"Whether more than one answer can be picked."},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","description":"The choice, in a few words."},"description":{"type":"string","description":"What picking it means."},"preview":{"description":"Something to look at while deciding: a mock-up, a snippet, a layout.","type":"string"}},"required":["label","description"],"additionalProperties":false},"description":"The choices offered. A free-text answer is always possible as well."}},"required":["question","header","multiSelect","options"],"additionalProperties":false},"description":"What it wants to know."}},"required":["kind","requestId","questions"],"additionalProperties":false},{"type":"object","properties":{"toolName":{"type":"string","description":"Which tool it wants to use."},"title":{"description":"The whole question, as a sentence, exactly as the runtime words it.","type":"string"},"displayName":{"description":"A short phrase for the button, such as read file.","type":"string"},"description":{"description":"More about what it is asking for.","type":"string"},"reason":{"description":"Why it is asking at all: a rule, the current mode, something that looked risky.","type":"string"},"path":{"description":"Which file it concerns, when it concerns one.","type":"string"},"alwaysLabel":{"description":"The wording for an always-allow answer. Present only when there is something an always could actually remember; without it the only answers are once and no.","type":"string"},"kind":{"type":"string","const":"permission","description":"The agent wants to use a tool it needs permission for."},"requestId":{"type":"string","description":"What to send back when you answer."}},"required":["toolName","kind","requestId"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"browser_help"},"requestId":{"type":"string"},"session":{"type":"string"},"account":{"type":"string"},"message":{"type":"string"}},"required":["kind","requestId","session","account","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"terminal_help"},"requestId":{"type":"string"},"session":{"type":"string"},"message":{"type":"string"}},"required":["kind","requestId","session","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service_offer"},"requestId":{"type":"string"},"offer":{"type":"object","properties":{"slug":{"type":"string","description":"Which service."},"name":{"type":"string","description":"What it is called."},"publisher":{"type":"string","description":"Who runs it."},"description":{"type":"string","description":"What it does."},"creditsPerRun":{"type":"number","description":"What one run costs. Stated by the platform rather than by the agent asking, which is what makes the price impossible to misquote."},"probation":{"description":"The listing is new and has not yet served enough runs cleanly to be trusted. The honest form of listed automatically but not vouched for, and the person approving the spend is who should know it.","type":"boolean"},"credits":{"description":"Your own meter, as the platform stated it.","type":"object","properties":{"allowance":{"type":"number","description":"How many credits the period gives."},"remaining":{"type":"number","description":"How many are left."},"resetsAt":{"type":"string","description":"When they refill."}},"required":["allowance","remaining","resetsAt"],"additionalProperties":false},"request":{"type":"string","description":"Exactly what would be sent, so you can see what leaves before agreeing to it."},"why":{"description":"The agent's case for spending, and the only words on this card that are the agent's.","type":"string"}},"required":["slug","name","publisher","description","creditsPerRun","request"],"additionalProperties":false}},"required":["kind","requestId","offer"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service_event"},"requestId":{"type":"string"},"event":{"oneOf":[{"type":"object","properties":{"event":{"type":"string","const":"status","description":"Progress. Each one replaces the last: a label, not a log."},"text":{"type":"string","description":"What it is doing."}},"required":["event","text"],"additionalProperties":false},{"type":"object","properties":{"event":{"type":"string","const":"result","description":"The answer. Exactly one of these ends a run."},"data":{"description":"The answer itself, in whatever shape that service returns."}},"required":["event","data"],"additionalProperties":false}]}},"required":["kind","requestId","event"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service_receipt"},"requestId":{"type":"string"},"outcome":{"type":"string","enum":["ok","refunded","refused"]},"credits":{"type":"number"},"remaining":{"type":"number"}},"required":["kind","requestId","outcome","credits"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"capability_offer"},"requestId":{"type":"string"},"offer":{"type":"object","properties":{"card":{"type":"string","description":"Which connection is being asked for."},"name":{"type":"string","description":"What it is called, as the catalogue titles it rather than as the agent named it."},"why":{"description":"The agent's case for connecting it, and the only words on this card that are the agent's.","type":"string"}},"required":["card","name"],"additionalProperties":false}},"required":["kind","requestId","offer"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"capability_outcome"},"requestId":{"type":"string"},"outcome":{"type":"string","enum":["connected","unfinished"]},"id":{"type":"string"}},"required":["kind","requestId","outcome"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"payment_offer"},"requestId":{"type":"string"},"offer":{"type":"object","properties":{"url":{"type":"string","description":"What is being paid for."},"description":{"description":"What the endpoint says it is.","type":"string"},"payTo":{"type":"string","description":"Where the money goes, taken verbatim from the endpoint's own demand."},"network":{"type":"string","description":"On which network."},"asset":{"type":"string","description":"In which token."},"assetName":{"type":"string","description":"That token's name. It is pegged to the dollar, which is what lets every amount here read as dollars."},"amountUsd":{"type":"string","description":"The exact price. Not a ceiling: this scheme has no ranges, so this is the whole spend."},"spentTodayUsd":{"type":"string","description":"What has already gone out today."},"dailyCapUsd":{"type":"string","description":"What may go out in a day."},"why":{"description":"The agent's case for paying, and the only words on this card that are the agent's.","type":"string"}},"required":["url","payTo","network","asset","assetName","amountUsd","spentTodayUsd","dailyCapUsd"],"additionalProperties":false}},"required":["kind","requestId","offer"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"payment_receipt"},"requestId":{"type":"string"},"outcome":{"type":"string","enum":["paid","failed"]},"amountUsd":{"type":"string"},"transaction":{"type":"string"},"network":{"type":"string"}},"required":["kind","requestId","outcome","amountUsd"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"resolved"},"requestId":{"type":"string"},"reply":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"plan","description":"Answering a plan the agent proposed."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering, from the frame that raised it."},"approve":{"type":"boolean","description":"Whether to go ahead. Approving means the plan then runs without asking again per tool, because being asked whether a plan you just approved may run its first command is not a question worth having."},"feedback":{"description":"Why not, which goes back to the model as the reason.","type":"string"}},"required":["kind","requestId","approve"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"question","description":"Answering a question the agent asked."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"answers":{"description":"What you chose, keyed by the question, with the chosen labels or your own words.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"cancelled":{"description":"Dismissing it instead, which tells the agent to carry on using sensible defaults rather than leaving it waiting.","type":"boolean"}},"required":["kind","requestId"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"permission","description":"Answering a request to use a tool."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"decision":{"type":"string","enum":["once","always","deny"],"description":"Once allows this call alone; always allows that whole tool for the rest of the conversation; no blocks it."},"feedback":{"description":"Why not, which goes back to the model as the reason.","type":"string"}},"required":["kind","requestId","decision"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"browser_help","description":"Answering a request for help in the agent's browser: a captcha, a password it does not hold, a check on your phone."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"helped":{"type":"boolean","description":"Whether you cleared it. Yes means the turn carries on from the page as you left it; no tells the agent so, and it moves on rather than waiting for ever."},"note":{"description":"Anything the agent should know, which goes back to it either way.","type":"string"}},"required":["kind","requestId","helped"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"terminal_help","description":"Answering a request for help at a terminal: a code to type, a confirmation only a person can give."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"helped":{"type":"boolean","description":"Whether you did it. Yes also hands the agent what the terminal now says, because a person answering a prompt is exactly the moment the agent cannot see."},"note":{"description":"Anything the agent should know, which goes back to it either way.","type":"string"}},"required":["kind","requestId","helped"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service_offer","description":"Answering a request to spend on a paid service."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"approve":{"type":"boolean","description":"Yes releases exactly one run. Anything else charges nothing. This click is the only way the spend can happen."}},"required":["kind","requestId","approve"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"capability_offer","description":"Answering a request to connect something the agent needs."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"connect":{"type":"boolean","description":"Yes keeps the agent waiting while you set it up, and it carries on the moment the connection comes alive. No tells it to continue without. The reply itself connects nothing: setting it up is still your own doing."}},"required":["kind","requestId","connect"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"payment_offer","description":"Answering a request to pay for something."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"approve":{"type":"boolean","description":"Yes releases exactly one payment. Anything else spends nothing. This click is the only way the money can move."}},"required":["kind","requestId","approve"],"additionalProperties":false}]}},"required":["kind","requestId"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"mode"},"mode":{"type":"string","enum":["default","acceptEdits","plan","bypassPermissions"]}},"required":["kind","mode"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"error"},"message":{"type":"string"},"code":{"type":"string","enum":["session-not-found","rate_limit","codex-advisory","codex-reauth","claude-reauth","claude-token-refused","claude-not-entitled","provider-outage","trial-unavailable","trial-model-unavailable","trial-exhausted","unknown-command","grok-model-invalid","codex-model-invalid","context-window-too-small","subscription-required","agent-busy"]},"resetsAt":{"type":"number"},"autoResume":{"type":"string","enum":["scheduled","available"]},"outage":{"type":"object","properties":{"retryAt":{"type":"number"},"attempt":{"type":"number"},"maxAttempts":{"type":"number"}},"required":["retryAt","attempt","maxAttempts"],"additionalProperties":false}},"required":["kind","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"done"}},"required":["kind"],"additionalProperties":false}],"description":"What happened, as one of about forty shapes: the agent's words arriving piece by piece, a tool being called and answering, a plan or a question it is waiting on, a mode change, the turn's cost. Each carries its own `kind`."}},"required":["kind","seq","event"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"end","description":"The run is over and every frame has been delivered. A stream that closes without this was dropped mid-run, so re-attach with the last position you hold rather than assuming the turn finished."}},"required":["kind"],"additionalProperties":false}]},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/agent/reply":{"post":{"operationId":"agent.reply","summary":"Answer a question the agent asked","description":"Un-parks a turn that is waiting on you: approving a plan, choosing between options, or permitting a tool. The turn picks up where it stopped.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"plan","description":"Answering a plan the agent proposed."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering, from the frame that raised it."},"approve":{"type":"boolean","description":"Whether to go ahead. Approving means the plan then runs without asking again per tool, because being asked whether a plan you just approved may run its first command is not a question worth having."},"feedback":{"description":"Why not, which goes back to the model as the reason.","type":"string"}},"required":["kind","requestId","approve"]},{"type":"object","properties":{"kind":{"type":"string","const":"question","description":"Answering a question the agent asked."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"answers":{"description":"What you chose, keyed by the question, with the chosen labels or your own words.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"cancelled":{"description":"Dismissing it instead, which tells the agent to carry on using sensible defaults rather than leaving it waiting.","type":"boolean"}},"required":["kind","requestId"]},{"type":"object","properties":{"kind":{"type":"string","const":"permission","description":"Answering a request to use a tool."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"decision":{"type":"string","enum":["once","always","deny"],"description":"Once allows this call alone; always allows that whole tool for the rest of the conversation; no blocks it."},"feedback":{"description":"Why not, which goes back to the model as the reason.","type":"string"}},"required":["kind","requestId","decision"]},{"type":"object","properties":{"kind":{"type":"string","const":"browser_help","description":"Answering a request for help in the agent's browser: a captcha, a password it does not hold, a check on your phone."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"helped":{"type":"boolean","description":"Whether you cleared it. Yes means the turn carries on from the page as you left it; no tells the agent so, and it moves on rather than waiting for ever."},"note":{"description":"Anything the agent should know, which goes back to it either way.","type":"string"}},"required":["kind","requestId","helped"]},{"type":"object","properties":{"kind":{"type":"string","const":"terminal_help","description":"Answering a request for help at a terminal: a code to type, a confirmation only a person can give."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"helped":{"type":"boolean","description":"Whether you did it. Yes also hands the agent what the terminal now says, because a person answering a prompt is exactly the moment the agent cannot see."},"note":{"description":"Anything the agent should know, which goes back to it either way.","type":"string"}},"required":["kind","requestId","helped"]},{"type":"object","properties":{"kind":{"type":"string","const":"service_offer","description":"Answering a request to spend on a paid service."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"approve":{"type":"boolean","description":"Yes releases exactly one run. Anything else charges nothing. This click is the only way the spend can happen."}},"required":["kind","requestId","approve"]},{"type":"object","properties":{"kind":{"type":"string","const":"capability_offer","description":"Answering a request to connect something the agent needs."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"connect":{"type":"boolean","description":"Yes keeps the agent waiting while you set it up, and it carries on the moment the connection comes alive. No tells it to continue without. The reply itself connects nothing: setting it up is still your own doing."}},"required":["kind","requestId","connect"]},{"type":"object","properties":{"kind":{"type":"string","const":"payment_offer","description":"Answering a request to pay for something."},"requestId":{"type":"string","minLength":1,"description":"Which card you are answering."},"approve":{"type":"boolean","description":"Yes releases exactly one payment. Anything else spends nothing. This click is the only way the money can move."}},"required":["kind","requestId","approve"]}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/agent/steer":{"post":{"operationId":"agent.steer","summary":"Interrupt a running turn","description":"Slips a message into a turn already under way, without stopping it. This is how you redirect an agent mid-thought rather than waiting for it to finish being wrong.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","minLength":1,"description":"Which running conversation to interrupt."},"text":{"type":"string","maxLength":20000,"description":"What to say to it. It arrives mid-turn without stopping the turn."},"attachments":{"description":"Files to send with it, as workspace paths. A screenshot dropped in mid-turn with no words is a legitimate thing to send.","maxItems":20,"type":"array","items":{"type":"string","minLength":1}},"editorContext":{"description":"What you have open, folded in so that pointing words resolve.","type":"object","properties":{"file":{"type":"string","minLength":1,"description":"The file open in the editor, as a workspace path."},"startLine":{"description":"First line of the selection, counting from one. Leave both out when the whole file is the context.","type":"integer","minimum":1,"maximum":9007199254740991},"endLine":{"description":"Last line of the selection, counting from one.","type":"integer","minimum":1,"maximum":9007199254740991},"selection":{"description":"The selected text itself. Cut it down before sending if it is long: this is context, not an upload.","type":"string","maxLength":20000}},"required":["file"]}},"required":["conversationId","text"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/agent/stop":{"post":{"operationId":"agent.stop","summary":"Stop a turn now","description":"Cancels the running turn inside the sandbox. Whatever it had already written to disk stays written.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","minLength":1,"description":"Which conversation's running turn to cancel."}},"required":["conversationId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/agent/rewind":{"post":{"operationId":"agent.rewind","summary":"Go back to an earlier message","description":"Puts the files back as they stood at that point, drops every message after it, and forgets what the model remembered, so the next thing you say starts from there cleanly. Refused while a turn is running, because a restore cannot overwrite files an agent is editing, and refused for a message with no saved state to return to.","tags":["One agent"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","minLength":1,"description":"Which conversation to rewind."},"index":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which message to go back to, counting from the start. It is also how many messages survive: rewinding to the first keeps none of them and puts the files back to before it ran."}},"required":["conversationId","index"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"snapshot":{"description":"The saved point the files were put back to. Absent for a conversation working in its own copy, whose rewind moved a branch rather than the shared timeline.","type":"string"},"dropped":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many messages were removed."}},"required":["dropped"],"additionalProperties":false}}}}}}},"/agent/commands":{"get":{"operationId":"agent.commands","summary":"Shortcut commands the agent knows","description":"The commands a provider published the last time one of its turns ran, so a composer can offer them before this conversation has run anything. A running turn's own list wins over this one.","tags":["One agent"],"parameters":[{"name":"agent","in":"query","schema":{"description":"Whose commands to read. Leave it out for Claude.","type":"string","minLength":1},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"What to type, without the leading slash."},"description":{"type":"string","description":"What it does."},"hint":{"description":"What its argument should look like, shown after the name.","type":"string"}},"required":["name","description"],"additionalProperties":false},"description":"The shortcut commands, as the provider last published them."}},"required":["commands"],"additionalProperties":false}}}}}}},"/agent/refusals":{"get":{"operationId":"agent.refusals","summary":"The last time each provider said no","description":"What each model provider most recently refused and why. Read this alongside an account's usage: the usage says how full it was when last checked, this says whether it has since started turning work away.","tags":["One agent"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"refusals":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"at":{"type":"number","description":"When it refused, in milliseconds."},"kind":{"type":"string","enum":["limit","auth","entitlement"],"description":"Three different noes, kept apart because what fixes each is different. A spent allowance is answered by waiting; a refused credential by signing in again; and an entitlement refusal, where somebody has switched this off for your seat, by neither of those. That last one authenticates fine and reports healthy limits the whole time it refuses everything."},"message":{"type":"string","description":"The provider's own words, verbatim. The only part that says which limit or which credential."},"account":{"description":"Which account was serving, where that is known.","type":"string"}},"required":["at","kind","message"],"additionalProperties":false},"description":"The most recent refusal per provider. Read alongside an account's usage: that says how full it was when last checked, this says whether it has since started saying no."}},"required":["refusals"],"additionalProperties":false}}}}}}},"/agents":{"get":{"operationId":"agents.list","summary":"Every live conversation","description":"The fleet as the board draws it: each conversation with its title, what it is doing, when it last moved and whether anybody has read it since. Archived conversations are not in here.","tags":["The fleet"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false},"description":"The conversations."},"rev":{"type":"number","description":"Which version of the fleet this is. The fleet is published as whole snapshots, so without a version a list read before a change but delivered after it would silently undo that change. Drop any list older than the newest you have already applied."},"held":{"default":[],"description":"Automations waiting at the door for a yes, put alongside the running conversations so needs-you sits beside working rather than on a page nobody opens.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"This waiting item's own id, which approving and rejecting take."},"automationId":{"type":"string","description":"Which automation it came from."},"payload":{"description":"What set it off, kept whole so an approved wake carries the same thing it would have had. Absent for one on a schedule, which carries nothing.","type":"string"},"origin":{"description":"Where the message came from, kept alongside the payload so an approved wake appears on the board exactly as an automatic one would have.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"title":{"description":"What the conversation would be called.","type":"string"},"conversationId":{"description":"The thread this belongs to, when it has one, so approving continues that conversation rather than opening a new one. Without it, one visitor's chat becomes a card per approved message and an agent that meets them again every turn.","type":"string"},"sessionId":{"description":"The provider session that thread last ran on.","type":"string"},"createdAt":{"type":"number","description":"When it started waiting, in milliseconds."},"autoRunAt":{"description":"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person.","type":"number"}},"required":["id","automationId","createdAt"],"additionalProperties":false}}},"required":["agents","rev","held"],"additionalProperties":false}}}}}}},"/agents/archived":{"get":{"operationId":"agents.archived","summary":"Conversations put away","description":"The same shape as the live fleet, for the conversations somebody has decided are finished. Their work is kept, and any one of them can be brought back.","tags":["The fleet"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false},"description":"The conversations."},"rev":{"type":"number","description":"Which version of the fleet this is. The fleet is published as whole snapshots, so without a version a list read before a change but delivered after it would silently undo that change. Drop any list older than the newest you have already applied."},"held":{"default":[],"description":"Automations waiting at the door for a yes, put alongside the running conversations so needs-you sits beside working rather than on a page nobody opens.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"This waiting item's own id, which approving and rejecting take."},"automationId":{"type":"string","description":"Which automation it came from."},"payload":{"description":"What set it off, kept whole so an approved wake carries the same thing it would have had. Absent for one on a schedule, which carries nothing.","type":"string"},"origin":{"description":"Where the message came from, kept alongside the payload so an approved wake appears on the board exactly as an automatic one would have.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"title":{"description":"What the conversation would be called.","type":"string"},"conversationId":{"description":"The thread this belongs to, when it has one, so approving continues that conversation rather than opening a new one. Without it, one visitor's chat becomes a card per approved message and an agent that meets them again every turn.","type":"string"},"sessionId":{"description":"The provider session that thread last ran on.","type":"string"},"createdAt":{"type":"number","description":"When it started waiting, in milliseconds."},"autoRunAt":{"description":"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person.","type":"number"}},"required":["id","automationId","createdAt"],"additionalProperties":false}}},"required":["agents","rev","held"],"additionalProperties":false}}}}}}},"/agents/search":{"get":{"operationId":"agents.search","summary":"Find a conversation","description":"Searches the live fleet and the archive together. Both halves on purpose: the board hides finished work by design, and a filter that says it found nothing while the answer sits one click away is simply wrong.","tags":["The fleet"],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":2,"description":"What to look for. Searched against what was said, both sides of the conversation, and nothing else: not the thinking, not the tool output, which between them name nearly every identifier in the workspace and would return most of the board."},"allowEmptyValue":true,"allowReserved":true},{"name":"caseSensitive","in":"query","required":false,"schema":{"description":"Whether capitals matter.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which conversation matched."},"snippet":{"description":"Why, in its own words. Absent when the title was the match, which the card already shows: repeating it underneath is noise where evidence was wanted.","type":"object","properties":{"text":{"type":"string","description":"The matching line, with a little either side of it."},"speaker":{"type":"string","enum":["user","agent"],"description":"Who said it. Carried with the words rather than beside them, because a line of the agent's prose under a card reads as something you typed until the row says otherwise."}},"required":["text","speaker"],"additionalProperties":false}},"required":["id"],"additionalProperties":false},"description":"What matched, from the live fleet and the archive together."},"scanned":{"type":"number","description":"How many conversations were actually read, so a screen can say when a search saw less than everything rather than implying it saw all of it."},"indexing":{"type":"boolean","description":"Whether what was said is still being read in the background. True means this answer can still grow, so a screen must say it is incomplete rather than presenting it as the whole list."}},"required":["matches","scanned","indexing"],"additionalProperties":false}}}}}}},"/agents/{id}":{"get":{"operationId":"agents.get","summary":"One conversation's card","description":"Everything the board shows for a single conversation: its title, state, working branch, unread marker and timestamps.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/{id}/transcript":{"get":{"operationId":"agents.transcript","summary":"The full conversation record","description":"Every message in one conversation, in order, including the tool calls and their results. This is the record the chat replays and the next turn is seeded from.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant","notice"],"description":"Who said it. A notice is neither side: it is something that happened to the turn, recorded so a reopened conversation can say it. Without those, a turn a provider refused ends on the user's message and reads as broken."},"text":{"type":"string","description":"The words."},"sentAt":{"description":"When it was sent, in milliseconds. On the user's rows only, because that is the only moment actually known: a turn's own frames arrive with no clock, so stamping the agent's rows could only ever mean the whole turn's start or end.","type":"number"},"attachments":{"description":"Files attached to this message, as workspace paths.","type":"array","items":{"type":"string"}},"checkpointId":{"description":"The saved point this message can be rewound to. Looked up on each read rather than stored, so what is offered is exactly what is still there to go back to.","type":"string"},"thinking":{"description":"What the agent was reasoning about.","type":"string"},"tools":{"description":"The tool calls this part of the turn made.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"notes":{"description":"What the sandbox added to this message before the model saw it. Carried on the message rather than as rows of their own, because they genuinely were part of what was sent.","type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The one line a reader sees, on a row that opens to the text below."},"text":{"type":"string","description":"The note itself, which is also exactly what the model was told."}},"required":["title","text"],"additionalProperties":false}},"placed":{"description":"A person wrote this in the agent's voice, with no turn behind it. Marked for the human re-reading the conversation months later, so their own words do not pass as the agent's. The agent itself never sees the mark.","type":"boolean"},"noticeAction":{"description":"A one-press follow-up this recorded notice offers, by name. The chat decides what it does and whether it still applies.","type":"string","enum":["tierHold"]}},"required":["role","text"],"additionalProperties":false},"description":"The conversation, in order. Each block of the agent's prose is its own message with the tools that block introduced, which is what reproduces the way it actually unfolded."},"sessionId":{"description":"The provider session behind the last turn, when there is one.","type":"string"}},"required":["messages"],"additionalProperties":false,"$defs":{"__schema0":{"type":"object","properties":{"id":{"type":"string","description":"The call's id."},"name":{"type":"string","description":"Which tool."},"category":{"type":"string","enum":["read","edit","delete","move","search","execute","think","fetch","other"],"description":"What kind of thing it does: read, edit, delete, move, search, run, think, fetch. Named the same way whatever the backend called the tool."},"status":{"type":"string","enum":["pending","in_progress","completed","failed"],"description":"How it went."},"target":{"description":"What it acted on, in one line: a file, a command, an address.","type":"string"},"locations":{"description":"The files it touched.","type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path, whatever directory the tool was run from."},"line":{"description":"Which line, counting from one.","type":"number"}},"required":["path"],"additionalProperties":false}},"content":{"description":"What it produced: text, a change to a file, or a picture.","type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Plain output."},"text":{"type":"string","description":"What the tool said."}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"diff","description":"A change to a file."},"path":{"type":"string","description":"Which file, as a workspace path."},"oldText":{"description":"What was there. Absent for a new file, or where the previous contents are not known.","type":"string"},"newText":{"type":"string","description":"What is there now."},"truncated":{"description":"One of the two sides was too large to send whole.","type":"boolean"}},"required":["type","path","newText"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image","description":"A picture the tool produced."},"path":{"type":"string","description":"Where it is, as a workspace path. A path rather than the bytes, because the workspace already serves it, sending it inline would bloat every stored record, and this way the picture stays openable afterwards."}},"required":["type","path"],"additionalProperties":false}]}},"children":{"description":"Calls a delegated helper made, nested under the call that started it, so a reopened conversation redraws the delegation rather than collapsing it into one result.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"thinking":{"description":"What the agent was reasoning about around this call.","type":"string"}},"required":["id","name","category","status"],"additionalProperties":false}}}}}}}}},"/agents/{id}/place":{"post":{"operationId":"agents.place","summary":"Put words in the agent's mouth","description":"Writes a line into the record as though the agent had said it, with no turn behind it and no reply. Human readers see it marked as placed. The next real turn starts fresh from the record, where the line reads as the agent's own. Refused while a turn is running.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":8000,"description":"The words to put in the agent's mouth. Bounded just above what the next turn can carry whole, because a line too long to be handed over intact would reach the agent truncated and quietly break the very thing this is for."}},"required":["text"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/agents/{id}/rename":{"post":{"operationId":"agents.rename","summary":"Retitle a conversation","description":"Sets the title a person chose, replacing the one that was generated. Allowed while the conversation is working, and it does not count as activity.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it from now on."}},"required":["title"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/{id}/auto-land":{"post":{"operationId":"agents.autoLand","summary":"Whether this conversation merges its work automatically","description":"Overrides the sandbox-wide setting for one conversation; clear it to go back to following the default. Deliberately allowed mid-turn, because the setting is read when the turn finishes, so flipping it while the agent works means exactly hold this piece of work for review.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"autoLand":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether its work merges automatically when a turn finishes. Null clears the override and goes back to following the sandbox-wide setting, so a conversation does not sit holding a frozen copy of a default it has quietly stopped following."}},"required":["autoLand"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/{id}/resume-after-outage":{"post":{"operationId":"agents.resumeAfterOutage","summary":"Whether this conversation retries after a provider outage","description":"Overrides the sandbox-wide setting for one conversation; clear it to follow the default again. This is what the offer shown when a turn dies writes, because the press happens inside one conversation and honestly means finish this piece of work.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"resumeAfterOutage":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether it retries by itself when the model provider was what failed. Null clears the override back to the sandbox-wide setting."}},"required":["resumeAfterOutage"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/{id}/seen":{"post":{"operationId":"agents.seen","summary":"Mark a conversation read","description":"Stamps the read marker behind the unread badge on one card. Allowed while the conversation is working, and reading never counts as activity.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/seen":{"post":{"operationId":"agents.seenAll","summary":"Mark every conversation read","description":"Clears the unread badge across the whole fleet at once, and hands the refreshed list back.","tags":["The fleet"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false},"description":"The conversations."},"rev":{"type":"number","description":"Which version of the fleet this is. The fleet is published as whole snapshots, so without a version a list read before a change but delivered after it would silently undo that change. Drop any list older than the newest you have already applied."},"held":{"default":[],"description":"Automations waiting at the door for a yes, put alongside the running conversations so needs-you sits beside working rather than on a page nobody opens.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"This waiting item's own id, which approving and rejecting take."},"automationId":{"type":"string","description":"Which automation it came from."},"payload":{"description":"What set it off, kept whole so an approved wake carries the same thing it would have had. Absent for one on a schedule, which carries nothing.","type":"string"},"origin":{"description":"Where the message came from, kept alongside the payload so an approved wake appears on the board exactly as an automatic one would have.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"title":{"description":"What the conversation would be called.","type":"string"},"conversationId":{"description":"The thread this belongs to, when it has one, so approving continues that conversation rather than opening a new one. Without it, one visitor's chat becomes a card per approved message and an agent that meets them again every turn.","type":"string"},"sessionId":{"description":"The provider session that thread last ran on.","type":"string"},"createdAt":{"type":"number","description":"When it started waiting, in milliseconds."},"autoRunAt":{"description":"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person.","type":"number"}},"required":["id","automationId","createdAt"],"additionalProperties":false}}},"required":["agents","rev","held"],"additionalProperties":false}}}}}}},"/agents/{id}/diff":{"get":{"operationId":"agents.diff","summary":"Everything a conversation has changed","description":"One flat set of changed files per repo, measured against where each repo stood when the conversation started, with every file flagged as already merged or not. Not the staged-and-unstaged shape a working copy has, because nobody ever checks this branch out to stage into it.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"branch":{"description":"The branch this conversation's work sits on.","type":"string"},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"},"landed":{"type":"boolean","description":"Whether this change is already in the shared tree. The list is everything the conversation wrote, not just what is left over, because a clean turn merges in milliseconds and a list of leftovers would show an empty panel for work nobody had looked at yet."}},"required":["path","status","landed"],"additionalProperties":false},"description":"What it changed there."},"modules":{"type":"array","items":{"type":"object","properties":{"dir":{"type":"string","description":"Where the package lives, relative to its repository. Empty when the repository is itself one package."},"name":{"type":"string","description":"The name the package declares for itself."}},"required":["dir","name"],"additionalProperties":false},"description":"The packages of the tree these changes came from, so a review can group by package. Carried with the changes rather than looked up separately, because a package the conversation has just created exists only in its own copy and the shared tree has never heard of it."}},"required":["repo","changes","modules"],"additionalProperties":false},"description":"One entry per repository the conversation touched."},"conflicts":{"description":"Why the last merge refused, when one did. Carried here as well as in the merge's own answer, because a conflict is found the moment a turn ends and dealt with hours later on this surface, which would otherwise open with nothing to explain what it promised to resolve.","type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"paths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Which file."},"reason":{"type":"string","enum":["workspace","diverged","binary"],"description":"Why it would not merge, and the three have nothing in common but the symptom. Your own uncommitted edits on that path, where yours is the copy at risk. The shared tree having moved under the conversation since it started, where nothing of yours is at risk. Or a file git cannot merge at all, where no automatic answer exists."}},"required":["path","reason"],"additionalProperties":false},"description":"The files that genuinely would not apply. Not the whole change: reporting everything whenever the cause could not be pinned down turned four real conflicts into a wall of fourteen."},"clean":{"type":"number","description":"How many files would apply regardless, so a screen can say how much is being held back by how little and offer to take it. Zero alongside an empty list means the repository could not be reached at all."},"mainBranch":{"description":"The branch your own checkout is on, which is what the conversation has to rebase onto. Carried because only the sandbox can see it. Absent where there is no name to give.","type":"string"}},"required":["repo","paths","clean"],"additionalProperties":false}}},"required":["repos"],"additionalProperties":false}}}}}}},"/agents/{id}/{repo}/file-diff":{"get":{"operationId":"agents.fileDiff","summary":"One file's before and after in a conversation's work","description":"Both sides of a single file: what it held when the conversation started and what it holds on its branch now.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}},{"name":"repo","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which repository."}},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which file, relative to that repository."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"before":{"description":"The whole file as it was. Absent when it did not exist yet.","type":"string"},"after":{"description":"The whole file as it is now. Absent when it was deleted.","type":"string"},"binary":{"description":"The file is not text, so neither side is sent.","type":"boolean"},"truncated":{"description":"The file was too large to send whole, so what you have is the start of it.","type":"boolean"}},"additionalProperties":false}}}}}}},"/agents/{id}/land":{"post":{"operationId":"agents.land","summary":"Merge a conversation's work into the workspace","description":"Brings the conversation's branches into the main tree, one repo at a time. A conflict is reported rather than raised and nothing is lost when it fails. Refused while a turn is running, and refused for a conversation that works directly in the shared tree, which has nothing to merge.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation's work to merge."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"description":"How to apply it. The default applies all of it or none, so a refusal leaves the workspace exactly as it was. The other lands every clean file and leaves the rest with conflict markers to resolve by hand.","type":"string","enum":["check","merge","measure"]},"span":{"description":"How much of the work to take. Leave it out for everything not yet merged.","type":"string","enum":["cumulative","outstanding"]},"force":{"description":"Go ahead despite a check that would otherwise refuse.","type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"landed":{"type":"boolean","description":"Whether anything was applied."},"conflicts":{"description":"What stopped it, per repository.","type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"paths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Which file."},"reason":{"type":"string","enum":["workspace","diverged","binary"],"description":"Why it would not merge, and the three have nothing in common but the symptom. Your own uncommitted edits on that path, where yours is the copy at risk. The shared tree having moved under the conversation since it started, where nothing of yours is at risk. Or a file git cannot merge at all, where no automatic answer exists."}},"required":["path","reason"],"additionalProperties":false},"description":"The files that genuinely would not apply. Not the whole change: reporting everything whenever the cause could not be pinned down turned four real conflicts into a wall of fourteen."},"clean":{"type":"number","description":"How many files would apply regardless, so a screen can say how much is being held back by how little and offer to take it. Zero alongside an empty list means the repository could not be reached at all."},"mainBranch":{"description":"The branch your own checkout is on, which is what the conversation has to rebase onto. Carried because only the sandbox can see it. Absent where there is no name to give.","type":"string"}},"required":["repo","paths","clean"],"additionalProperties":false}},"resolving":{"description":"Files left half-merged, when you asked for the mode that lands what it can and leaves the rest marked up.","type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"paths":{"type":"array","items":{"type":"string"},"description":"Which files now hold conflict markers to sort out by hand."}},"required":["repo","paths"],"additionalProperties":false}},"held":{"description":"Nothing was applied and nothing failed: there is work waiting on the branch for a deliberate merge. Not merged on its own cannot say that, because on its own it means refused.","type":"boolean"}},"required":["landed"],"additionalProperties":false}}}}}}},"/agents/{id}/request-land":{"post":{"operationId":"agents.requestLand","summary":"Ask a maintainer to merge this work","description":"For a collaborator who is not allowed to merge: marks the conversation as waiting for review, with who asked. The request shows on every maintainer's board and clears when somebody merges or discards it.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}}}}}}},"/agents/{id}/discard":{"post":{"operationId":"agents.discard","summary":"Throw a conversation's work away","description":"Deletes the conversation's working copies, its branches and its entry. Nothing is kept. Refused while a turn is running, and refused for a conversation working in the shared tree.","tags":["The fleet"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Which conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/agents/archive":{"post":{"operationId":"agents.archive","summary":"Put conversations away","description":"The gentle counterpart to discarding. Commits whatever the conversation still has in progress onto its own branch, releases its working copy, and keeps the entry and the record. It leaves the live fleet and joins the archive. Refused for a conversation that is running.","tags":["The fleet"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"description":"Which conversations to put away. Leave it out for every finished one that can be archived right now.","maxItems":500,"type":"array","items":{"type":"string","minLength":1}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"moved":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false},"description":"What actually moved, whole, rather than the fleet afterwards. Two archives finishing at once would each carry a snapshot from a different instant, and swapping one in wholesale would let the slower answer resurrect what the faster one just filed away."},"rev":{"type":"number","description":"The version of the fleet that includes this move, so a caller can hold its own optimistic change until it sees a list at least that new."},"failed":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which conversation stayed on the board."},"reason":{"type":"string","description":"Why its working copy could not be released, in the words the failure came with."}},"required":["id","reason"],"additionalProperties":false},"description":"The conversations this press could not put away, each with the reason, so the board can say it instead of reporting silence."}},"required":["moved","rev","failed"],"additionalProperties":false}}}}}}},"/agents/unarchive":{"post":{"operationId":"agents.unarchive","summary":"Bring conversations back","description":"Returns archived conversations to the live fleet. The next turn picks up a fresh working copy from the branch that was kept.","tags":["The fleet"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","minLength":1},"description":"Which conversations."}},"required":["ids"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"moved":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false},"description":"What actually moved, whole, rather than the fleet afterwards. Two archives finishing at once would each carry a snapshot from a different instant, and swapping one in wholesale would let the slower answer resurrect what the faster one just filed away."},"rev":{"type":"number","description":"The version of the fleet that includes this move, so a caller can hold its own optimistic change until it sees a list at least that new."}},"required":["moved","rev"],"additionalProperties":false}}}}}}},"/agents/purge":{"post":{"operationId":"agents.purge","summary":"Empty the archive for good","description":"Discards every conversation already in the archive: working copies, branches and entries. The whole archive rather than a chosen few, because the archive is the pile somebody has already decided is over. A teardown that fails on one conversation leaves that one behind instead of taking the rest down with it.","tags":["The fleet"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"array","items":{"type":"string"},"description":"Which conversations were deleted, as ids. Ids rather than whole cards, because these no longer exist anywhere: there is nothing left to show and nothing to put back."}},"required":["removed"],"additionalProperties":false}}}}}}},"/sessions":{"get":{"operationId":"sessions.list","summary":"Past conversations in this workspace","description":"Summaries for a history menu, filtered when you pass a search. Covers conversations that worked in their own private copies too, so nothing is hidden just because it happened on a branch.","tags":["Past sessions"],"parameters":[{"name":"query","in":"query","schema":{"type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"caseSensitive","in":"query","schema":{"type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Its id."},"title":{"type":"string","description":"What it is called."},"updatedAt":{"type":"number","description":"When it last moved, in milliseconds."},"snippet":{"description":"Why a search matched: the line it hit, with a little around it, and who said it. Absent on an unfiltered list, and on a match the title already shows, where repeating it would be noise rather than evidence.","type":"object","properties":{"text":{"type":"string","description":"The matching line, with a little either side of it."},"speaker":{"type":"string","enum":["user","agent"],"description":"Who said it. Carried with the words rather than beside them, because a line of the agent's prose under a card reads as something you typed until the row says otherwise."}},"required":["text","speaker"],"additionalProperties":false}},"required":["id","title","updatedAt"],"additionalProperties":false},"description":"Past conversations, newest first."}},"required":["sessions"],"additionalProperties":false}}}}}}},"/sessions/{id}":{"get":{"operationId":"sessions.get","summary":"Read one past conversation","description":"The full record of a single conversation, restored for display.","tags":["Past sessions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which past conversation."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant","notice"],"description":"Who said it. A notice is neither side: it is something that happened to the turn, recorded so a reopened conversation can say it. Without those, a turn a provider refused ends on the user's message and reads as broken."},"text":{"type":"string","description":"The words."},"sentAt":{"description":"When it was sent, in milliseconds. On the user's rows only, because that is the only moment actually known: a turn's own frames arrive with no clock, so stamping the agent's rows could only ever mean the whole turn's start or end.","type":"number"},"attachments":{"description":"Files attached to this message, as workspace paths.","type":"array","items":{"type":"string"}},"checkpointId":{"description":"The saved point this message can be rewound to. Looked up on each read rather than stored, so what is offered is exactly what is still there to go back to.","type":"string"},"thinking":{"description":"What the agent was reasoning about.","type":"string"},"tools":{"description":"The tool calls this part of the turn made.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"notes":{"description":"What the sandbox added to this message before the model saw it. Carried on the message rather than as rows of their own, because they genuinely were part of what was sent.","type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The one line a reader sees, on a row that opens to the text below."},"text":{"type":"string","description":"The note itself, which is also exactly what the model was told."}},"required":["title","text"],"additionalProperties":false}},"placed":{"description":"A person wrote this in the agent's voice, with no turn behind it. Marked for the human re-reading the conversation months later, so their own words do not pass as the agent's. The agent itself never sees the mark.","type":"boolean"},"noticeAction":{"description":"A one-press follow-up this recorded notice offers, by name. The chat decides what it does and whether it still applies.","type":"string","enum":["tierHold"]}},"required":["role","text"],"additionalProperties":false},"description":"The conversation, in order. Each block of the agent's prose is its own message with the tools that block introduced, which is what reproduces the way it actually unfolded."}},"required":["messages"],"additionalProperties":false,"$defs":{"__schema0":{"type":"object","properties":{"id":{"type":"string","description":"The call's id."},"name":{"type":"string","description":"Which tool."},"category":{"type":"string","enum":["read","edit","delete","move","search","execute","think","fetch","other"],"description":"What kind of thing it does: read, edit, delete, move, search, run, think, fetch. Named the same way whatever the backend called the tool."},"status":{"type":"string","enum":["pending","in_progress","completed","failed"],"description":"How it went."},"target":{"description":"What it acted on, in one line: a file, a command, an address.","type":"string"},"locations":{"description":"The files it touched.","type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path, whatever directory the tool was run from."},"line":{"description":"Which line, counting from one.","type":"number"}},"required":["path"],"additionalProperties":false}},"content":{"description":"What it produced: text, a change to a file, or a picture.","type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Plain output."},"text":{"type":"string","description":"What the tool said."}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"diff","description":"A change to a file."},"path":{"type":"string","description":"Which file, as a workspace path."},"oldText":{"description":"What was there. Absent for a new file, or where the previous contents are not known.","type":"string"},"newText":{"type":"string","description":"What is there now."},"truncated":{"description":"One of the two sides was too large to send whole.","type":"boolean"}},"required":["type","path","newText"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image","description":"A picture the tool produced."},"path":{"type":"string","description":"Where it is, as a workspace path. A path rather than the bytes, because the workspace already serves it, sending it inline would bloat every stored record, and this way the picture stays openable afterwards."}},"required":["type","path"],"additionalProperties":false}]}},"children":{"description":"Calls a delegated helper made, nested under the call that started it, so a reopened conversation redraws the delegation rather than collapsing it into one result.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"thinking":{"description":"What the agent was reasoning about around this call.","type":"string"}},"required":["id","name","category","status"],"additionalProperties":false}}}}}}}}},"/workflows":{"get":{"operationId":"workflows.list","summary":"Saved workflows and their runs","description":"Every workflow somebody has designed, each with its own run history, newest first. One answer rather than two, because a workflow that has never been run is the interesting case rather than a mistake.","tags":["Workflows"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"workflows":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"],"additionalProperties":false},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"],"additionalProperties":false},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."},"runs":{"type":"array","items":{"type":"object","properties":{"runId":{"type":"string","minLength":1,"description":"This run's id."},"workflow":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"],"additionalProperties":false},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"],"additionalProperties":false},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."}},"required":["id","name","steps","maxParallel"],"additionalProperties":false,"description":"The design as it stood when the run started, copied rather than looked up. The run has to keep showing the graph it actually ran, not the one edited twice since, and a run of a deleted workflow has to stay readable."},"repos":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"],"additionalProperties":false},"description":"The workspace as this run began, one exact commit per repository. Every step branches from these, even if the shared tree moves while a wide fan-out is still opening its copies, so the steps can be compared with each other afterwards."},"request":{"description":"What this run was asked to do, handed to every step on top of its own instructions. It is what makes one saved design worth keeping: two models, one task is a shape, and the task is different every time. Absent for a run started with nowhere to type one.","type":"string"},"state":{"type":"string","enum":["running","done","failed","stopped","overspent","error"],"description":"How the run is going. Finished means every step that ran got there; a run with skipped steps counts as failed, because a graph that never reached its end did not do what it was asked whatever the survivors managed."},"startedAt":{"type":"number","description":"When it began, in milliseconds."},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"resumed":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times the sandbox restarted under it and picked it back up."},"detail":{"description":"What went wrong, when something did.","type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"stepId":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step this is."},"state":{"type":"string","enum":["pending","running","done","failed","skipped","stopped"],"description":"How it went. Skipped carries what the others cannot: it never ran, because something it was waiting on did not finish. That is why a failed run shows one red step and a trail of grey ones."},"conversationId":{"type":"string","description":"The conversation it ran on, and the way from a node on the graph to a real record. Shared with the step before it when they were chained, which is what makes those two one card."},"startedAt":{"description":"When it began, in milliseconds.","type":"number"},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"iterations":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many rounds it took."},"costUsd":{"description":"What it cost, in dollars.","type":"number"},"loopState":{"description":"How its repeating ended. Out of rounds and stuck both come out as a failed step, and the difference between them is the difference between give it more room and more room will not help.","type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"]},"detail":{"description":"What went wrong, when something did.","type":"string"},"document":{"description":"What it produced, once it has produced something that passes its own declared shape. This is what the steps after it are handed.","type":"object","properties":{"done":{"type":"boolean","description":"Whether the goal is met. Reading this is the whole point of the file."},"reason":{"type":"string","description":"Why, in one line. The most-read sentence in the feature: the next round reads it first and the history shows it."},"evidence":{"description":"What was checked to know that. Optional, so a round with nothing to point at says so by leaving it out rather than by inventing a sentence.","type":"string"},"data":{"description":"The declared answer, for a loop that asked for one, checked against the shape it declared.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["done","reason"],"additionalProperties":false},"report":{"description":"The start of its closing words. Bounded, so a long answer is not silently cut down to its last few thousand characters and the record stays a sensible size.","type":"string"},"reportPath":{"description":"Where the whole answer is, as a workspace path. Every step can read it, so a long handoff need not be copied into anybody's prompt.","type":"string"}},"required":["stepId","state","conversationId","iterations"],"additionalProperties":false},"description":"One entry per step, in the design's own order. Every one is written down as waiting when the run starts, so the picture is complete from the first frame and a missing step never has to mean two things."},"archivedAt":{"description":"When it was put away, in milliseconds. The record stays readable and every step's branch, transcript and counters are untouched. Its conversations are put away with it, and brought back with it. Absent means live on the board.","type":"number"}},"required":["runId","workflow","repos","state","startedAt","resumed","steps"],"additionalProperties":false},"description":"Its runs, newest first."}},"required":["id","name","steps","maxParallel","runs"],"additionalProperties":false},"description":"Every saved design with its own run history."}},"required":["workflows"],"additionalProperties":false}}}}}},"post":{"operationId":"workflows.save","summary":"Create or replace a workflow","description":"Writes a workflow design. Say which of the two you mean, so an id that happens to collide cannot silently overwrite somebody's work. A design that could never run is refused, in the same words the editor shows while you type: a loop in the steps, a step waiting on one that is not there, a step with no way of knowing it is finished.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workflow":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"]},"description":"The shape that answer has to match."}},"required":["kind","fields"]}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"]},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"]}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"]},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"]},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."}},"required":["id","name","steps","maxParallel"],"description":"The design to write."},"create":{"type":"boolean","description":"Whether you mean to make a new one or replace an existing one. Said outright rather than inferred, so an id that happens to collide is a refusal instead of one saved design quietly overwriting another."}},"required":["workflow","create"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"],"additionalProperties":false},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"],"additionalProperties":false},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."}},"required":["id","name","steps","maxParallel"],"additionalProperties":false}}}}}}},"/workflows/{id}":{"delete":{"operationId":"workflows.remove","summary":"Delete a workflow","description":"Removes the design. A run of it that is already going keeps going and stays readable and stoppable, because a run takes its own copy of the design when it starts.","tags":["Workflows"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which workflow."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workflows/{id}/run":{"post":{"operationId":"workflows.run","summary":"Start a workflow","description":"Kicks a workflow off and answers immediately with the run as recorded; the work carries on without you. Point it at a question and every step gets that on top of its own instructions. Every step is written down as waiting up front, so the picture is complete from the first frame. Several runs of one design can be in flight at once without colliding.","tags":["Workflows"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which workflow."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"request":{"description":"What to point it at. Optional, because a design whose steps already say what they want is complete on its own; only one written as a shape needs today's sentence.","type":"string","minLength":1,"maxLength":20000}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string","minLength":1,"description":"This run's id."},"workflow":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"],"additionalProperties":false},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"],"additionalProperties":false},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."}},"required":["id","name","steps","maxParallel"],"additionalProperties":false,"description":"The design as it stood when the run started, copied rather than looked up. The run has to keep showing the graph it actually ran, not the one edited twice since, and a run of a deleted workflow has to stay readable."},"repos":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"],"additionalProperties":false},"description":"The workspace as this run began, one exact commit per repository. Every step branches from these, even if the shared tree moves while a wide fan-out is still opening its copies, so the steps can be compared with each other afterwards."},"request":{"description":"What this run was asked to do, handed to every step on top of its own instructions. It is what makes one saved design worth keeping: two models, one task is a shape, and the task is different every time. Absent for a run started with nowhere to type one.","type":"string"},"state":{"type":"string","enum":["running","done","failed","stopped","overspent","error"],"description":"How the run is going. Finished means every step that ran got there; a run with skipped steps counts as failed, because a graph that never reached its end did not do what it was asked whatever the survivors managed."},"startedAt":{"type":"number","description":"When it began, in milliseconds."},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"resumed":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times the sandbox restarted under it and picked it back up."},"detail":{"description":"What went wrong, when something did.","type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"stepId":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step this is."},"state":{"type":"string","enum":["pending","running","done","failed","skipped","stopped"],"description":"How it went. Skipped carries what the others cannot: it never ran, because something it was waiting on did not finish. That is why a failed run shows one red step and a trail of grey ones."},"conversationId":{"type":"string","description":"The conversation it ran on, and the way from a node on the graph to a real record. Shared with the step before it when they were chained, which is what makes those two one card."},"startedAt":{"description":"When it began, in milliseconds.","type":"number"},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"iterations":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many rounds it took."},"costUsd":{"description":"What it cost, in dollars.","type":"number"},"loopState":{"description":"How its repeating ended. Out of rounds and stuck both come out as a failed step, and the difference between them is the difference between give it more room and more room will not help.","type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"]},"detail":{"description":"What went wrong, when something did.","type":"string"},"document":{"description":"What it produced, once it has produced something that passes its own declared shape. This is what the steps after it are handed.","type":"object","properties":{"done":{"type":"boolean","description":"Whether the goal is met. Reading this is the whole point of the file."},"reason":{"type":"string","description":"Why, in one line. The most-read sentence in the feature: the next round reads it first and the history shows it."},"evidence":{"description":"What was checked to know that. Optional, so a round with nothing to point at says so by leaving it out rather than by inventing a sentence.","type":"string"},"data":{"description":"The declared answer, for a loop that asked for one, checked against the shape it declared.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["done","reason"],"additionalProperties":false},"report":{"description":"The start of its closing words. Bounded, so a long answer is not silently cut down to its last few thousand characters and the record stays a sensible size.","type":"string"},"reportPath":{"description":"Where the whole answer is, as a workspace path. Every step can read it, so a long handoff need not be copied into anybody's prompt.","type":"string"}},"required":["stepId","state","conversationId","iterations"],"additionalProperties":false},"description":"One entry per step, in the design's own order. Every one is written down as waiting when the run starts, so the picture is complete from the first frame and a missing step never has to mean two things."},"archivedAt":{"description":"When it was put away, in milliseconds. The record stays readable and every step's branch, transcript and counters are untouched. Its conversations are put away with it, and brought back with it. Absent means live on the board.","type":"number"}},"required":["runId","workflow","repos","state","startedAt","resumed","steps"],"additionalProperties":false}}}}}}},"/workflows/runs":{"get":{"operationId":"workflows.runs","summary":"Every workflow run","description":"All runs across all workflows, newest first. This is also the only place the runs of a deleted workflow are still reachable.","tags":["Workflows"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"runId":{"type":"string","minLength":1,"description":"This run's id."},"workflow":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The workflow's id."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"What to call it."},"description":{"description":"What it is for.","type":"string","maxLength":400},"steps":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"This step's own name, which other steps use to say they wait on it."},"title":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it on screen. Short: the instruction below is where the detail goes."},"goal":{"description":"What done means for this step, in your words. It is what the step is judged against, and a different sentence from what it is told to do.","type":"string","minLength":1},"prompt":{"description":"What the step is told to do. The goal is the suite is green; this is run the tests, take the top failure, fix it. Leaving it out hands over the run's own request untouched, which is right for a step whose whole job is do what was asked.","type":"string","minLength":1},"needs":{"type":"array","items":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$"},"description":"Which steps must finish first. Empty means it starts when the run does. Naming a step that does not exist, or a loop between steps, is refused when the workflow is saved."},"handoff":{"type":"string","enum":["fresh","continue"],"description":"How it meets what came before: a fresh conversation handed the previous step's result, or the same conversation carried on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce for the step to count."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What has to pass before it counts as done."},"context":{"type":"string","enum":["fresh","continue"],"description":"How the step's own repeats meet each other. A long-running step wants to start clean each round; a short polish-this step wants to carry on."},"maxSpendUsd":{"description":"A ceiling on what this step may spend. The one resource that cannot be recovered after an unattended fan-out, which is why it is here and iteration limits are not. Absent is uncapped.","type":"number","exclusiveMinimum":0},"agent":{"description":"Which provider runs it.","type":"string","minLength":1},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays for it.","type":"string"},"model":{"description":"Which model runs it.","type":"string"},"actsAs":{"description":"Which persona it acts as. Unpinned, a step gets the strict unwatched default: every tool, and no signed-in accounts at all. Pinning one is how a release check gets a voice, a folder to work in, or the single account it may post from.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["id","title","needs","handoff","output","checks","context"],"additionalProperties":false},"description":"The steps, each with what it waits on. Every one runs in its own private copy of the repos, always, because parallel steps sharing a tree collide."},"gate":{"description":"Present means a machine can run this design and get a ship-it answer back. Absent means an ordinary workflow, started by a person, with no outside door onto it at all.","type":"object","properties":{"step":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step's answer carries the decision. Usually a last step that weighs up the ones before it, though nothing requires that."},"field":{"type":"string","minLength":1,"description":"Which of that step's declared answers to read. A declared field is the one part of a step's answer that was checked rather than fished out of prose, which is the whole rule here. Checked when the workflow is saved."},"pass":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"Which values mean ship it. Everything else fails. A list of what passes rather than what fails, because a step answering mostly-pass or pass-with-notes must not ship, and this gets that right without anybody having had to enumerate the ways a model can hedge."},"token":{"description":"The credential the calling pipeline presents. It is the only one in the exchange, because a build runner has no identity of its own here.","type":"string"},"dailyMax":{"description":"How many runs a day, across every caller. A gate is a paid door with nobody in the loop: one wired into a push-triggered pipeline is a fan-out of conversations per commit. Absent is a small default rather than unlimited.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["step","field","pass"],"additionalProperties":false},"maxParallel":{"type":"integer","minimum":1,"maximum":8,"description":"How many steps may run at once. Bounded, because a fan-out of twelve is twelve model sessions, twelve working copies and twelve times the burn rate, on one machine."}},"required":["id","name","steps","maxParallel"],"additionalProperties":false,"description":"The design as it stood when the run started, copied rather than looked up. The run has to keep showing the graph it actually ran, not the one edited twice since, and a run of a deleted workflow has to stay readable."},"repos":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"],"additionalProperties":false},"description":"The workspace as this run began, one exact commit per repository. Every step branches from these, even if the shared tree moves while a wide fan-out is still opening its copies, so the steps can be compared with each other afterwards."},"request":{"description":"What this run was asked to do, handed to every step on top of its own instructions. It is what makes one saved design worth keeping: two models, one task is a shape, and the task is different every time. Absent for a run started with nowhere to type one.","type":"string"},"state":{"type":"string","enum":["running","done","failed","stopped","overspent","error"],"description":"How the run is going. Finished means every step that ran got there; a run with skipped steps counts as failed, because a graph that never reached its end did not do what it was asked whatever the survivors managed."},"startedAt":{"type":"number","description":"When it began, in milliseconds."},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"resumed":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times the sandbox restarted under it and picked it back up."},"detail":{"description":"What went wrong, when something did.","type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"stepId":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which step this is."},"state":{"type":"string","enum":["pending","running","done","failed","skipped","stopped"],"description":"How it went. Skipped carries what the others cannot: it never ran, because something it was waiting on did not finish. That is why a failed run shows one red step and a trail of grey ones."},"conversationId":{"type":"string","description":"The conversation it ran on, and the way from a node on the graph to a real record. Shared with the step before it when they were chained, which is what makes those two one card."},"startedAt":{"description":"When it began, in milliseconds.","type":"number"},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"iterations":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many rounds it took."},"costUsd":{"description":"What it cost, in dollars.","type":"number"},"loopState":{"description":"How its repeating ended. Out of rounds and stuck both come out as a failed step, and the difference between them is the difference between give it more room and more room will not help.","type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"]},"detail":{"description":"What went wrong, when something did.","type":"string"},"document":{"description":"What it produced, once it has produced something that passes its own declared shape. This is what the steps after it are handed.","type":"object","properties":{"done":{"type":"boolean","description":"Whether the goal is met. Reading this is the whole point of the file."},"reason":{"type":"string","description":"Why, in one line. The most-read sentence in the feature: the next round reads it first and the history shows it."},"evidence":{"description":"What was checked to know that. Optional, so a round with nothing to point at says so by leaving it out rather than by inventing a sentence.","type":"string"},"data":{"description":"The declared answer, for a loop that asked for one, checked against the shape it declared.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["done","reason"],"additionalProperties":false},"report":{"description":"The start of its closing words. Bounded, so a long answer is not silently cut down to its last few thousand characters and the record stays a sensible size.","type":"string"},"reportPath":{"description":"Where the whole answer is, as a workspace path. Every step can read it, so a long handoff need not be copied into anybody's prompt.","type":"string"}},"required":["stepId","state","conversationId","iterations"],"additionalProperties":false},"description":"One entry per step, in the design's own order. Every one is written down as waiting when the run starts, so the picture is complete from the first frame and a missing step never has to mean two things."},"archivedAt":{"description":"When it was put away, in milliseconds. The record stays readable and every step's branch, transcript and counters are untouched. Its conversations are put away with it, and brought back with it. Absent means live on the board.","type":"number"}},"required":["runId","workflow","repos","state","startedAt","resumed","steps"],"additionalProperties":false},"description":"Every run across every workflow, newest first, including runs of workflows since deleted."}},"required":["runs"],"additionalProperties":false}}}}}}},"/workflows/runs/{runId}/stop":{"post":{"operationId":"workflows.stopRun","summary":"Stop a run now","description":"Nothing further starts, and the steps already going are cut off where they stand. Whatever they had written stays on their branches. Deliberately abrupt rather than letting the current step finish: a step is a whole agent turn, and a stop that kept spending for minutes afterwards is indistinguishable from a button that does nothing. It always ends the run, including one left stranded by a daemon that was replaced mid-flight.","tags":["Workflows"],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","description":"Which run."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workflows/runs/{runId}/archive":{"post":{"operationId":"workflows.archiveRun","summary":"Take a finished run off the board","description":"Nothing is lost and the working copies are reclaimed. Every conversation the run started is put away with it, which is what makes this an archive rather than a dismissal: a step has no card of its own, so merely dropping the run would spill its conversations onto the board at the moment somebody said they were done. Refused while the run is still going.","tags":["Workflows"],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","description":"Which run."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workflows/runs/{runId}/unarchive":{"post":{"operationId":"workflows.unarchiveRun","summary":"Bring an archived run back","description":"Puts a run and every conversation it started back on the board.","tags":["Workflows"],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","description":"Which run."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/loops":{"get":{"operationId":"loops.list","summary":"Every loop that has run","description":"The loops this workspace has run, newest first, kept after they end. Why it stopped on the fourth round is the question a loop gets read for, and the round-by-round history is the answer.","tags":["Loops"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"loops":{"type":"array","items":{"type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"The conversation to loop. It need not exist yet: naming a fresh one opens it, which is what lets run this until it passes be the first thing you ever say."},"goal":{"type":"string","minLength":1,"description":"What done means, in your words. It goes into every round's instructions and into the judge's question, so the model is told the bar rather than left to infer it."},"prompt":{"type":"string","minLength":1,"description":"What each round is asked to do. The suite passes is the goal; run the tests, take the top failure, fix it is the instruction."},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last. Starting fresh makes the files the memory rather than the conversation, so the twentieth round reads the tree as clearly as the first, and costs a re-read each time. Carrying on is cheaper and keeps the reasoning, which suits a short polish-this loop and degrades on long ones: a session that has spent eleven rounds arguing for its own approach is the worst available judge of whether that approach is finished."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}]},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What else has to be true, all of them together. A list because the suite passes and the report is written is a real bar, and running it as two loops would do the work twice."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up. A loop that has not got there in fifty is not one round short of it."},"maxSpendUsd":{"description":"A ceiling on what the whole loop may spend, in dollars. Optional for a short loop somebody is watching, and strongly wanted otherwise: this is the first thing here that can keep spending with nobody pressing anything between rounds.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing on disk. The guard that matters most: a loop's failure is not runaway success, it is an agent re-reading the same three files, restating the same plan and declaring more work remains, eleven times. Every one of those rounds succeeds, so only the tree not moving catches it."},"isolated":{"type":"boolean","description":"Whether it works in the conversation's own private copy or in the shared tree. It also decides where a check runs: testing the shared tree would be testing code this loop has not merged yet."},"agent":{"description":"Which provider the rounds run on. Absent falls back to the conversation's own last choice.","type":"string","minLength":1},"harness":{"description":"Which agentic loop they run on.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays.","type":"string"},"model":{"description":"Which model.","type":"string"},"actsAs":{"description":"Which persona the rounds act as. It matters here: every round is unwatched, and an unwatched turn naming no persona reaches no signed-in account at all, so pinning one is how a loop gets hands.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"worktreeBase":{"description":"Pin the private copy to these exact commits, so a restart cannot quietly change what the loop is working on.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"],"additionalProperties":false}},"autoLand":{"description":"Whether the work merges as it goes.","type":"boolean"},"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How it ended, and each of these is a different thing to be told. Out of rounds says give it more room; stalled says it is not making progress and more room will not help. Overspent, stopped by a person, and the loop itself failing are all their own answers."},"startedAt":{"type":"number","description":"When it began, in milliseconds."},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"resumed":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times the sandbox restarted under it and picked it back up. Counted rather than flagged, so a loop whose round reliably kills the sandbox is not resurrected on every boot for ever."},"detail":{"description":"Why it ended, for the endings whose reason is not in their name.","type":"string"},"iterations":{"type":"array","items":{"type":"object","properties":{"n":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Which round this was."},"at":{"type":"number","description":"When it ran, in milliseconds."},"outcome":{"type":"string","enum":["continue","done","error"],"description":"How the round ended, which is not the same question as how the loop did. A round that errored does not end the loop by itself: a failing turn is often exactly what the next round is meant to fix."},"detail":{"description":"What the check said, in its own words. What a run history is actually read for: why it kept going, and why it stopped.","type":"string"},"costUsd":{"description":"What the round cost, in dollars.","type":"number"},"changed":{"type":"boolean","description":"Whether anything on disk moved. Three unchanged rounds in a row is the shape of a loop that is not working."},"sessionId":{"description":"The session it ran on, and the way from a history row to a readable record.","type":"string"}},"required":["n","at","outcome","changed"],"additionalProperties":false},"description":"Every round, in order. Why it stopped at the fourth is the question a loop gets read for, and this is the answer."}},"required":["conversationId","goal","prompt","context","output","checks","maxIterations","stallLimit","isolated","state","startedAt","resumed","iterations"],"additionalProperties":false},"description":"Every loop this workspace has run, newest first, kept after they end."}},"required":["loops"],"additionalProperties":false}}}}}},"post":{"operationId":"loops.start","summary":"Run a conversation until it is done","description":"Starts repeating a conversation towards a goal and answers straight away with the loop as recorded; the work carries on without you. The conversation need not exist yet, so run this until it passes can be the first thing you ever say to a new agent. A conversation already looping is refused.","tags":["Loops"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"The conversation to loop. It need not exist yet: naming a fresh one opens it, which is what lets run this until it passes be the first thing you ever say."},"goal":{"type":"string","minLength":1,"description":"What done means, in your words. It goes into every round's instructions and into the judge's question, so the model is told the bar rather than left to infer it."},"prompt":{"type":"string","minLength":1,"description":"What each round is asked to do. The suite passes is the goal; run the tests, take the top failure, fix it is the instruction."},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last. Starting fresh makes the files the memory rather than the conversation, so the twentieth round reads the tree as clearly as the first, and costs a re-read each time. Carrying on is cheaper and keeps the reasoning, which suits a short polish-this loop and degrades on long ones: a session that has spent eleven rounds arguing for its own approach is the worst available judge of whether that approach is finished."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"]},"description":"The shape that answer has to match."}},"required":["kind","fields"]}]},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"]},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"]}]},"description":"What else has to be true, all of them together. A list because the suite passes and the report is written is a real bar, and running it as two loops would do the work twice."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up. A loop that has not got there in fifty is not one round short of it."},"maxSpendUsd":{"description":"A ceiling on what the whole loop may spend, in dollars. Optional for a short loop somebody is watching, and strongly wanted otherwise: this is the first thing here that can keep spending with nobody pressing anything between rounds.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing on disk. The guard that matters most: a loop's failure is not runaway success, it is an agent re-reading the same three files, restating the same plan and declaring more work remains, eleven times. Every one of those rounds succeeds, so only the tree not moving catches it."},"isolated":{"type":"boolean","description":"Whether it works in the conversation's own private copy or in the shared tree. It also decides where a check runs: testing the shared tree would be testing code this loop has not merged yet."},"agent":{"description":"Which provider the rounds run on. Absent falls back to the conversation's own last choice.","type":"string","minLength":1},"harness":{"description":"Which agentic loop they run on.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays.","type":"string"},"model":{"description":"Which model.","type":"string"},"actsAs":{"description":"Which persona the rounds act as. It matters here: every round is unwatched, and an unwatched turn naming no persona reaches no signed-in account at all, so pinning one is how a loop gets hands.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"worktreeBase":{"description":"Pin the private copy to these exact commits, so a restart cannot quietly change what the loop is working on.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"]}},"autoLand":{"description":"Whether the work merges as it goes.","type":"boolean"}},"required":["conversationId","goal","prompt","context","output","checks","maxIterations","stallLimit","isolated"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"The conversation to loop. It need not exist yet: naming a fresh one opens it, which is what lets run this until it passes be the first thing you ever say."},"goal":{"type":"string","minLength":1,"description":"What done means, in your words. It goes into every round's instructions and into the judge's question, so the model is told the bar rather than left to infer it."},"prompt":{"type":"string","minLength":1,"description":"What each round is asked to do. The suite passes is the goal; run the tests, take the top failure, fix it is the instruction."},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last. Starting fresh makes the files the memory rather than the conversation, so the twentieth round reads the tree as clearly as the first, and costs a re-read each time. Carrying on is cheaper and keeps the reasoning, which suits a short polish-this loop and degrades on long ones: a session that has spent eleven rounds arguing for its own approach is the worst available judge of whether that approach is finished."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}]},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What else has to be true, all of them together. A list because the suite passes and the report is written is a real bar, and running it as two loops would do the work twice."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up. A loop that has not got there in fifty is not one round short of it."},"maxSpendUsd":{"description":"A ceiling on what the whole loop may spend, in dollars. Optional for a short loop somebody is watching, and strongly wanted otherwise: this is the first thing here that can keep spending with nobody pressing anything between rounds.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing on disk. The guard that matters most: a loop's failure is not runaway success, it is an agent re-reading the same three files, restating the same plan and declaring more work remains, eleven times. Every one of those rounds succeeds, so only the tree not moving catches it."},"isolated":{"type":"boolean","description":"Whether it works in the conversation's own private copy or in the shared tree. It also decides where a check runs: testing the shared tree would be testing code this loop has not merged yet."},"agent":{"description":"Which provider the rounds run on. Absent falls back to the conversation's own last choice.","type":"string","minLength":1},"harness":{"description":"Which agentic loop they run on.","type":"string","enum":["native","claude-code"]},"account":{"description":"Which account pays.","type":"string"},"model":{"description":"Which model.","type":"string"},"actsAs":{"description":"Which persona the rounds act as. It matters here: every round is unwatched, and an unwatched turn naming no persona reaches no signed-in account at all, so pinning one is how a loop gets hands.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"worktreeBase":{"description":"Pin the private copy to these exact commits, so a restart cannot quietly change what the loop is working on.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"base":{"type":"string","minLength":1}},"required":["repo","base"],"additionalProperties":false}},"autoLand":{"description":"Whether the work merges as it goes.","type":"boolean"},"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How it ended, and each of these is a different thing to be told. Out of rounds says give it more room; stalled says it is not making progress and more room will not help. Overspent, stopped by a person, and the loop itself failing are all their own answers."},"startedAt":{"type":"number","description":"When it began, in milliseconds."},"endedAt":{"description":"When it ended, in milliseconds.","type":"number"},"resumed":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times the sandbox restarted under it and picked it back up. Counted rather than flagged, so a loop whose round reliably kills the sandbox is not resurrected on every boot for ever."},"detail":{"description":"Why it ended, for the endings whose reason is not in their name.","type":"string"},"iterations":{"type":"array","items":{"type":"object","properties":{"n":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Which round this was."},"at":{"type":"number","description":"When it ran, in milliseconds."},"outcome":{"type":"string","enum":["continue","done","error"],"description":"How the round ended, which is not the same question as how the loop did. A round that errored does not end the loop by itself: a failing turn is often exactly what the next round is meant to fix."},"detail":{"description":"What the check said, in its own words. What a run history is actually read for: why it kept going, and why it stopped.","type":"string"},"costUsd":{"description":"What the round cost, in dollars.","type":"number"},"changed":{"type":"boolean","description":"Whether anything on disk moved. Three unchanged rounds in a row is the shape of a loop that is not working."},"sessionId":{"description":"The session it ran on, and the way from a history row to a readable record.","type":"string"}},"required":["n","at","outcome","changed"],"additionalProperties":false},"description":"Every round, in order. Why it stopped at the fourth is the question a loop gets read for, and this is the answer."}},"required":["conversationId","goal","prompt","context","output","checks","maxIterations","stallLimit","isolated","state","startedAt","resumed","iterations"],"additionalProperties":false}}}}}}},"/loops/{conversationId}/stop":{"post":{"operationId":"loops.stop","summary":"Make this round the last","description":"Means do not start another round, not stop what is running. Somebody watching the sixth round do good work can say this is the last one without throwing that work away. To cut the current round off as well, stop the conversation too.","tags":["Loops"],"parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$","description":"Which conversation's loop."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/loops/designs":{"get":{"operationId":"loops.designs","summary":"Saved loop designs","description":"Loops somebody authored once and can point at a different job each time. A saved loop is the same loop with its goal left blank until you type one, not a different feature.","tags":["Loops"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"designs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The design's id."},"name":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it. Short, because it has to be readable on a small badge."},"description":{"description":"What it is for, in one line. Optional, because a well-named loop has already said it.","type":"string","maxLength":280},"prompt":{"description":"What each round is asked to do, when that is worth saying separately from the goal. Absent means each round works towards the goal however it sees fit.","type":"string"},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last: starting clean, or carrying on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What else has to be true."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up."},"maxSpendUsd":{"description":"A ceiling on what it may spend, in dollars.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing."}},"required":["id","name","context","output","checks","maxIterations","stallLimit"],"additionalProperties":false},"description":"Saved loops: the machinery with the goal left out, so one design can be pointed at a different job every time."}},"required":["designs"],"additionalProperties":false}}}}}},"post":{"operationId":"loops.saveDesign","summary":"Create or replace a saved loop","description":"Say which of the two you mean, so a name that happens to collide cannot silently overwrite somebody's work. A design that could never finish, with nothing to produce and nothing to check, is refused in the same words an ad-hoc loop would be: catching that at save time is the whole advantage of saving.","tags":["Loops"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"design":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The design's id."},"name":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it. Short, because it has to be readable on a small badge."},"description":{"description":"What it is for, in one line. Optional, because a well-named loop has already said it.","type":"string","maxLength":280},"prompt":{"description":"What each round is asked to do, when that is worth saying separately from the goal. Absent means each round works towards the goal however it sees fit.","type":"string"},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last: starting clean, or carrying on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"]},"description":"The shape that answer has to match."}},"required":["kind","fields"]}],"description":"What it has to produce."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"]},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"]}]},"description":"What else has to be true."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up."},"maxSpendUsd":{"description":"A ceiling on what it may spend, in dollars.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing."}},"required":["id","name","context","output","checks","maxIterations","stallLimit"],"description":"The design to write."},"create":{"type":"boolean","description":"Whether you mean to make a new one or replace an existing one, so an id that happens to collide cannot silently overwrite the one you had."}},"required":["design","create"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The design's id."},"name":{"type":"string","minLength":1,"maxLength":60,"description":"What to call it. Short, because it has to be readable on a small badge."},"description":{"description":"What it is for, in one line. Optional, because a well-named loop has already said it.","type":"string","maxLength":280},"prompt":{"description":"What each round is asked to do, when that is worth saying separately from the goal. Absent means each round works towards the goal however it sees fit.","type":"string"},"context":{"type":"string","enum":["fresh","continue"],"description":"How each round meets the last: starting clean, or carrying on."},"output":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"none","description":"It produces nothing but its work. The classic make the suite pass: what it leaves behind is a passing suite, and asking it to also file a report is asking it to spend a round on paperwork."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"claim","description":"Each round says whether it is done and why. Structured prose: done is a value read rather than a sentence interpreted. Self-assessment, so advisory by construction; it exists because plenty of goals have no command that could check them."}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"json","description":"Each round writes a real answer in a shape you declared. This is the one that makes a step's output usable as the next step's input: a paragraph mentioning three files cannot be fed to anything, a list of three files can."},"fields":{"minItems":1,"maxItems":16,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]{0,39}$"},"type":{"type":"string","enum":["string","number","boolean","string[]"]},"description":{"type":"string","minLength":1},"required":{"type":"boolean"}},"required":["name","type","description","required"],"additionalProperties":false},"description":"The shape that answer has to match."}},"required":["kind","fields"],"additionalProperties":false}],"description":"What it has to produce."},"checks":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command","description":"Run something and see if it passes. Deterministic, free, and the only signal here whose answer does not come from a model. A passing test suite beats any amount of self-report."},"command":{"type":"string","minLength":1,"description":"The command to run in the conversation's own tree. Exiting cleanly means satisfied."}},"required":["kind","command"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"judge","description":"Put the question to a separate model with no tools, which reads the round's own report and rules on it, having done none of the work and nothing invested in its being finished."},"rubric":{"type":"string","minLength":1,"description":"What that judge is asked."},"model":{"description":"Which model judges. Leave it out for the cheap one the other small jobs use.","type":"string"}},"required":["kind","rubric"],"additionalProperties":false}]},"description":"What else has to be true."},"maxIterations":{"type":"integer","minimum":1,"maximum":50,"description":"How many rounds before it gives up."},"maxSpendUsd":{"description":"A ceiling on what it may spend, in dollars.","type":"number","exclusiveMinimum":0},"stallLimit":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Stop after this many rounds in a row that changed nothing."}},"required":["id","name","context","output","checks","maxIterations","stallLimit"],"additionalProperties":false}}}}}}},"/loops/designs/{id}":{"delete":{"operationId":"loops.removeDesign","summary":"Delete a saved loop","description":"Removes the design. A loop already running from it keeps going on its own terms, because it took a copy of what it needed when it started.","tags":["Loops"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which saved loop."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations":{"get":{"operationId":"automations.list","summary":"Things that wake an agent on their own","description":"Every automation with its recent runs and when it fires next.","tags":["Automations"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The automation's id."},"trigger":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"schedule","description":"On a clock."},"cron":{"type":"string","minLength":1,"description":"When, in cron notation."}},"required":["kind","cron"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"event","description":"When something calls its webhook."},"token":{"description":"The credential a caller presents. It is the only one in the exchange, because an outside sender has no identity here.","type":"string","minLength":1}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"listener","description":"When a message arrives from somewhere outside."},"provider":{"type":"string","minLength":1,"description":"Which service to listen to."},"channelId":{"description":"Narrow it to one channel or thread.","type":"string","minLength":1},"eventType":{"description":"Narrow it to one kind of event.","type":"string","minLength":1},"mentioned":{"description":"Only when the agent is actually addressed, rather than on everything said in earshot.","type":"boolean"},"branch":{"description":"Narrow it to one branch, for the sources that have branches. Absent means every branch of the repositories it matches.","type":"string","minLength":1},"allowedOrigins":{"description":"Which websites may reach the chat widget. Absent or empty admits nobody.","type":"array","items":{"type":"string"}}},"required":["kind","provider"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"workspace","description":"When something happens to the files or the repositories."},"event":{"type":"string","enum":["turn.settled","agent.landed","deps.broken","deps.fixed"],"description":"Which happening."},"repo":{"description":"Narrow it to one repository. Absent means any of them.","type":"string","minLength":1}},"required":["kind","event"],"additionalProperties":false}],"description":"What sets it off: a schedule, an event in the workspace, a message arriving from outside, or a webhook."},"guard":{"description":"A command run before the wake that decides whether there is anything to do. Skipped by the guard is often the most useful thing an automation can report.","type":"string","minLength":1},"prompt":{"type":"string","minLength":1,"description":"What the woken agent is told."},"webchat":{"description":"Settings for the public chat widget, for an automation that answers visitors.","type":"object","properties":{"access":{"description":"Who may write to it. Absent means anyone, which is the anonymous support box it looks like.","type":"string","enum":["public","google"]},"requireName":{"description":"Ask a visitor for a name first. Cosmetic: the name is typed, so it reaches the model as something a stranger said, never as identity.","type":"boolean"},"antiBot":{"description":"How to keep bots out: a third-party check that needs the site's own keys, or a puzzle the sandbox sets and the widget solves, so a site with no such account still has something. Absent leaves the site allowlist and the rate limit as the whole boundary.","type":"string","enum":["turnstile","pow"]},"turnstileSiteKey":{"description":"The public half of those keys, which ships to the visitor's browser.","type":"string"},"turnstileSecret":{"description":"The private half, which the sandbox keeps and the widget never sees.","type":"string"},"googleClientId":{"description":"The site's own sign-in client id. It cannot be ours: a sign-in is only issued to an approved origin, and no single client can list every customer's domain.","type":"string"},"title":{"type":"string","maxLength":80},"greeting":{"type":"string","maxLength":500},"accent":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"},"position":{"type":"string","enum":["top-right","top-left","bottom-right","bottom-left"]},"dailyMessageMax":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"conversationMessageMax":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sessionTtlMinutes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"allowedTools":{"description":"Narrow the woken turn to these tools. For one driven by an outside message this list is the real boundary, because prompt wording is only advice and an empty toolbox is not.","type":"array","items":{"type":"string","minLength":1}},"agent":{"description":"Which provider serves the wake.","type":"string","minLength":1},"account":{"description":"Which account pays for it.","type":"string"},"actsAs":{"description":"Which persona it speaks as. An unwatched turn naming none reaches no signed-in account at all.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"model":{"description":"Which model runs it.","type":"string"},"requireApproval":{"description":"Hold every fire for a person instead of running it. Only a person can release one of those.","type":"boolean"},"holdForSeconds":{"description":"Hold each fire this long before running it anyway, which is a delay rather than a decision.","type":"number"},"chore":{"description":"This automation is a maintenance job, which is what files it under chores rather than among ordinary automations.","type":"boolean"},"enabled":{"type":"boolean","description":"Whether it fires at all."},"runs":{"type":"array","items":{"type":"object","properties":{"at":{"type":"number"},"outcome":{"type":"string","enum":["completed","skipped","error","interrupted"]},"detail":{"type":"string"},"conversationId":{"type":"string"}},"required":["at","outcome"],"additionalProperties":false}},"nextRun":{"type":"number"}},"required":["id","trigger","prompt","enabled","runs"],"additionalProperties":false}}},"required":["automations"],"additionalProperties":false}}}}}},"post":{"operationId":"automations.upsert","summary":"Create or edit an automation","description":"Writes an automation by id. Nothing needs provisioning: the scheduler picks it up on its next sweep.","tags":["Automations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The automation's id."},"trigger":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"schedule","description":"On a clock."},"cron":{"type":"string","minLength":1,"description":"When, in cron notation."}},"required":["kind","cron"]},{"type":"object","properties":{"kind":{"type":"string","const":"event","description":"When something calls its webhook."},"token":{"description":"The credential a caller presents. It is the only one in the exchange, because an outside sender has no identity here.","type":"string","minLength":1}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"listener","description":"When a message arrives from somewhere outside."},"provider":{"type":"string","minLength":1,"description":"Which service to listen to."},"channelId":{"description":"Narrow it to one channel or thread.","type":"string","minLength":1},"eventType":{"description":"Narrow it to one kind of event.","type":"string","minLength":1},"mentioned":{"description":"Only when the agent is actually addressed, rather than on everything said in earshot.","type":"boolean"},"branch":{"description":"Narrow it to one branch, for the sources that have branches. Absent means every branch of the repositories it matches.","type":"string","minLength":1},"allowedOrigins":{"description":"Which websites may reach the chat widget. Absent or empty admits nobody.","type":"array","items":{"type":"string"}}},"required":["kind","provider"]},{"type":"object","properties":{"kind":{"type":"string","const":"workspace","description":"When something happens to the files or the repositories."},"event":{"type":"string","enum":["turn.settled","agent.landed","deps.broken","deps.fixed"],"description":"Which happening."},"repo":{"description":"Narrow it to one repository. Absent means any of them.","type":"string","minLength":1}},"required":["kind","event"]}],"description":"What sets it off: a schedule, an event in the workspace, a message arriving from outside, or a webhook."},"guard":{"description":"A command run before the wake that decides whether there is anything to do. Skipped by the guard is often the most useful thing an automation can report.","type":"string","minLength":1},"prompt":{"type":"string","minLength":1,"description":"What the woken agent is told."},"webchat":{"description":"Settings for the public chat widget, for an automation that answers visitors.","type":"object","properties":{"access":{"description":"Who may write to it. Absent means anyone, which is the anonymous support box it looks like.","type":"string","enum":["public","google"]},"requireName":{"description":"Ask a visitor for a name first. Cosmetic: the name is typed, so it reaches the model as something a stranger said, never as identity.","type":"boolean"},"antiBot":{"description":"How to keep bots out: a third-party check that needs the site's own keys, or a puzzle the sandbox sets and the widget solves, so a site with no such account still has something. Absent leaves the site allowlist and the rate limit as the whole boundary.","type":"string","enum":["turnstile","pow"]},"turnstileSiteKey":{"description":"The public half of those keys, which ships to the visitor's browser.","type":"string"},"turnstileSecret":{"description":"The private half, which the sandbox keeps and the widget never sees.","type":"string"},"googleClientId":{"description":"The site's own sign-in client id. It cannot be ours: a sign-in is only issued to an approved origin, and no single client can list every customer's domain.","type":"string"},"title":{"type":"string","maxLength":80},"greeting":{"type":"string","maxLength":500},"accent":{"type":"string","pattern":"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"},"position":{"type":"string","enum":["top-right","top-left","bottom-right","bottom-left"]},"dailyMessageMax":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"conversationMessageMax":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"sessionTtlMinutes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}},"allowedTools":{"description":"Narrow the woken turn to these tools. For one driven by an outside message this list is the real boundary, because prompt wording is only advice and an empty toolbox is not.","type":"array","items":{"type":"string","minLength":1}},"agent":{"description":"Which provider serves the wake.","type":"string","minLength":1},"account":{"description":"Which account pays for it.","type":"string"},"actsAs":{"description":"Which persona it speaks as. An unwatched turn naming none reaches no signed-in account at all.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"harness":{"description":"Which agentic loop runs it.","type":"string","enum":["native","claude-code"]},"model":{"description":"Which model runs it.","type":"string"},"requireApproval":{"description":"Hold every fire for a person instead of running it. Only a person can release one of those.","type":"boolean"},"holdForSeconds":{"description":"Hold each fire this long before running it anyway, which is a delay rather than a decision.","type":"number"},"chore":{"description":"This automation is a maintenance job, which is what files it under chores rather than among ordinary automations.","type":"boolean"},"enabled":{"type":"boolean","description":"Whether it fires at all."}},"required":["id","trigger","prompt","enabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations/catalog":{"get":{"operationId":"automations.catalog","summary":"What can trigger an automation here","description":"Every trigger this sandbox understands and every template worth starting from, the daemon's own merged with each installed extension's. Writing an automation is checked against this same list, so a screen and the daemon can never disagree about what is allowed.","tags":["Automations"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"logo":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"events":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","minLength":1},"label":{"type":"string","minLength":1}},"required":["value","label"],"additionalProperties":false}},"channel":{"type":"object","properties":{"label":{"type":"string","minLength":1},"placeholder":{"type":"string","minLength":1},"hint":{"type":"string","minLength":1}},"required":["label","placeholder"],"additionalProperties":false},"branchField":{"type":"object","properties":{"label":{"type":"string","minLength":1},"placeholder":{"type":"string","minLength":1},"hint":{"type":"string","minLength":1}},"required":["label","placeholder"],"additionalProperties":false},"mentionLabel":{"type":"string","minLength":1},"starterPrompt":{"type":"string","minLength":1},"requires":{"default":[],"type":"array","items":{"type":"string","minLength":1}},"enabled":{"type":"boolean"}},"required":["provider","label","events","channel","requires","enabled"],"additionalProperties":false}},"templates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"title":{"type":"string","minLength":1},"logo":{"type":"string","minLength":1},"icon":{"type":"string","minLength":1},"requires":{"default":[],"type":"array","items":{"type":"string","minLength":1}},"trigger":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"schedule","description":"On a clock."},"cron":{"type":"string","minLength":1,"description":"When, in cron notation."}},"required":["kind","cron"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"event","description":"When something calls its webhook."},"token":{"description":"The credential a caller presents. It is the only one in the exchange, because an outside sender has no identity here.","type":"string","minLength":1}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"listener","description":"When a message arrives from somewhere outside."},"provider":{"type":"string","minLength":1,"description":"Which service to listen to."},"channelId":{"description":"Narrow it to one channel or thread.","type":"string","minLength":1},"eventType":{"description":"Narrow it to one kind of event.","type":"string","minLength":1},"mentioned":{"description":"Only when the agent is actually addressed, rather than on everything said in earshot.","type":"boolean"},"branch":{"description":"Narrow it to one branch, for the sources that have branches. Absent means every branch of the repositories it matches.","type":"string","minLength":1},"allowedOrigins":{"description":"Which websites may reach the chat widget. Absent or empty admits nobody.","type":"array","items":{"type":"string"}}},"required":["kind","provider"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"workspace","description":"When something happens to the files or the repositories."},"event":{"type":"string","enum":["turn.settled","agent.landed","deps.broken","deps.fixed"],"description":"Which happening."},"repo":{"description":"Narrow it to one repository. Absent means any of them.","type":"string","minLength":1}},"required":["kind","event"],"additionalProperties":false}]},"guard":{"type":"string","minLength":1},"holdForSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"prompt":{"type":"string","minLength":1},"note":{"type":"string","minLength":1},"setup":{"type":"string","minLength":1},"description":{"type":"string","minLength":1},"offer":{"type":"string","enum":["create","configure"]},"chore":{"type":"boolean"}},"required":["id","title","requires","trigger","prompt"],"additionalProperties":false}}},"required":["sources","templates"],"additionalProperties":false}}}}}}},"/automations/{id}/enabled":{"post":{"operationId":"automations.setEnabled","summary":"Turn an automation on or off","description":"Flips only the switch, so a row in a list can be toggled without rebuilding the whole record.","tags":["Automations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations/{id}":{"delete":{"operationId":"automations.remove","summary":"Delete an automation","description":"Removes it, so nothing fires from it again.","tags":["Automations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations/{id}/run":{"post":{"operationId":"automations.run","summary":"Fire an automation by hand","description":"The answer to writing something that runs at three in the morning and having no way to try it. It takes exactly the path the real trigger takes, including the check that decides whether there was anything to do, since skipped by the guard is the most useful thing this can tell you. A switched-off automation fires too, because trying it before switching it on is the main reason to press this. Not available for the trigger that listens for incoming messages, where a hand-fire would produce an agent asked to handle events and handed none; send the bot a message instead. Answers straight away and runs detached.","tags":["Automations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations/pending":{"get":{"operationId":"automations.pendingList","summary":"Automations waiting for a yes","description":"The queue an automation set to ask first lands in each time it would have fired.","tags":["Automations"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"approvals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"This waiting item's own id, which approving and rejecting take."},"automationId":{"type":"string","description":"Which automation it came from."},"payload":{"description":"What set it off, kept whole so an approved wake carries the same thing it would have had. Absent for one on a schedule, which carries nothing.","type":"string"},"origin":{"description":"Where the message came from, kept alongside the payload so an approved wake appears on the board exactly as an automatic one would have.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"title":{"description":"What the conversation would be called.","type":"string"},"conversationId":{"description":"The thread this belongs to, when it has one, so approving continues that conversation rather than opening a new one. Without it, one visitor's chat becomes a card per approved message and an agent that meets them again every turn.","type":"string"},"sessionId":{"description":"The provider session that thread last ran on.","type":"string"},"createdAt":{"type":"number","description":"When it started waiting, in milliseconds."},"autoRunAt":{"description":"When it goes ahead on its own, in milliseconds, for a hold that is only a delay. Absent for one that genuinely waits on a person.","type":"number"}},"required":["id","automationId","createdAt"],"additionalProperties":false},"description":"Everything waiting for a yes."}},"required":["approvals"],"additionalProperties":false}}}}}}},"/automations/pending/{id}/approve":{"post":{"operationId":"automations.approve","summary":"Let a held automation run","description":"Releases one waiting automation and runs the wake it was holding. Answers straight away and runs detached.","tags":["Automations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which waiting item."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/automations/pending/{id}/reject":{"post":{"operationId":"automations.reject","summary":"Drop a held automation","description":"Throws one waiting fire away. The automation stays on, and the next trigger queues as usual.","tags":["Automations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which waiting item."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/tree":{"get":{"operationId":"workspace.tree","summary":"The workspace file tree","description":"Every folder and file under the workspace root, as one walk. Name a conversation to read its own private copy of the tree instead of the shared one. Folders the daemon skips, such as installed packages, come back without their contents; ask for those separately.","tags":["Workspace"],"parameters":[{"name":"agent","in":"query","schema":{"description":"Read a conversation's own private copy of the workspace rather than the shared tree. Leave it out for the shared tree. A conversation that is not working privately resolves back to the shared tree rather than failing, so a link need not know which mode it runs in.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"root":{"type":"string","description":"The path everything below is relative to."},"tree":{"type":"array","items":{"$ref":"#/$defs/__schema0"},"description":"The workspace, one entry per file and folder."},"hidden":{"type":"number","description":"How many entries at the top level were cut for size. Zero means the listing is complete."}},"required":["root","tree","hidden"],"additionalProperties":false,"$defs":{"__schema0":{"type":"object","properties":{"name":{"type":"string","description":"Just this entry's own name."},"path":{"type":"string","description":"Its full path from the workspace root, which feeds straight back into the file routes."},"type":{"type":"string","enum":["file","dir"],"description":"What it is. For a link, what it points at, so a link to a folder opens like a folder."},"size":{"description":"Size in bytes, for a file.","type":"number"},"ignored":{"description":"Tooling ignores it: installed packages, git internals, anything the ignore rules exclude. Usually drawn greyed out.","type":"boolean"},"link":{"description":"Present when this entry is a link.","type":"object","properties":{"to":{"type":"string","description":"What the link says, verbatim, rather than where it ends up. That is what the person who made it wrote, and what they would edit."},"state":{"description":"Absent for an ordinary link. Broken means there is nothing at the other end, and it is listed anyway because a dangling link is worth seeing. Outside means it leads out of the workspace, so it is shown and refused.","type":"string","enum":["broken","outside"]}},"required":["to"],"additionalProperties":false},"children":{"description":"What is inside a folder. Absent means it was not opened, either because it is ignored or because the walk ran out of budget above it, so ask for it separately. An empty list means it really is empty.","type":"array","items":{"$ref":"#/$defs/__schema0"}}},"required":["name","path","type"],"additionalProperties":false}}}}}}}}},"/workspace/children":{"get":{"operationId":"workspace.children","summary":"One folder's contents","description":"The entries directly inside a single folder. This is how you open a folder the full tree walk deliberately left closed, such as an installed-packages directory with a hundred thousand files in it.","tags":["Workspace"],"parameters":[{"name":"agent","in":"query","required":false,"schema":{"description":"Read a conversation's own private copy of the workspace rather than the shared tree. Leave it out for the shared tree. A conversation that is not working privately resolves back to the shared tree rather than failing, so a link need not know which mode it runs in.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"allowEmptyValue":true,"allowReserved":true},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The folder to open, as a workspace path."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/$defs/__schema0"},"description":"What is directly inside it. Folders in here carry no contents of their own, so they open the same way."},"hidden":{"type":"number","description":"How many entries were cut for size. Zero means the listing is complete."}},"required":["entries","hidden"],"additionalProperties":false,"$defs":{"__schema0":{"type":"object","properties":{"name":{"type":"string","description":"Just this entry's own name."},"path":{"type":"string","description":"Its full path from the workspace root, which feeds straight back into the file routes."},"type":{"type":"string","enum":["file","dir"],"description":"What it is. For a link, what it points at, so a link to a folder opens like a folder."},"size":{"description":"Size in bytes, for a file.","type":"number"},"ignored":{"description":"Tooling ignores it: installed packages, git internals, anything the ignore rules exclude. Usually drawn greyed out.","type":"boolean"},"link":{"description":"Present when this entry is a link.","type":"object","properties":{"to":{"type":"string","description":"What the link says, verbatim, rather than where it ends up. That is what the person who made it wrote, and what they would edit."},"state":{"description":"Absent for an ordinary link. Broken means there is nothing at the other end, and it is listed anyway because a dangling link is worth seeing. Outside means it leads out of the workspace, so it is shown and refused.","type":"string","enum":["broken","outside"]}},"required":["to"],"additionalProperties":false},"children":{"description":"What is inside a folder. Absent means it was not opened, either because it is ignored or because the walk ran out of budget above it, so ask for it separately. An empty list means it really is empty.","type":"array","items":{"$ref":"#/$defs/__schema0"}}},"required":["name","path","type"],"additionalProperties":false}}}}}}}}},"/workspace/file":{"get":{"operationId":"workspace.file","summary":"Read part of a text file","description":"A window of one file's text, plus how large the whole file is. Never the entire file: an unbounded read is how a single enormous log stalls the daemon for everyone, so ask for the slice you mean to show and page through if you need more.","tags":["Workspace"],"parameters":[{"name":"agent","in":"query","required":false,"schema":{"description":"Read a conversation's own private copy of the workspace rather than the shared tree. Leave it out for the shared tree. A conversation that is not working privately resolves back to the shared tree rather than failing, so a link need not know which mode it runs in.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"allowEmptyValue":true,"allowReserved":true},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The file to read, as a workspace path."},"allowEmptyValue":true,"allowReserved":true},{"name":"offset","in":"query","required":false,"schema":{"description":"Which byte to start at. A negative number reads that many bytes from the end, which is how you follow a growing log without knowing its size first.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","required":false,"schema":{"description":"How many bytes to read. Capped by the sandbox, so leaving it out or asking for too much gives you the cap rather than the whole file.","type":"integer","minimum":1,"maximum":9007199254740991},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"present":{"type":"boolean","const":true,"description":"There is something at that path."},"path":{"type":"string","description":"The path, as asked for."},"content":{"type":"string","description":"The bytes of the window you asked for, as text."},"size":{"type":"number","description":"How large the whole file is. Compare it with the window below to know whether there is more."},"offset":{"type":"number","description":"Which byte the window starts at."},"bytes":{"type":"number","description":"How many bytes the window holds."},"shared":{"type":"boolean","description":"Which tree answered. True when no conversation was named, and also when one was but its own copy has no such file, which is the case a reader has to be told about rather than left to assume."}},"required":["present","path","content","size","offset","bytes","shared"],"additionalProperties":false},{"type":"object","properties":{"present":{"type":"boolean","const":false,"description":"Nothing there. An answer, not a failure: reading a file that may not exist yet is the ordinary case for half the reads in this product."},"path":{"type":"string","description":"The path, as asked for."}},"required":["present","path"],"additionalProperties":false}]}}}}}}},"/workspace/media-ticket":{"post":{"operationId":"workspace.mediaTicket","summary":"Get a pass for streaming a media file","description":"Mints the short-lived ticket a video or audio element hands to the streaming route, which serves byte ranges and so cannot carry an ordinary header. Minting it here means a caller can tell whether this sandbox streams media at all, rather than discovering it mid-playback.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"description":"Read a conversation's own private copy of the workspace rather than the shared tree. Leave it out for the shared tree. A conversation that is not working privately resolves back to the shared tree rather than failing, so a link need not know which mode it runs in.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"path":{"type":"string","minLength":1,"description":"The media file the ticket should cover."}},"required":["path"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ticket":{"type":"string","description":"Hand this to the streaming route in the query string. It buys exactly the one file it was minted for."},"expiresAt":{"type":"number","description":"When it stops working, in milliseconds, so a player can tell a dead ticket from a dead file."}},"required":["ticket","expiresAt"],"additionalProperties":false}}}}}}},"/workspace/resolve":{"get":{"operationId":"workspace.resolve","summary":"Turn a written path into a real file","description":"Matches a path somebody wrote in prose against the real tree and says which file it means. A path mentioned in a message is often only the tail of the real one, so this is the lookup behind every clickable file reference rather than a plain existence check.","tags":["Workspace"],"parameters":[{"name":"agent","in":"query","required":false,"schema":{"description":"Read a conversation's own private copy of the workspace rather than the shared tree. Leave it out for the shared tree. A conversation that is not working privately resolves back to the shared tree rather than failing, so a link need not know which mode it runs in.","type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"allowEmptyValue":true,"allowReserved":true},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":512,"description":"The reference as somebody wrote it. Often only the tail of the real path, which is why this is matched against the tree rather than read as-is."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"description":"The real path it means. Absent when nothing in the workspace ends that way.","type":"string"}},"additionalProperties":false}}}}}}},"/workspace/search":{"get":{"operationId":"workspace.search","summary":"Search the code","description":"Ranked results across the whole workspace, grouped, each carrying why it matched and how fresh it is. Left alone it blends plain text, structure, meaning and history in one pass; narrow it to a single kind of search when you already know which you want. Long result sets resume from the cursor it hands back.","tags":["Workspace"],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":512,"description":"What to look for. Plain words, a pattern, a symbol name, or a question."},"allowEmptyValue":true,"allowReserved":true},{"name":"mode","in":"query","required":false,"schema":{"description":"Narrow the search to one kind: plain text, filenames, definitions, references, symbols, or code structure. Leave it out to blend them, which also answers a question asked in words.","type":"string","enum":["q","find","files","def","refs","sym","ast"]},"allowEmptyValue":true,"allowReserved":true},{"name":"includeIgnored","in":"query","required":false,"schema":{"description":"Search inside installed packages and other ignored folders too.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"literal","in":"query","required":false,"schema":{"description":"Treat the query as fixed text rather than a pattern.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"word","in":"query","required":false,"schema":{"description":"Match whole words only.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"caseSensitive","in":"query","required":false,"schema":{"description":"Whether capitals matter. Off means they do not, rather than being guessed at from the query.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"include","in":"query","required":false,"schema":{"description":"Which files to ask, in the same grammar an editor's files-to-include box takes: comma-separated patterns, matched at any depth unless anchored, a leading exclamation mark excluding instead.","type":"string","maxLength":512},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","required":false,"schema":{"description":"How many results to return.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","required":false,"schema":{"description":"Resume from the cursor a previous answer handed back.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","description":"Which kind of search actually ran, which matters when you let it choose."},"total":{"type":"number","description":"Matching lines across the whole workspace, not just this page."},"files":{"type":"number","description":"Files the query matched in total."},"shown":{"type":"number","description":"How many of those lines are on this page."},"groups":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file."},"score":{"type":"number","description":"How well it matched. Groups arrive best first, never in path order."},"hits":{"type":"array","items":{"type":"object","properties":{"line":{"type":"number","description":"Which line, counting from one."},"text":{"type":"string","description":"The line itself."},"spans":{"type":"array","items":{"type":"object","properties":{"start":{"type":"number","description":"First character of the match within the line."},"end":{"type":"number","description":"One past the last."}},"required":["start","end"],"additionalProperties":false},"description":"Where in the line the matches are, so you can highlight without searching again. Empty when the whole line is the match rather than part of it."},"tags":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["def","text","sem","bm25","rerank","path","import","call","type","write","fuzzy","heuristic"],"description":"Why this line matched: the literal text, its meaning, the path, a definition, a call, and so on. Several kinds can agree on one line."},"score":{"description":"How strongly that reason applied.","type":"number"}},"required":["kind"],"additionalProperties":false},"description":"Why it matched."},"context":{"description":"What it sits inside: the function, the class, the heading. Often enough that you need not open the file.","type":"string"}},"required":["line","text","spans","tags"],"additionalProperties":false},"description":"The matching lines in it."},"capped":{"description":"This file had more matches than are kept per file, so the count is a floor. Say fifty-plus rather than fifty.","type":"boolean"}},"required":["path","score","hits"],"additionalProperties":false},"description":"The results, grouped by file, best first."},"freshness":{"type":"object","properties":{"state":{"type":"string","enum":["fresh","building","stale"],"description":"Whether the index matches what is on disk, is still filling, or has fallen behind."},"ageMs":{"description":"How long since it last matched the disk, in milliseconds.","type":"number"},"progress":{"description":"How far through building it is, from zero to one.","type":"number"},"behind":{"description":"How many files it has not caught up with. Worth showing, because the word stale on its own reads as a warning about the answer, which it almost never is.","type":"number"}},"required":["state"],"additionalProperties":false,"description":"Whether the index behind the answer is up to date."},"truncated":{"type":"boolean","description":"This page is not all of it. Use the cursor."},"partial":{"description":"At least one file had more matches than are kept per file, so the total is a floor. Different from the page being truncated: a complete page can still count partially.","type":"boolean"},"cursor":{"description":"Pass this back as `after` to get the next page.","type":"string"},"hint":{"description":"A suggestion for getting a better answer out of this query.","type":"string"},"note":{"description":"What the engine did that you did not ask for: a pattern rerun as plain text because it was not valid, escapes rewritten, a language filter that matched nothing.","type":"string"},"related":{"description":"Places next door to the best results: where each is defined, and whatever calls it most.","type":"array","items":{"type":"string"}},"candidates":{"description":"Ranked places that scored but did not make the page, best first. The answer often sits at rank five to thirteen, so this saves paging through to find out.","type":"array","items":{"type":"string"}},"features":{"description":"Which stages of the search were switched off for this run. Absent means all of them ran.","type":"array","items":{"type":"string"}}},"required":["mode","total","files","shown","groups","freshness","truncated"],"additionalProperties":false}}}}}}},"/workspace/health":{"get":{"operationId":"workspace.health","summary":"A repo's shape in numbers","description":"Where one repo's risk sits: the files that change often and are complicated at once, what the index holds, and which modules the rest of the code leans on most. Scoped to a repo, because a codebase is a repo rather than the whole drop.","tags":["Workspace"],"parameters":[{"name":"repo","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which repository, using the same ids the git routes take."},"allowEmptyValue":true,"allowReserved":true},{"name":"since","in":"query","required":false,"schema":{"description":"How far back to count changes, written as a span such as 2d, 12h, 1w or 3m. Leave it out for all of history.","type":"string","maxLength":16},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","required":false,"schema":{"description":"How many files and modules to rank. A leaderboard rather than an inventory: past a screenful the ranking stops being the point.","type":"integer","exclusiveMinimum":0,"maximum":200},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository this describes."},"totals":{"type":"object","properties":{"files":{"type":"number","description":"Files counted."},"symbols":{"type":"number","description":"Named things they export."},"complexity":{"type":"number","description":"Branch points across all of them added up."},"hotspots":{"type":"number","description":"How many files qualify as hotspots at all. The list below is capped; this is not."}},"required":["files","symbols","complexity","hotspots"],"additionalProperties":false,"description":"Counts anybody could recount in the files themselves. Deliberately no single maintainability grade: those cannot be checked and are not comparable between projects."},"hotspots":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"commits":{"type":"number"},"adds":{"type":"number"},"dels":{"type":"number"},"complexity":{"type":"number"},"score":{"type":"number"},"latestMs":{"type":"number"}},"required":["path","commits","adds","dels","complexity","score","latestMs"],"additionalProperties":false},"description":"Files that change often and are complicated at the same time, worst first."},"modules":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"exports":{"type":"number"}},"required":["path","exports"],"additionalProperties":false},"description":"The parts of the codebase the rest of it leans on most."},"freshness":{"type":"object","properties":{"state":{"type":"string","enum":["fresh","building","stale"],"description":"Whether the index matches what is on disk, is still filling, or has fallen behind."},"ageMs":{"description":"How long since it last matched the disk, in milliseconds.","type":"number"},"progress":{"description":"How far through building it is, from zero to one.","type":"number"},"behind":{"description":"How many files it has not caught up with. Worth showing, because the word stale on its own reads as a warning about the answer, which it almost never is.","type":"number"}},"required":["state"],"additionalProperties":false,"description":"Whether the index these numbers were read from is up to date."}},"required":["repo","totals","hotspots","modules","freshness"],"additionalProperties":false}}}}}}},"/workspace/classify":{"get":{"operationId":"workspace.classify","summary":"Sort a messy drop into buckets","description":"Proposes which of the loose things in the workspace are code, documents, media or archives. A read-only suggestion by fixed rules, with no model involved: nothing moves until a caller applies the moves it likes through the move call.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"classifications":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"What was looked at."},"bucket":{"type":"string","enum":["repositories","documents","media","archives","other"],"description":"Which bucket it was sorted into."},"reason":{"type":"string","description":"The signal that decided it, so the proposal can be argued with rather than trusted."}},"required":["path","bucket","reason"],"additionalProperties":false},"description":"One entry per repository folder and loose file at the top of the workspace. A read-only proposal: nothing moves until you apply it."}},"required":["classifications"],"additionalProperties":false}}}}}}},"/workspace/dir":{"post":{"operationId":"workspace.mkdir","summary":"Create a folder","description":"Makes a folder, and any missing folders above it.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"The folder to create. Missing folders above it are created too."}},"required":["path"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/entry":{"delete":{"operationId":"workspace.delete","summary":"Delete a file or folder","description":"Removes one entry and everything under it. The path travels in the body rather than the address, the same as every other write in this group.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"The file or folder, as a workspace path."}},"required":["path"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/move":{"post":{"operationId":"workspace.move","summary":"Move or rename something","description":"Moves one entry to a new path, which is also how you rename it.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","minLength":1,"description":"What to move or copy, as a workspace path."},"to":{"type":"string","minLength":1,"description":"Where it should end up. Changing only the last part is how you rename something."}},"required":["from","to"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/copy":{"post":{"operationId":"workspace.copy","summary":"Copy a file or folder","description":"Duplicates one entry at a new path, recursively for a folder.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","minLength":1,"description":"What to move or copy, as a workspace path."},"to":{"type":"string","minLength":1,"description":"Where it should end up. Changing only the last part is how you rename something."}},"required":["from","to"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/setup":{"get":{"operationId":"workspace.setup","summary":"Which projects have their dependencies installed","description":"Per project, whether its dependencies are actually present. A project that arrives by import comes without them, so files landing is not the same as the project working: until this says a project is ready, its type checks and tests can only mislead you.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"dir":{"type":"string","description":"Where the project is, relative to the workspace root. Empty means the root itself."},"ecosystem":{"type":"string","enum":["node","python"],"description":"Which language's tooling it uses."},"manager":{"type":"string","description":"The tool that would do the installing."},"command":{"type":"string","description":"The exact command that would run."},"evidence":{"type":"string","description":"The file that decided all of the above, so the answer can be checked rather than trusted."},"state":{"type":"string","enum":["ready","installing","needs-setup","unsupported","stale"],"description":"Ready means its dependencies are really there. Stale means it was installed once and has since outgrown that, which is what an agent leaves behind when it adds a dependency without installing it. Unsupported means this sandbox has no such tool."},"missing":{"description":"How many declared dependencies cannot be found on disk. What separates never-installed from outgrown.","type":"number"}},"required":["dir","ecosystem","manager","command","evidence","state"],"additionalProperties":false},"description":"Every project the sandbox found, and whether each is usable."}},"required":["projects"],"additionalProperties":false}}}}}}},"/workspace/setup/install":{"post":{"operationId":"workspace.install","summary":"Install a project's dependencies","description":"Starts the install for one or more projects in a terminal you can attach to, and answers immediately. The run survives a page reload and its output stays in the terminal history.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dirs":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":500},"description":"Which projects to install, by folder. Ones already ready, already installing, or with no tool to install them are skipped rather than refused."}},"required":["dirs"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"queued":{"type":"array","items":{"type":"string"},"description":"Which of them actually started, which is not necessarily what you asked for."}},"required":["queued"],"additionalProperties":false}}}}}}},"/workspace/repos":{"get":{"operationId":"workspace.repos","summary":"Repos in the workspace","description":"Every git repo the daemon found in the workspace, with where each one sits and what it is called.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"string"},"description":"Every repository's id, sorted. An id is its folder relative to the workspace root, and \"root\" is the workspace itself."}},"required":["repos"],"additionalProperties":false}}}}}},"post":{"operationId":"workspace.addRepo","summary":"Clone a repo in","description":"Clones a repository into the workspace beside the others, using whatever forge credentials the sandbox already holds.","tags":["Workspace"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"What to call it in the workspace."},"cloneUrl":{"type":"string","minLength":1,"description":"Where to clone it from."},"branch":{"description":"Which branch to check out. Leave it out for the repository's default.","type":"string"}},"required":["name","cloneUrl"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"What it ended up called."},"path":{"type":"string","description":"Where it landed."}},"required":["name","path"],"additionalProperties":false}}}}}}},"/workspace/sync":{"post":{"operationId":"workspace.sync","summary":"Pull every repo up to date","description":"Fetches every repo that has a remote and fast-forwards the ones that can move safely, reporting what happened to each. This runs by itself at the start of a turn; call it directly to refresh on demand, or to re-sync a repo that had drifted.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"status":{"type":"string","enum":["updated","current","dirty","diverged","no-remote","skipped","error"],"description":"What happened to it. Dirty and diverged are why a repository was left alone: it had uncommitted work, or it had moved in a way that cannot be fast-forwarded."},"behind":{"description":"How many commits it was behind.","type":"number"},"ahead":{"description":"How many commits it was ahead.","type":"number"},"head":{"description":"The commit it ended up on.","type":"string"},"message":{"description":"What went wrong, when something did.","type":"string"}},"required":["repo","status"],"additionalProperties":false},"description":"One entry per repository, saying what happened to it."}},"required":["repos"],"additionalProperties":false}}}}}}},"/workspace/templates":{"get":{"operationId":"workspace.templates","summary":"App templates you can add","description":"The kinds of app the configured source repo knows how to scaffold, which is what an add-app picker lists.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The id to name when scaffolding one."},"label":{"type":"string","description":"What to call it on screen."},"description":{"type":"string","description":"What you get."}},"required":["key","label","description"],"additionalProperties":false},"description":"The kinds of app the configured source repository knows how to scaffold."}},"required":["templates"],"additionalProperties":false}}}}}}},"/workspace/repos/{repo}/apps":{"post":{"operationId":"workspace.addApps","summary":"Scaffold new apps into a repo","description":"Starts scaffolding one or more apps inside an existing multi-package repo and answers straight away. Watch the terminal it opens for progress and for anything that goes wrong.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository to scaffold into."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"minItems":1,"type":"array","items":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Which kind of app to scaffold, by its key in the template list."},"name":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9-]*$","description":"What to call this one."}},"required":["template","name"]},"description":"The apps to add."}},"required":["apps"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}},"get":{"operationId":"workspace.appsList","summary":"Apps inside a repo","description":"The apps in one multi-package repo, each with its preview address and whether its dev server is up.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"type":"array","items":{"type":"object","properties":{"app":{"type":"string","description":"The app's name, which is also its folder."},"kind":{"description":"What sort of app it is: the template it came from, or the framework worked out from its dependencies. Absent when it was found purely by having a dev script.","type":"string"},"previewUrl":{"description":"Where to open it. Absent when this sandbox has no outside address.","type":"string"},"running":{"type":"boolean","description":"Whether its dev server is up."},"healthy":{"type":"boolean","description":"Whether it is actually answering."}},"required":["app","running","healthy"],"additionalProperties":false},"description":"The apps in this repository."}},"required":["apps"],"additionalProperties":false}}}}}}},"/workspace/repos/{repo}/graph":{"get":{"operationId":"workspace.packageGraph","summary":"How a repo's packages depend on each other","description":"Every package in one multi-package repo and which of its siblings each one uses, which is what a dependency view draws.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"packages":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name the package declares."},"dir":{"type":"string","description":"Where it lives, relative to the repository."},"group":{"type":"string","description":"The top-level folder it sits under, which is what a diagram colours by."}},"required":["name","dir","group"],"additionalProperties":false},"description":"Every package in the repository."},"edges":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","description":"The package that depends."},"to":{"type":"string","description":"The package it depends on."},"type":{"type":"string","enum":["prod","dev","peer"],"description":"Which kind of dependency declared it."}},"required":["from","to","type"],"additionalProperties":false},"description":"Which of them use which. Pure data: how to lay it out is yours to decide."}},"required":["packages","edges"],"additionalProperties":false}}}}}}},"/workspace/modules":{"get":{"operationId":"workspace.modules","summary":"Every package across every repo","description":"The named packages in the whole workspace, which is what a review list groups changed files under when a reader wants packages rather than paths. Whole-workspace in one answer, because a review spans repos and asking per repo would be a fan-out on every open.","tags":["Workspace"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"modules":{"type":"array","items":{"type":"object","properties":{"dir":{"type":"string","description":"Where the package lives, relative to its repository. Empty when the repository is itself one package."},"name":{"type":"string","description":"The name the package declares for itself."}},"required":["dir","name"],"additionalProperties":false},"description":"Its packages."}},"required":["repo","modules"],"additionalProperties":false},"description":"Every repository with the packages inside it."}},"required":["repos"],"additionalProperties":false}}}}}}},"/workspace/repos/{repo}/apps/{app}/start":{"post":{"operationId":"workspace.startApp","summary":"Start an app's dev server","description":"Brings up one app's preview server in an attachable terminal, so its address starts answering.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}},{"name":"app","in":"path","required":true,"schema":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9-]*$","description":"Which app inside it."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/repos/{repo}/apps/{app}/stop":{"post":{"operationId":"workspace.stopApp","summary":"Stop an app's dev server","description":"Shuts one app's preview server down and frees its port.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}},{"name":"app","in":"path","required":true,"schema":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9-]*$","description":"Which app inside it."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/workspace/repos/{repo}/tests":{"post":{"operationId":"workspace.runTests","summary":"Run a project's tests","description":"Starts the test run for the projects you name in an attachable terminal and answers straight away. The terminal is where the results appear.","tags":["Workspace"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"session":{"type":"string","description":"What to call the terminal this runs in, so you can find it again."},"dirs":{"minItems":1,"type":"array","items":{"type":"string"},"description":"Which projects to test, as folders relative to the repository. Empty targets the repository root."}},"required":["session","dirs"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/changes":{"get":{"operationId":"git.changes","summary":"Uncommitted work across every repo","description":"The workspace's whole review set in one answer: every repo that has something uncommitted, and within it every changed file with its status and line counts. This is what the Changes panel draws, and it is the call to make when you want to know whether a workspace is clean without walking the repos yourself.","tags":["Git"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"branch":{"description":"The checked-out branch. Absent in a repository that has no commits yet.","type":"string"},"conflicted":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Paths a merge or rebase could not finish. First, because nothing anywhere in this repository can be committed until they are resolved. Held apart from the two lists below, because staged or not is not a question one of these has an answer to."},"operation":{"description":"What halted, when something did. This is the sentence that explains the conflicts above and names the way out of them.","type":"string","enum":["merge","rebase","cherry-pick","revert"]},"staged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"What a plain commit would record right now."},"unstaged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Edits on disk that are not staged, plus untracked files. A path can be in both lists at once with different line counts, which is why they are separate."},"truncated":{"description":"How many changes were cut from the two lists above. A freshly cloned monorepo or a mass delete runs to six figures, which no screen can draw, so past a budget the lists arrive short and this says by how much. Absent means they are complete.","type":"number"},"remote":{"description":"Where this repository stands against its remote.","type":"object","properties":{"remote":{"description":"The remote this branch pushes to. Absent means none is configured. In a fork with two remotes, pushing to the wrong one succeeds and leaves the count stuck, which is why this says which.","type":"string"},"branch":{"description":"The checked-out branch. Absent when the repository is on a bare commit, or has no commits yet.","type":"string"},"upstream":{"description":"The branch on the remote this one follows. Absent means the next push will publish it.","type":"string"},"ahead":{"type":"number","description":"Commits you have that the remote does not."},"behind":{"type":"number","description":"Commits the remote has that you do not, as of the last fetch. Fetch before trusting it."}},"required":["ahead","behind"],"additionalProperties":false},"origins":{"description":"Which conversation put each path here, newest first, keyed by path. Only work that went through a merge can appear: edits made in the shared tree, in a terminal, or by a person are simply absent rather than guessed at.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"error":{"description":"Why the repository could not be read at all, in git's own words. A repository left broken by a failed import arrives with empty lists and this set, rather than vanishing from the answer with nothing to act on.","type":"string"}},"required":["repo","conflicted","staged","unstaged"],"additionalProperties":false},"description":"One entry per repository that has something pending, is out of step with its remote, or could not be read. A clean repository is simply absent."},"originAgents":{"description":"Who each conversation named above is, keyed by id, so a caller need not look them up. Absent when nothing in the review can be attributed.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"title":{"description":"The conversation's title. Absent for one that never got as far as having a title.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it ran on."},"landedMessage":{"description":"What the merged work did, drafted by the conversation itself. Carried here as well as on its card, because merged lines outlive the card: archiving a finished conversation does not uncommit its work.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false}},"required":["provider"],"additionalProperties":false}},"committing":{"description":"Repositories with a commit running right now. The sandbox's answer rather than any one tab's, so a reload, a second window and another device all know. Absent means nothing is committing.","type":"array","items":{"type":"string"}}},"required":["repos"],"additionalProperties":false}}}}}}},"/git/repos":{"get":{"operationId":"git.repos","summary":"Every git repo in the workspace","description":"The repos the daemon found under the workspace root, each with the id every other call in this group expects as its `{repo}` segment. The workspace root itself is always present as `root`.","tags":["Git"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"string"},"description":"Every repository's id. The workspace itself is always present as \"root\"."}},"required":["repos"],"additionalProperties":false}}}}}}},"/git/remote-repos":{"get":{"operationId":"git.remoteRepos","summary":"Repos matched to their remotes","description":"The same repo list, but with the forge host and `owner/name` each one's remote points at. Use it to recognise a workspace repo in a list of names that came from somewhere else, such as a set of pull requests. Costs a remote lookup per repo, which is why it is separate from the plain repo list.","tags":["Git"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"The workspace repository."},"host":{"type":"string","description":"Which forge its remote points at."},"project":{"type":"string","description":"Which project there, as owner and name."}},"required":["repo","host","project"],"additionalProperties":false},"description":"Each repository matched to the project its remote points at."}},"required":["repos"],"additionalProperties":false}}}}}}},"/git/{repo}/log":{"get":{"operationId":"git.log","summary":"Commit history for one repo","description":"A page of commits on the current branch, newest first, each with its author, subject, timestamp and the refs pointing at it. Paginate with the cursor the answer hands back rather than by offset, so a commit landing mid-scroll does not shift the page under you.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"limit","in":"query","schema":{"description":"How many commits to return.","type":"integer","exclusiveMinimum":0,"maximum":2000},"allowEmptyValue":true,"allowReserved":true},{"name":"skip","in":"query","schema":{"description":"How many newer commits to step over, which is how you page further back. Paged rather than read whole, because a large repository's history is tens of thousands of rows.","type":"integer","minimum":0,"maximum":1000000},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"branch":{"description":"Which branch these are from.","type":"string"},"commits":{"type":"array","items":{"type":"object","properties":{"sha":{"type":"string","description":"The commit, in full."},"short":{"type":"string","description":"The abbreviated form, for showing."},"parents":{"type":"array","items":{"type":"string"},"description":"What it came from. None means the first commit, one is ordinary, two or more is a merge, which is what a graph draws its lanes from."},"subject":{"type":"string","description":"Its first line."},"body":{"type":"string","description":"Everything after that."},"author":{"type":"string","description":"Who wrote it."},"email":{"type":"string","description":"Their address."},"at":{"type":"number","description":"When they wrote it, in milliseconds."},"refs":{"type":"array","items":{"type":"string"},"description":"Branches and tags sitting on it."},"head":{"type":"boolean","description":"Whether this is where the repository currently stands."}},"required":["sha","short","parents","subject","body","author","email","at","refs","head"],"additionalProperties":false},"description":"The commits, newest first."},"hasMore":{"type":"boolean","description":"There are older ones behind this page. It is also what stops the last row being drawn as the beginning of history, which is how a truncated log used to claim it started where the page happened to stop."}},"required":["repo","commits","hasMore"],"additionalProperties":false}}}}}}},"/git/{repo}/commit-diff":{"get":{"operationId":"git.commitDiff","summary":"What one commit changed","description":"The list of files a single commit touched, with per-file status and line counts but not the content. Fetch the content of any one of them with the commit file diff call, so a commit with a thousand files stays one cheap answer.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"sha","in":"query","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Which files it touched, with counts but not contents. Fetch any one file's contents separately, so a commit with a thousand files stays one cheap answer."}},"required":["files"],"additionalProperties":false}}}}}}},"/git/{repo}/commit-file-diff":{"get":{"operationId":"git.commitFileDiff","summary":"One file's before and after at a commit","description":"Both sides of a single file as of one commit: the content its parent had and the content that commit left. The daemon returns whole sides rather than a patch, so a caller can render the comparison however it likes.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"sha","in":"query","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit."},"allowEmptyValue":true,"allowReserved":true},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which file in it."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"before":{"description":"The whole file as it was. Absent when it did not exist yet.","type":"string"},"after":{"description":"The whole file as it is now. Absent when it was deleted.","type":"string"},"binary":{"description":"The file is not text, so neither side is sent.","type":"boolean"},"truncated":{"description":"The file was too large to send whole, so what you have is the start of it.","type":"boolean"}},"additionalProperties":false}}}}}}},"/git/{repo}/operation":{"get":{"operationId":"git.operation","summary":"Whether a merge or rebase is halted mid-flight","description":"Names the git operation the worktree is stuck inside, if any: a conflicted merge, an interrupted rebase, a half-applied cherry-pick. Check this first when another call refuses, because a halted worktree is the usual reason and the abort call is the way out.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"The repository asked about."},"operation":{"description":"Which operation the working tree is stuck inside. Absent means it is not stuck at all, which is almost always. While one is present git refuses nearly everything else, and abandoning it is the only way out.","type":"string","enum":["merge","rebase","cherry-pick","revert"]}},"required":["repo"],"additionalProperties":false}}}}}}},"/git/{repo}/abort":{"post":{"operationId":"git.abort","summary":"Abandon a halted merge or rebase","description":"Runs git's own abort for whichever operation has the worktree halted, putting the repo back where it stood before the operation started. Nothing else clears that state.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/undo":{"get":{"operationId":"git.undoable","summary":"What undoing the last action would do","description":"Reads the branch's reflog to describe the move that undo would reverse, and hands back the commit it would land on. Pass that commit to the undo call as proof you looked, and an undo prepared against a view that has since moved is refused rather than landing somewhere unexamined.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"action":{"description":"What undoing would reverse. Absent means there is nothing to go back from.","type":"object","properties":{"kind":{"type":"string","enum":["commit","amend","merge","rebase","cherry-pick","revert","reset","pull","other"],"description":"What the last action was."},"description":{"type":"string","description":"What undoing it would do, in words."},"branch":{"type":"string","description":"Which branch would move."},"sha":{"type":"string","description":"Where it stands now."},"previousSha":{"type":"string","description":"Where it would go back to. Send this with the undo as proof you looked, so one prepared against a view that has since moved is refused rather than landing somewhere unexamined."},"changesWorkingTree":{"type":"boolean","description":"Undoing would rewrite files as well as moving the branch, so anything offering it should warn about losing work."}},"required":["kind","description","branch","sha","previousSha","changesWorkingTree"],"additionalProperties":false}},"required":["repo"],"additionalProperties":false}}}}}},"post":{"operationId":"git.undo","summary":"Move the branch back one step","description":"Walks the current branch back to where it pointed before its last action. This moves the branch ref and leaves the working tree alone, which is the opposite of restoring a checkpoint. Requires the commit the matching read handed you.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"previousSha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Where to go back to, from the matching read. It is also proof you looked: one prepared against a stale view is refused."},"discardChanges":{"description":"Also rewrite the files, rather than only moving the branch.","type":"boolean"}},"required":["previousSha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/stashes":{"get":{"operationId":"git.stashes","summary":"Everything set aside in the stash","description":"The repo's stash entries, newest first, each with the message and the commit behind it. A stash entry is a commit, so it reads the same way a log entry does and its contents come back from the stash diff call.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"stashes":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","description":"How to address it, which applying and dropping take."},"sha":{"type":"string","description":"The commit behind it, because a stash entry is a commit."},"short":{"type":"string","description":"The abbreviated form, for showing."},"subject":{"type":"string","description":"What it was set aside as, with git's own scaffolding stripped off."},"branch":{"description":"Which branch it was set aside from.","type":"string"},"at":{"type":"number","description":"When, in milliseconds."},"parents":{"type":"array","items":{"type":"string"},"description":"What it sits on, so a graph can draw it like any other commit."}},"required":["ref","sha","short","subject","at","parents"],"additionalProperties":false},"description":"What is set aside, newest first."}},"required":["repo","stashes"],"additionalProperties":false}}}}}}},"/git/{repo}/stash-diff":{"get":{"operationId":"git.stashDiff","summary":"What one stash entry holds","description":"The files a single stash entry would bring back, with per-file status and line counts. The same shape a commit diff has, because a stash entry is a commit.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"ref","in":"query","required":true,"schema":{"type":"string","pattern":"^stash@\\{\\d{1,4}\\}$","description":"Which entry."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Which files it touched, with counts but not contents. Fetch any one file's contents separately, so a commit with a thousand files stays one cheap answer."}},"required":["files"],"additionalProperties":false}}}}}}},"/git/{repo}/stash":{"post":{"operationId":"git.stashPush","summary":"Set the current changes aside","description":"Moves the working tree's changes onto the stash and leaves a clean tree behind. Nothing is lost: the entry is a commit you can inspect, apply or drop afterwards.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"description":"What to call it, so you know what it was later.","type":"string","maxLength":500},"includeUntracked":{"description":"Also set aside files git is not yet tracking, which are otherwise left where they are.","type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/stash/apply":{"post":{"operationId":"git.stashApply","summary":"Bring a stash entry back","description":"Replays one stash entry onto the working tree. A conflict is reported in the answer rather than raised as a failure, because a conflicting apply is an ordinary outcome a screen has to render.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","pattern":"^stash@\\{\\d{1,4}\\}$","description":"Which entry."},"pop":{"description":"Remove it from the stash once it has been applied cleanly.","type":"boolean"}},"required":["ref"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/stash/drop":{"post":{"operationId":"git.stashDrop","summary":"Discard a stash entry","description":"Deletes one stash entry. This is the only unrecoverable call in the stash set, so the daemon takes a checkpoint of the workspace first.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","pattern":"^stash@\\{\\d{1,4}\\}$","description":"Which entry."}},"required":["ref"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/branch":{"post":{"operationId":"git.createBranch","summary":"Start a branch at a commit","description":"Points a new branch name at any commit, without moving HEAD. Use the checkout call if you also want to switch to it.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to start it at."},"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"The new branch's name."}},"required":["sha","name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/tag":{"post":{"operationId":"git.createTag","summary":"Tag a commit","description":"Puts a tag on any commit. Local only: pushing it to the remote is a separate call.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to tag."},"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"The tag's name."}},"required":["sha","name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/tag/delete":{"post":{"operationId":"git.deleteTag","summary":"Remove a tag","description":"Deletes a tag locally. A tag already pushed stays on the remote until it is deleted there too.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"Which tag."},"remote":{"description":"Also delete it there. Leave it out to remove it locally only.","type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/tag/push":{"post":{"operationId":"git.pushTag","summary":"Send a tag to the remote","description":"Pushes one tag to the repo's remote. Reports the outcome rather than failing, since a missing remote or missing credentials are ordinary answers here.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"Which tag."},"remote":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"Which remote to send it to."}},"required":["name","remote"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/checkout":{"post":{"operationId":"git.checkout","summary":"Switch to a branch or commit","description":"Moves HEAD to a branch, tag or commit and reshapes the working tree to match. The daemon takes a checkpoint first, so an unexpected result is recoverable. Uncommitted work that would be overwritten is reported instead of being trampled.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"Where to switch to: a branch, a tag, or a commit."}},"required":["ref"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/cherry-pick":{"post":{"operationId":"git.cherryPick","summary":"Replay one commit onto this branch","description":"Applies a single commit's changes on top of the current branch as a new commit. A conflict comes back in the answer, with the halted state readable from the operation call.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to act on."}},"required":["sha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/revert":{"post":{"operationId":"git.revert","summary":"Undo a commit with a new commit","description":"Adds a commit that reverses an earlier one, leaving the history intact. This is the safe way to take something back on a branch other people have pulled.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to act on."}},"required":["sha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/drop":{"post":{"operationId":"git.drop","summary":"Remove a commit from history","description":"Rewrites the branch so one commit is no longer in it. History changes, so this is for branches nobody else has pulled. A checkpoint is taken first.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to act on."}},"required":["sha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/merge":{"post":{"operationId":"git.merge","summary":"Merge another branch in","description":"Merges a branch or commit into the current one. Conflicts are reported in the answer and leave the worktree halted, which the operation call explains and the abort call clears.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to act on."}},"required":["sha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/rebase":{"post":{"operationId":"git.rebase","summary":"Replay this branch onto another","description":"Moves the current branch's commits on top of a different base. History changes. Conflicts halt the rebase and are reported rather than raised, so the operation and abort calls are the way through.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to act on."}},"required":["sha"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/reset":{"post":{"operationId":"git.reset","summary":"Move the branch to a commit","description":"Repoints the current branch at another commit, optionally reshaping the working tree to match. The destructive modes take a checkpoint first.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{4,64}$","description":"Which commit to move the branch to."},"mode":{"type":"string","enum":["soft","mixed","hard"],"description":"How much to take with it: move the branch alone, also unstage, or also throw away what is on disk. The last one takes a checkpoint first."}},"required":["sha","mode"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/file-diff":{"get":{"operationId":"git.fileDiff","summary":"One file's committed and working copies","description":"Both sides of a file as it stands right now: what the last commit holds and what is on disk. This is what a review pane shows for an uncommitted change.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The file, relative to the repository root."},"allowEmptyValue":true,"allowReserved":true},{"name":"side","in":"query","required":true,"schema":{"type":"string","enum":["staged","unstaged","conflicted"],"description":"Which comparison you want. A file that is staged and then edited again has genuinely different answers for each, which is why this is required rather than assumed."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"before":{"description":"The whole file as it was. Absent when it did not exist yet.","type":"string"},"after":{"description":"The whole file as it is now. Absent when it was deleted.","type":"string"},"binary":{"description":"The file is not text, so neither side is sent.","type":"boolean"},"truncated":{"description":"The file was too large to send whole, so what you have is the start of it.","type":"boolean"}},"additionalProperties":false}}}}}}},"/git/{repo}/status":{"get":{"operationId":"git.status","summary":"One repo's branch and pending changes","description":"The current branch, its sync position against the remote, and every staged, unstaged and untracked path. The single-repo counterpart to the workspace-wide changes call.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"branch":{"type":"string","description":"The checked-out branch."},"dirty":{"type":"boolean","description":"Whether anything is uncommitted."},"files":{"type":"array","items":{"type":"string"},"description":"Every path with something pending, staged or not."}},"required":["branch","dirty","files"],"additionalProperties":false}}}}}}},"/git/{repo}/commit":{"post":{"operationId":"git.commit","summary":"Commit the pending changes","description":"Records a commit with your message. Give it a list of paths to commit only those, or leave it out to commit everything pending. The answer carries the commit it created.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","minLength":1,"description":"The commit message."},"all":{"description":"Stage every change in the repository first, then commit. An alternative to naming paths, not a companion to it.","type":"boolean"},"paths":{"description":"Stage exactly these paths, then commit everything staged. Leave this and `all` out to commit whatever is already staged.","maxItems":500,"type":"array","items":{"type":"string","minLength":1}}},"required":["message"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"committed":{"type":"boolean","description":"Whether a commit was actually recorded."},"changes":{"description":"What this repository looks like now, read in the same breath as the commit so a caller can redraw from here instead of asking for a fresh scan. Absent means there is nothing left to show.","type":"object","properties":{"repo":{"type":"string"},"branch":{"description":"The checked-out branch. Absent in a repository that has no commits yet.","type":"string"},"conflicted":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Paths a merge or rebase could not finish. First, because nothing anywhere in this repository can be committed until they are resolved. Held apart from the two lists below, because staged or not is not a question one of these has an answer to."},"operation":{"description":"What halted, when something did. This is the sentence that explains the conflicts above and names the way out of them.","type":"string","enum":["merge","rebase","cherry-pick","revert"]},"staged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"What a plain commit would record right now."},"unstaged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The path, relative to the repository root. For a rename this is the new one."},"status":{"type":"string","enum":["added","modified","deleted","renamed","type-changed","conflicted"],"description":"What happened to it. Conflicted is not a kind of edit: nothing can be committed anywhere in the repository while one exists."},"from":{"description":"Where a renamed file came from.","type":"string"},"additions":{"description":"Lines added. Absent for a binary file, and for an untracked one, which has nothing to compare against.","type":"number"},"deletions":{"description":"Lines removed. Absent for the same reasons additions is.","type":"number"}},"required":["path","status"],"additionalProperties":false},"description":"Edits on disk that are not staged, plus untracked files. A path can be in both lists at once with different line counts, which is why they are separate."},"truncated":{"description":"How many changes were cut from the two lists above. A freshly cloned monorepo or a mass delete runs to six figures, which no screen can draw, so past a budget the lists arrive short and this says by how much. Absent means they are complete.","type":"number"},"remote":{"description":"Where this repository stands against its remote.","type":"object","properties":{"remote":{"description":"The remote this branch pushes to. Absent means none is configured. In a fork with two remotes, pushing to the wrong one succeeds and leaves the count stuck, which is why this says which.","type":"string"},"branch":{"description":"The checked-out branch. Absent when the repository is on a bare commit, or has no commits yet.","type":"string"},"upstream":{"description":"The branch on the remote this one follows. Absent means the next push will publish it.","type":"string"},"ahead":{"type":"number","description":"Commits you have that the remote does not."},"behind":{"type":"number","description":"Commits the remote has that you do not, as of the last fetch. Fetch before trusting it."}},"required":["ahead","behind"],"additionalProperties":false},"origins":{"description":"Which conversation put each path here, newest first, keyed by path. Only work that went through a merge can appear: edits made in the shared tree, in a terminal, or by a person are simply absent rather than guessed at.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}},"error":{"description":"Why the repository could not be read at all, in git's own words. A repository left broken by a failed import arrives with empty lists and this set, rather than vanishing from the answer with nothing to act on.","type":"string"}},"required":["repo","conflicted","staged","unstaged"],"additionalProperties":false},"originAgents":{"description":"Who the conversations named in those changes are. Merge it over what you already hold rather than replacing: other repositories still name their own.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"title":{"description":"The conversation's title. Absent for one that never got as far as having a title.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it ran on."},"landedMessage":{"description":"What the merged work did, drafted by the conversation itself. Carried here as well as on its card, because merged lines outlive the card: archiving a finished conversation does not uncommit its work.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false}},"required":["provider"],"additionalProperties":false}}},"required":["committed"],"additionalProperties":false}}}}}}},"/git/{repo}/discard":{"post":{"operationId":"git.discard","summary":"Throw away pending changes","description":"Restores files to their committed state and deletes untracked ones. Give it paths to discard only those. The daemon checkpoints the workspace first, so this is recoverable from the timeline.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"paths":{"description":"Which paths to throw away. Leave it out to discard every uncommitted change in the repository.","maxItems":500,"type":"array","items":{"type":"string","minLength":1}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/stage":{"post":{"operationId":"git.stage","summary":"Mark paths for the next commit","description":"Adds paths to the index. Nothing on disk changes, so this is always safe and always reversible with the unstage call.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paths":{"maxItems":500,"type":"array","items":{"type":"string","minLength":1},"description":"The paths to move. Nothing on disk changes, so this is always safe and always reversible."}},"required":["paths"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/unstage":{"post":{"operationId":"git.unstage","summary":"Take paths back out of the next commit","description":"Removes paths from the index and leaves the file itself untouched. The exact reverse of staging.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paths":{"maxItems":500,"type":"array","items":{"type":"string","minLength":1},"description":"The paths to move. Nothing on disk changes, so this is always safe and always reversible."}},"required":["paths"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/branches":{"get":{"operationId":"git.branches","summary":"Local branches and how far each has drifted","description":"Every local branch with how many commits it sits ahead of and behind its remote counterpart, so a branch switcher can show sync state without a call per branch.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"branches":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The branch name."},"current":{"type":"boolean","description":"Whether this is the one checked out."},"upstream":{"description":"The branch on the remote it follows, if any.","type":"string"},"ahead":{"type":"number","description":"Commits this branch has that its remote counterpart does not."},"behind":{"type":"number","description":"Commits its remote counterpart has that it does not."},"gone":{"description":"The branch it followed no longer exists on the remote, usually because a merged pull request deleted it. The signal that this one is safe to delete.","type":"boolean"},"at":{"type":"number","description":"When its tip was committed, in milliseconds. Lists are newest first."}},"required":["name","current","ahead","behind","at"],"additionalProperties":false},"description":"Branches in this repository."},"remotes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The full name, such as origin/main."},"remote":{"type":"string","description":"Just the remote part, so a picker can group by it without re-parsing."},"branch":{"type":"string","description":"Just the branch part."},"at":{"type":"number","description":"When its tip was committed, in milliseconds, as this repository last saw it."}},"required":["name","remote","branch","at"],"additionalProperties":false},"description":"Branches on its remotes, as last seen. Sent together with the locals so a switcher never draws a half-filled list."}},"required":["branches","remotes"],"additionalProperties":false}}}}}},"post":{"operationId":"git.createBranchAt","summary":"Create a branch from a starting point","description":"Makes a branch at a named start point and optionally switches to it. The branch-switcher counterpart to creating a branch at a specific commit.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"The new branch's name."},"start":{"description":"Where to start it: a commit or another branch. Leave it out to start from where you are.","type":"string","minLength":1},"checkout":{"description":"Switch to it as well as creating it.","type":"boolean"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/branches/delete":{"post":{"operationId":"git.deleteBranch","summary":"Delete a local branch","description":"Removes a branch from the repo. Unmerged work is refused unless you ask for it to be forced, and the remote branch is untouched either way.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9][A-Za-z0-9._/-]*$","description":"The branch to delete."},"force":{"description":"Delete it even though it holds work that was never merged. The deliberate retry after the first attempt refuses.","type":"boolean"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/remote":{"get":{"operationId":"git.remote","summary":"Sync position against the remote","description":"How far the current branch sits ahead of and behind its remote, as of the last fetch, plus whether a remote and working credentials exist at all. This is a read of what the daemon already knows, not a network call, which is why fetching is a separate button.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"remote":{"description":"The remote this branch pushes to. Absent means none is configured. In a fork with two remotes, pushing to the wrong one succeeds and leaves the count stuck, which is why this says which.","type":"string"},"branch":{"description":"The checked-out branch. Absent when the repository is on a bare commit, or has no commits yet.","type":"string"},"upstream":{"description":"The branch on the remote this one follows. Absent means the next push will publish it.","type":"string"},"ahead":{"type":"number","description":"Commits you have that the remote does not."},"behind":{"type":"number","description":"Commits the remote has that you do not, as of the last fetch. Fetch before trusting it."}},"required":["ahead","behind"],"additionalProperties":false}}}}}}},"/git/{repo}/fetch":{"post":{"operationId":"git.fetch","summary":"Refresh what the remote holds","description":"Contacts the remote and updates the daemon's picture of it without touching your branch. Run this before trusting the sync position.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/pull":{"post":{"operationId":"git.pull","summary":"Bring remote commits down","description":"Fetches and integrates the remote's commits into the current branch. A pull that cannot fast-forward is reported in the answer rather than raised, because that is an ordinary thing to be told.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/push":{"post":{"operationId":"git.push","summary":"Send commits to the remote","description":"Pushes the current branch, setting its upstream on first push. A rejected push, a missing remote and missing credentials all come back as reported outcomes rather than failures.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"branch":{"description":"Which branch to push. Leave it out for the checked-out one. A branch with no upstream yet gets one set on this push.","type":"string","minLength":1}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether it worked."},"reason":{"description":"Why not, in git's own words. A conflict, a missing remote and missing credentials are all reported here rather than raised, because they are things a screen has to render rather than breakages.","type":"string"}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/files":{"get":{"operationId":"git.files","summary":"Every tracked path in the repo","description":"The flat list of files git tracks, which is what a file picker or a search box wants. Ignored and untracked files are not in it.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"},"description":"Every path git tracks, relative to the repository root. Ignored and untracked files are not here."}},"required":["files"],"additionalProperties":false}}}}}}},"/git/{repo}/file":{"get":{"operationId":"git.readFile","summary":"Read a file from the repo","description":"The contents of one file as it stands on disk. A path that climbs out of the repo is refused.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The file to read, relative to the repository root."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","description":"The path, as asked for."},"content":{"type":"string","description":"The file's contents as they stand on disk."}},"required":["path","content"],"additionalProperties":false}}}}}},"put":{"operationId":"git.writeFile","summary":"Write a file into the repo","description":"Replaces one file's contents, creating it and its parent folders if they are missing. Nothing is committed: the change shows up as pending work.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"Where to write, relative to the repository root. Missing folders are created."},"content":{"type":"string","description":"The file's whole new contents."}},"required":["path","content"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/git/{repo}/publish-file":{"post":{"operationId":"git.publishFile","summary":"Write, commit and push one file","description":"The three steps as a single call with a single answer, committing only the path you named and leaving any other pending work alone. Being on a side branch, having no remote and having no credentials are all reported rather than raised.","tags":["Git"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository. \"root\" is the workspace itself; anything else is a repository's folder relative to the workspace root, URL-encoded."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"Which file, relative to the repository."},"content":{"type":"string","description":"Its whole new contents."},"message":{"type":"string","minLength":1,"description":"The commit message."}},"required":["path","content","message"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","description":"Whether the whole thing went through."},"wrote":{"type":"boolean","description":"The file was written."},"committed":{"type":"boolean","description":"The commit was recorded."},"pushed":{"type":"boolean","description":"It reached the remote."},"branch":{"description":"Which branch it happened on.","type":"string"},"defaultBranch":{"description":"Which branch the repository considers its main one, so a caller can see it was on a side branch.","type":"string"},"reason":{"description":"Why it stopped where it did. Being on a side branch, having no remote and having no credentials are all reported here rather than raised.","type":"string"}},"required":["ok","wrote","committed","pushed"],"additionalProperties":false}}}}}}},"/history/snapshots":{"get":{"operationId":"history.list","summary":"Points you can go back to","description":"The saved states of the whole workspace, taken automatically as work happens. This is the timeline behind undoing a change that was never committed.","tags":["History"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"snapshots":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The saved point's id, which is what restoring and diffing take."},"at":{"type":"number","description":"When it was taken, in milliseconds."},"trigger":{"type":"string","enum":["turn","interval","pre-restore","restore","user"],"description":"What caused it. The automatic between-turn captures are a safety net and are not listed; they dissolve into the next visible point's differences."},"label":{"description":"What to call it. For one taken before a turn, that turn's prompt.","type":"string"}},"required":["id","at","trigger"],"additionalProperties":false},"description":"Every point you can go back to, newest first."}},"required":["snapshots"],"additionalProperties":false}}}}}}},"/history/diff":{"get":{"operationId":"history.diff","summary":"What changed since a saved point","description":"The files that differ between one saved point and the one before it, taking in everything that happened in between.","tags":["History"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which saved point."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"changes":{"type":"array","items":{"type":"object","properties":{"scope":{"type":"string","description":"Which part of the workspace the path belongs to: the workspace root, or one of the repositories inside it."},"path":{"type":"string","description":"The path, relative to that scope."},"status":{"type":"string","enum":["added","modified","deleted","type-changed"],"description":"What happened to it."}},"required":["scope","path","status"],"additionalProperties":false},"description":"Everything that differs between this saved point and the one before it."}},"required":["changes"],"additionalProperties":false}}}}}}},"/history/file-diff":{"get":{"operationId":"history.fileDiff","summary":"One file's before and after across a saved point","description":"Both sides of a single file at one point in the timeline.","tags":["History"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which saved point."},"allowEmptyValue":true,"allowReserved":true},{"name":"scope","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which part of the workspace the path belongs to."},"allowEmptyValue":true,"allowReserved":true},{"name":"path","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The file, relative to that scope."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"before":{"description":"The whole file as it was. Absent when it did not exist yet.","type":"string"},"after":{"description":"The whole file as it is now. Absent when it was deleted.","type":"string"},"binary":{"description":"The file is not text, so neither side is sent.","type":"boolean"},"truncated":{"description":"The file was too large to send whole, so what you have is the start of it.","type":"boolean"}},"additionalProperties":false}}}}}}},"/history/restore":{"post":{"operationId":"history.restore","summary":"Put the workspace back","description":"Returns every file to how it stood at a saved point. This restores the files; moving a branch is a different thing and lives with the git calls.","tags":["History"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which saved point."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/chores":{"get":{"operationId":"chores.list","summary":"What maintenance the repos are asking for","description":"Every repo's standing evidence in one read: what the last measurement found and how old it is, the cheap signals that are always current, and what has already been decided about each.","tags":["Chores"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"probes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["outdated","audit","knip","jscpd","ui","bundle"],"description":"Which measurement this is."},"state":{"type":"string","enum":["ok","unavailable","failed"],"description":"Whether the tool ran and reported, is not part of this repository at all, or broke. The middle one is not evidence of health: the check simply cannot be made here."},"ranAt":{"type":"number","description":"When it last finished, in milliseconds, which is what its age is measured from."},"tookMs":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How long it took. Worth knowing before asking for it again: some of these run for minutes."},"facts":{"description":"What it found, including finding nothing, which is a real answer and the one that keeps a chore quiet.","oneOf":[{"type":"object","properties":{"id":{"type":"string","const":"outdated"},"packages":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The dependency."},"current":{"type":"string","description":"What you are on."},"latest":{"type":"string","description":"What is published."},"kind":{"type":"string","enum":["major","minor","patch"],"description":"How far apart those are. This is not one number because forty patch releases behind is a morning's work and one major version is a project."},"section":{"type":"string","description":"Which part of the manifest declares it. A major version behind on a build-time tool is a different risk from one that ships."}},"required":["name","current","latest","kind","section"],"additionalProperties":false}}},"required":["id","packages"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","const":"audit"},"advisories":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The dependency it concerns."},"severity":{"type":"string","enum":["critical","high","moderate","low","info"],"description":"How bad it is said to be."},"title":{"type":"string","description":"What it is, in one line. No scoring vector and no reference list: those are for reading on the advisory's own page, and carrying them would put a kilobyte of prose per finding on every poll."},"patched":{"description":"Which versions fix it. Absent means no fix has been published, which is exactly when nothing should offer to upgrade and something should say so instead.","type":"string"},"dev":{"type":"boolean","description":"Whether it only reaches build-time tooling, which is a different problem from one that reaches what you ship."}},"required":["name","severity","title","dev"],"additionalProperties":false}}},"required":["id","advisories"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","const":"knip"},"deadCode":{"type":"object","properties":{"files":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Files nothing reaches."},"exports":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Exported things nothing uses."},"types":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Types nothing uses."},"dependencies":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Declared dependencies nothing imports."},"devDependencies":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"The same, for build-time ones."},"sample":{"type":"array","items":{"type":"string"},"description":"A handful of the files, so a reader need not take the count on faith. Counts and a sample rather than the whole list, because an agent re-measures against the live tree anyway."}},"required":["files","exports","types","dependencies","devDependencies","sample"],"additionalProperties":false}},"required":["id","deadCode"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","const":"jscpd"},"duplication":{"type":"object","properties":{"percentage":{"type":"number","description":"How much of the scanned code is duplicated. A share rather than a count, because a count grows with the repository and would mean something different every quarter."},"clones":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many duplicated stretches were found."},"top":{"type":"array","items":{"type":"object","properties":{"lines":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How long the duplicated stretch is."},"first":{"type":"string","description":"One of the two places."},"second":{"type":"string","description":"The other."}},"required":["lines","first","second"],"additionalProperties":false},"description":"The largest of them."}},"required":["percentage","clones","top"],"additionalProperties":false}},"required":["id","duplication"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","const":"ui"},"scan":{"type":"object","properties":{"components":{"type":"array","items":{"type":"string"},"description":"The interface's own source files, with tests, stories and generated output left out."},"bypasses":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file."},"count":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"How many times, in that file."}},"required":["path","count"],"additionalProperties":false},"description":"Where the design system was routed around and a value hard-coded instead. Counted per file, because a reader deciding what to open is served by a file and a number, not by eleven snippets."},"idioms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which outdated idiom. Looked up rather than listed here, so a sandbox one version behind can still report one this list has never heard of."},"files":{"type":"array","items":{"type":"string"},"description":"The files still on it."}},"required":["id","files"],"additionalProperties":false},"description":"Files still written the way their framework has since replaced."}},"required":["components","bypasses","idioms"],"additionalProperties":false}},"required":["id","scan"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","const":"bundle"},"bundle":{"type":"object","properties":{"dir":{"type":"string","description":"Which folder was measured. Read from build output already on disk rather than by building, so this is sometimes a commit behind and never leaves anything in your working tree."},"totalBytes":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"The whole thing, raw."},"totalGzip":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"The whole thing, compressed. The ratio between the two is the difference between big and big-and-incompressible, which are different problems."},"assets":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file."},"bytes":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Its raw size."},"gzip":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Its compressed size."}},"required":["path","bytes","gzip"],"additionalProperties":false},"description":"What is in it, piece by piece."}},"required":["dir","totalBytes","totalGzip","assets"],"additionalProperties":false}},"required":["id","bundle"],"additionalProperties":false}]},"reason":{"description":"Why it broke, quoted from the tool rather than summarised, or, when it never ran, what is missing. Never a sentence built from the check's own name, which would have an unmeasured check claiming there is nothing to measure.","type":"string"}},"required":["id","state","ranAt","tookMs"],"additionalProperties":false},"description":"The expensive measurements, served from a cache with an age on each rather than run on demand."},"signals":{"type":"object","properties":{"packages":{"type":"array","items":{"type":"object","properties":{"dir":{"type":"string","description":"Where the package lives."},"name":{"type":"string","description":"What it declares itself as."},"engines":{"description":"Which runtime versions it says it needs, verbatim.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"dependencies":{"type":"array","items":{"type":"string"},"description":"What it depends on."},"devDependencies":{"type":"array","items":{"type":"string"},"description":"What it needs only to build."},"documented":{"type":"boolean","description":"Whether it has a README, which in this workspace is what a package's own documentation is."}},"required":["dir","name","dependencies","devDependencies","documented"],"additionalProperties":false},"description":"Each package in the repository, as its own manifest declares it."},"shape":{"type":"object","properties":{"docs":{"type":"array","items":{"type":"string"},"description":"The repository's own architecture documents, when it has any. Their existence is the question: a repository with none has never been through the documentation flow at all."},"dockerfiles":{"type":"array","items":{"type":"string"},"description":"Container definitions in it."},"ci":{"type":"array","items":{"type":"string"},"description":"Pipeline definitions in it."},"lockfile":{"type":"boolean","description":"Whether dependencies are pinned to exact versions, which is what makes a security audit mean anything."},"packageManifest":{"type":"boolean","description":"Whether it is a JavaScript project at all. A Rust or Go repository has no majors to be behind on, and offering it those checks would be this surface guessing at what it is looking at."},"deps":{"type":"array","items":{"type":"string"},"description":"Every dependency name declared anywhere in the repository. Names rather than a verdict about which framework this is, because that judgement belongs to whatever reads this, not to a sandbox baked months ago."}},"required":["docs","dockerfiles","ci","lockfile","packageManifest","deps"],"additionalProperties":false,"description":"What the repository is made of, which decides whether a given chore is even a sensible question to ask of it."},"hotspots":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"commits":{"type":"number"},"adds":{"type":"number"},"dels":{"type":"number"},"complexity":{"type":"number"},"score":{"type":"number"},"latestMs":{"type":"number"}},"required":["path","commits","adds","dels","complexity","score","latestMs"],"additionalProperties":false},"description":"Files that change often and are complicated at once, capped tight: a chore only asks whether something has entered the top of the ranking."},"keyModules":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"exports":{"type":"number"}},"required":["path","exports"],"additionalProperties":false},"description":"The parts the rest of the code leans on most, capped the same way."},"totals":{"type":"object","properties":{"files":{"type":"number","description":"Files counted."},"symbols":{"type":"number","description":"Named things they export."},"complexity":{"type":"number","description":"Branch points added up."},"hotspots":{"type":"number","description":"How many files qualify as hotspots at all."}},"required":["files","symbols","complexity","hotspots"],"additionalProperties":false,"description":"The repository in numbers."},"indexed":{"type":"boolean","description":"Whether the index these rankings came from is finished. Nothing should act on a half-built one."}},"required":["packages","shape","hotspots","keyModules","totals","indexed"],"additionalProperties":false,"description":"The cheap facts, worked out fresh every time."}},"required":["repo","probes","signals"],"additionalProperties":false},"description":"Every repository's standing evidence. One answer for all of them, because a badge polls this on a timer and one request per repository is the kind of poll that shows up in a battery graph."},"ledger":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"chore":{"type":"string","description":"Which chore."},"ranAt":{"type":"number","description":"When it ran, in milliseconds."},"runId":{"type":"string","description":"The conversation that ran it, so its whole record can be opened."},"outcome":{"type":"string","enum":["acted","reported","clean"],"description":"What it concluded: it did something, it wrote something down, or it looked and found the finding to be false. That last one matters most, or the same turn starts again for ever."},"digest":{"type":"string","description":"A fingerprint of the evidence standing at the time. A chore whose evidence has since changed is due again on its own merits; one whose evidence has not stays quiet."},"snoozedUntil":{"description":"Not until then, in milliseconds. The chore stays visible and stays out of the badge. Different from switching it off, which is a setting.","type":"number"}},"required":["repo","chore","ranAt","runId","outcome","digest"],"additionalProperties":false},"description":"What has already been done about all of it."},"running":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"id":{"type":"string","enum":["outdated","audit","knip","jscpd","ui","bundle"],"description":"Which measurement."},"askedAt":{"type":"number","description":"When it was asked for, in milliseconds, so one still waiting can say how long it has waited."},"startedAt":{"description":"When it actually began. Absent while it is queued behind another, which is a real and common state: there is one lane for the whole sandbox.","type":"number"}},"required":["repo","id","askedAt"],"additionalProperties":false},"description":"What is being measured right now and what is waiting behind it. Part of this read rather than a route of its own, because a screen that had to ask twice would show the two halves disagreeing."},"node":{"type":"string","description":"The runtime version this sandbox is actually running, read off the process rather than off a manifest, because what is installed is the fact that matters and a declared range is a wish."}},"required":["repos","ledger","running","node"],"additionalProperties":false}}}}}}},"/chores/probe":{"post":{"operationId":"chores.probe","summary":"Measure one repo again now","description":"Re-runs a single check without waiting for it to go stale. Answers immediately: the work happens in the background and the result turns up in the next read, because some of these sweeps outlive any sane request.","tags":["Chores"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","minLength":1,"description":"Which repository."},"id":{"type":"string","enum":["outdated","audit","knip","jscpd","ui","bundle"],"description":"Which measurement to retake, ahead of its usual schedule."}},"required":["repo","id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/chores/ledger":{"post":{"operationId":"chores.record","summary":"Record a verdict, or snooze one","description":"Writes what somebody concluded about one repo's chore, replacing the previous verdict. A chore has one current answer, not a growing pile of times it was fine.","tags":["Chores"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"chore":{"type":"string","description":"Which chore."},"ranAt":{"type":"number","description":"When it ran, in milliseconds."},"runId":{"type":"string","description":"The conversation that ran it, so its whole record can be opened."},"outcome":{"type":"string","enum":["acted","reported","clean"],"description":"What it concluded: it did something, it wrote something down, or it looked and found the finding to be false. That last one matters most, or the same turn starts again for ever."},"digest":{"type":"string","description":"A fingerprint of the evidence standing at the time. A chore whose evidence has since changed is due again on its own merits; one whose evidence has not stays quiet."},"snoozedUntil":{"description":"Not until then, in milliseconds. The chore stays visible and stays out of the badge. Different from switching it off, which is a setting.","type":"number"}},"required":["repo","chore","ranAt","runId","outcome","digest"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/panels":{"get":{"operationId":"panels.list","summary":"Repos you can run and preview","description":"Every repo with whether its dev server is up and what the sandbox worked out about its contents.","tags":["Panels"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"panels":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which repository."},"hasPanel":{"type":"boolean","description":"Whether it has anything runnable at all."},"running":{"type":"boolean","description":"Whether the sandbox has it running."},"healthy":{"type":"boolean","description":"Whether anything it owns is actually answering. A different question: a server still installing is running and not yet healthy, and one somebody started by hand is healthy without the sandbox running it."},"port":{"description":"The port the sandbox told it to use. What it actually bound is below, and for a repository that pins its own ports those are different numbers.","type":"number"},"servers":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Where it answers, with the right scheme: a server on its own certificate is served over https."},"dir":{"description":"Which part of the repository it belongs to, which for a repository whose dev command fans out is the only thing telling them apart.","type":"string"},"session":{"description":"The terminal it runs in: the sandbox's when it started it, yours when you did, and absent when nothing here owns it, which is the case worth designing for.","type":"string"}},"required":["url"],"additionalProperties":false},"description":"Every server this repository is really serving, found by looking at what is listening. Empty when nothing answers."},"previewUrl":{"description":"Where to open it from outside. Absent on a sandbox with no outside address.","type":"string"},"role":{"description":"Which of the workspace's three fixed roles this repository fills. Absent for one that was simply cloned in.","type":"string","enum":["intent","desired-state","app"]},"deployConfig":{"type":"boolean","description":"It declares infrastructure."},"desiredState":{"type":"boolean","description":"That declaration has been resolved at least once."},"directoryUi":{"type":"boolean","description":"It carries a small interface of its own."},"monorepo":{"type":"boolean","description":"It holds several packages."},"vitest":{"type":"boolean","description":"It has tests that can be run."},"userStories":{"type":"boolean","description":"It carries stories an agent could test the running app against. The one fact here that says nothing about the language."},"docs":{"type":"boolean","description":"It carries generated architecture documentation."}},"required":["repo","hasPanel","running","healthy","servers","deployConfig","desiredState","directoryUi","monorepo","vitest","userStories","docs"],"additionalProperties":false},"description":"One entry per repository, worked out in a single pass so nothing has to walk the workspace file by file."}},"required":["panels"],"additionalProperties":false}}}}}}},"/panels/{repo}/start":{"post":{"operationId":"panels.start","summary":"Start a repo's dev server","description":"Brings the repo's own runnable app up in a terminal you can attach to, so its preview address starts answering.","tags":["Panels"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/panels/{repo}/stop":{"post":{"operationId":"panels.stop","summary":"Stop a repo's dev server","description":"Shuts it down and frees the port.","tags":["Panels"],"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Which repository."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/ports":{"get":{"operationId":"ports.list","summary":"What is listening inside the sandbox","description":"Every port something is answering on, and whether each one is reachable from outside.","tags":["Ports"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ports":{"type":"array","items":{"type":"object","properties":{"port":{"type":"number","description":"The port number."},"host":{"type":"string","enum":["127.0.0.1","::1"],"description":"Which loopback address it actually answers on. Some tools bind only one of the two, and anything dialling it has to know which."},"forwardable":{"type":"boolean","description":"Whether it can be exposed at all. Some listeners answer only at their own address and nowhere else; those are listed for honesty and refused for forwarding."},"kind":{"type":"string","enum":["workspace","system"],"description":"Whether somebody's own work put it there, or the sandbox's own machinery did. Only the first kind is worth previewing."},"title":{"type":"string","description":"What a person would call it. Always present: a listener nothing can explain is still named, because the button beside it publishes the port to the internet."},"purpose":{"type":"string","description":"One sentence about what it is for, including when the honest answer is that nothing could work it out."},"origin":{"type":"string","enum":["terminal","agent","panel","extension","container","sandbox","unknown"],"description":"Who put it there, which is the question somebody is really asking: mine, my agent's, or the box's own."},"pid":{"description":"The process holding it. Absent when nothing could be matched to the socket.","type":"number"},"command":{"description":"The command behind it, as it was run. Absent only when nothing could be attributed at all.","type":"string"},"cwd":{"description":"Where it is running from, which is how a port gets attributed to a repository.","type":"string"},"session":{"description":"The terminal it came from, to watch it in or stop it from. Absent when nothing in its ancestry is one, which is the honest \"you cannot reach this from here\".","type":"string"},"forwarded":{"type":"boolean","description":"Whether it is currently reachable from outside."},"previewUrl":{"description":"Where to open it. Present only while forwarded, and only on a sandbox that has an outside address.","type":"string"}},"required":["port","host","forwardable","kind","title","purpose","origin","forwarded"],"additionalProperties":false},"description":"Everything listening inside the sandbox right now, read fresh each time rather than from a register the sandbox keeps."}},"required":["ports"],"additionalProperties":false}}}}}}},"/ports/forward":{"post":{"operationId":"ports.forward","summary":"Make a port reachable","description":"Gives one port an address on the outside. Asking twice is harmless: the second call hands back the address the first one made.","tags":["Ports"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":"integer","minimum":1,"maximum":65535,"description":"Which port."}},"required":["port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"previewUrl":{"description":"Where it can now be reached. Absent on a sandbox with no outside address, where the mapping exists but has no public name.","type":"string"}},"additionalProperties":false}}}}}}},"/ports/unforward":{"post":{"operationId":"ports.unforward","summary":"Stop exposing a port","description":"Frees the slot at once. The address keeps resolving; it simply stops leading anywhere.","tags":["Ports"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port":{"type":"integer","minimum":1,"maximum":65535,"description":"Which port."}},"required":["port"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/personas":{"get":{"operationId":"personas.list","summary":"The characters an agent can wear","description":"Each persona with the connected accounts it speaks for, what a conversation wearing it is allowed to do, and where it works.","tags":["Personas"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"personas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The persona's id."},"label":{"description":"What to call it on screen. Absent falls back to the id, which somebody chose anyway.","type":"string","maxLength":60},"capabilities":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"description":"Which connected accounts are its hands. Named individually rather than by site, because two accounts on one site is the whole problem this solves. Naming one that is not connected yet is not an error: it is a card describing an account this sandbox has still to sign into."},"repos":{"description":"Which repositories prefer this persona, so a conversation opened on one starts with the right choice already made. A preference rather than a fence.","maxItems":50,"type":"array","items":{"type":"string","minLength":1}},"powers":{"description":"What a conversation wearing it may do. Absent means the full toolbox, so a card written before this existed behaves exactly as it did.","type":"object","properties":{"files":{"default":"write","description":"What it may do with files: nothing, look and search, or also create and change.","type":"string","enum":["none","read","write"]},"shell":{"default":true,"description":"Whether it may run commands, and with them the terminals, the test runs and every tool on the image. The switch the strength of the others depends on.","type":"boolean"},"code":{"default":true,"description":"Whether it may write and run a script rather than a command line. Its fence is real where the shell's is not: reads and writes follow the files answer, and it can start no other program unless commands are allowed too. The one stated gap is that the fence cannot cut the network.","type":"boolean"},"web":{"default":true,"description":"Whether it may fetch a page or run a search.","type":"boolean"},"browser":{"default":true,"type":"boolean"},"delegate":{"default":true,"type":"boolean"},"sandbox":{"default":true,"type":"boolean"},"connectors":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"computers":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"mcp":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}}},"required":["files","shell","code","web","browser","delegate","sandbox"],"additionalProperties":false},"workspace":{"description":"Where it works. Absent means the whole workspace.","type":"object","properties":{"startIn":{"description":"Which folder a conversation opens in.","type":"string","maxLength":200},"folders":{"description":"Which folders it may touch at all. Absent means the whole workspace.","maxItems":50,"type":"array","items":{"type":"string","minLength":1}}},"additionalProperties":false},"systemPromptMode":{"type":"string","enum":["intentic","claude","custom"]}},"required":["id","capabilities"],"additionalProperties":false},"description":"The characters an agent can wear."},"connected":{"type":"array","items":{"type":"string"},"description":"Which accounts are actually connected right now, so a persona naming one that has since been disconnected can be shown as broken rather than as working."}},"required":["personas","connected"],"additionalProperties":false}}}}}},"post":{"operationId":"personas.save","summary":"Create or edit a persona","description":"Writes the whole card; sending an id that exists edits it. Nothing is connected, installed or spent by saving one, because a persona only records a decision about accounts that already exist. It is stored as a file you can equally well edit by hand, which is why this writes the card whole rather than patching a field: a round trip through a screen should leave a change a reviewer recognises.","tags":["Personas"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The persona's id."},"label":{"description":"What to call it on screen. Absent falls back to the id, which somebody chose anyway.","type":"string","maxLength":60},"capabilities":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"description":"Which connected accounts are its hands. Named individually rather than by site, because two accounts on one site is the whole problem this solves. Naming one that is not connected yet is not an error: it is a card describing an account this sandbox has still to sign into."},"repos":{"description":"Which repositories prefer this persona, so a conversation opened on one starts with the right choice already made. A preference rather than a fence.","maxItems":50,"type":"array","items":{"type":"string","minLength":1}},"powers":{"description":"What a conversation wearing it may do. Absent means the full toolbox, so a card written before this existed behaves exactly as it did.","type":"object","properties":{"files":{"default":"write","description":"What it may do with files: nothing, look and search, or also create and change.","type":"string","enum":["none","read","write"]},"shell":{"default":true,"description":"Whether it may run commands, and with them the terminals, the test runs and every tool on the image. The switch the strength of the others depends on.","type":"boolean"},"code":{"default":true,"description":"Whether it may write and run a script rather than a command line. Its fence is real where the shell's is not: reads and writes follow the files answer, and it can start no other program unless commands are allowed too. The one stated gap is that the fence cannot cut the network.","type":"boolean"},"web":{"default":true,"description":"Whether it may fetch a page or run a search.","type":"boolean"},"browser":{"default":true,"type":"boolean"},"delegate":{"default":true,"type":"boolean"},"sandbox":{"default":true,"type":"boolean"},"connectors":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"computers":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"mcp":{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}}}},"workspace":{"description":"Where it works. Absent means the whole workspace.","type":"object","properties":{"startIn":{"description":"Which folder a conversation opens in.","type":"string","maxLength":200},"folders":{"description":"Which folders it may touch at all. Absent means the whole workspace.","maxItems":50,"type":"array","items":{"type":"string","minLength":1}}}},"systemPromptMode":{"type":"string","enum":["intentic","claude","custom"]}},"required":["id","capabilities"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/personas/{id}":{"delete":{"operationId":"personas.remove","summary":"Delete a persona","description":"Takes away the character, never the accounts: every login it named stays connected. Its own prompt and skills go with it, since a folder nothing can reach is worse than deleting what somebody just asked to delete. Anything still pointed at it goes quiet rather than falling back to speaking as everyone.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/personas/{id}/kit":{"get":{"operationId":"personas.kit","summary":"What one persona carries","description":"The instructions this persona is given and the skills only its conversations can reach. A different question from what the agent knows generally, with a different answer.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","description":"What this persona is told, on top of everything else. Empty means it simply follows the sandbox's own instructions."},"skills":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The skill's name."},"description":{"type":"string","description":"What it is for."}},"required":["name","description"],"additionalProperties":false},"description":"Skills only this persona's conversations can reach. A different question from what the agent knows generally, with a different answer."}},"required":["prompt","skills"],"additionalProperties":false}}}}}}},"/personas/{id}/prompt":{"post":{"operationId":"personas.savePrompt","summary":"Write a persona's instructions","description":"Sets what this persona is told. Saving an empty one removes it entirely rather than storing a blank, so the persona simply falls back to the sandbox's own instructions.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","maxLength":20000,"description":"What to tell this persona. Sending an empty one removes it entirely rather than storing a blank, so the persona falls back to the sandbox's own instructions."}},"required":["prompt"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/personas/{id}/skills/read":{"get":{"operationId":"personas.readSkill","summary":"Read one of a persona's skills","description":"The full text of a single skill belonging to this persona.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}},{"name":"name","in":"query","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which skill."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The skill's name."},"description":{"type":"string","description":"What it is for."},"body":{"type":"string","description":"The skill itself, in full."}},"required":["name","description","body"],"additionalProperties":false}}}}}}},"/personas/{id}/skills":{"post":{"operationId":"personas.saveSkill","summary":"Write one of a persona's skills","description":"Creates or replaces a skill by name. There is nothing to switch on: a persona's skill is available exactly when that persona is worn, which is what belonging to it has to mean.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"What to call it. Saving over an existing name rewrites it, which is also how one is renamed."},"description":{"type":"string","minLength":1,"maxLength":1024,"description":"What it is for, which is what the agent reads to decide whether to reach for it."},"body":{"type":"string","minLength":1,"description":"The skill itself."}},"required":["name","description","body"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/personas/{id}/skills/remove":{"post":{"operationId":"personas.removeSkill","summary":"Delete one of a persona's skills","description":"Removes a single skill from this persona and leaves the rest of its kit alone.","tags":["Personas"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which persona."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which skill."}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/skills":{"get":{"operationId":"skills.list","summary":"What the agent knows how to do","description":"Every skill available here and whether it is switched on, joined from all four places they come from: the owner's own, the settings, plugins a connection installed, and folders inside extensions.","tags":["Skills"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Its handle, which reading and deleting take. A skill of your own is simply its name; one belonging to something else is qualified, because two packages may each ship a review."},"name":{"type":"string","description":"Its name."},"description":{"type":"string","description":"What it is for, which is the line the agent reads to decide whether to reach for it. Empty when the skill declares none, which is worth showing as the blank it is: a skill with no description is rarely picked."},"origin":{"type":"string","enum":["builtin","own","capability","extension","plugin","persona","dropped"],"description":"Where it came from."},"owner":{"description":"Who ships it, as the row would name them.","type":"string"},"enabled":{"type":"boolean","description":"Whether the agent can reach it."},"switchable":{"type":"boolean","description":"Whether this surface can switch it. Everything else is on because its extension or its plugin is, and a switch here that silently did nothing would be worse than none, so the row names its owner instead."},"editable":{"type":"boolean","description":"Whether it can be rewritten here. Your own only: editing somebody else's in place would be undone the next time the thing that ships it catches up."},"removable":{"type":"boolean"}},"required":["id","name","description","origin","enabled","switchable","editable","removable"],"additionalProperties":false}}}}}}},"post":{"operationId":"skills.save","summary":"Write a skill","description":"Creates or rewrites a skill by name, and switches it on, because you wrote it in order to use it. Renaming is saving under the new name and deleting the old.","tags":["Skills"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"What to call it. Saving over an existing name rewrites it, which is also how one is renamed."},"description":{"type":"string","minLength":1,"maxLength":1024,"description":"What it is for, which is what the agent reads to decide whether to reach for it."},"body":{"type":"string","minLength":1,"description":"The skill itself."}},"required":["name","description","body"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/skills/read":{"get":{"operationId":"skills.read","summary":"Read one skill","description":"The full text of a single skill. The name travels in the query rather than the address, because a name can carry the owner it came from and that will not fit in a path.","tags":["Skills"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which skill. It travels in the query rather than the address, because an id can name the owner it came from and that will not fit in a path."},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The skill's id, which can carry the owner it came from."},"name":{"type":"string","description":"Its name."},"body":{"type":"string","description":"The instructions themselves, as written."}},"required":["id","name","body"],"additionalProperties":false}}}}}}},"/skills/remove":{"post":{"operationId":"skills.remove","summary":"Delete a skill","description":"Removes the text and takes it off the enabled list in one step, so a screen never has to sequence two calls and never leaves one half done.","tags":["Skills"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Which skill to delete. The text and the enabled list are both updated, so nothing is left half done."}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions":{"get":{"operationId":"extensions.list","summary":"Installed extensions","description":"Every extension installed here, resolved to the manifest the owner approved, which is what the app boots its extension host from. The code itself is served separately, because raw script bytes are not a JSON answer.","tags":["Extensions"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"extensions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":121,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$","description":"The extension's id."},"manifest":{"type":"object","properties":{"$schema":{"description":"The authoring schema, for editor completion and validation. Nothing at runtime reads it.","type":"string"},"publisher":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"version":{"type":"string","minLength":1,"description":"Your own semver, display and identity only. The installed code's identity is the pinned commit sha."},"category":{"description":"Which section of the Extensions tab this sits under: a grouping by what it is FOR, which cannot be derived from what it contributes. A section this app has never heard of lands in 'Other' rather than failing to install.","type":"string","minLength":1},"art":{"description":"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.","type":"string","maxLength":4096},"logo":{"description":"A simple-icons slug, fetched from a CDN: right for standing in for somebody else's product. Add a \"/<hex>\" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.","type":"string"},"icon":{"description":"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.","type":"string"},"engines":{"type":"object","properties":{"intentic":{"type":"string","minLength":1}},"required":["intentic"],"additionalProperties":false,"description":"A semver range over the host's extension API version, checked before your code is activated."},"entry":{"description":"Repo-relative path of your prebuilt single-file ESM bundle, built with `vue` and `@intentic/extension-api` as externals. Absent ⇒ an extension with no UI.","type":"string","minLength":1},"server":{"description":"Repo-relative path of your prebuilt single-file node ESM server bundle, exporting `activateServer`. Served under your own route namespace, which the daemon proxies. Nothing is provided at runtime but node builtins, so bundle everything else in. Absent ⇒ no backend.","type":"string","minLength":1},"permissions":{"description":"How far this extension may reach into the daemon, as \"<METHOD> <path-glob>\" entries where `*` matches one path segment: e.g. \"GET /panels\", \"POST /panels/*/start\". The install dialog shows these, the host refuses anything undeclared, and the usage ledger records which were actually earned.","type":"object","properties":{"sandbox":{"description":"Daemon routes your UI half may call. Your own backend namespace needs no entry: its backend is your own code.","type":"array","items":{"type":"string"}},"daemon":{"description":"Daemon routes your SERVER half may call. Separate from `sandbox` because the two halves run as different principals: the UI as the owner's session, the backend as a minted per-extension token, so a grant to one must never quietly widen the other.","type":"array","items":{"type":"string"}}},"additionalProperties":false},"contributes":{"type":"object","properties":{"views":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"label":{"type":"string","minLength":1,"description":"The name shown on the tile or tab. The manifest's value wins over the one passed at registration."},"surface":{"type":"string","enum":["rail","directory","sandbox"],"description":"Where it appears. `rail` is a tile in the global left rail; `directory` is a panel opened from a repo in the Workspace tree; `sandbox` is a tab on the Sandbox hub, for a view whose subject is the box rather than the work."},"badge":{"description":"Allow this view to put a count on its tile. Declared because a badge interrupts from every other screen in the app; leave it out and any badge the extension registers is dropped.","type":"boolean"}},"required":["id","label","surface"],"additionalProperties":false},"description":"Sidebar elements this extension may register at runtime. Each entry reserves an id and a surface; the extension supplies the component with api.views.register, and the host refuses any registration this list does not cover."},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"Workspace-root-relative, forward-slash, matched by prefix, so one entry covers an exact file (`.intentic/config/automations.json`), a directory (`.intentic/config/drafts/`, with the trailing slash so it cannot match a sibling file) or a name family (`.intentic/environment.`). Not a glob."},"invalidates":{"minItems":1,"type":"array","items":{"type":"string","minLength":1},"description":"The query keys this path makes stale, the first element of your own api.sandbox.key(...) keys. Keep both this and the path as narrow as the view actually needs: a broad prefix costs every connected browser a refetch on every matching write."}},"required":["path","invalidates"],"additionalProperties":false},"description":"Which workspace files back your views, so the daemon's file watcher can tell the browser they went stale instead of you polling for it. The agent edits the workspace out of band from every HTTP route, and this push is the only thing that can notice."},"viewers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"extensions":{"minItems":1,"type":"array","items":{"type":"string","pattern":"^[a-z0-9]+$"},"description":"Bare file extensions, no dot: e.g. [\"docx\", \"xlsx\"]."},"fetch":{"type":"string","enum":["text","blob","url"],"description":"How much of the file the host hands you. `text` for a format that is text (svg, a subtitle track). `blob` for one that must be parsed end to end before any of it shows (a .docx, a spreadsheet), bounded by the daemon's raw-read cap. `url` for anything range-read rather than parsed (audio, video): your component gets a streaming URL to point an element at, never the bytes."}},"required":["id","extensions","fetch"],"additionalProperties":false},"description":"File formats this extension can render. The host resolves an opened file to your viewer by its extension, fetches the content, and renders your component with it: you keep none of the fetch lifecycle and none of the daemon credentials."},"documents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"label":{"type":"string","minLength":1,"description":"The family's name, shown in the install dialog beside your other contributions. Per-row wording stays with the provider, which is the only thing that knows what it found."}},"required":["id","label"],"additionalProperties":false},"description":"Per-directory documents this extension can offer. Your provider marks the rows in the Workspace tree it has something to say about, and the host opens your component as a tab."},"commands":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+$"},"title":{"type":"string","minLength":1,"description":"What the command palette shows. The manifest's value wins over the one passed at registration."},"icon":{"description":"A name from the host's icon set, drawn beside the title.","type":"string"},"keybinding":{"description":"A global keyboard shortcut, e.g. \"Mod+Shift+K\" — `Mod` is ⌘ on Apple and Ctrl elsewhere. Declared here because a global shortcut is consequential: the owner approves it at install, and the host binds only what was approved.","type":"string","pattern":"^\\S+$"},"when":{"description":"When the shortcut applies, as a condition over the shell's context keys, `tabSurface == 'chat'`, `!editableTarget`. Without one the chord is claimed everywhere, including inside a terminal where a bare key belongs to the program running in it. The command palette ignores this: a command is always runnable by name.","type":"string"}},"required":["command","title"],"additionalProperties":false},"description":"Commands this extension may register handlers for, surfaced in the command palette. Title, icon and shortcut all come from here rather than from the registration call, because this is what the owner approved at install."},"settings":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z0-9][a-zA-Z0-9-]*$"},"type":{"type":"string","enum":["boolean","string","number","enum"],"description":"Which control the Settings page draws. `enum` reads its choices from `enum`."},"title":{"type":"string","minLength":1},"description":{"description":"The line under the control.","type":"string"},"default":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"enum":{"description":"The choices, for type \"enum\". Meaningless otherwise.","type":"array","items":{"type":"string"}},"secret":{"description":"Mask the value in the UI and strip it from reads: a set secret round-trips as 'still set', never as its value.","type":"boolean"},"env":{"description":"Inject the stored value into the agent's shell environment under this name, every turn. How a credential you hold reaches the agent's command-line tools.","type":"string","pattern":"^[A-Z][A-Z0-9_]*$"}},"required":["key","type","title"],"additionalProperties":false},"description":"Typed settings the host renders into the Settings page for you and persists daemon-side. You never draw the form or store the value; you read it back with api.settings.get."},"processes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"command":{"type":"string","minLength":1},"cwd":{"description":"Relative to the extension checkout. Absent ⇒ the checkout root.","type":"string"},"port":{"description":"Assign a free port and inject it as PORT.","type":"string","const":"auto"},"preview":{"description":"Expose the port on a tunnelled preview hostname.","type":"boolean"},"autoStart":{"description":"Launch it on install and on daemon boot, rather than waiting to be started.","type":"boolean"}},"required":["name","command"],"additionalProperties":false},"description":"Long-lived background processes the daemon runs for this extension: a gateway holding a connection the daemon must not, a dev server. Managed the same way panel dev servers are, and startable and stoppable from the Extensions tab."},"agent":{"type":"object","properties":{"path":{"description":"Relative to the extension checkout. Absent ⇒ the checkout root.","type":"string"}},"additionalProperties":false,"description":"Declare that this checkout is also a Claude Code plugin, so the agent picks up its skills, agents, hooks, commands and MCP servers each turn. The daemon hands the directory to the plugin loader and never parses what is in it."},"environment":{"type":"object","properties":{"fragment":{"type":"string","minLength":1,"description":"Checkout-relative path to a file holding ONLY RUN and ENV instructions. FROM and privileged directives are rejected: those stay daemon-owned."}},"required":["fragment"],"additionalProperties":false,"description":"A Dockerfile fragment baked into the sandbox image so your tools are actually installed at runtime: a whisper binary, a psql client. The owner approves the composed overlay and rebuilds out of band, so this does not take effect immediately."},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"catalog":{"type":"object","properties":{"name":{"type":"string","minLength":1},"art":{"description":"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.","type":"string","maxLength":4096},"logo":{"description":"A simple-icons slug, fetched from a CDN: right for standing in for somebody else's product. Add a \"/<hex>\" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.","type":"string"},"icon":{"description":"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.","type":"string"},"description":{"type":"string","minLength":1,"description":"ONE LINE: aim for 60 characters or fewer. The grid clamps it at two lines in a narrow pane, so a paragraph here is a paragraph the reader gets truncated. Everything longer belongs in `hint`."},"category":{"type":"string","minLength":1},"hint":{"description":"The paragraph, shown under the add form and searched from the catalog, so the words that identify this card to someone hunting for it (\"webauthn\", \"socket mode\") belong here even when the tile cannot show them.","type":"string"},"guide":{"description":"The walkthrough the install dialog renders for getting the credential this card asks for.","type":"object","properties":{"url":{"type":"string"},"urlFromField":{"type":"string"},"path":{"type":"string"},"linkLabel":{"type":"string"},"scopes":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"required":["name","description","category"],"additionalProperties":false},"fields":{"minItems":1,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9]*$"},"label":{"type":"string","minLength":1},"placeholder":{"type":"string"},"secret":{"description":"Mask it, and never echo it back.","type":"boolean"},"optional":{"type":"boolean"},"multiline":{"type":"boolean"},"boolean":{"description":"Render it as a switch, carrying \"on\"/\"off\". For an opt-in EXTRA rather than a decision: a two-option picker says the same thing but presents a choice the user must make to proceed, sized like the required fields around it. A switch always holds a value, so a field like this never blocks a submit.","type":"boolean"},"hint":{"description":"A line under this control, for what the label alone cannot say: a host requirement, when a value takes effect. The card's own `hint` speaks for the whole card; this one is bound to the field it qualifies.","type":"string"},"rebuild":{"description":"This value only takes effect after the sandbox is rebuilt, because it rides the image overlay. Shown as a chip beside the label: two switches side by side, identical in every visible way, can otherwise cost five seconds or five minutes with no way to tell which.","type":"boolean"},"default":{"type":"string"},"options":{"description":"Turns the field into a select.","type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false}},"when":{"description":"Only show this field while a condition over the answers already given holds: `auth == 'key'`, `provider in ['ipsec', 'fortinet']`, `!advanced`. Supports `&&`, `||`, `!`, comparisons and `in`.","type":"string"},"value":{"description":"A fixed value baked into the config rather than asked for: how a card pins its discriminator (platform=\"reddit\", provider=\"stripe\"). Renders as nothing.","type":"string"},"totp":{"description":"This field holds a TOTP seed, the base32 key or otpauth:// URI a service shows when enrolling an authenticator app. Declare it with `secret: true`. Unlike an ordinary secret it never enters the agent's environment: the daemon mints the six-digit codes on demand and only those cross.","type":"boolean"}},"required":["key","label"],"additionalProperties":false}},"kind":{"type":"string","const":"cli"},"env":{"type":"object","propertyNames":{"type":"string","pattern":"^[A-Z][A-Z0-9_]*$"},"additionalProperties":{"type":"string"},"description":"The environment the agent's shell gets, as value templates over the fields: `${field}` substitutes, `${field:uri}` percent-encodes. Each name is suffixed per instance."},"skill":{"type":"string","minLength":1,"description":"Checkout-relative SKILL.md teaching the agent this tool. `${id}` in it is replaced with the instance name at apply time."},"fragment":{"description":"A Dockerfile fragment holding the client binary this tool needs (psql, mysql, whisper).","type":"string","minLength":1},"pack":{"description":"A sandbox feature pack name (whisper, llamacpp, browser, …) supplying this tool. Preferred over `fragment`: an image that already bakes the pack needs no rebuild, and there is no copy to drift.","type":"string","minLength":1}},"required":["id","catalog","fields","kind","env","skill"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"catalog":{"type":"object","properties":{"name":{"type":"string","minLength":1},"art":{"description":"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.","type":"string","maxLength":4096},"logo":{"description":"A simple-icons slug, fetched from a CDN: right for standing in for somebody else's product. Add a \"/<hex>\" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.","type":"string"},"icon":{"description":"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.","type":"string"},"description":{"type":"string","minLength":1,"description":"ONE LINE: aim for 60 characters or fewer. The grid clamps it at two lines in a narrow pane, so a paragraph here is a paragraph the reader gets truncated. Everything longer belongs in `hint`."},"category":{"type":"string","minLength":1},"hint":{"description":"The paragraph, shown under the add form and searched from the catalog, so the words that identify this card to someone hunting for it (\"webauthn\", \"socket mode\") belong here even when the tile cannot show them.","type":"string"},"guide":{"description":"The walkthrough the install dialog renders for getting the credential this card asks for.","type":"object","properties":{"url":{"type":"string"},"urlFromField":{"type":"string"},"path":{"type":"string"},"linkLabel":{"type":"string"},"scopes":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"required":["name","description","category"],"additionalProperties":false},"fields":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9]*$"},"label":{"type":"string","minLength":1},"placeholder":{"type":"string"},"secret":{"description":"Mask it, and never echo it back.","type":"boolean"},"optional":{"type":"boolean"},"multiline":{"type":"boolean"},"boolean":{"description":"Render it as a switch, carrying \"on\"/\"off\". For an opt-in EXTRA rather than a decision: a two-option picker says the same thing but presents a choice the user must make to proceed, sized like the required fields around it. A switch always holds a value, so a field like this never blocks a submit.","type":"boolean"},"hint":{"description":"A line under this control, for what the label alone cannot say: a host requirement, when a value takes effect. The card's own `hint` speaks for the whole card; this one is bound to the field it qualifies.","type":"string"},"rebuild":{"description":"This value only takes effect after the sandbox is rebuilt, because it rides the image overlay. Shown as a chip beside the label: two switches side by side, identical in every visible way, can otherwise cost five seconds or five minutes with no way to tell which.","type":"boolean"},"default":{"type":"string"},"options":{"description":"Turns the field into a select.","type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false}},"when":{"description":"Only show this field while a condition over the answers already given holds: `auth == 'key'`, `provider in ['ipsec', 'fortinet']`, `!advanced`. Supports `&&`, `||`, `!`, comparisons and `in`.","type":"string"},"value":{"description":"A fixed value baked into the config rather than asked for: how a card pins its discriminator (platform=\"reddit\", provider=\"stripe\"). Renders as nothing.","type":"string"},"totp":{"description":"This field holds a TOTP seed, the base32 key or otpauth:// URI a service shows when enrolling an authenticator app. Declare it with `secret: true`. Unlike an ordinary secret it never enters the agent's environment: the daemon mints the six-digit codes on demand and only those cross.","type":"boolean"}},"required":["key","label"],"additionalProperties":false}},"kind":{"type":"string","const":"browser"},"loginUrl":{"description":"What the sign-in window opens; the profile it persists IS the credential. Optional so one card can be the generic one that asks for the URL on its form instead, but a card must either pin this or declare a field that supplies it, or the window opens on nothing.","type":"string","format":"uri"},"homeUrl":{"description":"Where that same profile opens once it HAS a session: the owner's own hands on the connected browser. Separate from loginUrl because for some platforms the login lives on another site entirely (YouTube signs in at accounts.google.com).","type":"string","format":"uri"},"skill":{"type":"string","minLength":1,"description":"Checkout-relative SKILL.md teaching the agent this site's actions: rendered once per site, all its connected accounts on one roster (`${accounts}`), the core tool note at `${tools}`."}},"required":["id","catalog","fields","kind","skill"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"catalog":{"type":"object","properties":{"name":{"type":"string","minLength":1},"art":{"description":"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.","type":"string","maxLength":4096},"logo":{"description":"A simple-icons slug, fetched from a CDN: right for standing in for somebody else's product. Add a \"/<hex>\" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.","type":"string"},"icon":{"description":"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.","type":"string"},"description":{"type":"string","minLength":1,"description":"ONE LINE: aim for 60 characters or fewer. The grid clamps it at two lines in a narrow pane, so a paragraph here is a paragraph the reader gets truncated. Everything longer belongs in `hint`."},"category":{"type":"string","minLength":1},"hint":{"description":"The paragraph, shown under the add form and searched from the catalog, so the words that identify this card to someone hunting for it (\"webauthn\", \"socket mode\") belong here even when the tile cannot show them.","type":"string"},"guide":{"description":"The walkthrough the install dialog renders for getting the credential this card asks for.","type":"object","properties":{"url":{"type":"string"},"urlFromField":{"type":"string"},"path":{"type":"string"},"linkLabel":{"type":"string"},"scopes":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"required":["name","description","category"],"additionalProperties":false},"fields":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9]*$"},"label":{"type":"string","minLength":1},"placeholder":{"type":"string"},"secret":{"description":"Mask it, and never echo it back.","type":"boolean"},"optional":{"type":"boolean"},"multiline":{"type":"boolean"},"boolean":{"description":"Render it as a switch, carrying \"on\"/\"off\". For an opt-in EXTRA rather than a decision: a two-option picker says the same thing but presents a choice the user must make to proceed, sized like the required fields around it. A switch always holds a value, so a field like this never blocks a submit.","type":"boolean"},"hint":{"description":"A line under this control, for what the label alone cannot say: a host requirement, when a value takes effect. The card's own `hint` speaks for the whole card; this one is bound to the field it qualifies.","type":"string"},"rebuild":{"description":"This value only takes effect after the sandbox is rebuilt, because it rides the image overlay. Shown as a chip beside the label: two switches side by side, identical in every visible way, can otherwise cost five seconds or five minutes with no way to tell which.","type":"boolean"},"default":{"type":"string"},"options":{"description":"Turns the field into a select.","type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false}},"when":{"description":"Only show this field while a condition over the answers already given holds: `auth == 'key'`, `provider in ['ipsec', 'fortinet']`, `!advanced`. Supports `&&`, `||`, `!`, comparisons and `in`.","type":"string"},"value":{"description":"A fixed value baked into the config rather than asked for: how a card pins its discriminator (platform=\"reddit\", provider=\"stripe\"). Renders as nothing.","type":"string"},"totp":{"description":"This field holds a TOTP seed, the base32 key or otpauth:// URI a service shows when enrolling an authenticator app. Declare it with `secret: true`. Unlike an ordinary secret it never enters the agent's environment: the daemon mints the six-digit codes on demand and only those cross.","type":"boolean"}},"required":["key","label"],"additionalProperties":false}},"kind":{"type":"string","const":"host"},"skill":{"type":"string","minLength":1,"description":"Checkout-relative SKILL.md teaching the agent that machine's shell."}},"required":["id","catalog","fields","kind","skill"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"catalog":{"type":"object","properties":{"name":{"type":"string","minLength":1},"art":{"description":"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.","type":"string","maxLength":4096},"logo":{"description":"A simple-icons slug, fetched from a CDN: right for standing in for somebody else's product. Add a \"/<hex>\" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.","type":"string"},"icon":{"description":"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.","type":"string"},"description":{"type":"string","minLength":1,"description":"ONE LINE: aim for 60 characters or fewer. The grid clamps it at two lines in a narrow pane, so a paragraph here is a paragraph the reader gets truncated. Everything longer belongs in `hint`."},"category":{"type":"string","minLength":1},"hint":{"description":"The paragraph, shown under the add form and searched from the catalog, so the words that identify this card to someone hunting for it (\"webauthn\", \"socket mode\") belong here even when the tile cannot show them.","type":"string"},"guide":{"description":"The walkthrough the install dialog renders for getting the credential this card asks for.","type":"object","properties":{"url":{"type":"string"},"urlFromField":{"type":"string"},"path":{"type":"string"},"linkLabel":{"type":"string"},"scopes":{"type":"string"},"steps":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"required":["name","description","category"],"additionalProperties":false},"fields":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9]*$"},"label":{"type":"string","minLength":1},"placeholder":{"type":"string"},"secret":{"description":"Mask it, and never echo it back.","type":"boolean"},"optional":{"type":"boolean"},"multiline":{"type":"boolean"},"boolean":{"description":"Render it as a switch, carrying \"on\"/\"off\". For an opt-in EXTRA rather than a decision: a two-option picker says the same thing but presents a choice the user must make to proceed, sized like the required fields around it. A switch always holds a value, so a field like this never blocks a submit.","type":"boolean"},"hint":{"description":"A line under this control, for what the label alone cannot say: a host requirement, when a value takes effect. The card's own `hint` speaks for the whole card; this one is bound to the field it qualifies.","type":"string"},"rebuild":{"description":"This value only takes effect after the sandbox is rebuilt, because it rides the image overlay. Shown as a chip beside the label: two switches side by side, identical in every visible way, can otherwise cost five seconds or five minutes with no way to tell which.","type":"boolean"},"default":{"type":"string"},"options":{"description":"Turns the field into a select.","type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false}},"when":{"description":"Only show this field while a condition over the answers already given holds: `auth == 'key'`, `provider in ['ipsec', 'fortinet']`, `!advanced`. Supports `&&`, `||`, `!`, comparisons and `in`.","type":"string"},"value":{"description":"A fixed value baked into the config rather than asked for: how a card pins its discriminator (platform=\"reddit\", provider=\"stripe\"). Renders as nothing.","type":"string"},"totp":{"description":"This field holds a TOTP seed, the base32 key or otpauth:// URI a service shows when enrolling an authenticator app. Declare it with `secret: true`. Unlike an ordinary secret it never enters the agent's environment: the daemon mints the six-digit codes on demand and only those cross.","type":"boolean"}},"required":["key","label"],"additionalProperties":false}},"kind":{"type":"string","const":"agent"}},"required":["id","catalog","fields","kind"],"additionalProperties":false}]},"description":"Capability cards this pack adds to the \"+\" grid: a connected CLI tool, a site the agent acts on as the owner through the shared browser, an operating system pack, or a preset over a core kind. The card and its form are data here; the machinery that acts on them is core, which is why a card may only name one of these four kinds."},"listener":{"type":"object","properties":{"provider":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"The slug this source's automation triggers fire on."},"events":{"minItems":1,"type":"array","items":{"type":"object","properties":{"type":{"type":"string","pattern":"^[a-z0-9][a-z0-9_]*$"},"label":{"type":"string","minLength":1}},"required":["type","label"],"additionalProperties":false},"description":"The event types this source can fire, with the wording the automation editor offers them under. The daemon accepts no others."},"automation":{"type":"object","properties":{"label":{"type":"string","minLength":1},"mentionLabel":{"description":"Only for a source whose message events distinguish being addressed. Absent ⇒ the editor offers no mention-only filter, rather than inventing semantics you did not promise.","type":"string","minLength":1},"channel":{"type":"object","properties":{"label":{"type":"string","minLength":1},"placeholder":{"type":"string","minLength":1},"hint":{"description":"The sentence under the input, for a filter whose empty case is easy to get wrong.","type":"string","minLength":1}},"required":["label","placeholder"],"additionalProperties":false,"description":"The primary narrowing filter, a channel, a room, a repo."},"branchField":{"description":"A second narrowing axis, for a source whose events carry one: a pipeline's git ref, so a trigger can say \"the branch that ships\" rather than \"every agent's every failure\".","type":"object","properties":{"label":{"type":"string","minLength":1},"placeholder":{"type":"string","minLength":1},"hint":{"description":"The sentence under the input, for a filter whose empty case is easy to get wrong.","type":"string","minLength":1}},"required":["label","placeholder"],"additionalProperties":false},"starterPrompt":{"type":"string","minLength":1,"description":"The first prompt a new automation on this source is prefilled with. You own the payload vocabulary, so you own the prompt that explains it."}},"required":["label","channel","starterPrompt"],"additionalProperties":false,"description":"How the generic automation editor presents this source: its name, its filters, and the prompt it starts people on."}},"required":["provider","events","automation"],"additionalProperties":false,"description":"A realtime event source this extension supplies, so automations can trigger on it. One declaration feeds both halves: the daemon accepts these event types and serves this provider's control surface, and the automation editor derives its source picker, filters and starter prompt from it, so a newly installed listener is configurable without a matching app release."},"automationTemplates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Prefills the automation name, and is what \"does one of these exist already\" is asked by, so spell it as an id, not as prose."},"title":{"type":"string","minLength":1},"logo":{"description":"A simple-icons slug for the card.","type":"string","minLength":1},"icon":{"description":"A name from the host's icon set, drawn when no simple-icons slug fits.","type":"string","minLength":1},"requires":{"description":"Capability providers that make this template work: any one connected is enough (fixing CI rides github or gitlab). Omitted ⇒ nothing to connect, so it is always offered.","type":"array","items":{"type":"string","minLength":1}},"trigger":{"type":"object","properties":{"kind":{"type":"string","enum":["schedule","event","listener","workspace"]},"cron":{"type":"string","minLength":1},"provider":{"type":"string","minLength":1},"eventType":{"type":"string","minLength":1},"event":{"type":"string","minLength":1}},"required":["kind"],"additionalProperties":false,"description":"What wakes it. Checked against the real trigger schema when the daemon builds the catalogue, so a template can never offer one that would be refused."},"guard":{"description":"A condition that must hold before the turn runs: what makes a template safe to leave switched on.","type":"string","minLength":1},"holdForSeconds":{"description":"Wait this long and coalesce repeats, rather than firing on every event.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"prompt":{"type":"string","minLength":1,"description":"The turn this starts. You own the trigger's payload vocabulary, so you own the prompt that reads it."},"note":{"type":"string","minLength":1},"setup":{"description":"What the user must do themselves before this can work.","type":"string","minLength":1},"description":{"type":"string","minLength":1},"offer":{"description":"Absent ⇒ it waits in the gallery, where you go once you know what you want. `create` puts a card on the page that makes it, switched off, in one click. `configure` puts one there that opens the dialog prefilled, for a template that cannot work unconfigured. Both are for what a user would never think to go looking for: mark everything as offered and you have rebuilt the gallery with extra steps.","type":"string","enum":["create","configure"]},"chore":{"description":"Whether what this makes watches THIS codebase rather than the outside world. Declared rather than read off the trigger: a nightly dependency sweep and a nightly Stripe poll are both schedules.","type":"boolean"}},"required":["id","title","trigger","prompt"],"additionalProperties":false},"description":"Starting points this pack offers in the automation composer, a trigger, a prompt written for that trigger's payload, and whatever guard makes it safe to leave on. Declared by whoever knows the service rather than by the composer, so they appear when your pack is installed and disappear with it. Pure prefill: creating one makes an ordinary automation."},"bin":{"type":"string","minLength":1,"description":"A checkout-relative directory of executables the daemon puts on the agent's PATH every turn, how you ship the agent a command-line tool. The files are the approved code themselves: they ride the pinned checkout, and the daemon only adds the directory to PATH."}},"additionalProperties":false}},"required":["publisher","name","version","engines"],"additionalProperties":false,"description":"What it declares about itself: what it contributes, what it needs, and what it may reach."},"commit":{"type":"string","description":"Exactly which commit is installed."},"source":{"type":"string","enum":["builtin","installed","workspace"],"description":"Where the code comes from: baked into the sandbox image and not removable, installed from a repository at a pinned commit, or written in this workspace and edited in place."},"enabled":{"type":"boolean","description":"The owner's switch. A switched-off extension is still listed, which is what makes it switchable back on, but nothing it contributes is wired up."},"essential":{"description":"Its switch is fixed on, because it is the only way to see or stop an engine the sandbox runs regardless. Hiding that page would not stop the spending, only your ability to notice it. Declared by the core about its own surfaces, never by an extension about itself, which would be a pack making itself un-removable.","type":"boolean"},"usage":{"description":"How much of the reach it asked for it has actually used, keyed by what it declared. Absent means never observed doing anything, which is a different claim from uses none of them, and the two have to stay tellable apart: reading either as these permissions are unnecessary turns evidence into a guess with a number on it.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"calls":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many times."},"last":{"type":"string","description":"When, most recently."}},"required":["calls","last"],"additionalProperties":false}},"backend":{"description":"Present only for an extension that ships a server half.","type":"object","properties":{"state":{"type":"string","enum":["running","error","absent","incompatible","starting","stopped"],"description":"How its server half is doing. Absent means the code is not in this image at all; incompatible means it needs a different sandbox version."},"detail":{"description":"What went wrong, so a backend that failed to start is a sentence rather than an address that answers nothing.","type":"string"}},"required":["state"],"additionalProperties":false},"update":{"description":"A newer version waiting. All five of these exist only for one installed from a repository: a built-in updates with the image and one written here is edited live.","type":"object","properties":{"ref":{"type":"string","description":"The commit being offered."},"version":{"description":"What it calls itself.","type":"string"},"url":{"type":"string","description":"Where it comes from."},"path":{"description":"Where inside that repository it lives.","type":"string"},"trust":{"type":"string","enum":["verified","listed"],"description":"Whether anybody vouched for it, or it is merely listed."},"securityFix":{"description":"This release fixes a security problem in earlier ones, so here the old version is the dangerous one.","type":"boolean"},"registry":{"type":"string","description":"Which registry said so."},"at":{"type":"string","description":"When it was published."},"needsReview":{"description":"Why this one was not taken automatically and is asking for a person instead: it wants more than it used to, or nobody has vouched for it.","type":"string"},"review":{"description":"An agent has already read the difference between what is installed and this, so the card can link to what it found rather than offer to start looking.","type":"object","properties":{"conversationId":{"type":"string","description":"Where to read what it found."},"at":{"type":"string","description":"When it looked."}},"required":["conversationId","at"],"additionalProperties":false}},"required":["ref","url","trust","registry","at"],"additionalProperties":false},"advisory":{"description":"A security warning about the installed version.","type":"object","properties":{"reason":{"type":"string","description":"Why the registry pulled the listing, in its own words. Delisting protects people browsing; this record is for the person already running it."},"registry":{"type":"string","description":"Which registry said so."},"at":{"type":"string","description":"When."},"autoDisabled":{"type":"boolean","description":"Whether the sandbox has already switched it off."}},"required":["reason","registry","at","autoDisabled"],"additionalProperties":false},"health":{"description":"How it has behaved since the last update, which is what decides whether that update sticks.","type":"object","properties":{"state":{"type":"string","enum":["watching","healthy","unhealthy"],"description":"How it has behaved since the last update. Checks catch broken, not wrong, so for a while after a swap it is simply watched."},"detail":{"description":"What is going wrong, when something is.","type":"string"},"fromRef":{"description":"Which version it was updated from, which is what going back would return to.","type":"string"},"at":{"type":"string","description":"When the watching started."},"autoReverted":{"description":"The update was already rolled back without anybody asking. The record stays rather than pretending the attempt never happened.","type":"boolean"}},"required":["state","at"],"additionalProperties":false},"previous":{"description":"The version kept one step back, which is what going back means.","type":"object","properties":{"ref":{"type":"string","description":"The commit that was running before."},"version":{"description":"What it called itself.","type":"string"}},"required":["ref"],"additionalProperties":false},"updatePolicy":{"description":"The owner's standing answer for this one: tell me, have an agent look, or just do it.","type":"object","properties":{"updates":{"type":"string","enum":["notify","agent","auto"]},"advisories":{"type":"string","enum":["auto-disable","notify"]}},"required":["updates","advisories"],"additionalProperties":false}},"required":["id","manifest","commit","source","enabled"],"additionalProperties":false},"description":"What is installed."},"invalid":{"type":"array","items":{"type":"object","properties":{"dir":{"type":"string","description":"Which folder."},"error":{"type":"string","description":"Why it could not be read."}},"required":["dir","error"],"additionalProperties":false},"description":"Extensions written here that could not be read at all. Listed rather than dropped, because there is no install moment at which to reject a broken one, so this is its only way of saying anything."},"updatesCheckedAt":{"description":"When updates were last looked for. Absent until the first check has run. Sent so a screen can say checked an hour ago rather than presenting staleness as certainty.","type":"string"}},"required":["extensions","invalid"],"additionalProperties":false}}}}}}},"/extensions/workspace":{"post":{"operationId":"extensions.create","summary":"Write a new extension in place","description":"Scaffolds a working extension into this workspace and installs it. The only call here that creates one, and it exists because that folder is otherwise reachable only through an agent's file tools, which is a fine way to change an extension and a poor way to meet the idea of one.","tags":["Extensions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"publisher":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Who it is by, which together with the name makes its id."},"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"What it is called."}},"required":["publisher","name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The id it was given."},"dir":{"type":"string","description":"Where its files are, so you can open them."}},"required":["id","dir"],"additionalProperties":false}}}}}}},"/extensions/{id}/settings":{"get":{"operationId":"extensions.settings","summary":"An extension's settings","description":"The current values for the settings this extension declared it has.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"The values, minus anything marked secret."},"secretsSet":{"type":"array","items":{"type":"string"},"description":"Which of its secret settings actually hold a value. Names only: the values themselves never come back."}},"required":["settings","secretsSet"],"additionalProperties":false}}}}}},"post":{"operationId":"extensions.setSettings","summary":"Change an extension's settings","description":"Writes new values. A key the extension never declared is refused rather than quietly stored, the same honesty rule that governs everything else an extension claims.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"The values to write. A key the extension never declared is refused rather than quietly stored."}},"required":["settings"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions/{id}/enabled":{"post":{"operationId":"extensions.setEnabled","summary":"Turn an extension on or off","description":"The owner's switch. Turning one off stops its background processes at once. What it contributes to an agent's tools is rebuilt at the start of the next turn, and anything it adds to the sandbox image only at the next rebuild.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"On or off."}},"required":["enabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions/{id}/usage":{"post":{"operationId":"extensions.recordUsage","summary":"Record what an extension just used","description":"Written by the app rather than measured by the daemon, because the permission gate runs in the browser: from the sandbox's side an extension's traffic is indistinguishable from anyone else's. This is how the record of which powers an extension actually exercises gets kept.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"used":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"description":"Which of its declared powers it exercised, and how many times."}},"required":["used"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions/{id}/readiness":{"get":{"operationId":"extensions.readiness","summary":"Whether an extension is fit to share","description":"The checks that can be answered from an extension's own files, for an author about to publish. Read on demand rather than carried on the list, because it reads the code off disk each time.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which check."},"label":{"type":"string","description":"What it is called."},"status":{"type":"string","enum":["pass","warn","fail"],"description":"How it went. A warning is a real third answer rather than a soft failure."},"detail":{"type":"string","description":"What it found."}},"required":["id","label","status","detail"],"additionalProperties":false},"description":"Everything that can be checked from the extension's own files, for an author about to publish."}},"required":["checks"],"additionalProperties":false}}}}}}},"/extensions/updates/check":{"post":{"operationId":"extensions.checkUpdates","summary":"Look for extension updates now","description":"Compares every installed extension against its source and reports what is newer, what carries an advisory and what looks unhealthy. This also happens on a schedule; call it to check on demand.","tags":["Extensions"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"The check ran."},"checkedAt":{"type":"string","description":"When, so a screen can date the answer."}},"required":["ok","checkedAt"],"additionalProperties":false}}}}}}},"/extensions/{id}/update/preview":{"post":{"operationId":"extensions.updatePreview","summary":"What an update would change","description":"The read before the click: which versions are involved and exactly which powers the new code asks for that the running one does not. Costs one throwaway copy of the source, the same as browsing a registry entry.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":121,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$","description":"Which extension."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"description":"Which commit, in full. Leave it out for whatever the last check found, which is what most callers mean.","type":"string","pattern":"^[0-9a-f]{40}$"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"type":"string","description":"The commit this would install."},"version":{"type":"string","description":"What that version calls itself."},"installedVersion":{"type":"string","description":"What is running now."},"engines":{"type":"string","description":"Which sandbox versions the new one says it needs."},"compatible":{"type":"boolean","description":"Whether this sandbox is one of them."},"powers":{"type":"object","properties":{"added":{"type":"array","items":{"type":"string"},"description":"What the new version asks for that the running one does not. The whole point of the comparison."},"removed":{"type":"array","items":{"type":"string"},"description":"What it no longer asks for."},"unchanged":{"type":"array","items":{"type":"string"},"description":"What stays the same."}},"required":["added","removed","unchanged"],"additionalProperties":false,"description":"Exactly what the new code asks for that the running one does not. This is what approving an update is approving."}},"required":["ref","version","installedVersion","engines","compatible","powers"],"additionalProperties":false}}}}}}},"/extensions/{id}/update":{"post":{"operationId":"extensions.applyUpdate","summary":"Update an extension","description":"The whole swap as one transaction: fetch, check, quiet the running one, replace it while keeping the outgoing copy one step back, restart and watch it come up. The existing configuration is kept, so a token for a private source survives what removing and re-adding would lose. Owner only, because it changes what code runs.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":121,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$","description":"Which extension."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ref":{"description":"Which commit, in full. Leave it out for whatever the last check found, which is what most callers mean.","type":"string","pattern":"^[0-9a-f]{40}$"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"It went through."},"ref":{"type":"string","description":"Which commit is now running."},"rebuildNeeded":{"description":"The new version changes what the sandbox image contains, so a one-time rebuild is still pending and the update is not wholly landed yet.","type":"boolean"}},"required":["ok","ref"],"additionalProperties":false}}}}}}},"/extensions/{id}/revert":{"post":{"operationId":"extensions.revert","summary":"Go back to the previous version","description":"Swaps the copy kept from before the last update back into place. Owner only, for the same reason updating is.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"It went through."},"ref":{"type":"string","description":"Which commit is now running."},"rebuildNeeded":{"description":"The new version changes what the sandbox image contains, so a one-time rebuild is still pending and the update is not wholly landed yet.","type":"boolean"}},"required":["ok","ref"],"additionalProperties":false}}}}}}},"/extensions/{id}/update-policy":{"post":{"operationId":"extensions.setUpdatePolicy","summary":"How an extension should handle its own updates","description":"The owner's standing answer for one extension: tell me, have an agent look at it, or just do it. Security advisories can be opted out of separately.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":121,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$","description":"Which extension."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"description":"What to do about a newer version: tell you, have an agent read the difference first, or just take it.","type":"string","enum":["notify","agent","auto"]},"advisories":{"description":"What to do about a security warning: switch it off at once, or tell you.","type":"string","enum":["auto-disable","notify"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions/{id}/processes/{name}":{"get":{"operationId":"extensions.processStatus","summary":"Whether an extension's background process is up","description":"The state of one process an extension declared, with the port it was given and its preview address if it has one.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}},{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which of its declared processes."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Which process."},"running":{"type":"boolean","description":"Whether it is up."},"port":{"description":"The port it was given.","type":"number"},"previewUrl":{"description":"Where to open it, when it has an address.","type":"string"}},"required":["name","running"],"additionalProperties":false}}}}}}},"/extensions/{id}/processes/{name}/start":{"post":{"operationId":"extensions.processStart","summary":"Start an extension's background process","description":"Brings one of an extension's declared processes up in an attachable terminal.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}},{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which of its declared processes."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/extensions/{id}/processes/{name}/stop":{"post":{"operationId":"extensions.processStop","summary":"Stop an extension's background process","description":"Shuts one of an extension's declared processes down and frees its port.","tags":["Extensions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which extension."}},{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which of its declared processes."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/settings":{"get":{"operationId":"settings.get","summary":"How this sandbox is configured","description":"Every setting that governs how agents behave here, with the defaults filled in for anything nobody has chosen.","tags":["Settings"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"stableSystemPrompt":{"default":false,"description":"Keep the instructions identical between turns so the provider can cache them, moving anything that varies into the message instead. Cheaper, at the cost of some flexibility.","type":"boolean"},"skills":{"default":["lsp"],"description":"Which skills are switched on.","type":"array","items":{"type":"string"}},"hashlineEdits":{"default":false,"description":"Have the agent edit files by line number rather than by quoting the text it wants replaced. Cheaper on large files, and less forgiving of a stale read.","type":"boolean"},"terseOutput":{"default":false,"description":"Ask the agent to say less. It changes how much it narrates, not how much it does.","type":"boolean"},"terseHoldout":{"default":0,"description":"What share of turns to run without that instruction, so the two can be compared honestly. It has to be measured this way, because a turn cannot be re-run to see what it would have said. Zero means no measurement, which is the default, since the comparison costs the very tokens it is measuring.","type":"number","minimum":0,"maximum":1},"systemPromptMode":{"default":"intentic","description":"Which instructions the agent starts from: intentic's own, the ones the installed Claude Code carries, or your own. The first two both get this product's own guidance added on top; your own gets nothing added, which is the point of it.","type":"string","enum":["intentic","claude","custom"]},"systemPrompt":{"default":"","description":"Your own instructions, used only when the mode above says custom. Then it is the whole of them: both built-in bases go, and so does everything this product would otherwise add, including the guidance the chat's own cards are driven by. That is the price of total control.","type":"string","maxLength":20000},"iqSearch":{"default":false,"description":"Teach the agent how to use this workspace's own search tool, rather than leaving it to grep around.","type":"boolean"},"iqSearchHoldout":{"default":0,"description":"What share of conversations to run without that teaching, so the two can be compared. Whole conversations rather than individual turns, because once the teaching is in a session, withholding it from the next request does not make the model forget it.","type":"number","minimum":0,"maximum":1},"workspaceMap":{"default":false,"description":"Open every conversation with a map of the project it starts in: what is in it, what each part is for, and where the agent is standing. Worked out fresh each time rather than written down anywhere, because a written layout is wrong within a fortnight. Off by default, since it spends tokens on the first message of every conversation.","type":"boolean"},"outputCleaners":{"default":"off","description":"Which command outputs to trim before the agent reads them, cutting the noise a build tool prints without cutting what it said.","type":"string"},"outputHoldout":{"default":0,"description":"What share of commands to leave untrimmed, so the saving can be measured against a real comparison rather than estimated.","type":"number","minimum":0,"maximum":1},"quickModel":{"default":[],"description":"Which models do the small automatic jobs that are not a conversation, such as writing a commit message. A list rather than one pick, tried in order, because the interesting failure is a model that is connected and simply will not answer today. Empty means work it out from whatever is connected, which improves by itself as accounts are added.","maxItems":10,"type":"array","items":{"type":"string"}},"changelogRepos":{"default":[],"description":"Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.","maxItems":50,"type":"array","items":{"type":"string"}},"agentRunModels":{"default":[],"description":"Which models run the work a screen starts rather than a person: fixing a red pipeline, a maintenance chore, an acceptance run. Tried in order, so one spent account does not take every such run down. Empty falls back to whatever the chat would have used, which is the honest floor because it is the model you already chose to work with.","maxItems":10,"type":"array","items":{"type":"string"}},"agentRunEffort":{"default":"","description":"How hard those runs should think.","type":"string"},"autoTier":{"default":"shadow","description":"Whether an easy-looking turn may run on a cheaper model from the same provider. Three states rather than a switch, because the middle one is the only honest road to the third: it scores every turn and routes nothing, so the guess can become a measurement before it changes anything. It can only ever route down, so the worst case is one turn's quality rather than a bill nobody asked for.","type":"string","enum":["off","shadow","on"]},"autoTierEagerness":{"default":"balanced","description":"How readily a turn counts as simple enough for the cheaper model. It moves only the cutoff: at every setting a turn still has to say something positively easy, so nothing here can downgrade a short vague request.","type":"string","enum":["cautious","balanced","eager"]},"autoFastModels":{"default":[],"description":"Which cheaper model a downgraded turn lands on. A list so a sandbox spanning providers can name a rung on each, but not a fallback ladder: an entry naming a different provider than the turn is on is skipped rather than tried, because switching provider retires the conversation and starting over to save a fraction of a penny is not a saving. Empty picks the cheapest the turn's own provider publishes.","maxItems":10,"type":"array","items":{"type":"string"}},"agentRetentionDays":{"default":3,"description":"How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.","type":"number","minimum":0,"maximum":365},"resumeAfterOutage":{"default":false,"description":"Whether a turn killed by the model provider failing is re-run automatically, backing off between attempts. The sandbox-wide default; any one conversation can say otherwise. Off to begin with, because a retry spends your allowance on a turn you sent once and only you can say whether it was worth paying for twice. Worth turning on for a sandbox whose work mostly happens with nobody in the room.","type":"boolean"},"autoResumeOnRestart":{"default":false,"description":"Whether a turn killed by the sandbox restarting is re-run once it comes back. Off to begin with, for the same reason: it would spend your allowance on work you are not watching and edit files while you are still waiting for the sandbox to return. Either way the interruption is recorded rather than silently lost.","type":"boolean"},"rules":{"default":[],"description":"Standing instructions you give the sandbox about its own work: ask for proof before a turn ends, run something before a push, hold or release finished work. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment.","maxItems":50,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"label":{"type":"string","minLength":1,"maxLength":80},"moment":{"type":"string","enum":["turn.ending","push.starting","agent.finished"]},"when":{"type":"object","properties":{"repo":{"type":"string","minLength":1},"paths":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1}},"outcome":{"type":"array","items":{"type":"string","enum":["clean","error","conflict"]}}},"additionalProperties":false},"action":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command"},"command":{"type":"string","maxLength":500},"timeoutMs":{"default":900000,"type":"number","minimum":60000,"maximum":3600000}},"required":["kind","command","timeoutMs"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"instruct"},"text":{"type":"string","minLength":1,"maxLength":4000}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"verdict"},"verdict":{"type":"string","enum":["allow","hold"]}},"required":["kind","verdict"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"builtin"},"name":{"type":"string","enum":["verify-edits"]}},"required":["kind","name"],"additionalProperties":false}]},"enabled":{"default":true,"type":"boolean"}},"required":["id","label","moment","action","enabled"],"additionalProperties":false}},"automationFailureLimit":{"default":0,"description":"How many failures in a row before an automation switches itself off. Zero means never, which is the default, because the failure is not always the automation's fault and a job disabled at three in the morning is one nobody re-enables. Only real errors count: a guard deciding there was nothing to do, or the sandbox dying mid-run, say nothing about the automation.","type":"number","minimum":0,"maximum":20},"admission":{"description":"Whether work started from outside may run, per kind of trigger: let it, hold it for approval, or refuse it. Composes with each automation's own setting, and the stricter of the two wins, so holding every visitor's message needs no edit to each automation.","type":"object","properties":{"schedule":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"event":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"listener":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"webchat":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"workspace":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"workflow":{"default":"allow","type":"string","enum":["allow","deny"]}},"required":["schedule","event","listener","webchat","workspace","workflow"],"additionalProperties":false},"actionRules":{"default":{},"description":"What an agent may do out in the world, per kind of action: go ahead, ask first, or never.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["allow","hold","deny"]}},"commandRules":{"default":{},"description":"What an agent may run inside the sandbox, for the five kinds of command that are hard to take back: rewriting git history, deleting recursively, reading credential files, publishing a package, reaching out to the network. Everything else is recoverable in a container that is itself disposable, and gating it would be friction bought with nothing.","type":"object","propertyNames":{"type":"string","enum":["git.destructive","files.destructive","secrets.access","package.publish","network.outbound"]},"additionalProperties":{"type":"string","enum":["allow","hold","deny"]}},"subagentsAtOnce":{"default":20,"description":"How many helper agents may work at the same time.","type":"number","minimum":1,"maximum":200},"subagentsPerTurn":{"default":200,"description":"How many a single turn may start in total.","type":"number","minimum":1,"maximum":2000},"subagentDepth":{"default":3,"description":"How many levels deep the delegation may go, since a helper can start helpers of its own.","type":"number","minimum":1,"maximum":10}},"required":["stableSystemPrompt","skills","hashlineEdits","terseOutput","terseHoldout","systemPromptMode","systemPrompt","iqSearch","iqSearchHoldout","workspaceMap","outputCleaners","outputHoldout","quickModel","changelogRepos","agentRunModels","agentRunEffort","autoTier","autoTierEagerness","autoFastModels","agentRetentionDays","resumeAfterOutage","autoResumeOnRestart","rules","automationFailureLimit","admission","actionRules","commandRules","subagentsAtOnce","subagentsPerTurn","subagentDepth"],"additionalProperties":false}}}}}},"post":{"operationId":"settings.set","summary":"Change the sandbox settings","description":"Writes the settings whole, so send the complete object rather than the fields you changed.","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stableSystemPrompt":{"default":false,"description":"Keep the instructions identical between turns so the provider can cache them, moving anything that varies into the message instead. Cheaper, at the cost of some flexibility.","type":"boolean"},"skills":{"default":["lsp"],"description":"Which skills are switched on.","type":"array","items":{"type":"string"}},"hashlineEdits":{"default":false,"description":"Have the agent edit files by line number rather than by quoting the text it wants replaced. Cheaper on large files, and less forgiving of a stale read.","type":"boolean"},"terseOutput":{"default":false,"description":"Ask the agent to say less. It changes how much it narrates, not how much it does.","type":"boolean"},"terseHoldout":{"default":0,"description":"What share of turns to run without that instruction, so the two can be compared honestly. It has to be measured this way, because a turn cannot be re-run to see what it would have said. Zero means no measurement, which is the default, since the comparison costs the very tokens it is measuring.","type":"number","minimum":0,"maximum":1},"systemPromptMode":{"default":"intentic","description":"Which instructions the agent starts from: intentic's own, the ones the installed Claude Code carries, or your own. The first two both get this product's own guidance added on top; your own gets nothing added, which is the point of it.","type":"string","enum":["intentic","claude","custom"]},"systemPrompt":{"default":"","description":"Your own instructions, used only when the mode above says custom. Then it is the whole of them: both built-in bases go, and so does everything this product would otherwise add, including the guidance the chat's own cards are driven by. That is the price of total control.","type":"string","maxLength":20000},"iqSearch":{"default":false,"description":"Teach the agent how to use this workspace's own search tool, rather than leaving it to grep around.","type":"boolean"},"iqSearchHoldout":{"default":0,"description":"What share of conversations to run without that teaching, so the two can be compared. Whole conversations rather than individual turns, because once the teaching is in a session, withholding it from the next request does not make the model forget it.","type":"number","minimum":0,"maximum":1},"workspaceMap":{"default":false,"description":"Open every conversation with a map of the project it starts in: what is in it, what each part is for, and where the agent is standing. Worked out fresh each time rather than written down anywhere, because a written layout is wrong within a fortnight. Off by default, since it spends tokens on the first message of every conversation.","type":"boolean"},"outputCleaners":{"default":"off","description":"Which command outputs to trim before the agent reads them, cutting the noise a build tool prints without cutting what it said.","type":"string"},"outputHoldout":{"default":0,"description":"What share of commands to leave untrimmed, so the saving can be measured against a real comparison rather than estimated.","type":"number","minimum":0,"maximum":1},"quickModel":{"default":[],"description":"Which models do the small automatic jobs that are not a conversation, such as writing a commit message. A list rather than one pick, tried in order, because the interesting failure is a model that is connected and simply will not answer today. Empty means work it out from whatever is connected, which improves by itself as accounts are added.","maxItems":10,"type":"array","items":{"type":"string"}},"changelogRepos":{"default":[],"description":"Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.","maxItems":50,"type":"array","items":{"type":"string"}},"agentRunModels":{"default":[],"description":"Which models run the work a screen starts rather than a person: fixing a red pipeline, a maintenance chore, an acceptance run. Tried in order, so one spent account does not take every such run down. Empty falls back to whatever the chat would have used, which is the honest floor because it is the model you already chose to work with.","maxItems":10,"type":"array","items":{"type":"string"}},"agentRunEffort":{"default":"","description":"How hard those runs should think.","type":"string"},"autoTier":{"default":"shadow","description":"Whether an easy-looking turn may run on a cheaper model from the same provider. Three states rather than a switch, because the middle one is the only honest road to the third: it scores every turn and routes nothing, so the guess can become a measurement before it changes anything. It can only ever route down, so the worst case is one turn's quality rather than a bill nobody asked for.","type":"string","enum":["off","shadow","on"]},"autoTierEagerness":{"default":"balanced","description":"How readily a turn counts as simple enough for the cheaper model. It moves only the cutoff: at every setting a turn still has to say something positively easy, so nothing here can downgrade a short vague request.","type":"string","enum":["cautious","balanced","eager"]},"autoFastModels":{"default":[],"description":"Which cheaper model a downgraded turn lands on. A list so a sandbox spanning providers can name a rung on each, but not a fallback ladder: an entry naming a different provider than the turn is on is skipped rather than tried, because switching provider retires the conversation and starting over to save a fraction of a penny is not a saving. Empty picks the cheapest the turn's own provider publishes.","maxItems":10,"type":"array","items":{"type":"string"}},"agentRetentionDays":{"default":3,"description":"How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.","type":"number","minimum":0,"maximum":365},"resumeAfterOutage":{"default":false,"description":"Whether a turn killed by the model provider failing is re-run automatically, backing off between attempts. The sandbox-wide default; any one conversation can say otherwise. Off to begin with, because a retry spends your allowance on a turn you sent once and only you can say whether it was worth paying for twice. Worth turning on for a sandbox whose work mostly happens with nobody in the room.","type":"boolean"},"autoResumeOnRestart":{"default":false,"description":"Whether a turn killed by the sandbox restarting is re-run once it comes back. Off to begin with, for the same reason: it would spend your allowance on work you are not watching and edit files while you are still waiting for the sandbox to return. Either way the interruption is recorded rather than silently lost.","type":"boolean"},"rules":{"default":[],"description":"Standing instructions you give the sandbox about its own work: ask for proof before a turn ends, run something before a push, hold or release finished work. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment.","maxItems":50,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"},"label":{"type":"string","minLength":1,"maxLength":80},"moment":{"type":"string","enum":["turn.ending","push.starting","agent.finished"]},"when":{"type":"object","properties":{"repo":{"type":"string","minLength":1},"paths":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1}},"outcome":{"type":"array","items":{"type":"string","enum":["clean","error","conflict"]}}}},"action":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"command"},"command":{"type":"string","maxLength":500},"timeoutMs":{"default":900000,"type":"number","minimum":60000,"maximum":3600000}},"required":["kind","command"]},{"type":"object","properties":{"kind":{"type":"string","const":"instruct"},"text":{"type":"string","minLength":1,"maxLength":4000}},"required":["kind","text"]},{"type":"object","properties":{"kind":{"type":"string","const":"verdict"},"verdict":{"type":"string","enum":["allow","hold"]}},"required":["kind","verdict"]},{"type":"object","properties":{"kind":{"type":"string","const":"builtin"},"name":{"type":"string","enum":["verify-edits"]}},"required":["kind","name"]}]},"enabled":{"default":true,"type":"boolean"}},"required":["id","label","moment","action"]}},"automationFailureLimit":{"default":0,"description":"How many failures in a row before an automation switches itself off. Zero means never, which is the default, because the failure is not always the automation's fault and a job disabled at three in the morning is one nobody re-enables. Only real errors count: a guard deciding there was nothing to do, or the sandbox dying mid-run, say nothing about the automation.","type":"number","minimum":0,"maximum":20},"admission":{"description":"Whether work started from outside may run, per kind of trigger: let it, hold it for approval, or refuse it. Composes with each automation's own setting, and the stricter of the two wins, so holding every visitor's message needs no edit to each automation.","default":{},"type":"object","properties":{"schedule":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"event":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"listener":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"webchat":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"workspace":{"default":"allow","type":"string","enum":["allow","hold","deny"]},"workflow":{"default":"allow","type":"string","enum":["allow","deny"]}}},"actionRules":{"default":{},"description":"What an agent may do out in the world, per kind of action: go ahead, ask first, or never.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["allow","hold","deny"]}},"commandRules":{"default":{},"description":"What an agent may run inside the sandbox, for the five kinds of command that are hard to take back: rewriting git history, deleting recursively, reading credential files, publishing a package, reaching out to the network. Everything else is recoverable in a container that is itself disposable, and gating it would be friction bought with nothing.","type":"object","propertyNames":{"type":"string","enum":["git.destructive","files.destructive","secrets.access","package.publish","network.outbound"]},"additionalProperties":{"type":"string","enum":["allow","hold","deny"]}},"subagentsAtOnce":{"default":20,"description":"How many helper agents may work at the same time.","type":"number","minimum":1,"maximum":200},"subagentsPerTurn":{"default":200,"description":"How many a single turn may start in total.","type":"number","minimum":1,"maximum":2000},"subagentDepth":{"default":3,"description":"How many levels deep the delegation may go, since a helper can start helpers of its own.","type":"number","minimum":1,"maximum":10}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/settings/savings":{"get":{"operationId":"settings.savings","summary":"What the token-saving measures were worth","description":"Measured rather than estimated: what each mechanism actually saved over a range of days. The same day range the spending ledger takes, so one calendar filters both.","tags":["Settings"],"parameters":[{"name":"from","in":"query","schema":{"description":"First day to include, as YYYY-MM-DD in UTC. Leave it out for everything up to the end day.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"to","in":"query","schema":{"description":"Last day to include, as YYYY-MM-DD in UTC, and it is included rather than excluded. Leave it out for everything from the start day onwards.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"input":{"type":"object","properties":{"updatedAt":{"type":"number"},"commands":{"type":"number"},"rawTokens":{"type":"number"},"emittedTokens":{"type":"number"},"savedPct":{"type":"number"},"perCleaner":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"commands":{"type":"number"},"savedTokens":{"type":"number"}},"required":["id","commands","savedTokens"],"additionalProperties":false}},"holdout":{"type":"object","properties":{"cleaned":{"type":"number"},"heldOut":{"type":"number"},"measuredSavedPct":{"type":"number"}},"required":["cleaned","heldOut"],"additionalProperties":false},"gaps":{"type":"array","items":{"type":"object","properties":{"command":{"type":"string"},"commands":{"type":"number"},"tokens":{"type":"number"}},"required":["command","commands","tokens"],"additionalProperties":false}}},"required":["commands","rawTokens","emittedTokens","savedPct","perCleaner","holdout","gaps"],"additionalProperties":false},"output":{"type":"object","properties":{"metrics":{"type":"array","prefixItems":[{"type":"object","properties":{"metric":{"type":"string","enum":["proseChars","searchCalls","openingSearches"]},"on":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"off":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"controlTurnsNeeded":{"type":"number"},"marginPct":{"type":"number"},"deltaPct":{"type":"number"},"saved":{"type":"number"}},"required":["metric","on","off"],"additionalProperties":false}],"items":{"type":"object","properties":{"metric":{"type":"string","enum":["proseChars","searchCalls","openingSearches"]},"on":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"off":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"controlTurnsNeeded":{"type":"number"},"marginPct":{"type":"number"},"deltaPct":{"type":"number"},"saved":{"type":"number"}},"required":["metric","on","off"],"additionalProperties":false}},"minTurns":{"type":"number"},"sampleUnit":{"type":"string","enum":["turns","conversations"]},"cohort":{"type":"string"}},"required":["metrics","minTurns"],"additionalProperties":false},"search":{"type":"object","properties":{"metrics":{"type":"array","prefixItems":[{"type":"object","properties":{"metric":{"type":"string","enum":["proseChars","searchCalls","openingSearches"]},"on":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"off":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"controlTurnsNeeded":{"type":"number"},"marginPct":{"type":"number"},"deltaPct":{"type":"number"},"saved":{"type":"number"}},"required":["metric","on","off"],"additionalProperties":false}],"items":{"type":"object","properties":{"metric":{"type":"string","enum":["proseChars","searchCalls","openingSearches"]},"on":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"off":{"type":"object","properties":{"turns":{"type":"number"},"mean":{"type":"number"}},"required":["turns","mean"],"additionalProperties":false},"controlTurnsNeeded":{"type":"number"},"marginPct":{"type":"number"},"deltaPct":{"type":"number"},"saved":{"type":"number"}},"required":["metric","on","off"],"additionalProperties":false}},"minTurns":{"type":"number"},"sampleUnit":{"type":"string","enum":["turns","conversations"]},"cohort":{"type":"string"}},"required":["metrics","minTurns"],"additionalProperties":false},"tier":{"type":"object","properties":{"judged":{"type":"number"},"fast":{"type":"number"},"atStakeUsd":{"type":"number"},"routed":{"type":"number"},"routedUsd":{"type":"number"},"escalated":{"type":"number"},"denied":{"type":"number"}},"required":["judged","fast","atStakeUsd","routed","routedUsd","escalated","denied"],"additionalProperties":false}},"required":["input"],"additionalProperties":false}}}}}}},"/settings/system-prompt/{base}":{"get":{"operationId":"settings.builtinPrompt","summary":"Read a built-in system prompt","description":"The actual text behind one of the built-in modes, so a settings screen can show the prompt instead of asking anyone to trust a description of it, and so either can be forked into a custom one.","tags":["Settings"],"parameters":[{"name":"base","in":"path","required":true,"schema":{"type":"string","enum":["intentic","claude"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"version":{"type":"string"}},"required":["text","version"],"additionalProperties":false}}}}}}},"/settings/rule-firings":{"get":{"operationId":"settings.firings","summary":"When each rule last did something","description":"A separate read rather than a field on the settings, because a rule firing is not somebody editing anything: folding it in would turn every firing into a settings write and put a self-changing value inside the object a screen edits.","tags":["Settings"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}}}}}}},"/capabilities":{"get":{"operationId":"capabilities.list","summary":"Everything this sandbox is connected to","description":"Each connection with its live state, the settings that are safe to show, and the names of the credentials it holds. The values of those credentials are never in the answer, on any route but one.","tags":["Capabilities"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"capabilities":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The connection's id."},"kind":{"type":"string","enum":["devops","monorepo","mcp","service","integration","cli","plugin","extension","ssh","vpn","exit","docker","browser","identity","host","agent","endpoint","localmodel","wallet"],"description":"What sort of thing it is."},"status":{"type":"object","properties":{"state":{"type":"string","enum":["active","pending","error","inactive"],"description":"Whether it is live, still coming up, broken, or switched off."},"detail":{"description":"What is wrong, in words a person can act on.","type":"string"},"code":{"description":"A short marker for that reason, for anything deciding what to do about it.","type":"string"}},"required":["state"],"additionalProperties":false,"description":"Whether it is working."},"config":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"Its settings, minus anything secret."},"secrets":{"default":[],"description":"Which credentials it holds, by name. The values are on one route only, and it is not this one.","type":"array","items":{"type":"string"}}},"required":["id","kind","status","config","secrets"],"additionalProperties":false},"description":"What this sandbox is connected to."},"recommendations":{"default":[],"description":"Things worth connecting, worked out from what is actually in the workspace rather than from anything you configured. Re-derived on every read, so one whose evidence has moved simply stops being suggested.","type":"array","items":{"type":"object","properties":{"card":{"type":"string","description":"Which connection is being suggested."},"evidence":{"type":"string","description":"What was seen that prompted it: a file, a remote, printed verbatim so the claim can be checked rather than believed."},"reason":{"type":"string","description":"The same claim in words, without repeating the evidence into it."},"prefill":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Settings the scan could read, to fill the form so you supply only the credential. Never a secret, even when one is sitting in a checked-in file: the suggestion points at such a file, it does not absorb what is in it."}},"required":["card","evidence","reason","prefill"],"additionalProperties":false}}},"required":["capabilities","recommendations"],"additionalProperties":false}}}}}},"post":{"operationId":"capabilities.add","summary":"Connect something, or change a connection","description":"Writes a connection and streams the work of applying it, because some kinds provision real infrastructure and take a while. Sending an id that already exists edits that connection: this is the edit as well as the create. Since a caller is never shown stored credentials, it marks the ones it is leaving alone and the daemon fills them in, which is the only way to change one setting without retyping a key.","tags":["Capabilities"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"devops"},"config":{"type":"object","properties":{}}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"monorepo"},"config":{"type":"object","properties":{}}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"mcp"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Where the tool server answers."},"token":{"description":"The credential it needs, if any. Stored, never echoed back.","type":"string"}},"required":["url"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"service"},"config":{"type":"object","properties":{"service":{"type":"string","enum":["signoz","outline","paperless","openproject","invoiceninja","infisical"],"description":"Which service to provision."},"domain":{"type":"string","minLength":1,"description":"The address it should answer on."},"on":{"type":"string","minLength":1,"description":"Which machine to put it on."},"expose":{"type":"string","minLength":1,"description":"How it should be reachable."}},"required":["service","domain","on","expose"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"integration"},"config":{"type":"object","properties":{"provider":{"type":"string","const":"stripe","description":"Which outside service's credential to make available to deployed apps."}},"required":["provider"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"cli"},"config":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which tool to give the agent. The rest of the fields are whatever that tool's own card declares it needs, and are checked against it when you connect."}},"required":["provider"],"additionalProperties":{"type":"string"}}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"plugin"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The repository to take the plugin from."},"ref":{"description":"A branch, tag or commit to pin to. Leave it out to follow the default branch.","type":"string","minLength":1},"path":{"description":"Where inside the repository the plugin lives, for one that sits in a larger checkout.","type":"string","minLength":1},"token":{"description":"A credential for a private repository. Stored, never echoed back.","type":"string","minLength":1}},"required":["url"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"extension"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The repository to take the extension from."},"ref":{"type":"string","pattern":"^[0-9a-f]{40}$","description":"The exact commit to install, in full. Required rather than optional because extension code runs with your browser's trust: the owner approves precisely the code that runs, and an update is a deliberate re-install at a new commit."},"path":{"description":"Where inside the repository the extension lives, for one that sits in a larger checkout.","type":"string","minLength":1},"token":{"description":"A credential for a private repository. Stored, never echoed back.","type":"string","minLength":1},"tier":{"description":"Whether installing this donates credits to its publisher. Absent means free, which donates nothing and asks for nothing. Taken from the listing rather than checked against it, which is the honesty an open-source posture accepts.","type":"string","enum":["free","premium"]},"registry":{"description":"Which registry this install came from, which is what update checks and security advisories are read against. Absent falls back to the official one.","type":"string","format":"uri"}},"required":["url","ref"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"ssh"},"config":{"oneOf":[{"type":"object","properties":{"auth":{"type":"string","const":"key","description":"Sign in with a key."},"host":{"type":"string","minLength":1,"description":"The machine's address."},"port":{"default":22,"description":"Which port it listens on.","type":"number"},"user":{"type":"string","minLength":1,"description":"Which user to connect as."},"privateKey":{"type":"string","minLength":1,"description":"The private key, whole. Stored with tight permissions and never echoed back."}},"required":["auth","host","user","privateKey"]},{"type":"object","properties":{"auth":{"type":"string","const":"password","description":"Sign in with a password."},"host":{"type":"string","minLength":1,"description":"The machine's address."},"port":{"default":22,"description":"Which port it listens on.","type":"number"},"user":{"type":"string","minLength":1,"description":"Which user to connect as."},"password":{"type":"string","minLength":1,"description":"The password. Stored, never echoed back."}},"required":["auth","host","user","password"]}]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"vpn"},"config":{"oneOf":[{"type":"object","properties":{"provider":{"type":"string","const":"wireguard"},"config":{"type":"string","minLength":1},"autoConnect":{"default":"on","type":"string","enum":["on","off"]}},"required":["provider","config"]},{"type":"object","properties":{"provider":{"type":"string","const":"fortinet"},"server":{"type":"string","minLength":1},"port":{"default":443,"type":"integer","minimum":1,"maximum":65535},"username":{"type":"string","minLength":1},"password":{"type":"string","minLength":1},"trustedCert":{"type":"string","minLength":1},"realm":{"type":"string","minLength":1},"autoConnect":{"default":"on","type":"string","enum":["on","off"]}},"required":["provider","server","username","password"]},{"type":"object","properties":{"provider":{"type":"string","const":"ipsec"},"server":{"type":"string","minLength":1},"presharedKey":{"type":"string","minLength":1},"localId":{"type":"string","minLength":1},"remoteId":{"type":"string","minLength":1},"username":{"type":"string","minLength":1},"password":{"type":"string","minLength":1},"ikeVersion":{"default":"1","type":"string","enum":["1","2"]},"pfs":{"default":"on","type":"string","enum":["on","off"]},"dhGroup":{"default":"14","type":"string","enum":["2","5","14","15","16","19","20"]},"aggressive":{"default":"on","type":"string","enum":["on","off"]},"routedNetworks":{"default":"0.0.0.0/0","type":"string"},"autoConnect":{"default":"on","type":"string","enum":["on","off"]}},"required":["provider","server","presharedKey"]}]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"exit"},"config":{"oneOf":[{"type":"object","properties":{"provider":{"type":"string","const":"tor"},"country":{"type":"string","pattern":"^[A-Za-z]{2}$"},"autoStart":{"default":"off","type":"string","enum":["on","off"]}},"required":["provider"]},{"type":"object","properties":{"provider":{"type":"string","const":"vpngate"},"country":{"type":"string","pattern":"^[A-Za-z]{2}$"},"autoStart":{"default":"off","type":"string","enum":["on","off"]}},"required":["provider"]},{"type":"object","properties":{"provider":{"type":"string","const":"wireguard"},"config":{"type":"string","minLength":1},"country":{"type":"string","pattern":"^[A-Za-z]{2}$"},"autoStart":{"default":"off","type":"string","enum":["on","off"]}},"required":["provider","config"]}]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"docker"},"config":{"type":"object","properties":{"gpu":{"default":"off","type":"string","enum":["on","off"]},"registryMirror":{"type":"string","format":"uri"},"insecureRegistries":{"type":"string"},"addressPool":{"type":"string"}}}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"browser"},"config":{"type":"object","properties":{"platform":{"type":"string","minLength":1},"username":{"type":"string"},"password":{"type":"string"},"identity":{"type":"string"},"purpose":{"type":"string"},"openedAt":{"type":"string"},"exit":{"type":"string"}},"required":["platform"],"additionalProperties":{"type":"string"}}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"identity"},"config":{"type":"object","properties":{"email":{"type":"string","minLength":3},"password":{"type":"string"},"mailbox":{"type":"string"},"loginUrl":{"type":"string","format":"uri"},"openAccounts":{"default":"off","type":"string","enum":["on","off"]},"exit":{"type":"string"}},"required":["email"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"host"},"config":{"type":"object","properties":{"shell":{"default":"on","type":"string","enum":["on","off"]},"write":{"default":"off","type":"string","enum":["on","off"]},"screen":{"default":"on","type":"string","enum":["on","off"]},"control":{"default":"off","type":"string","enum":["on","off"]},"sandboxes":{"default":"off","type":"string","enum":["on","off"]},"sandboxRemove":{"default":"off","type":"string","enum":["on","off"]},"roots":{"type":"string"},"platform":{"type":"string","minLength":1}},"required":["platform"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"agent"},"config":{"type":"object","properties":{"command":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"env":{"type":"string"},"loginCommand":{"type":"string","minLength":1}},"required":["command"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"endpoint"},"config":{"type":"object","properties":{"baseUrl":{"type":"string","format":"uri"},"protocol":{"default":"openai","type":"string","enum":["openai","anthropic"]},"apiKey":{"type":"string"},"headers":{"type":"string"}},"required":["baseUrl"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"localmodel"},"config":{"type":"object","properties":{"model":{"type":"string","minLength":1},"gpu":{"default":"off","type":"string","enum":["on","off"]},"url":{"type":"string","format":"uri"},"context":{"default":"65536","anyOf":[{"type":"string","enum":["16384","32768","65536","131072"]},{"type":"string","const":"custom"}]},"contextTokens":{"type":"integer","minimum":2048,"maximum":1048576}},"required":["model"]}},"required":["id","kind","config"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"kind":{"type":"string","const":"wallet"},"config":{"type":"object","properties":{"network":{"default":"eip155:8453","type":"string","enum":["eip155:8453","eip155:84532"]},"address":{"type":"string"},"perPaymentMaxUsd":{"default":"1.00","type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"},"autoApproveUnderUsd":{"default":"0","type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"},"dailyCapUsd":{"default":"5.00","type":"string","pattern":"^\\d+(\\.\\d{1,6})?$"},"allow":{"type":"string"},"deny":{"type":"string"}}}},"required":["id","kind","config"]}]}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/capabilities/{id}":{"delete":{"operationId":"capabilities.remove","summary":"Disconnect something","description":"Tears a connection down. The kinds that own real infrastructure refuse, because deleting those would be losing data rather than losing a connection.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/capabilities/{id}/rename":{"post":{"operationId":"capabilities.rename","summary":"Rename a connection","description":"Carries everything the old name keyed across with it: a browser profile and its logins, an enrolled machine, an extension's copy of its source. Removing and re-adding would lose exactly the state that made the connection worth keeping. Kinds whose name is part of what they are refuse.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"}},"required":["to"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/capabilities/{id}/secret":{"post":{"operationId":"capabilities.setSecret","summary":"Replace a stored credential","description":"Swaps one connection's key or token for a new one and re-applies it, without touching any of its other settings.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","minLength":1,"description":"The new credential. Its other settings are left alone."}},"required":["value"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/capabilities/{id}/status":{"get":{"operationId":"capabilities.status","summary":"Re-check one connection","description":"Probes a single connection right now, for a screen that wants to refresh one row rather than the whole list.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string","enum":["active","pending","error","inactive"],"description":"Whether it is live, still coming up, broken, or switched off."},"detail":{"description":"What is wrong, in words a person can act on.","type":"string"},"code":{"description":"A short marker for that reason, for anything deciding what to do about it.","type":"string"}},"required":["state"],"additionalProperties":false}}}}}}},"/capabilities/{id}/connection":{"get":{"operationId":"capabilities.connection","summary":"A connection's settings, credentials included","description":"The one call that hands back stored secrets, so an extension's own backend can dial the service behind a connection. Never answered for a signed-in person: only a machine credential reaches it, and an extension's only if its manifest asked for this route out loud at install time.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The connection's id."},"kind":{"type":"string","description":"What sort of thing it is."},"config":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Its settings exactly as stored, credentials included. The field names are its own kind's, which the caller already knows."}},"required":["id","kind","config"],"additionalProperties":false}}}}}}},"/capabilities/marketplace":{"post":{"operationId":"capabilities.marketplace","summary":"Read a plugin marketplace","description":"Resolves a plugin marketplace source into the list of connections you could install from it.","tags":["Capabilities"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The registry to read."},"token":{"description":"A credential for a private one. Sent as a body rather than in the address, so it never lands in a log.","type":"string","minLength":1}},"required":["url"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"What the registry calls itself."},"plugins":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"string"},"kind":{"type":"string","enum":["plugin","extension"]},"trust":{"type":"string","enum":["verified","listed","blocked"]},"trustReason":{"type":"string"},"securityReview":{"type":"object","properties":{"sha":{"type":"string","pattern":"^[0-9a-f]{40}$"},"url":{"type":"string","minLength":1},"path":{"type":"string","minLength":1},"policy":{"type":"string","minLength":1},"reviewer":{"type":"string","minLength":1},"reviewedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"runId":{"type":"string","minLength":1},"deterministic":{"type":"object","properties":{"policy":{"type":"string","minLength":1},"scanner":{"type":"string","minLength":1},"version":{"type":"string","minLength":1},"runId":{"type":"string","minLength":1}},"required":["policy","scanner","version","runId"],"additionalProperties":false}},"required":["sha","url","policy","reviewer","reviewedAt","runId","deterministic"],"additionalProperties":false},"admitted":{"type":"boolean"},"securityFix":{"type":"boolean"},"tier":{"type":"string","enum":["free","premium"]},"category":{"type":"string"},"art":{"type":"string"},"logo":{"type":"string"},"icon":{"type":"string"},"homepage":{"type":"string"},"install":{"type":"object","properties":{"url":{"type":"string"},"ref":{"type":"string"},"path":{"type":"string"}},"required":["url"],"additionalProperties":false},"stars":{"type":"integer","minimum":0,"maximum":9007199254740991},"pushedAt":{"type":"string"},"checks":{"type":"object","properties":{"sha":{"type":"string"},"manifest":{"type":"string"},"bundle":{"type":"string"},"engines":{"type":"string"}},"required":["sha","manifest","bundle"],"additionalProperties":false}},"required":["name","kind","trust","admitted","tier"],"additionalProperties":false},"description":"What it lists, each with the curated decision, the resolved pointer and what a scan found upstream."}},"required":["name","plugins"],"additionalProperties":false}}}}}}},"/capabilities/recommendations/{card}":{"delete":{"operationId":"capabilities.dismiss","summary":"Stop suggesting this connection","description":"Not needed, for now. Nothing is torn down. The suggestion comes back if what prompted it in the workspace changes, because what is remembered is the evidence, not the refusal.","tags":["Capabilities"],"parameters":[{"name":"card","in":"path","required":true,"schema":{"type":"string","description":"Which suggestion to stop making."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/capabilities/{id}/login":{"post":{"operationId":"capabilities.login","summary":"Sign in to a connection by hand","description":"Opens the connection's own sign-in in a terminal a person can type into, for the flows that need a code pasted or a device confirmed. The answer names the terminal to attach to.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"session":{"type":"string","description":"The terminal the sign-in is happening in. Attach to it to type."}},"required":["session"],"additionalProperties":false}}}}}}},"/capabilities/{id}/otp":{"get":{"operationId":"capabilities.otp","summary":"Mint a one-time code","description":"Generates a single two-factor code from a stored seed. The one credential-adjacent read an agent is allowed, and it is safe because a code expires in seconds and never reveals the seed, so an agent can answer a prompt without ever holding the factor.","tags":["Capabilities"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"The code."},"secondsRemaining":{"type":"number","description":"How long it lasts. Its expiring is what makes handing one to an agent safe, since the seed behind it is never revealed."}},"required":["code","secondsRemaining"],"additionalProperties":false}}}}}}},"/secrets":{"post":{"operationId":"secrets.set","summary":"Store a secret","description":"Writes one name and value into the sandbox's own store, where running processes pick it up without a restart. Refused until the sandbox has somewhere to keep them.","tags":["Secrets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","maxLength":128,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$","description":"The name to store it under, which is the name a process will find it by."},"value":{"type":"string","minLength":1,"description":"The value. It goes straight to your sandbox and never through the platform."}},"required":["key","value"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}},"get":{"operationId":"secrets.list","summary":"Names of the stored secrets","description":"Which secrets exist here. Names only, never values.","tags":["Secrets"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"The names that exist here. Only the names: the values never leave the sandbox."}},"required":["keys"],"additionalProperties":false}}}}}}},"/secrets/{key}":{"delete":{"operationId":"secrets.remove","summary":"Delete a secret","description":"Removes one by name.","tags":["Secrets"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","description":"Which secret, by name."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/secrets/inventory":{"get":{"operationId":"secrets.inventory","summary":"Every secret this sandbox holds, from everywhere","description":"One view across all the places secrets live here: what exists, where it came from and whether it is working. Never any values. This one always answers, even before there is a store to write to.","tags":["Secrets"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"What identifies it. Unique across the whole inventory, so several accounts of one provider each get their own entry."},"kind":{"type":"string","enum":["env","generated","capability","provider"],"description":"Where it came from: you set it, the sandbox generated it, a connection needs it, or it is a model account's credential."},"label":{"description":"A friendlier name, for entries that have one.","type":"string"},"status":{"type":"string","enum":["missing","set","connected"],"description":"Whether it exists and, for a connection, whether it is working."},"requiredBy":{"type":"array","items":{"type":"object","properties":{"resourceId":{"type":"string","description":"Which resource."},"type":{"type":"string","description":"What kind of resource it is."}},"required":["resourceId","type"],"additionalProperties":false},"description":"What is waiting on it. Empty for a connection's or an account's own credential."},"storedAt":{"type":"string","description":"Where it actually lives, in words."},"revealable":{"type":"boolean","description":"Whether its value can be shown at all. Everything except a model account's credential can be."},"ci":{"description":"Whether a copy has been given to the build pipeline.","type":"object","properties":{"synced":{"type":"boolean","description":"Whether the pipeline has it."},"pushedAt":{"description":"When it was last sent there.","type":"string"}},"required":["synced"],"additionalProperties":false},"lastUse":{"description":"The last time an agent actually spent this secret. Absent while it never has been, which most never are.","type":"object","properties":{"at":{"type":"number","description":"When, in milliseconds."},"lane":{"type":"string","enum":["shell","code","browser"],"description":"How it was used: a command, a script, or typed into a page."},"detail":{"description":"Where it went: the start of the command or script, or the site. Names and destinations only, never values.","type":"string"}},"required":["at","lane"],"additionalProperties":false}},"required":["key","kind","status","requiredBy","storedAt","revealable"],"additionalProperties":false},"description":"One entry per secret this sandbox knows about, from every place they live. No values, ever."}},"required":["entries"],"additionalProperties":false}}}}}}},"/secrets/reveal":{"post":{"operationId":"secrets.reveal","summary":"Show one secret's value","description":"The only call that hands a value back, and it is for the owner alone. Sent as a body rather than in the address, so the name never ends up in a log or a browser's history.","tags":["Secrets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"Which secret, by name."}},"required":["key"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","description":"The value itself. The only place in this API one is ever returned."}},"required":["value"],"additionalProperties":false}}}}}}},"/vpn":{"get":{"operationId":"vpn.list","summary":"Configured tunnels and which are up","description":"Every stored VPN with its live link state, read back from the operating system rather than from memory, so a tunnel dropped from a shell and one dropped from a screen look the same here.","tags":["VPN"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which tunnel."},"provider":{"type":"string","enum":["wireguard","fortinet","ipsec"],"description":"What kind of tunnel it is."},"state":{"type":"string","enum":["connected","connecting","disconnected","unavailable","failed"],"description":"Whether it is up, dialling, resting, failed, or not installable yet because its client needs a rebuild to arrive."},"gateway":{"description":"What it dials. For display only, and never a credential.","type":"string"},"interface":{"description":"The network interface carrying it, once one exists.","type":"string"},"address":{"description":"The address the far end gave this sandbox, which is the single most useful answer to whether you are on the VPN.","type":"string"},"routes":{"default":[],"description":"What goes through it. Everything, when the range covers the whole internet. Empty until it is up.","type":"array","items":{"type":"string"}},"dns":{"default":[],"description":"Name servers it pushed, when it pushed any.","type":"array","items":{"type":"string"}},"since":{"description":"When it came up, in milliseconds. Absent unless it is.","type":"number"},"autoConnect":{"type":"boolean","description":"Whether it dials itself when the sandbox starts."},"detail":{"description":"Why it failed, or a note about a healthy one. Never a credential.","type":"string"}},"required":["id","provider","state","routes","dns","autoConnect"],"additionalProperties":false},"description":"Every configured tunnel with its live state, read back from the operating system each time rather than remembered."}},"required":["links"],"additionalProperties":false}}}}}}},"/vpn/{id}/connect":{"post":{"operationId":"vpn.connect","summary":"Dial a VPN","description":"Brings a stored tunnel up, streaming the client's progress as it authenticates and then sets up routing. Streamed because a dial takes seconds and can fail with something you have to read: a wrong password, a gateway certificate nobody trusts, a code it wants. Connecting one that is already up simply says so.","tags":["VPN"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which tunnel to dial."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"otp":{"description":"A one-time code, where the gateway wants one. Supplied per dial and never stored; without it such a gateway refuses and says so.","type":"string","minLength":1}}}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/vpn/{id}/disconnect":{"post":{"operationId":"vpn.disconnect","summary":"Drop a tunnel","description":"Takes the tunnel down. One that was already down is fine: the promise is that it is not up afterwards.","tags":["VPN"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which tunnel."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/vpn/import-forticlient":{"post":{"operationId":"vpn.importForticlient","summary":"Read connections out of an exported config","description":"Turns an exported FortiClient configuration into a list of connections you can add, so somebody holding that file picks from a list instead of retyping a host and port for every tunnel.","tags":["VPN"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"xml":{"type":"string","minLength":1,"description":"The exported configuration file, whole. Nothing is stored: it is read and thrown away."}},"required":["xml"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"connections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The id it would be added under."},"label":{"type":"string","description":"Its name as the file has it, so somebody recognises the connection they are picking."},"provider":{"type":"string","enum":["wireguard","fortinet","ipsec"],"description":"What kind of tunnel it is."},"server":{"type":"string","description":"Where it dials."},"port":{"type":"number","description":"On which port."},"username":{"description":"The username, but only when the file stored it in the clear. An encrypted one is dropped rather than guessed at.","type":"string"},"description":{"description":"Whatever the file said about it.","type":"string"},"localId":{"description":"An identity some tunnel types need, when the file stored it readably.","type":"string"},"aggressive":{"description":"Which negotiation mode it used.","type":"boolean"},"pfs":{"description":"Whether it asked for forward secrecy.","type":"boolean"},"dhGroup":{"description":"Which key-exchange group it used. Together with the setting above, this is what decides whether the connection can complete at all.","type":"string"},"needs":{"type":"array","items":{"type":"string"},"description":"What you still have to type in before it can dial. Always at least the password, because the export wraps credentials in encryption that cannot be undone here."}},"required":["id","label","provider","server","port","needs"],"additionalProperties":false},"description":"The connections found in the file, ready to be added one at a time."}},"required":["connections"],"additionalProperties":false}}}}}}},"/exit":{"get":{"operationId":"exit.list","summary":"Ways to come out somewhere else","description":"Every configured exit with its live state, the country it was asked to appear in, and the country it actually appears in. Those last two disagreeing is the whole reason this reports both.","tags":["Exit locations"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Which exit."},"provider":{"type":"string","enum":["tor","vpngate","wireguard"],"description":"What it runs on."},"state":{"type":"string","enum":["up","starting","down","unavailable","failed"],"description":"Whether it is carrying traffic, coming up, resting, failed, or not installable yet because its client needs a rebuild to arrive."},"proxy":{"type":"string","description":"Where to point traffic that should go through it. Fixed per exit and unchanged by a country switch, which is what lets a long job move country halfway through without reconfiguring anything."},"country":{"description":"Where it was asked to come out. Absent means the provider chose.","type":"string"},"observedCountry":{"description":"Where it actually comes out, as last checked. Kept separate from what was asked for, because those two disagreeing is the most useful fault signal this whole feature has.","type":"string"},"ip":{"description":"The address behind that observation.","type":"string"},"checkedAt":{"description":"When that was checked, in milliseconds, so an old reading can be shown as old.","type":"number"},"interface":{"description":"The network interface, for the kinds that have one.","type":"string"},"since":{"description":"When it came up, in milliseconds.","type":"number"},"autoStart":{"type":"boolean","description":"Whether it starts itself when the sandbox does."},"detail":{"description":"Why it failed, or a note about a healthy one.","type":"string"}},"required":["id","provider","state","proxy","autoStart"],"additionalProperties":false},"description":"Every configured exit, with where it was asked to come out and where it actually does."}},"required":["links"],"additionalProperties":false}}}}}}},"/exit/{id}/countries":{"get":{"operationId":"exit.countries","summary":"Countries one exit can reach","description":"Where this exit can put you, ranked by how much capacity is really there. Asked of the provider when it answers and taken from a built-in list when it does not, and the answer says which of those you got.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","description":"The country's code."},"countryName":{"type":"string","description":"Its name, spelled out."},"servers":{"type":"number","description":"How many servers this provider has there."},"share":{"description":"How much of the provider's actual capacity is there, from zero to one. This is what a list should be sorted by: a third of the countries on offer are one overloaded machine behind a flag, and a count of servers would rank them first.","type":"number"}},"required":["country","countryName","servers"],"additionalProperties":false},"description":"Where this exit can put you, best-supplied first."},"live":{"type":"boolean","description":"Whether the provider answered, or this came from a built-in list. Said out loud rather than presenting an old list as current."}},"required":["countries","live"],"additionalProperties":false}}}}}}},"/exit/{id}/start":{"post":{"operationId":"exit.start","summary":"Bring an exit up","description":"Starts the exit in the country it was configured for. Streamed, because a first start fetches a catalogue, raises a tunnel and then checks the address, which takes tens of seconds on the free providers and can fail at each step with something worth reading. Starting one that is already up simply says so.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/exit/{id}/use":{"post":{"operationId":"exit.use","summary":"Move to another country","description":"Switches the exit's country, starting it first if it was down. It ends by checking where the world actually sees you and fails if that does not match what you asked for. A switch that quietly left your traffic where it was is the exact failure this whole feature exists to rule out.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"country":{"description":"Where to come out. Leaving it out means letting the provider choose, so clearing a country is something you can actually say rather than only setting one.","type":"string","pattern":"^[A-Za-z]{2}$"}}}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/exit/{id}/rotate":{"post":{"operationId":"exit.rotate","summary":"Take a different address, same country","description":"Swaps to another address in the country you are already in. Fails if the address does not actually change, which on a small pool it sometimes cannot.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/exit/{id}/check":{"post":{"operationId":"exit.check","summary":"Where the world sees you right now","description":"Looks up the address and country as seen through this exit. Cheap, and the honest answer to whether you are really where you meant to be, which is what every other call here is judged against.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"The address the world sees, looked up through the exit's own proxy rather than assumed."},"country":{"description":"Which country that address is in. Absent when the lookup gave an address and no country, in which case a switch is judged on the address having changed instead.","type":"string"},"countryName":{"description":"That country's name, spelled out.","type":"string"}},"required":["ip"],"additionalProperties":false}}}}}}},"/exit/{id}/stop":{"post":{"operationId":"exit.stop","summary":"Take an exit down","description":"Shuts the exit off. One that was already down is fine: the promise is that it is not up afterwards, not that it was up before.","tags":["Exit locations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which exit."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/inventory":{"get":{"operationId":"inventory.list","summary":"Machines and services you have declared","description":"What the deployment configuration says this setup owns and what it wants provisioned.","tags":["Inventory"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"backend","description":"Something you already have: a machine, an account with a hosting provider."},"provider":{"type":"string","enum":["host","cloudflare","github","gitlab","stripe"],"description":"Which provider it is with."},"name":{"type":"string","description":"What to call it, which is also how everything else refers to it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings. Anything secret is stored separately and referred to here, never written in."}},"required":["kind","provider","name","values"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service","description":"Something you want provisioned."},"service":{"type":"string","enum":["signoz","outline","paperless","openproject","invoiceninja","infisical"],"description":"Which service."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","service","name","values","on","expose"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"app","description":"An app of your own, built from source and deployed."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings, including the address it should answer on."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","name","values","on","expose"],"additionalProperties":false}]},"description":"Everything declared: what you have, and what you want provisioned."}},"required":["entries"],"additionalProperties":false}}}}}},"post":{"operationId":"inventory.add","summary":"Declare a machine or service","description":"Writes the entry into the configuration file and commits it, exactly as an agent editing that file by hand would. Answers with the whole updated list, so a screen redraws from one response.","tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"backend","description":"Something you already have: a machine, an account with a hosting provider."},"provider":{"type":"string","enum":["host","cloudflare","github","gitlab","stripe"],"description":"Which provider it is with."},"name":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings. Anything secret is stored separately and referred to here, never written in."}},"required":["kind","provider","name","values"]},{"type":"object","properties":{"kind":{"type":"string","const":"service","description":"Something you want provisioned."},"service":{"type":"string","enum":["signoz","outline","paperless","openproject","invoiceninja","infisical"],"description":"Which service."},"name":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","service","name","values","on","expose"]},{"type":"object","properties":{"kind":{"type":"string","const":"app","description":"An app of your own, built from source and deployed."},"name":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings, including the address it should answer on."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","name","values","on","expose"]}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"backend","description":"Something you already have: a machine, an account with a hosting provider."},"provider":{"type":"string","enum":["host","cloudflare","github","gitlab","stripe"],"description":"Which provider it is with."},"name":{"type":"string","description":"What to call it, which is also how everything else refers to it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings. Anything secret is stored separately and referred to here, never written in."}},"required":["kind","provider","name","values"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service","description":"Something you want provisioned."},"service":{"type":"string","enum":["signoz","outline","paperless","openproject","invoiceninja","infisical"],"description":"Which service."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","service","name","values","on","expose"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"app","description":"An app of your own, built from source and deployed."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings, including the address it should answer on."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","name","values","on","expose"],"additionalProperties":false}]},"description":"Everything declared: what you have, and what you want provisioned."}},"required":["entries"],"additionalProperties":false}}}}}}},"/inventory/{name}":{"delete":{"operationId":"inventory.remove","summary":"Undeclare a machine or service","description":"Takes the entry back out of the configuration and commits that too. Answers with the whole updated list.","tags":["Inventory"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which entry, by name."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"backend","description":"Something you already have: a machine, an account with a hosting provider."},"provider":{"type":"string","enum":["host","cloudflare","github","gitlab","stripe"],"description":"Which provider it is with."},"name":{"type":"string","description":"What to call it, which is also how everything else refers to it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings. Anything secret is stored separately and referred to here, never written in."}},"required":["kind","provider","name","values"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"service","description":"Something you want provisioned."},"service":{"type":"string","enum":["signoz","outline","paperless","openproject","invoiceninja","infisical"],"description":"Which service."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","service","name","values","on","expose"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"app","description":"An app of your own, built from source and deployed."},"name":{"type":"string","description":"What to call it."},"values":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Its settings, including the address it should answer on."},"on":{"type":"string","description":"Which of your machines to put it on."},"expose":{"type":"string","description":"How it should be reachable."}},"required":["kind","name","values","on","expose"],"additionalProperties":false}]},"description":"Everything declared: what you have, and what you want provisioned."}},"required":["entries"],"additionalProperties":false}}}}}}},"/intentic":{"post":{"operationId":"intentic.run","summary":"Run an infrastructure command","description":"Runs the sandbox's own command-line tool and streams its output as it arrives, so progress is visible rather than arriving all at once at the end. A failure surfaces once the stream closes.","tags":["Platform CLI"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"}}},"required":["args"]}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/intentic/apply":{"post":{"operationId":"intentic.apply","summary":"Bring the infrastructure into line","description":"Starts the long reconcile that makes the running world match what was declared, and answers immediately. It takes minutes, so it runs in a terminal you attach to rather than on a held-open request.","tags":["Platform CLI"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/intentic/apply/events":{"get":{"operationId":"intentic.applyEvents","summary":"Follow the reconcile","description":"The same progress the terminal shows, as structured events, kept on disk so a page refresh does not lose it. It replays from the start of the run and then follows live, closing when the run ends.","tags":["Platform CLI"],"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/claude/oauth/start":{"post":{"operationId":"claude.start","summary":"Begin connecting a Claude account","description":"Hands back the address to send somebody to, and the proof this sandbox will need to finish the exchange. The sandbox holds the credential afterwards, not the browser.","tags":["Claude accounts"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"authorizeUrl":{"type":"string","description":"Where to send somebody to sign in."},"verifier":{"type":"string","description":"Keep this and send it back when finishing. It is what proves the code that comes back belongs to this handshake."},"state":{"type":"string","description":"The handshake's own id, sent back with it."}},"required":["authorizeUrl","verifier","state"],"additionalProperties":false}}}}}}},"/claude/oauth/exchange":{"post":{"operationId":"claude.exchange","summary":"Finish connecting a Claude account","description":"Trades the code from the sign-in for stored tokens and answers with the account it just connected. A sandbox can hold several Claude accounts side by side.","tags":["Claude accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":1,"description":"The code the sign-in handed back."},"verifier":{"type":"string","minLength":1,"description":"The proof from the start of the handshake, which is what stops somebody else's code being redeemed here."},"state":{"type":"string","minLength":1,"description":"The handshake this belongs to. A mismatch is refused."},"label":{"description":"What to call the account. Blank derives one from the sign-in.","type":"string"}},"required":["code","verifier","state"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false}}}}}}},"/claude/accounts":{"get":{"operationId":"claude.accounts","summary":"Connected Claude accounts","description":"Each connected account with how full its plan limits were when last measured. Ask for a fresh measurement and it takes one before answering, which is slower.","tags":["Claude accounts"],"parameters":[{"name":"force","in":"query","schema":{"description":"Measure the plan limits again before answering, rather than serving a recent reading. Slower, and the right thing when somebody has just changed a plan and is asking whether what they can see is still true.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false},"description":"The connected accounts. Tokens never travel in this shape: being in this list is what connected means."}},"required":["accounts"],"additionalProperties":false}}}}}}},"/claude/account/rename":{"post":{"operationId":"claude.rename","summary":"Rename a Claude account","description":"Changes the label one account shows under, so several are tellable apart.","tags":["Claude accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which account."},"label":{"type":"string","maxLength":80,"description":"The new name. Blank restores the one derived from the sign-in, rather than leaving a nameless row."}},"required":["id","label"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false}}}}}}},"/claude/account/disconnect":{"post":{"operationId":"claude.disconnect","summary":"Disconnect a Claude account","description":"Clears the stored tokens for one account. The others stay connected.","tags":["Claude accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which account."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/grok/oauth/start":{"post":{"operationId":"grok.start","summary":"Begin connecting a Grok account","description":"Hands back the page to open and the one-time code to type there. Nothing is pasted back afterwards: the sandbox waits for the sign-in to complete on its own, so poll the accounts call until it appears.","tags":["Grok accounts"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The page to open, which already has the code in it."},"code":{"type":"string","description":"The one-time code, shown as well so the page and the card say the same thing. Nothing is pasted back: the sandbox waits for the sign-in to complete on its own."}},"required":["url","code"],"additionalProperties":false}}}}}}},"/grok/accounts":{"get":{"operationId":"grok.accounts","summary":"Connected Grok account","description":"What is signed in. A list for consistency with the other providers, though there is at most one.","tags":["Grok accounts"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false},"description":"The connected accounts. Tokens never travel in this shape: being in this list is what connected means."}},"required":["accounts"],"additionalProperties":false}}}}}}},"/grok/account/disconnect":{"post":{"operationId":"grok.disconnect","summary":"Disconnect the Grok account","description":"Clears the stored tokens.","tags":["Grok accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which account."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/cursor/login/start":{"post":{"operationId":"cursor.start","summary":"Begin connecting a Cursor account","description":"Hands back the page to sign in on. The sandbox finishes the handshake itself and stores the credential, so nothing has to be pasted back: watch the account list instead.","tags":["Cursor accounts"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The page to open and sign in on. It is already addressed to this attempt, so there is no code to type."},"handshake":{"type":"string","description":"This attempt's id, for abandoning it. Not a credential and not redeemable: the proof that finishes the sign-in never leaves the sandbox."},"expiresAt":{"type":"number","description":"When this attempt stops being answerable, in milliseconds, so a card can stop waiting instead of spinning."}},"required":["url","handshake","expiresAt"],"additionalProperties":false}}}}}}},"/cursor/login/cancel":{"post":{"operationId":"cursor.cancel","summary":"Abandon a Cursor sign-in","description":"Stops waiting on a sign-in nobody completed. An abandoned attempt also expires on its own.","tags":["Cursor accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"handshake":{"type":"string","minLength":1,"description":"Which attempt to stop waiting on."}},"required":["handshake"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/cursor/accounts":{"get":{"operationId":"cursor.accounts","summary":"Connected Cursor accounts","description":"Each connected account, and whether its stored key is still good. Cursor publishes no plan-wide allowance, so these rows carry no usage reading.","tags":["Cursor accounts"],"parameters":[{"name":"force","in":"query","schema":{"description":"Measure the plan limits again before answering, rather than serving a recent reading. Slower, and the right thing when somebody has just changed a plan and is asking whether what they can see is still true.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false},"description":"The connected accounts. Tokens never travel in this shape: being in this list is what connected means."}},"required":["accounts"],"additionalProperties":false}}}}}}},"/cursor/account/rename":{"post":{"operationId":"cursor.rename","summary":"Rename a Cursor account","description":"Changes the label one account shows under, so several are tellable apart.","tags":["Cursor accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which account."},"label":{"type":"string","maxLength":80,"description":"The new name. Blank restores the one derived from the sign-in, rather than leaving a nameless row."}},"required":["id","label"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The account's id, which is what a turn names to spend on it and what disconnecting takes."},"label":{"type":"string","description":"What it is called here, which somebody can change."},"email":{"description":"Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.","type":"string"},"organization":{"description":"Which organisation it belongs to, where the provider says.","type":"string"},"scope":{"description":"What the credential is permitted to do, in the provider's terms.","type":"string"},"connectedAt":{"type":"number","description":"When it was connected, in milliseconds."},"needsReauth":{"description":"Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet.","type":"boolean"},"detail":{"description":"Why, in words a person can act on.","type":"string"},"usage":{"description":"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.","type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["id","label","connectedAt"],"additionalProperties":false}}}}}}},"/cursor/account/disconnect":{"post":{"operationId":"cursor.disconnect","summary":"Disconnect a Cursor account","description":"Clears the stored key for one account. The others stay connected.","tags":["Cursor accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which account."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/translator/accounts":{"get":{"operationId":"translator.accounts","summary":"Subscriptions connected through the translator","description":"What is signed in per provider. Each provider can hold several accounts at once, and the translator spreads work across them.","tags":["Routed providers"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"codex":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"label":{"type":"string"},"usage":{"type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["name","label"],"additionalProperties":false}},"grok":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"label":{"type":"string"},"usage":{"type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["name","label"],"additionalProperties":false}},"kimi":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"label":{"type":"string"},"usage":{"type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["name","label"],"additionalProperties":false}},"gemini":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"label":{"type":"string"},"usage":{"type":"object","properties":{"windows":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"utilization":{"type":"number"},"resetsAt":{"type":"number"}},"required":["kind","utilization"],"additionalProperties":false}},"measuredAt":{"type":"number"}},"required":["windows","measuredAt"],"additionalProperties":false}},"required":["name","label"],"additionalProperties":false}}},"required":["codex","grok","kimi","gemini"],"additionalProperties":false}}}}}}},"/translator/{provider}/connect":{"post":{"operationId":"translator.connect","summary":"Start connecting a subscription","description":"Begins the sign-in for one provider and says which of the two shapes it is: a code you type into a device page, which finishes by itself in the background, or a redirect whose landing address you hand back afterwards.","tags":["Routed providers"],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["codex","grok","kimi","gemini"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The page to open."},"code":{"type":"string","description":"The one-time code, where the provider uses one."},"state":{"type":"string","description":"The handshake's id, which the finishing call sends back."},"flow":{"type":"string","enum":["device","redirect"],"description":"Which shape this is. A device sign-in finishes by itself and you poll the account list; a redirect needs the address it landed on handed back. Said outright rather than guessed at from whether a code happens to exist."}},"required":["url","code","state","flow"],"additionalProperties":false}}}}}}},"/translator/{provider}/complete":{"post":{"operationId":"translator.complete","summary":"Finish a redirect sign-in","description":"For the providers that redirect somewhere this sandbox cannot receive: hand back the address you landed on and the connection completes.","tags":["Routed providers"],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["codex","grok","kimi","gemini"],"description":"Which provider."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redirectUrl":{"type":"string","minLength":1,"description":"The address the browser was sent to, whole. The grant is inside it."},"state":{"type":"string","minLength":1,"description":"The handshake this belongs to. A mismatch is refused."}},"required":["redirectUrl","state"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/translator/{provider}/disconnect":{"post":{"operationId":"translator.disconnect","summary":"Disconnect one subscription","description":"Clears a single account by name. Any others under the same provider stay connected.","tags":["Routed providers"],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["codex","grok","kimi","gemini"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/endpoints/{id}/models":{"get":{"operationId":"endpoints.models","summary":"Models a connected server offers","description":"Asks one configured model server what it serves. There is no built-in list and no fallback: what a server offers is knowable only by asking it, so an empty answer is the honest report that we could not.","tags":["Endpoints"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Which connection."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What to name when asking for this model."},"label":{"type":"string","description":"What to call it on screen."},"efforts":{"description":"The thinking levels it accepts, where the provider says. Empty means use your own defaults.","type":"array","items":{"type":"string"}},"description":{"description":"What it is good for, in the provider's own words. Absent where the provider publishes only ids, which is the honest answer rather than something to paper over with a hand-written table.","type":"string"},"badges":{"description":"What it is known for, where the provider says so.","type":"array","items":{"type":"string","enum":["reasoning","fast"]}},"contextWindow":{"description":"How many tokens this model will accept in one request, where the server publishes it.","type":"number"}},"required":["id","label"],"additionalProperties":false},"description":"What this provider serves, in its own preference order, which is not rearranged here. Never empty."},"default":{"type":"string","description":"Which one a fresh conversation starts on. Always present."}},"required":["models","default"],"additionalProperties":false}}}}}}},"/endpoints/trial/status":{"get":{"operationId":"endpoints.trial","summary":"What is left of the free trial","description":"The allowance, what has been used, when it resets, and which model actually answered the last message. Not being available is the ordinary answer rather than a failure: most sandboxes run against a platform that offers no trial at all.","tags":["Endpoints"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"allowance":{"type":"integer","minimum":0,"maximum":9007199254740991},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"remaining":{"type":"integer","minimum":0,"maximum":9007199254740991},"health":{"type":"string","enum":["unknown","healthy","degraded","unavailable"]},"resetsAt":{"type":"string"},"retryAt":{"type":"string"},"servedModel":{"type":"string"}},"required":["available","allowance","used","remaining","health"],"additionalProperties":false}}}}}}},"/providers/{provider}/models":{"get":{"operationId":"providers.models","summary":"Models one provider offers","description":"Every model this provider serves and which one it defaults to. Never empty: it is discovered live with a stored list behind it. The order is the provider's own preference and is not rearranged here.","tags":["Providers"],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["claude","codex","grok","kimi","gemini","cursor"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What to name when asking for this model."},"label":{"type":"string","description":"What to call it on screen."},"efforts":{"description":"The thinking levels it accepts, where the provider says. Empty means use your own defaults.","type":"array","items":{"type":"string"}},"description":{"description":"What it is good for, in the provider's own words. Absent where the provider publishes only ids, which is the honest answer rather than something to paper over with a hand-written table.","type":"string"},"badges":{"description":"What it is known for, where the provider says so.","type":"array","items":{"type":"string","enum":["reasoning","fast"]}},"contextWindow":{"description":"How many tokens this model will accept in one request, where the server publishes it.","type":"number"}},"required":["id","label"],"additionalProperties":false},"description":"What this provider serves, in its own preference order, which is not rearranged here. Never empty."},"default":{"type":"string","description":"Which one a fresh conversation starts on. Always present."}},"required":["models","default"],"additionalProperties":false}}}}}}},"/usage/rollup":{"get":{"operationId":"usage.rollup","summary":"What was spent, grouped","description":"The spending record over a range of days, grouped by day, provider, account and model. Everything a cost screen shows is a rearrangement of this one answer, so nothing needs a second call. Read-only: rows are written by the sandbox as turns end, which is what makes it worth trusting.","tags":["Usage"],"parameters":[{"name":"from","in":"query","schema":{"description":"First day to include, as YYYY-MM-DD in UTC. Leave it out for everything up to the end day.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"to","in":"query","schema":{"description":"Last day to include, as YYYY-MM-DD in UTC, and it is included rather than excluded. Leave it out for everything from the start day onwards.","type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string","description":"The day, as YYYY-MM-DD in UTC."},"provider":{"type":"string","description":"Which model provider."},"account":{"description":"Which account. Absent for work run on a plain key.","type":"string"},"model":{"description":"Which model.","type":"string"},"harness":{"type":"string","description":"Which agentic loop."},"conversationId":{"description":"Which conversation.","type":"string"},"turns":{"type":"number","description":"Turns in this group."},"inputTokens":{"type":"number","description":"Tokens sent."},"outputTokens":{"type":"number","description":"Tokens received."},"cacheReadTokens":{"type":"number","description":"Tokens served from cache."},"cacheCreationTokens":{"type":"number","description":"Tokens written to cache."},"costUsd":{"type":"number","description":"What the group cost, in dollars."},"durationMs":{"type":"number","description":"Time spent, in milliseconds."}},"required":["day","provider","harness","turns","inputTokens","outputTokens","cacheReadTokens","cacheCreationTokens","costUsd","durationMs"],"additionalProperties":false},"description":"Spending grouped by day, provider, account, model and conversation. Everything a cost screen shows is a rearrangement of these rows, which is why there is no second call for any of it."}},"required":["rows"],"additionalProperties":false}}}}}}},"/ci/runs":{"get":{"operationId":"ci.runs","summary":"Pipeline runs across the repos","description":"What the forges are reporting for every workspace repo that has a remote, served from a cache and filled in on demand. Repos whose notifications are not wired up say so.","tags":["Pipelines"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository."},"host":{"type":"string","enum":["github","gitlab"],"description":"Which forge it lives on."},"project":{"type":"string","description":"The project there."},"url":{"type":"string","description":"Its page on the forge."},"hookWarning":{"description":"Present when the sandbox could not register for instant notifications, with what happened and what to paste in by hand. Without them the sandbox polls instead, so this costs a couple of minutes' delay rather than the feature.","type":"string"}},"required":["repo","host","project","url"],"additionalProperties":false},"description":"Which workspace repositories are wired to a forge, and how each one's notifications are set up."},"runs":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository it belongs to."},"host":{"type":"string","enum":["github","gitlab"],"description":"Which forge is running it."},"project":{"type":"string","description":"The project there, as that forge names it."},"runId":{"type":"number","description":"The forge's own id for the run, which is what re-running and cancelling take."},"title":{"description":"The run's headline, usually the commit subject or the pull request's title. Absent means falling back to the branch and commit.","type":"string"},"authorName":{"description":"Who the forge credits for setting it off.","type":"string"},"authorAvatarUrl":{"description":"Their picture, hosted by the forge. Absent means drawing their initials instead.","type":"string"},"trigger":{"description":"What set it off, in the forge's own word rather than flattened into a shared vocabulary, because the forge's word is the precise one.","type":"string"},"branch":{"type":"string","description":"Which branch."},"sha":{"type":"string","description":"Which commit."},"status":{"type":"string","enum":["running","success","failed","canceled","skipped"],"description":"How it is going. Running covers everything still moving, since the only distinction that matters is that against the three ways it can stop."},"url":{"type":"string","description":"Its page on the forge."},"createdAt":{"type":"number","description":"When it started, in milliseconds."},"durationSeconds":{"description":"How long it took.","type":"number"},"failedJobs":{"description":"What broke, by name. Fetched only for failed runs, so that a notification or a screen can say what went wrong rather than just that something did.","type":"array","items":{"type":"string"}}},"required":["repo","host","project","runId","branch","sha","status","url","createdAt"],"additionalProperties":false},"description":"Runs across all of them, newest first."},"seenAt":{"description":"When this was last looked at, in milliseconds, so a badge can tell new breakages from ones already read without a second call. Absent means never, so everything counts as new.","type":"number"}},"required":["repos","runs"],"additionalProperties":false}}}}}}},"/ci/runs/rerun":{"post":{"operationId":"ci.rerun","summary":"Run a pipeline again","description":"Asks the forge to re-run one pipeline. The daemon only passes the request along.","tags":["Pipelines"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository. The project behind it is resolved fresh each call, so a stale screen cannot act on one the workspace no longer maps to."},"runId":{"type":"number","description":"Which run, by the forge's own id."}},"required":["repo","runId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/ci/runs/cancel":{"post":{"operationId":"ci.cancel","summary":"Cancel a pipeline run","description":"Asks the forge to stop a run in progress.","tags":["Pipelines"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository. The project behind it is resolved fresh each call, so a stale screen cannot act on one the workspace no longer maps to."},"runId":{"type":"number","description":"Which run, by the forge's own id."}},"required":["repo","runId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/ci/runs/jobs":{"post":{"operationId":"ci.jobs","summary":"The steps inside one pipeline run","description":"Each job in a run with its outcome, which is where you look to find out what actually broke.","tags":["Pipelines"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository. The project behind it is resolved fresh each call, so a stale screen cannot act on one the workspace no longer maps to."},"runId":{"type":"number","description":"Which run, by the forge's own id."}},"required":["repo","runId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"jobs":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The job's name."},"status":{"type":"string","enum":["running","success","failed","canceled","skipped"],"description":"How it went."},"stage":{"description":"Which stage it belongs to, where the pipeline groups its jobs that way.","type":"string"},"needs":{"description":"Which jobs in this run it declared it waits on: the real shape of the pipeline. Absent means nothing could be read, which is different from an empty list, which is the claim that it waits on nothing.","type":"array","items":{"type":"string"}},"startedAt":{"description":"When it began, in milliseconds. Absent while it is queued.","type":"number"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"},"durationSeconds":{"description":"How long it took.","type":"number"},"webUrl":{"description":"Its page on the forge, which is the shortest path from this step failed to the log that says why.","type":"string"}},"required":["name","status"],"additionalProperties":false},"description":"The steps inside one run. Fetched separately from the run list, so that list stays cheap."}},"required":["jobs"],"additionalProperties":false}}}}}}},"/ci/fix":{"post":{"operationId":"ci.fix","summary":"Put an agent on a broken pipeline","description":"Opens a fresh isolated conversation already holding the failure: which job, which repo, what it said. The answer names the conversation so you can open it.","tags":["Pipelines"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repo":{"type":"string","description":"Which workspace repository. The project behind it is resolved fresh each call, so a stale screen cannot act on one the workspace no longer maps to."},"runId":{"type":"number","description":"Which run, by the forge's own id."},"pick":{"description":"Which model to open the conversation on, when somebody chose one. Leave it out for the sandbox's own choice, which is the ordinary path.","type":"object","properties":{"agent":{"type":"string","minLength":1,"description":"Which provider."},"model":{"type":"string","minLength":1,"description":"Which of its models. Both or neither, because a model name only means anything to the provider that serves it."}},"required":["agent","model"]}},"required":["repo","runId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","description":"The conversation that was opened, already holding the failure. Open it to watch, or attach to its turn."}},"required":["conversationId"],"additionalProperties":false}}}}}}},"/ci/seen":{"post":{"operationId":"ci.seen","summary":"Mark the pipelines as read","description":"Silences the badge for breakages already looked at. Takes nothing, because the view is read as a whole, and the daemon stamps its own clock so a browser with the wrong time cannot mark future failures as already seen.","tags":["Pipelines"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"seenAt":{"type":"number","description":"The timestamp that was written, handed back so a caller can update without asking again."}},"required":["seenAt"],"additionalProperties":false}}}}}}},"/prepush/state":{"get":{"operationId":"prepush.state","summary":"How the pre-push check is going","description":"The verdict, or the progress so far. Nothing is addressed by id here, because there is one working tree and so exactly one check.","tags":["Pre-push check"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["idle","running","passed","failed","error","cancelled"],"description":"Where the run is. Failed and error are deliberately different: failed means the code is wrong, error means the command could not be run at all, and calling the second one a test failure would send an agent hunting a bug that is not there."},"command":{"type":"string","description":"What actually ran, echoed here rather than read back from the settings, so a result looked at after the setting changed still says what produced it."},"startedAt":{"description":"When it began, in milliseconds.","type":"number"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"},"exitCode":{"description":"How the command exited.","type":"number"},"timedOut":{"description":"It was killed for taking too long rather than finishing.","type":"boolean"},"session":{"description":"The terminal it runs in, which is where to watch it. Absent where the sandbox has no terminals, in which case there is nothing to attach to.","type":"string"},"output":{"type":"string","description":"The end of what it printed, as plain text with the colour codes and redrawn progress lines resolved away. The end rather than the beginning, because a suite's verdict is at the end. Empty while it runs, and for one that was killed."}},"required":["status","command","output"],"additionalProperties":false}}}}}}},"/prepush/run":{"post":{"operationId":"prepush.run","summary":"Run the checks before pushing","description":"Starts the suite the workspace runs before anything leaves the machine, and answers immediately. A suite takes minutes, and a request held open that long dies at the first proxy. It runs in a real terminal, so watch it there and poll for the verdict.","tags":["Pre-push check"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/prepush/cancel":{"post":{"operationId":"prepush.cancel","summary":"Stop the pre-push check","description":"Kills the run. It settles as cancelled and the push it was gating does not go.","tags":["Pre-push check"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/public":{"get":{"operationId":"public.list","summary":"What is published to the internet","description":"Everything currently in the outbox and the address it answers on. There is no call to read a published file back: it is served openly to anyone with the link, which is the entire point of having put it there.","tags":["Outbox"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"description":"Your public address, which every file's own hangs off. Absent on a sandbox with nowhere to publish to.","type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Where it sits inside the outbox."},"size":{"type":"number","description":"Size in bytes."},"modifiedAt":{"type":"number","description":"When it last changed, in milliseconds."},"url":{"description":"Its public address. Absent when this sandbox has no outside address, or when the file is being refused.","type":"string"},"blocked":{"description":"Why a file sitting in the outbox is not being served: a hidden name, a credential-shaped name, contents that look like a token, or sheer size. Only the publisher sees this; a stranger asking for the same file gets the same nothing every other miss gets.","type":"string"}},"required":["path","size","modifiedAt"],"additionalProperties":false},"description":"What the outbox holds."}},"required":["files"],"additionalProperties":false}}}}}}},"/public/publish":{"post":{"operationId":"public.publish","summary":"Put a file on the internet","description":"Copies a workspace file or folder into the outbox, where it is served to anyone with the link and no sign-in. Answers with the address.","tags":["Outbox"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"What to publish, as a workspace path. It is copied rather than moved, so a repository does not lose its build output because somebody shared it."}},"required":["path"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","description":"Where it landed inside the outbox."},"url":{"description":"Its public address. Absent on a sandbox with nowhere to publish to.","type":"string"}},"required":["path"],"additionalProperties":false}}}}}}},"/public/unpublish":{"post":{"operationId":"public.unpublish","summary":"Take something off the internet","description":"Withdraws one published entry. When the last one goes, the outbox goes with it, so its existing at all always means something is published.","tags":["Outbox"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"What to withdraw, as a path inside the outbox rather than a workspace path."}},"required":["path"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/share":{"get":{"operationId":"share.list","summary":"Conversations published as pages","description":"Every conversation that has been turned into a read-only page, with its link. There is no call to read one back: the page itself is the read, and it answers to anyone who has the link.","tags":["Sharing"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"shares":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The share's own id, minted fresh each time, so sharing one conversation twice gives two links. Deliberately not the conversation's id, which is memorable by design and would make a page's address guessable."},"conversationId":{"type":"string","description":"Which conversation it was taken from."},"title":{"type":"string","description":"The title on the page, which is the sharer's choice rather than the conversation's own."},"detail":{"type":"string","enum":["messages","everything"],"description":"How much travels: the two speakers' words alone, or the whole record including the agent's work and thinking, which necessarily publishes the code and command output in it."},"sharedAt":{"type":"number","description":"When the snapshot was taken, in milliseconds. A share is frozen, so this dates what a recipient can see rather than when the conversation happened."},"messages":{"type":"number","description":"How many messages are behind the link."},"url":{"description":"The page's address. Absent on a sandbox with nowhere to publish to.","type":"string"}},"required":["id","conversationId","title","detail","sharedAt","messages"],"additionalProperties":false},"description":"Every conversation currently published as a page."}},"required":["shares"],"additionalProperties":false}}}}}},"post":{"operationId":"share.create","summary":"Publish a conversation","description":"Renders a conversation into a page anybody with the link can read, without signing in. Answers with the link, so nothing has to be listed again to find it.","tags":["Sharing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","minLength":1,"description":"Which conversation to publish."},"title":{"type":"string","minLength":1,"maxLength":80,"description":"The title for the page. The conversation's own name is only what a dialog would open with."},"detail":{"type":"string","enum":["messages","everything"],"description":"How much to publish. Two levels rather than a set of switches, because every extra toggle is another thing to get wrong about a link that cannot be recalled."}},"required":["conversationId","title","detail"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The share's own id, minted fresh each time, so sharing one conversation twice gives two links. Deliberately not the conversation's id, which is memorable by design and would make a page's address guessable."},"conversationId":{"type":"string","description":"Which conversation it was taken from."},"title":{"type":"string","description":"The title on the page, which is the sharer's choice rather than the conversation's own."},"detail":{"type":"string","enum":["messages","everything"],"description":"How much travels: the two speakers' words alone, or the whole record including the agent's work and thinking, which necessarily publishes the code and command output in it."},"sharedAt":{"type":"number","description":"When the snapshot was taken, in milliseconds. A share is frozen, so this dates what a recipient can see rather than when the conversation happened."},"messages":{"type":"number","description":"How many messages are behind the link."},"url":{"description":"The page's address. Absent on a sandbox with nowhere to publish to.","type":"string"}},"required":["id","conversationId","title","detail","sharedAt","messages"],"additionalProperties":false}}}}}}},"/share/update":{"post":{"operationId":"share.update","summary":"Refresh a published page","description":"Re-renders an existing page from the conversation as it stands now. Same link, newer contents.","tags":["Sharing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which share to re-take. Its link stays the same, which matters because it has already been sent."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The share's own id, minted fresh each time, so sharing one conversation twice gives two links. Deliberately not the conversation's id, which is memorable by design and would make a page's address guessable."},"conversationId":{"type":"string","description":"Which conversation it was taken from."},"title":{"type":"string","description":"The title on the page, which is the sharer's choice rather than the conversation's own."},"detail":{"type":"string","enum":["messages","everything"],"description":"How much travels: the two speakers' words alone, or the whole record including the agent's work and thinking, which necessarily publishes the code and command output in it."},"sharedAt":{"type":"number","description":"When the snapshot was taken, in milliseconds. A share is frozen, so this dates what a recipient can see rather than when the conversation happened."},"messages":{"type":"number","description":"How many messages are behind the link."},"url":{"description":"The page's address. Absent on a sandbox with nowhere to publish to.","type":"string"}},"required":["id","conversationId","title","detail","sharedAt","messages"],"additionalProperties":false}}}}}}},"/share/remove":{"post":{"operationId":"share.remove","summary":"Unpublish a conversation","description":"Takes the page down, so the link stops answering.","tags":["Sharing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which share to take down."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/drafts":{"get":{"operationId":"drafts.list","summary":"Posts waiting for your approval","description":"The queue of things an agent has written and would like to publish. Nothing here has gone anywhere yet.","tags":["Post drafts"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"drafts":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string","minLength":1,"description":"Where it should go. A plain name, so a new site needs no change here; an unknown one simply fails when it tries to post."},"actsAs":{"description":"Whose name it goes out under. Needed for anywhere that requires being logged in, because an unwatched turn naming nobody is allowed no account at all. Never guessed: one site can be connected five times over, and picking for you means picking wrong in public with no undo.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"content":{"type":"string","minLength":1,"description":"The post itself."},"title":{"description":"A title, where the site wants one.","type":"string"},"target":{"description":"Where on the site: a community, a channel. Or the address of the thing this replies to, in which case it is a reply, and on some sites the difference between a thread's address and one comment's is the difference between talking to the room and answering the person.","type":"string"},"media":{"description":"Anything to attach, as workspace paths.","type":"array","items":{"type":"string"}},"scheduledAt":{"description":"When it should go out, in milliseconds. An agent may propose without one and you set it when approving; an approved draft with no time goes as soon as it is picked up.","type":"number"},"status":{"default":"proposed","description":"Where it is: proposed by the agent, approved by you, being sent, sent, or failed. Rejecting is deleting it; retrying is approving a failed one again.","type":"string","enum":["proposed","approved","posting","posted","failed"]},"createdAt":{"description":"When it was written, in milliseconds.","type":"number"},"postingAt":{"description":"When sending started, in milliseconds. Needed to tell a send that is under way from one whose run died mid-flight, which the scheduled time cannot: a post due last week is not a post that has been sending since last week.","type":"number"},"postedAt":{"description":"When it went out, in milliseconds.","type":"number"},"postedUrl":{"description":"Where it landed, when the site hands back an address. The one thing a sent draft can offer that reading it cannot: the post itself, to go and look at.","type":"string"},"error":{"description":"Why it failed, written as a sentence for a person to read rather than as a code.","type":"string"},"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The draft's id."}},"required":["platform","content","status","id"],"additionalProperties":false},"description":"The queue."},"invalid":{"type":"array","items":{"type":"string"},"description":"Drafts that could not be read at all. Listed rather than skipped, because an agent writes these files directly and a malformed one would otherwise never post and never say why."}},"required":["drafts","invalid"],"additionalProperties":false}}}}}},"post":{"operationId":"drafts.upsert","summary":"Approve, edit or retry a draft","description":"All three are the same act with a different field changed, so they share one call. Send the draft back as you want it.","tags":["Post drafts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","minLength":1,"description":"Where it should go. A plain name, so a new site needs no change here; an unknown one simply fails when it tries to post."},"actsAs":{"description":"Whose name it goes out under. Needed for anywhere that requires being logged in, because an unwatched turn naming nobody is allowed no account at all. Never guessed: one site can be connected five times over, and picking for you means picking wrong in public with no undo.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"},"content":{"type":"string","minLength":1,"description":"The post itself."},"title":{"description":"A title, where the site wants one.","type":"string"},"target":{"description":"Where on the site: a community, a channel. Or the address of the thing this replies to, in which case it is a reply, and on some sites the difference between a thread's address and one comment's is the difference between talking to the room and answering the person.","type":"string"},"media":{"description":"Anything to attach, as workspace paths.","type":"array","items":{"type":"string"}},"scheduledAt":{"description":"When it should go out, in milliseconds. An agent may propose without one and you set it when approving; an approved draft with no time goes as soon as it is picked up.","type":"number"},"status":{"default":"proposed","description":"Where it is: proposed by the agent, approved by you, being sent, sent, or failed. Rejecting is deleting it; retrying is approving a failed one again.","type":"string","enum":["proposed","approved","posting","posted","failed"]},"createdAt":{"description":"When it was written, in milliseconds.","type":"number"},"postingAt":{"description":"When sending started, in milliseconds. Needed to tell a send that is under way from one whose run died mid-flight, which the scheduled time cannot: a post due last week is not a post that has been sending since last week.","type":"number"},"postedAt":{"description":"When it went out, in milliseconds.","type":"number"},"postedUrl":{"description":"Where it landed, when the site hands back an address. The one thing a sent draft can offer that reading it cannot: the post itself, to go and look at.","type":"string"},"error":{"description":"Why it failed, written as a sentence for a person to read rather than as a code.","type":"string"},"id":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"The draft's id."}},"required":["platform","content","id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/drafts/{id}":{"delete":{"operationId":"drafts.remove","summary":"Reject a draft","description":"Throws one away unposted.","tags":["Post drafts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]*$","description":"Which draft."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/info":{"get":{"operationId":"system.info","summary":"What this sandbox is","description":"The sandbox's own identity and state: which workspace it holds, which image it runs, what it is called, and the list of calls it actually implements. Start here, because a browser is routinely newer than the sandbox it is talking to and this is how it finds out what is there.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"What this sandbox is called.","type":"string"},"image":{"description":"The image it is running.","type":"string"},"version":{"description":"The version of that image.","type":"string"},"latest":{"description":"The newest published version on its channel.","type":"string"},"updateAvailable":{"description":"Whether those two differ.","type":"boolean"},"runtimes":{"description":"Which agent runtimes can serve a turn right now, keyed by runtime. Absent until the first check has run, which reads the same as every entry being unknown.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"state":{"type":"string","enum":["ready","unavailable","unknown"],"description":"Whether this runtime can serve a turn. Unknown is a real answer rather than a soft no: a check that could not run must not grey out a provider you can in fact use."},"detail":{"description":"Why it cannot, and what to do about it. Absent when it can.","type":"string"},"checkedAt":{"type":"number","description":"When it was last checked, in milliseconds."}},"required":["state","checkedAt"],"additionalProperties":false}},"channel":{"description":"Which release channel this sandbox follows.","type":"string"},"previousImage":{"description":"The image the last update replaced, which is what a rollback would return to. Absent means there is nothing to go back to.","type":"string"},"updateNotes":{"description":"What is in the update, in the words of the people it is for, newest first. Absent or empty whenever there is nothing worth saying, which reads on screen exactly as it did before there were notes at all.","type":"array","items":{"type":"string"}},"moreUpdateNotes":{"description":"How many further notes there are beyond the ones sent, for a sandbox left alone a long time. Absent or zero means you have all of them.","type":"number"},"breakingNotes":{"description":"What the update takes away, uncapped, because a warning that fell off a shortened list is a breaking update taken unwarned. Absent for the overwhelming majority, which break nothing.","type":"array","items":{"type":"string"}},"staged":{"description":"An update already downloaded and built on the machine running this container, waiting only for the restart that applies it. That restart is seconds, where an unprepared update is minutes, which is a different decision entirely. Absent when nothing is waiting.","type":"object","properties":{"version":{"description":"What the downloaded build says it is. Absent means ready but unnamed, never that nothing is ready.","type":"string"},"channel":{"type":"string","description":"Which channel it was taken from. Not necessarily the one this sandbox follows: downloading a beta build is not the same as moving onto beta."},"at":{"type":"number","description":"When the download finished, in milliseconds, which answers whether this is still the update being offered."}},"required":["channel","at"],"additionalProperties":false}},"additionalProperties":false}}}}}}},"/system/manifest-problems":{"get":{"operationId":"system.manifestProblems","summary":"Settings files the sandbox could not read","description":"Anything the daemon tripped over in its own configuration on disk: a file it had to fall back from, a key it did not recognise, an entry it skipped. Separate from the identity call because it goes stale for a different reason, namely a file changing.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path. The file is the unit somebody fixes, which is why problems are grouped by it."},"problems":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["unreadable","unknownKey","invalidEntry"],"description":"What to do about it. Unreadable means the whole file is being ignored and everything in it is at its default. An unknown key means only that key is ignored. An invalid entry means one item of a list was skipped and the rest is fine."},"detail":{"type":"string","description":"What exactly was wrong."},"suggestion":{"description":"The name it was probably meant to be, when one is close enough to guess honestly.","type":"string"}},"required":["kind","detail"],"additionalProperties":false},"description":"Everything currently wrong with it. A file with nothing wrong is absent rather than present and empty."}},"required":["path","problems"],"additionalProperties":false}}}}}}}},"/system/session":{"post":{"operationId":"system.session","summary":"Trade a sign-in for a session","description":"Exchanges a verified sign-in, or a session that has not expired yet, for a fresh session the daemon minted. That session is the credential every other call carries, and calling this again with a live one renews it.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"The credential every other call carries. Present it as a bearer token."},"expiresAt":{"type":"number","description":"When it stops working, in milliseconds, so a caller can renew ahead of it without reading the token."},"email":{"type":"string","description":"Who the sandbox verified you as."}},"required":["token","expiresAt","email"],"additionalProperties":false}}}}}}},"/events":{"get":{"operationId":"system.events","summary":"The live event stream","description":"A stream held open for as long as you want it, carrying heartbeats so a caller notices the sandbox dying at once, batches of file changes so a tree or an editor can refresh itself, and the roster of who else is looking. Give it an id for this connection to appear in that roster; leave it out and you watch without being seen.","tags":["System"],"parameters":[{"name":"clientId","in":"query","schema":{"type":"string"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"hello"},"workspaceId":{"type":"string"},"routes":{"type":"array","items":{"type":"string"}},"shapes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"build":{"type":"string"},"boot":{"type":"object","properties":{"ready":{"type":"boolean"},"startedAt":{"type":"number"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"state":{"type":"string","enum":["pending","running","done","failed"]},"ms":{"type":"number"}},"required":["key","label","state"],"additionalProperties":false}}},"required":["ready","startedAt","steps"],"additionalProperties":false}},"required":["kind","workspaceId"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"heartbeat"}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"boot"},"ready":{"type":"boolean"},"startedAt":{"type":"number"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"state":{"type":"string","enum":["pending","running","done","failed"]},"ms":{"type":"number"}},"required":["key","label","state"],"additionalProperties":false}}},"required":["kind","ready","startedAt","steps"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"workspaceChanged"},"paths":{"type":"array","items":{"type":"string"}}},"required":["kind","paths"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"reposChanged"},"repos":{"type":"array","items":{"type":"string"}}},"required":["kind","repos"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"refsChanged"},"repos":{"type":"array","items":{"type":"string"}}},"required":["kind","repos"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"runtimeChanged"},"domains":{"type":"array","items":{"type":"string"}}},"required":["kind","domains"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"presence"},"users":{"type":"array","items":{"type":"object","properties":{"clientId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"picture":{"type":"string"},"role":{"type":"string","enum":["viewer","collaborator","maintainer","owner"]},"idle":{"type":"boolean"},"view":{"type":"string"},"sessionId":{"type":"string"},"path":{"type":"string"}},"required":["clientId","email","role","idle"],"additionalProperties":false}}},"required":["kind","users"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"agents"},"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The conversation id, which is how every other call addresses it."},"sessionId":{"description":"The provider session behind the last turn. It is retired whenever the model or account changes.","type":"string"},"title":{"description":"What to call it: the first prompt cut to one line, unless somebody renamed it.","type":"string"},"status":{"type":"string","enum":["idle","running","awaiting","stopping","stopped","resuming","ready","landed","conflict","error","interrupted"],"description":"What it is doing. Stopping and stopped are the two halves of somebody pressing stop, because a cancel is not instant; resuming means the sandbox is already putting right whatever killed the turn."},"failure":{"description":"Why the last turn failed, in the words it died on. Absent unless it did, and cleared the moment it runs again. Carried here because the word error on its own is not an answer, least of all for a run nobody was watching.","type":"string"},"provider":{"type":"string","minLength":1,"description":"Which model provider it runs on."},"harness":{"type":"string","enum":["native","claude-code"],"description":"Which agentic loop it runs on."},"model":{"description":"What its last turn ran with. Kept per conversation so opening it restores the choices made in it, rather than whatever some other tab last picked.","type":"string"},"effort":{"description":"How hard that turn was told to think.","type":"string"},"thinking":{"description":"Whether that turn showed its reasoning.","type":"boolean"},"fast":{"description":"Whether that turn asked for higher speed. What was asked for, not what was served.","type":"boolean"},"tier":{"description":"How hard its last turn looked to the complexity judge. What the next turn's preview needs, not what actually ran.","type":"string","enum":["fast","standard"]},"tierHold":{"description":"Whether this conversation is pinned to the picked model, so a turn that looks simple is never moved to a cheaper one.","type":"boolean"},"account":{"description":"Which connected account paid for it.","type":"string"},"branch":{"description":"The branch its private copy works on. Absent for a conversation that works directly in the shared tree.","type":"string"},"autoLand":{"description":"This conversation's own answer to whether its work merges automatically. Absent means it follows the sandbox-wide setting, which is the common case.","type":"boolean"},"resumeAfterOutage":{"type":"boolean"},"landRequested":{"description":"A collaborator has asked a maintainer to merge this work. Cleared by whichever merge or discard answers it. Absent means nobody is waiting.","type":"object","properties":{"email":{"type":"string","description":"Who asked."},"name":{"description":"Their display name.","type":"string"},"at":{"type":"number","description":"When they asked, in milliseconds."}},"required":["email","at"],"additionalProperties":false},"origin":{"description":"Where the conversation came from when nobody typed it: a chat mention, a visitor's message, a webhook. Absent means a person started it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"forkedFrom":{"description":"The conversation this one was cut from. Recorded once and never cleared: it is the relationship, not a pending state.","type":"object","properties":{"conversationId":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$"},"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"files":{"type":"string","enum":["then","now"]}},"required":["conversationId","index","files"],"additionalProperties":false},"base":{"description":"The commit its private copy started from, shortened.","type":"string"},"costUsd":{"description":"What it has cost so far, in dollars. A helper agent's spend is its own and is not folded in here.","type":"number"},"inputTokens":{"description":"Tokens sent.","type":"number"},"outputTokens":{"description":"Tokens received.","type":"number"},"contextTokens":{"description":"How much of the window the conversation currently fills.","type":"number"},"contextWindow":{"description":"How large that window is.","type":"number"},"activity":{"description":"What it is doing at this moment.","type":"object","properties":{"tool":{"description":"The last tool it reached for.","type":"string"},"target":{"description":"What it reached for that tool with: a file, a command, a URL.","type":"string"},"todo":{"description":"The item on its own list that it is working through.","type":"string"}},"additionalProperties":false},"landedMessageDraft":{"description":"The whole story of this merge's commit message being written: which models were asked, how long each took, what refused and in what words. Forgotten on restart, which is right, because a restart also killed the drafting it describes.","type":"object","properties":{"startedAt":{"type":"number","description":"When the drafting began, in milliseconds."},"steps":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","minLength":1,"description":"Which provider was asked."},"model":{"type":"string","minLength":1,"description":"Which of its models."},"status":{"type":"string","enum":["asking","answered","refused","skipped"],"description":"How this one went. Skipped means it was not asked at all, because it refused a few minutes ago and the walk stepped over it."},"at":{"description":"When it started being asked, in milliseconds. Absent for one that was skipped, which cost no time.","type":"number"},"ms":{"description":"How long it took. Absent while it is still being asked.","type":"number"},"reason":{"description":"Why it refused, in its own words.","type":"string"}},"required":["provider","model","status"],"additionalProperties":false},"description":"Each model that was asked, in the order they were spent, so the list is the timeline. Empty with no outcome means the diff is still being read."},"outcome":{"description":"How it ended. Absent means it is still going.","type":"string","enum":["written","failed"]},"reason":{"description":"The one-line account of a failure, for a screen with one line to spend. The steps carry each model's own words.","type":"string"},"finishedAt":{"description":"When it ended, in milliseconds.","type":"number"}},"required":["startedAt","steps"],"additionalProperties":false},"landedMessage":{"description":"What this conversation's merged work is called, once the drafting above has finished. It arrives on the same push that ends the draft, so the promise and the answer travel together.","type":"object","properties":{"subject":{"type":"string","description":"One line saying what the merged work did, read off the code rather than off the opening request. A conversation that asks for an audit and then spends four turns fixing what it found needs a subject about the fixes."},"note":{"description":"The same change said to somebody who uses the product, for a repository that keeps a changelog. Usually absent, because most changes are not ones a user would notice.","type":"string"},"breaking":{"description":"What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions.","type":"string"}},"required":["subject"],"additionalProperties":false},"startedAt":{"description":"When the running turn started, in milliseconds. Absent when none is running.","type":"number"},"updatedAt":{"type":"number","description":"When it last did something, in milliseconds. Reading it does not count."},"seenAt":{"description":"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.","type":"number"},"attention":{"type":"object","properties":{"plan":{"type":"boolean","description":"It has proposed a plan and is waiting for a yes."},"question":{"type":"boolean","description":"It has asked you something."},"permission":{"type":"boolean","description":"It wants to use a tool it needs permission for."},"service":{"type":"boolean","description":"It wants to spend money on a paid service and is waiting for approval. The one pause where waiting costs it the whole call."},"capability":{"type":"boolean","description":"It needs something connected that is not connected yet."},"conflict":{"type":"boolean","description":"Its work cannot be merged without somebody resolving a clash."}},"required":["plan","question","permission","service","capability","conflict"],"additionalProperties":false,"description":"Which kinds of waiting-for-you it is doing."},"turns":{"description":"Turns it has finished.","type":"number"},"toolUses":{"description":"Tools it has used, over its whole life.","type":"number"},"subagents":{"description":"Helper agents this one delegated to. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.","type":"object","properties":{"running":{"type":"number","description":"Helpers working right now."},"total":{"type":"number","description":"Helpers it has started over its whole life."}},"required":["running","total"],"additionalProperties":false},"diff":{"description":"Everything it has written, measured from where it started. Independent of how much has been merged.","type":"object","properties":{"files":{"type":"number","description":"Files touched."},"insertions":{"type":"number","description":"Lines added."},"deletions":{"type":"number","description":"Lines removed."}},"required":["files","insertions","deletions"],"additionalProperties":false},"landedPresence":{"description":"Present only when some of what it merged has since been thrown away. Absent is the steady state: its presence is the signal, so an ordinary card spends no line on it.","type":"object","properties":{"landed":{"type":"number","description":"Paths this conversation merged in."},"present":{"type":"number","description":"How many of them are still there, either pending or committed."}},"required":["landed","present"],"additionalProperties":false},"loop":{"description":"The loop driving this conversation, if one is. Absent for an ordinary conversation, which is nearly all of them.","type":"object","properties":{"state":{"type":"string","enum":["running","done","exhausted","stalled","overspent","stopped","error"],"description":"How the loop is going."},"iteration":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Which round it is on."},"maxIterations":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many rounds it will attempt before giving up."},"goal":{"type":"string","description":"What it is looping towards."}},"required":["state","iteration","maxIterations","goal"],"additionalProperties":false},"workflow":{"description":"The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.","type":"object","properties":{"runId":{"type":"string","description":"The run this belongs to, which is how a board groups its steps together."},"name":{"type":"string","description":"The workflow's name."},"step":{"type":"string","description":"Which step this conversation is on now. It moves when steps are chained."},"index":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"This step's place in the workflow, counting from one."},"total":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"How many steps the workflow has."}},"required":["runId","name","step","index","total"],"additionalProperties":false},"archivedAt":{"description":"When it was put away, in milliseconds. Nothing was lost: its branch, its record and every counter stayed, and bringing it back gives it a fresh working copy. Absent means it is live on the board.","type":"number"}},"required":["id","status","provider","harness","updatedAt","attention"],"additionalProperties":false}},"rev":{"type":"number"}},"required":["kind","agents","rev"],"additionalProperties":false}]},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/system/presence":{"post":{"operationId":"system.presence","summary":"Say what you are looking at","description":"Reports which view, conversation or file this connection is on, or that it has gone idle. The daemon fans it back out on the event stream so everyone else's roster updates.","tags":["System"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clientId":{"type":"string","description":"This connection's own id, the same one it gave the event stream."},"idle":{"type":"boolean","description":"Whether the person has stopped doing anything."},"view":{"description":"Which view they are on.","type":"string"},"sessionId":{"description":"Which conversation they have open.","type":"string"},"path":{"description":"Which file they are looking at. Sent whole rather than merged: leaving a field out clears it, so a tab that closes a file drops the path in the same report.","type":"string"}},"required":["clientId","idle"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/system/usage":{"get":{"operationId":"system.usage","summary":"What has been spent","description":"Token and cost totals per account, added up from the record of every finished turn.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string"},"account":{"type":"string"},"turns":{"type":"number"},"inputTokens":{"type":"number"},"outputTokens":{"type":"number"},"cacheReadTokens":{"type":"number"},"cacheCreationTokens":{"type":"number"},"costUsd":{"type":"number"}},"required":["provider","account","turns","inputTokens","outputTokens","cacheReadTokens","cacheCreationTokens","costUsd"],"additionalProperties":false}}},"required":["accounts"],"additionalProperties":false}}}}}}},"/system/terminals":{"get":{"operationId":"system.terminals","summary":"Open terminals","description":"The terminal sessions this sandbox is holding, which is what a terminal panel rebuilds its tabs from after a reload. The live typing and output run over a separate socket; this is the list.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Its id, and what the close route takes."},"label":{"description":"What to call it on screen.","type":"string"},"kind":{"type":"string","enum":["shell","panel","agent","job","process"],"description":"What sort of thing it is: a terminal somebody opened, a repository's dev server, where an agent's commands run, a job the sandbox started, or a background process that is watched rather than typed into."},"running":{"type":"boolean","description":"Whether it is alive. A finished one-shot job leaves a dead shell behind, which reads as false and is how it gets swept up."},"activityAt":{"type":"number","description":"When it last produced output, in milliseconds. Zero means it did not say, which is unknown rather than 1970."},"exitCode":{"description":"How the last thing in it ended. Absent while that pane is still alive.","type":"number"},"command":{"description":"What is running in it right now. Absent when it is sitting at a prompt. Not a second spelling of whether it is alive: this says whether anything is happening, which is what a close button should ask about before it ends something.","type":"string"},"extensionId":{"description":"Which extension declared this process, when one did.","type":"string"},"processName":{"description":"Which of that extension's processes it is, which together with the id above addresses its start and stop routes.","type":"string"},"help":{"description":"The agent has stopped at something only a person can clear, and is waiting at this terminal. Present only while it is waiting.","type":"object","properties":{"requestId":{"type":"string","description":"What to send back when you answer, through the agent reply route."},"message":{"type":"string","description":"What the agent needs, in its own words."},"requestedAt":{"type":"number","description":"When it asked, in milliseconds."}},"required":["requestId","message","requestedAt"],"additionalProperties":false}},"required":["name","kind","running","activityAt"],"additionalProperties":false},"description":"Every live surface the sandbox is holding, in one list, because the question they all answer is the same one."}},"required":["sessions"],"additionalProperties":false}}}}}}},"/system/terminals/{name}":{"delete":{"operationId":"system.killTerminal","summary":"Close a terminal","description":"Destroys one terminal session and whatever was running inside it.","tags":["System"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which terminal."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/system/terminals/{name}/scrollback":{"get":{"operationId":"system.terminalScrollback","summary":"A terminal's history as plain text","description":"What has scrolled past in one terminal, as text you can select and copy. The live view is a picture of a screen on the far side of a socket, with nothing in the page to select, so scrolling back and copying is this call rather than a gesture.","tags":["System"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which terminal."}},{"name":"lines","in":"query","schema":{"default":20000,"description":"How far back to ask for. Clamped to the history that actually exists.","type":"number","minimum":1,"maximum":100000},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Which terminal this is from."},"text":{"type":"string","description":"The history, oldest line first, with wrapped lines rejoined so a copied address or path comes back whole."},"lines":{"type":"number","description":"How many lines you got."},"truncated":{"type":"boolean","description":"It stopped because you asked for that many, not because the history ran out."}},"required":["name","text","lines","truncated"],"additionalProperties":false}}}}}}},"/system/browsers":{"get":{"operationId":"system.browsers","summary":"Browsers the agent has open","description":"Every browser a conversation currently has running and the pages inside each one. The picture of what they are showing comes over a separate socket; this is the roster.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Its id, and what the close route takes."},"label":{"type":"string","description":"What to call it on screen: the open page's title, or its site, or which browser this is."},"server":{"type":"string","description":"Which browser drives it: the credential-free one, or a signed-in account's. The difference between a throwaway page and one logged in as you, which is worth saying out loud."},"running":{"type":"boolean","description":"Whether it is still open. A closed one is listed for a while with the pages it had, as the record of where the agent went."},"activityAt":{"type":"number","description":"When it last did anything, in milliseconds."},"finishedAt":{"description":"When it closed, in milliseconds. Absent while it is open.","type":"number"},"help":{"description":"The agent has hit something only a person can clear: a captcha, a password it does not hold, a check on your phone. Present only while it is waiting.","type":"object","properties":{"requestId":{"type":"string","description":"What to send back when you answer, through the agent reply route."},"message":{"type":"string","description":"What the agent needs, in its own words."},"requestedAt":{"type":"number","description":"When it asked, in milliseconds."}},"required":["requestId","message","requestedAt"],"additionalProperties":false},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable for the life of the page, which is what lets a tab survive a refresh of this list. Its address changes as the agent navigates and its position changes when a sibling closes."},"title":{"description":"The page's title. Absent mid-navigation, which is exactly when a tab still has to be drawn.","type":"string"},"url":{"type":"string","description":"Where it is."},"active":{"type":"boolean","description":"The one the agent last touched, or for a finished session, the one it ended on. Exactly one page has this."}},"required":["id","url","active"],"additionalProperties":false},"description":"Every page it has open. A browser holds several at once, which is the reason it is listed apart from the terminals."}},"required":["name","label","server","running","activityAt","pages"],"additionalProperties":false},"description":"Every browser the agents have running, open or recently closed."}},"required":["sessions"],"additionalProperties":false}}}}}}},"/system/browsers/{name}":{"delete":{"operationId":"system.closeBrowser","summary":"Shut a browser down","description":"Closes one of the agent's browsers. Its next attempt to use that browser then fails as though it had crashed, which is the honest account of somebody pulling the plug.","tags":["System"],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Which browser."}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/system/subagents":{"get":{"operationId":"system.subagents","summary":"Helpers the agents have started","description":"Every helper agent this sandbox's conversations have delegated work to, whichever tool started it, with what each one is doing.","tags":["System"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The id of the tool call that started it, which every side already holds, so a card links to its helper with the id it has and the helper points back the same way."},"kind":{"type":"string","enum":["subagent","codex","grok"],"description":"What sort of helper: one the runtime spawned, or a separate tool the agent drove from a shell. It changes only how you watch it."},"conversationId":{"type":"string","description":"The conversation whose turn started it, and the way back to the chat it belongs to."},"agentType":{"description":"What kind of helper it is.","type":"string"},"description":{"description":"What it was asked to do, in one line.","type":"string"},"model":{"description":"Which model it runs on.","type":"string"},"spawnDepth":{"description":"How deep in the chain it sits, where one means the turn itself started it. A helper can start helpers, and a flat list that could not say so would read as though the turn started all of them.","type":"number"},"background":{"description":"The parent carried on working instead of waiting for it. This is the whole reason the list exists: such a helper used to be invisible until its result landed, sometimes minutes later.","type":"boolean"},"status":{"type":"string","enum":["pending","running","blocked","completed","failed","killed","paused"],"description":"How it is going. Blocked means it needs an answer, which a parent and an operator act on differently from it simply working."},"startedAt":{"type":"number","description":"When it started, in milliseconds."},"endedAt":{"description":"When it finished, in milliseconds. Absent while it works.","type":"number"},"activityAt":{"type":"number","description":"When it last did anything, in milliseconds."},"tokens":{"description":"What it has spent. Its own, so a parent's cost and the sum of its helpers' are two different true numbers.","type":"number"},"toolUses":{"description":"How many tools it has used.","type":"number"},"lastTool":{"description":"The last one it reached for.","type":"string"},"summary":{"description":"Its report: what it concluded, without opening its record. The question a finished helper gets read for.","type":"string"},"error":{"description":"Why it failed, when it did.","type":"string"},"terminal":{"description":"The terminal its command runs in, when there is one. Absent for a helper with no process of its own, which is watched by reading its record instead.","type":"string"}},"required":["id","kind","conversationId","status","startedAt","activityAt"],"additionalProperties":false},"description":"Every helper this sandbox's conversations have started."}},"required":["sessions"],"additionalProperties":false}}}}}}},"/system/subagents/{id}/transcript":{"get":{"operationId":"system.subagentTranscript","summary":"A helper agent's record","description":"The full record of one delegated helper, in the same shape as any other conversation. It comes live from the parent turn while the helper works, and from stored history once it has finished.","tags":["System"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant","notice"],"description":"Who said it. A notice is neither side: it is something that happened to the turn, recorded so a reopened conversation can say it. Without those, a turn a provider refused ends on the user's message and reads as broken."},"text":{"type":"string","description":"The words."},"sentAt":{"description":"When it was sent, in milliseconds. On the user's rows only, because that is the only moment actually known: a turn's own frames arrive with no clock, so stamping the agent's rows could only ever mean the whole turn's start or end.","type":"number"},"attachments":{"description":"Files attached to this message, as workspace paths.","type":"array","items":{"type":"string"}},"checkpointId":{"description":"The saved point this message can be rewound to. Looked up on each read rather than stored, so what is offered is exactly what is still there to go back to.","type":"string"},"thinking":{"description":"What the agent was reasoning about.","type":"string"},"tools":{"description":"The tool calls this part of the turn made.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"notes":{"description":"What the sandbox added to this message before the model saw it. Carried on the message rather than as rows of their own, because they genuinely were part of what was sent.","type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The one line a reader sees, on a row that opens to the text below."},"text":{"type":"string","description":"The note itself, which is also exactly what the model was told."}},"required":["title","text"],"additionalProperties":false}},"placed":{"description":"A person wrote this in the agent's voice, with no turn behind it. Marked for the human re-reading the conversation months later, so their own words do not pass as the agent's. The agent itself never sees the mark.","type":"boolean"},"noticeAction":{"description":"A one-press follow-up this recorded notice offers, by name. The chat decides what it does and whether it still applies.","type":"string","enum":["tierHold"]}},"required":["role","text"],"additionalProperties":false},"description":"The conversation, in order. Each block of the agent's prose is its own message with the tools that block introduced, which is what reproduces the way it actually unfolded."}},"required":["messages"],"additionalProperties":false,"$defs":{"__schema0":{"type":"object","properties":{"id":{"type":"string","description":"The call's id."},"name":{"type":"string","description":"Which tool."},"category":{"type":"string","enum":["read","edit","delete","move","search","execute","think","fetch","other"],"description":"What kind of thing it does: read, edit, delete, move, search, run, think, fetch. Named the same way whatever the backend called the tool."},"status":{"type":"string","enum":["pending","in_progress","completed","failed"],"description":"How it went."},"target":{"description":"What it acted on, in one line: a file, a command, an address.","type":"string"},"locations":{"description":"The files it touched.","type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The file, as a workspace path, whatever directory the tool was run from."},"line":{"description":"Which line, counting from one.","type":"number"}},"required":["path"],"additionalProperties":false}},"content":{"description":"What it produced: text, a change to a file, or a picture.","type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"text","description":"Plain output."},"text":{"type":"string","description":"What the tool said."}},"required":["type","text"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"diff","description":"A change to a file."},"path":{"type":"string","description":"Which file, as a workspace path."},"oldText":{"description":"What was there. Absent for a new file, or where the previous contents are not known.","type":"string"},"newText":{"type":"string","description":"What is there now."},"truncated":{"description":"One of the two sides was too large to send whole.","type":"boolean"}},"required":["type","path","newText"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"image","description":"A picture the tool produced."},"path":{"type":"string","description":"Where it is, as a workspace path. A path rather than the bytes, because the workspace already serves it, sending it inline would bloat every stored record, and this way the picture stays openable afterwards."}},"required":["type","path"],"additionalProperties":false}]}},"children":{"description":"Calls a delegated helper made, nested under the call that started it, so a reopened conversation redraws the delegation rather than collapsing it into one result.","type":"array","items":{"$ref":"#/$defs/__schema0"}},"thinking":{"description":"What the agent was reasoning about around this call.","type":"string"}},"required":["id","name","category","status"],"additionalProperties":false}}}}}}}}},"/system/computers/{id}/sandboxes/{slug}":{"post":{"operationId":"system.manageMachineSandbox","summary":"Drive a sandbox on one of your own computers","description":"Start, stop, restart, update, rebuild, roll back or remove a sandbox running on a machine you own, relayed over the connection that machine holds open. The answer is a stream because the slowest of these takes minutes, and it is the same stream whichever you ask for. The daemon adds no opinion: the machine enforces its own permissions and a refusal arrives as the last line, in the machine's words, naming the switch to flip.","tags":["System"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"op":{"type":"string","enum":["start","stop","restart","prepare","update","rebuild","rollback","remove","logs"]},"hash":{"type":"string"}},"required":["op"]}}}},"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"oneOf":[{"type":"object","properties":{"event":{"const":"message"},"data":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"line"},"text":{"type":"string"}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"result"},"message":{"type":"string"}},"required":["kind","message"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"error"},"message":{"type":"string"}},"required":["kind","message"],"additionalProperties":false}]},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event","data"]},{"type":"object","properties":{"event":{"const":"done"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]},{"type":"object","properties":{"event":{"const":"error"},"data":{},"id":{"type":"string"},"retry":{"type":"number"}},"required":["event"]}]}}}}}}},"/activity":{"get":{"operationId":"activity.list","summary":"What the agent has done out in the world","description":"The audit trail of actions taken on outside services. Read-only on purpose: entries are written by the sandbox alone, which is what makes it a record worth trusting.","tags":["Activity"],"parameters":[{"name":"provider","in":"query","schema":{"description":"Narrow it to one outside service.","type":"string"},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"default":100,"description":"How many entries to return.","type":"number","minimum":1,"maximum":500},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"description":"Only entries older than this timestamp, so paging walks backwards through the feed.","type":"number"},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The entry's own id."},"at":{"type":"number","description":"When it happened, in milliseconds. Also what you page by."},"provider":{"description":"Which outside service, when one was involved. Absent for the sandbox's own events.","type":"string"},"account":{"description":"Which account handled it. Absent for the sandbox's own events and for work run on a provider's default.","type":"string"},"direction":{"type":"string","enum":["in","out","system"],"description":"Whether something arrived, something went out, or the sandbox did it to itself."},"type":{"type":"string","description":"Exactly what happened: a message received or sent, a reaction, a turn starting or ending, a rule doing something. A rule that ran and passed says nothing here, because a feed of green ticks is one the eye learns to skip."},"channelId":{"description":"Which channel or thread it happened in.","type":"string"},"author":{"description":"Who sent it, for something that arrived.","type":"string"},"content":{"description":"The message, in full, whichever direction it went.","type":"string"},"method":{"description":"The verb of an outgoing call.","type":"string"},"endpoint":{"description":"The address of an outgoing call. Credentials travel in headers, so they are never here.","type":"string"},"sessionId":{"description":"The provider session behind it.","type":"string"},"turnId":{"description":"Ties one turn's entries together. A turn writes several, and read as separate rows they say one thing several times, so a feed groups on this.","type":"string"},"conversationId":{"description":"Which conversation. This, rather than the provider session, is what the same agent means across a feed, because a session is retired whenever the model changes.","type":"string"},"title":{"description":"What that conversation was called at the time. Copied in rather than looked up, because an audit entry must still read as words years later, after the conversation has been renamed or pruned.","type":"string"},"origin":{"description":"What woke the conversation from outside, when something did. It is how a turn gets filed under the chat service that caused it rather than under the model that served it.","type":"object","properties":{"automationId":{"type":"string"},"provider":{"type":"string"},"channelId":{"type":"string"},"author":{"type":"string"}},"required":["automationId","provider"],"additionalProperties":false},"automationIds":{"description":"Which automations were involved.","type":"array","items":{"type":"string"}},"outcome":{"description":"How it ended.","type":"string","enum":["ok","error"]},"error":{"description":"What went wrong, when something did.","type":"string"},"extra":{"description":"Whatever else the source had to say: attachments, participants, a recording's path. Shape varies by source.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","at","direction","type"],"additionalProperties":false},"description":"The audit entries, newest first."}},"required":["events"],"additionalProperties":false}}}}}}},"/activity/status":{"get":{"operationId":"activity.status","summary":"Whether the audit trail is being kept","description":"Which sources are feeding the record and whether each is working.","tags":["Activity"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"connections":{"type":"array","items":{"type":"object","properties":{"capabilityId":{"type":"string","description":"Which connection."},"provider":{"type":"string","description":"Which service it is."},"gateway":{"type":"string","enum":["ready","connecting","pairing","disconnected","idle"],"description":"Idle means it is up but has nothing to listen for, which is different from a connection that should be up and is not. Pairing means somebody started a sign-in and never finished it, which no amount of waiting will fix."},"lastError":{"description":"The most recent thing that went wrong on it.","type":"string"}},"required":["capabilityId","provider","gateway"],"additionalProperties":false},"description":"Each source feeding the record, and whether it is working. Probed now rather than remembered."},"voice":{"description":"A voice call the sandbox is currently in, when it is in one.","type":"object","properties":{"channelId":{"type":"string","description":"Which channel."},"channelName":{"type":"string","description":"What it is called."},"startedAt":{"type":"number","description":"When it joined, in milliseconds."},"participants":{"type":"array","items":{"type":"string"},"description":"Who else is in it."}},"required":["channelId","channelName","startedAt","participants"],"additionalProperties":false}},"required":["connections"],"additionalProperties":false}}}}}}},"/logs":{"get":{"operationId":"logs.list","summary":"Logs the sandbox keeps","description":"Every log file the daemon owns: captured terminal output, command runs, and the daemon's own log. Read-only, because only the sandbox writes them.","tags":["Logs"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Its name, which is what the read route takes."},"sizeBytes":{"type":"number","description":"Size in bytes."},"modifiedAt":{"type":"number","description":"When it last changed, in milliseconds."}},"required":["name","sizeBytes","modifiedAt"],"additionalProperties":false},"description":"Every log the sandbox keeps: captured terminal output, command runs, and its own log."}},"required":["files"],"additionalProperties":false}}}}}}},"/logs/file":{"get":{"operationId":"logs.read","summary":"Read part of a log","description":"A window of one log file's text. A window rather than the whole thing, because a busy log outgrows any single answer.","tags":["Logs"],"parameters":[{"name":"name","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Which log. It travels in the query rather than the address, because log names contain slashes."},"allowEmptyValue":true,"allowReserved":true},{"name":"bytes","in":"query","required":false,"schema":{"default":65536,"description":"How much of the end to read. The newest bytes win when the file is larger.","type":"number","minimum":1,"maximum":1048576},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Which log this is from."},"sizeBytes":{"type":"number","description":"How large the whole file is."},"text":{"type":"string","description":"The end of it, as text."},"truncated":{"type":"boolean","description":"There is more before what you got."}},"required":["name","sizeBytes","text","truncated"],"additionalProperties":false}}}}}}},"/logs/client":{"post":{"operationId":"logs.report","summary":"Report what the browser saw","description":"Errors the app caught, stalls it measured, and recoveries it performed, written to a log of their own. The browser is the only witness to these, so without it a bug someone hit in their own browser leaves no record at all.","tags":["Logs"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"events":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"seenAt":{"type":"number","description":"When the browser saw it, in milliseconds."},"level":{"type":"string","enum":["warn","error"],"description":"How bad it was."},"event":{"type":"string","minLength":1,"maxLength":100,"description":"What kind of thing it was, as a stable name."},"message":{"type":"string","maxLength":2000,"description":"What it said."},"route":{"description":"Which page they were on.","type":"string","maxLength":300},"requestId":{"description":"Which daemon call it belonged to, when it belonged to one.","type":"string","maxLength":100},"build":{"description":"Which build of the app was running.","type":"string","maxLength":100},"fields":{"description":"Whatever else was worth keeping.","type":"object","propertyNames":{"type":"string","maxLength":60},"additionalProperties":{"anyOf":[{"type":"string","maxLength":4000},{"type":"number"},{"type":"boolean"}]}}},"required":["seenAt","level","event","message"]},"description":"What the browser has to report, oldest first."}},"required":["events"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"recorded":{"type":"number","description":"How many were written down."}},"required":["recorded"],"additionalProperties":false}}}}}}},"/push/config":{"get":{"operationId":"push.config","summary":"What a device needs to subscribe","description":"The public key and settings a browser or app needs before it can register for notifications from this sandbox.","tags":["Push notifications"],"parameters":[{"name":"id","in":"query","schema":{"description":"Which device is asking. Without it the answer can only speak for the sandbox as a whole, which is rarely the question.","type":"string","minLength":1},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"publicKey":{"type":"string","description":"The key a browser needs in order to subscribe. Native apps ignore it."},"subscribed":{"type":"boolean","description":"Whether the asking device is already registered, so a toggle can show its real state instead of trusting the device's own permission, which can be granted with nothing behind it."}},"required":["publicKey","subscribed"],"additionalProperties":false}}}}}}},"/push/subscribe":{"post":{"operationId":"push.subscribe","summary":"Send notifications to this device","description":"Registers one device. The sandbox only interrupts you on the three moments where attention is genuinely wanted: a turn has finished, the agent is stuck on a question, and something is waiting for approval.","tags":["Push notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"webpush","description":"A browser, which the sandbox can reach directly and encrypt end to end."},"endpoint":{"type":"string","format":"uri","description":"Where that browser's push service accepts sends. It also identifies the device everywhere else in this group."},"keys":{"type":"object","properties":{"p256dh":{"type":"string","minLength":1,"description":"The browser's public key, for encrypting what is sent."},"auth":{"type":"string","minLength":1,"description":"The browser's secret, for the same."}},"required":["p256dh","auth"],"description":"What the browser handed you when it subscribed. Post it back exactly as it came; nothing reshapes it."}},"required":["kind","endpoint","keys"]},{"type":"object","properties":{"kind":{"type":"string","const":"relay","description":"A native app, whose operating system only accepts sends from the app's publisher, so the sandbox posts through a relay instead. The message passes through that relay readable, which is the price of the publisher having to be in the loop."},"url":{"type":"string","format":"uri","description":"Where to post a send. Recorded rather than assumed, so the sandbox need not know any platform by name."},"deviceId":{"type":"string","minLength":1,"description":"The device's id, which also identifies this registration everywhere else in this group."},"secret":{"type":"string","minLength":1,"description":"Proof that this sandbox may notify this device. The relay never learns which sandbox is calling."}},"required":["kind","url","deviceId","secret"]}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/push/unsubscribe":{"post":{"operationId":"push.unsubscribe","summary":"Stop notifying a device","description":"Removes one registered device. Others keep receiving.","tags":["Push notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Which device: a browser's push address, or a native install's device id."}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true,"description":"Always true. A route that answers this either did the thing or refused with a status; there is no third outcome to report."}},"required":["ok"],"additionalProperties":false}}}}}}},"/push/test":{"post":{"operationId":"push.test","summary":"Send a test notification","description":"Proves the whole chain end to end. Worth having, because there are four separate places a notification can be lost that nobody can inspect from the outside: the device's permission, its registration, the sandbox's key, and the delivery service.","tags":["Push notifications"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"delivered":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many devices actually accepted it. A count rather than a yes, because this button exists to prove a chain nobody can inspect, and the sandbox having accepted the request is not the question being asked."}},"required":["delivered"],"additionalProperties":false}}}}}}}},"components":{"securitySchemes":{"session":{"type":"http","scheme":"bearer","description":"A signed-in person's session, minted by `POST /system/session` and presented as `authorization: Bearer …`. It reaches everything the owner reaches."},"control":{"type":"apiKey","in":"header","name":"x-intentic-control","description":"A program's credential, minted by the owner with a scope fixed at that moment. The raw `ict_…` value is returned exactly once; only its hash is stored, and it can be revoked per token.\n\nScopes, widening downward:\n- `editor` — One conversation: run a turn, answer a card it parked on, read transcripts, search the tree. What an editor bridge holds. It cannot see the fleet and it cannot land work.\n- `read` — Observation only: the fleet, past sessions, workspace search, listening ports. The one genuinely narrow rung, which is why it exists separately rather than as a politeness.\n- `drive` — Everything read sees, plus making an agent work: start, answer, steer and stop a turn. Stops short of anything that moves code into the main tree. A stolen token at this rung is the agent's reach.\n- `land` — Everything drive does, plus merging a conversation's worktree into the main tree, and discarding one. Separate because the usual arrangement is a program that works and a person who decides."}}}}