# Clankie documentation Generated from https://docs.clankie.bot. Each section below is one page or one repository document; relative links have been rewritten to the repository. ---
Clankie, a little robot with a sprout on his head

The Clankie field guide

Start with
a hello.

Clankie is your personal assistant with a memory and a personality of his own. Talk through an idea, make something together, or give him a bigger job and meet the team he brings along.

New here? Meet him in the film. Ready to talk? Find your path below.

01 · Have it handled

Meet him in the app

A private machine looked after for you. Start a conversation on your iPhone or iPad.

Start with hosted Clankie →
02 · Make it your own

Run him yourself

Open source. Your machine, your models, your tools. Begin on a Mac and grow from there.

Start with the DIY setup →
Everyday company. Useful work.

What shall we do today?

You can start small. These are example requests, not a transcript.

Think it throughHelp me turn these notes into a plan for the weekend. Keep what mattersRemember that I like quiet places and short walks. Make somethingLet's make a little website for my bakery. Show me a preview first.

Learn to work with Clankie →

A window into his world

Messages first. A little town underneath.

Talk to Clankie in Messages. When a job grows into a team, Commons lets you see who's working and open the conversation behind each figure. Terminal is there when you want a closer look.

The app reaches your Clankie, whether his machine is hosted or one you run yourself. Available features depend on that setup and your device's access.

Find your way around the app · Pair your own Mac

For curious minds and busy hands

There is more under the hood.

Choose his models and skills, connect your services, or bring your own coding agents. The friendly face sits on an open-source service you can inspect and build on.

01

Customize Clankie

Models, personality, skills, agents, Discord, voice, and play.

02

How he works

One persistent service, many conversations, and the connections around it.

03

The reference shelf

Console, CLI, API, architecture, and the complete technical library.

--- # Get started Clankie is a personal assistant with a memory, a personality, and tools to get things done. You can have his machine looked after for you, or run him yourself. Choose the setup you want; both start with a conversation. | | Hosted Clankie | Run him yourself | | ------------------------- | --------------------------------------- | ----------------------------------------------------------------------- | | Where he lives | A private machine managed for you | Your Mac, or an advanced Linux deployment | | Start here | Your account and the iPhone or iPad app | Install, connect a model, open the console | | Models | AI credits from your plan or account | Your provider subscription, API key, or local model | | Optional depth | Helper agents and the app's work views | Models, skills, coding agents, Discord, voice, and service integrations | | Who maintains the machine | The hosted service | You | Current plans and app availability live on [clankie.bot](https://clankie.bot). The Discord calls and game night in the promo use the Mac setup; they are not part of the hosted app experience advertised there. ## Hosted: start in the app New hosted signup is currently closed. The steps below describe the journey when signup opens and the app can be installed; existing accounts can still sign in. 1. Check the [official app link](https://clankie.bot/#app) to make sure you can install the iPhone or iPad app before buying a plan. That page names the current distribution channel. 2. Open [Get Clankie](https://clankie.bot/#get) to create or sign in to your account. Choose a plan with AI credits and complete checkout. A machine-only plan needs a pack or top-up from your account before Clankie can answer. 3. Your account shows when Clankie is ready. Open its secure app link on your iPhone or iPad, review the connection, and connect. If you are using another screen, scan the QR or copy the complete secure link into the app. 4. Open **Messages** and choose **Clankie**. Say hello, tell him what you are working on, or ask for help. Your account manages the hosted machine and plan. The app is where you talk to him. Your first conversation uses the managed model; you do not need a Mac installation, a model subscription, or an API key. If your AI credits run out, return to your account to add credits. Prefer a terminal as well? The Mac console can connect to an existing hosted Clankie with `clankie connect hosted`. The [connection reference](https://docs.clankie.bot/cli/#local-and-hosted-connection-modes) explains sign-in, supported commands, and device revocation. ## DIY: start on your Mac The downloadable bundle supports **Apple silicon and macOS 14 or newer**. It includes the runtime; you do not need Node or a source checkout. ```sh curl -fsSL https://raw.githubusercontent.com/Volpestyle/clankie/main/install.sh | sh clankie ``` On first launch, choose **Run Clankie on this Mac**. The launcher starts his service and opens the terminal console. `/setup` asks how he should think: connect a supported subscription, API key, or local provider, then choose a model. Sign-ins and keys go into the credential broker. Stay in `/setup`: it next signs this Mac in for phone access and opens the pairing QR. Open the app from the [official app link](https://clankie.bot/#app), scan the QR and accept access. Setup waits until your phone is active, then lets you connect services through `/connect` and give your first agent a folder and a task. Review and send the request to Clankie; he handles the native hire and any harness sign-in. `/agents` opens the team once a live seat is observed. Each optional step can be skipped, and Escape or `/cancel` stops the flow. Return to `/setup` to continue from the actual device and agent state. `/setup rooms` lists the other settings. An app account, Discord and worker agents are not required for a local conversation. The software is free to run. Your model providers and other connected services may charge for use. See [installation details](https://github.com/Volpestyle/clankie/blob/main/docs/distribution.md) for checksums, version pinning, and the installed layout. Developers can [run from source](https://github.com/Volpestyle/clankie/blob/main/CONTRIBUTING.md); experienced operators can use the [Linux deployment](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md). ## Bring your Mac's Clankie into the app The guided `/setup` path handles sign-in and pairing. For individual steps or a direct route: 1. Choose a route to your Mac. For account-based remote access, open `/remote-access` and sign in with the emailed code. For a direct connection on your own network, configure a device-reachable direct route with `clankie gateway direct`; this path does not require a Clankie account. The [pairing reference](https://docs.clankie.bot/cli/#pair-json-timeout-sec-review-days-n-count-n) explains the device doorway and supported addresses. 2. Run `clankie autostart enable` if you want him to start when you log in. Leave the Mac awake and online while you want to reach it. Optionally run [`clankie awake on`](https://docs.clankie.bot/cli/#awake) to keep it awake while plugged in. 3. Install the app through the [official app link](https://clankie.bot/#app). 4. Run `clankie pair`. Scan the secure QR or paste the **complete secure link** into the app, review the offered access, and connect. Pairing offers are single-use. The QR or complete link carries the configured gateway and direct routes; the app prefers the gateway when both are available. Short codes are for direct private connections. `clankie devices` lists paired devices and lets you revoke them. The [pairing reference](https://docs.clankie.bot/cli/#pair-json-timeout-sec-review-days-n-count-n) covers options and recovery. Closing the console leaves the local service running. Sleeping or shutting down the Mac makes that Clankie unavailable until the machine returns. Pairing the app does not move a local Clankie to hosted infrastructure. ## Your first conversation Try: “I'm putting together a small project. Help me turn this idea into a plan, and remember that I prefer short, practical answers.” Give him the idea and any constraints that matter. For a bigger task, ask him to explain his plan and show the result when it is ready. Next: [using Clankie](https://docs.clankie.bot/using-clankie/) for everyday requests and working with his team, or [customize Clankie](https://docs.clankie.bot/diy/) with models, skills, and connections. ## If you cannot reach him | What you see | Where to start | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Local console cannot answer | Run `clankie doctor` and `clankie status`; `/setup` handles missing model setup. | | App cannot reach your Mac | Check that the Mac is awake and online, then check remote-access sign-in or the configured direct route. | | Pairing offer expired or was already used | Create a new offer; use its full secure link or QR. | | Hosted connection or account problem | Check your account, then use [support](https://clankie.bot/support/). | | A particular feature is unavailable | Ask Clankie what is configured, or consult [the reference index](https://docs.clankie.bot/reference/). | Share error messages and versions with support, never pairing links, keys, or sign-in codes. --- # Using Clankie Start with what you want to do. Clankie can help you think something through, make a draft or picture, remember a preference, and bring in help for a larger job. You do not need to learn agent terminology to talk to him. ## Everyday help Give him a little context and a useful outcome. A few things to try: - “Turn these scattered notes into a plan for the weekend.” - “Help me draft a kind, clear reply. Here's what happened.” - “Compare these three ideas and tell me what you would choose.” - “Make a birthday-card picture with a sleepy robot in a garden.” You can steer him as he works: add a constraint, correct an assumption, or ask for a shorter answer. If a request needs a connection he does not have, he should say what is missing. A request to draft a message and a request to send it are different instructions; tell him which outcome you want. ## Memory and personality Tell him what matters, and ask him to keep it: “Remember that I prefer quiet places and short walks.” You can ask what he remembers and correct a stale note. In the local console, `/memory` lets you inspect, edit, and forget memories directly. His memories are selected notes, separate from conversation history. Notes stay until forgotten. He can search them, but memory is not a promise to reproduce every past message. The [memory reference](https://github.com/Volpestyle/clankie/blob/main/docs/memory.md) explains storage and privacy between conversations and Discord rooms. Clankie has a character of his own. On a DIY installation, `/persona` lets you shape his name and character; it does not require rebuilding the software. ## Making things together Explain the purpose, audience, and constraints. A useful brief might be: “Help me make a simple website for my bakery. Start with the opening page and show me a preview before publishing anything.” Ask for the finished file or a preview you can inspect. Files he delivers belong to the conversation, so you can return to the result. Images require an image model; short video generation is a separate, optional capability on a configured DIY installation. A render may take time. Ask what is still running rather than starting a duplicate request. ## Bigger jobs and helper agents Clankie can do work himself or assemble a team. Tell him the outcome, your constraints, and any decisions you want to make yourself. You can ask who is doing what, open a helper's conversation, and steer the work as it develops. Tell him how you want agents to work: “Commit and push without asking, ask me before official releases, and keep reports short and plain.” Owner defaults apply across projects; a project can override each choice or inherit it. Verification can request independent review and sealing, or making the change, running relevant checks and reading the results. View or change these choices by talking to Clankie or using the app's project settings. Every helper receives the resolved preferences for its project. On a DIY installation, `clankie fleet status` and `clankie doctor --json` expose the same workspace preferences for agents you launch yourself. Preferences keep the existing access boundaries. For DIY users, helper agents use the installed and authenticated tools you choose. [Customize Clankie](https://docs.clankie.bot/diy/#bring-your-own-team) explains the setup. Hosted worker availability depends on the service and plan; the [current plans](https://clankie.bot/#plans) are the source for those limits. The tiny town in **Commons** shows the team at work. Select a figure to reach the agent behind it, see their progress, or ask a follow-up. The **Bulletin** lists every open task, who assigned it and who holds it, with stuck work first. Tap a notice to message whoever holds it. ## The app **Messages is home.** Clankie is pinned at the top. Start there for a question, an idea, or a piece of work. Agent contacts and shared conversations let you follow a larger job without keeping a terminal open. | View | When it helps | | -------- | ---------------------------------------------------------------------------------------------------------------------- | | Messages | Talk to Clankie, read replies and files, or speak to an individual helper. | | Commons | See the team's activity as a small world of agent figures; select one to open its conversation or controls. | | Terminal | Inspect the actual terminal behind a connected worker, with direct input when your device has the required permission. | The app reaches the same service as your other connected devices. Available controls depend on the host's capabilities and the access granted to that device. You can use Messages without learning the deeper views. ## Discord, voice, and game night On a configured Mac, Clankie can join Discord conversations, speak in voice, play requested YouTube music, and play Pokémon from his own seat in a separate PokeAgents world. These are optional integrations, not part of basic setup. The official bot supports text, voice, and Activity sharing for existing play, art, animations, demos and audio. Hosted Activity routing is included in the service; customers set up no application or tunnel. The official Activity application and its verification remain release gates. Watching someone else's screen share and publishing Discord Go Live use the separate personal-lab body, with its own explicit opt-in and restrictions. Those distinctions matter when you try something you saw in the promo. Start with [Discord and play setup](https://docs.clankie.bot/diy/#hang-out-and-play). ## Leaving work running Closing a window does not stop the service. A local Mac must stay awake and online; hosted availability follows its account and resource limits. For a DIY task that should continue across turns in a Pi-owned conversation, use `/goal` and explicitly enable `/autonomy`. Goals have a finite token budget; you can pause the goal or turn continuation off. This is separate from keeping an ordinary conversation open. See [ongoing work](https://docs.clankie.bot/diy/#give-him-ongoing-work) for the controls and their limits. Next: [how he works](https://docs.clankie.bot/how-it-works/) explains the service, memory, models, and connections underneath. [Get started](https://docs.clankie.bot/get-started/) covers installation and pairing. ## Give him a visual persona On a DIY installation, put PNG/JPEG/WebP images or MOV/MP4/WebM videos in a folder and run `clankie persona images set ~/Pictures/clankie-vibe`, or choose **Persona images** in `/persona`. Check `clankie persona images status`, then restart Clankie. Top-level files color his vibe: the feel of who he is, not what he looks like. Put physical character references in `appearance/`; only these feed self-portraits. He uses up to eight stills/contact sheets total. Videos need ffmpeg/ffprobe and each contributes one sheet of ten chronological tiles; audio is ignored. Status lists viewable sheet paths. His written character wins. Voice uses a short description instead of images. `clankie persona images clear` clears the selection without deleting the originals. Images are sent to your configured models when used. Hosted paths refer to folders already on the hosted machine; this does not upload files from your phone or Mac. See the [CLI reference](https://docs.clankie.bot/cli/). ## Machines and devices A **machine** is where agents run. A **device** is a paired phone or desktop portal. `clankie machines` shows each machine's Herdr sessions, availability and worker count; add `--json` for scripts. `clankie machines discover` refreshes local sessions and SSH candidates without prompting or starting remote software. Add a machine with `clankie machines add pc --ssh my-pc`, then inspect `clankie machines sessions pc`. Connect an existing session with `clankie machines sessions pc --connect work --id pc-work`. Transcript access is available immediately after adding. Named connections apply without a restart; removing a machine detaches Clankie without stopping its workers. Changing the default workspace still requires `clankie restart captain`. `clankie herdr` opens the full workspace. `clankie herdr status` prints the machine summary; `herdr status --json` includes machine rows and default-binding details. Phones and desktop portals remain under `clankie pair` and `clankie devices`. --- # Customize Clankie Make him yours: a name he answers to, a character you enjoy, a voice that sounds right, and habits that fit your life. Start with what you want him to be like; the models and connections can come after. New installation? Follow [Get started](https://docs.clankie.bot/get-started/#diy-start-on-your-mac). This guide covers customization on your own Mac. Use the console or supported CLI commands to change settings; Clankie can help you run them. Keep API keys in the setup wizards, never in a chat message. ## Persona “Keep your replies short, be curious, and use a little dry humor.” Open `/persona` to save his name, aliases, and character notes. The same character follows him across conversations, with room to speak differently at work or among friends. The [CLI equivalent](https://docs.clankie.bot/cli/#persona-set-flags) is: ```bash clankie persona set --display-name Clankie --aliases Clank,Clanks \ --character-notes "Curious, a little dry, and happy to disagree." \ --chattiness quiet --reply-policy addressed ``` Chattiness can be `quiet`, `balanced`, or `chatty`. Reply policy controls which messages he sees in admitted Discord text channels: `addressed` starts with his name or a mention and lets him follow a few messages after replying; `all` lets him read every admitted message. Neither makes him answer every message. He can always stay quiet. [`clankie persona status`](https://docs.clankie.bot/cli/#persona-status) shows what you saved. Follow the returned restart instruction to apply character changes. ## Look and vibe ### Give him a visual persona Point him at a folder in the console: “Use the images in ~/Pictures/clankie-vibe as your persona.” Or run [`clankie persona images set ~/Pictures/clankie-vibe`](https://docs.clankie.bot/cli/#persona-images-status-set-folder-clear). Files in the folder shape his **vibe**—the feel of who he is, not what he looks like. Put references for his physical appearance in an **`appearance/` subfolder**; those are the ones he uses for self-portraits. Videos work too: with ffmpeg and ffprobe installed, each becomes one contact sheet of ten chronological frames. Run [`clankie persona images status`](https://docs.clankie.bot/cli/#persona-images-status-set-folder-clear) to see what loaded and find the contact-sheet paths. Restart Clankie to apply the selection or changes to the files. His written character still takes precedence. ## Voice Open `/voice` → **Voice stack** to choose how he sounds. Realtime voice handles listening and conversation; speech output is the voice you hear. Choose OpenAI Realtime or Grok Voice for their native voices, or ElevenLabs for an external voice speaking the text OpenAI produces. ElevenLabs currently pairs with OpenAI, not Grok Voice. For ElevenLabs, paste a voice ID from your ElevenLabs voice library and choose its model in `/voice`. You need both an OpenAI API key and an ElevenLabs API key. Native OpenAI or Grok voice needs that provider's API key. The wizard stores keys in the credential broker; a chat-model subscription alone does not supply them. Once ElevenLabs is configured, the [voice CLI](https://docs.clankie.bot/cli/#voice-status-voice-model-set-model-id-voice-model-clear) can inspect it and change its model: ```bash clankie voice status clankie voice model set eleven_v4_turbo ``` `eleven_v4_turbo` selects the dialogue speech model. An unset model keeps `eleven_flash_v2_5`; `clankie voice model clear` restores that default. Check `effectiveVoice` in status for environment overrides, then [`clankie restart clankie`](https://docs.clankie.bot/cli/#restart-service) when you're ready to interrupt active calls. Provider and voice-ID selection still need `/voice`; there is no headless setter for them. Use this voice setup for a [Discord voice room](#discord). The [voice operating guide](https://github.com/Volpestyle/clankie/blob/main/apps/discord-bridge/README.md) has setup and troubleshooting details. ## Preferences “Keep the team small and use efficient models. Ask Codex to implement, then have another agent review.” Open `/fleet` to save how you like him to work. The same editor lets you choose who closes delivered work and who prepares Clankie's own setup on already-linked machines. Both default to the lead: he closes after landing, passing checks and attaching evidence, and you can reopen. Choose Owner to keep closure In Review or to require approval for machine setup. Projects may override either setting independently. Sign-ins, payments, accounts and credentials retain your decision; setup does not restart existing work. The [fleet CLI](https://docs.clankie.bot/cli/#fleet-status-fleet-set-notes-text-size-size-models-mode-fleet-clear) lets you set the same preferences: ```bash clankie fleet set --size small --models efficient \ --notes "Use Codex for implementation and another agent for review." ``` Sizes are `max`, `large`, `small`, and `solo`; model preferences are `optimal` or `efficient`. These are targets for his judgment, not hard worker or spending caps. They do not install a harness or give it credentials. Use `/effort` to pick a reasoning level supported by his model. The CLI is [`clankie effort set LEVEL`](https://docs.clankie.bot/cli/#effort-set-level-model-provider-model-effort-clear-model-provider-model). Use `/routing` to choose a routine model for everyday chat while keeping his main model for work. The CLI equivalent is [`clankie model routing set provider/model`](https://docs.clankie.bot/cli/#model-routing-status), using a model from your catalog; `clankie model routing status` shows the result. Opinionated working skills are on by default. `/skills` lets you turn them off or back on; the CLI equivalents are [`clankie skills opinionated off` and `clankie skills opinionated on`](https://docs.clankie.bot/cli/#skill-setup). Product and tool references stay available. Start a fresh session to drop guidance already loaded. ## Skills “Make a skill for how we review this project's changes.” Type `$` in the console to browse available skills, or `/skill-name task` to use one. Skills give him reusable instructions and tool knowledge; they do not grant credentials or machine access. Add your own `SKILL.md` in `~/.agents/skills/my-skill/`, or in a project's `.agents/skills/my-skill/` for conversations working in that project. He also reads his bundled roots and the skills directory under Pi's agent directory (normally `~/.pi/agent/skills`). Use a distinct name: bundled names take precedence. Skills from those machine-wide folders are not listed on every turn; he finds them with `skill_search` when a task calls for one, and `/skill-name` still works. For the bundled selection, use `/skills` or the [skills CLI](https://docs.clankie.bot/cli/#skill-setup): ```bash clankie skills clankie skills exclude reflect clankie skills include reflect ``` Only bundled opinionated skills can be excluded; product and repo-authored skills stay on. `include` removes that exclusion but does not turn the whole class back on. There is no CLI skill installer: adding your own skill means adding its files. Start a fresh session and reopen the console to refresh its picker. The [bundled-skills guide](https://github.com/Volpestyle/clankie/blob/main/docs/bundled-skills.md) explains discovery and which worker routes receive the guidance. ## Discord “Hang out in our server, but only jump in when we address you.” Use `/discord` to choose his servers and channels, and `/persona` for reply policy and chattiness. For a quieter room: ```bash clankie persona set --reply-policy addressed --chattiness quiet clankie discord status ``` The [persona flags](https://docs.clankie.bot/cli/#persona-set-flags) control his conversational habits; the [Discord CLI](https://docs.clankie.bot/cli/#discord-set-field-value-discord-clear-field) controls where he participates. With reply policy `addressed`, `--live-message-window` sets how many messages he follows live after his last reply (default 5), before later messages wait for a catch-up. Zero removes that live follow-up window; `all` reads every admitted message regardless. It controls what he sees, never what he must say. For tokens and first setup, follow the [Discord connection guide](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md#configure-discord). ### The voice room “Join me in voice.” Configure the allowed voice servers and channels in `/discord`, then use `/voice` for his speech setup. The equivalent [Discord fields](https://docs.clankie.bot/cli/#discord-set-field-value-discord-clear-field) include `--voice-enabled`, `--voice-guild-ids`, `--voice-channel-ids`, and `--voice-channel-id`. `--voice-join-policy ambient` keeps invitations with the configured ambient participants; `guild_members` lets members of allowed servers summon him, subject to the channel rules. When you invite him, he can answer in text, greet the room aloud, or arrive quietly. Room joins and departures give him context to decide whether to stay, speak, or leave; an empty room does not start an automatic leave timer. You can ask him to leave, too. [Voice behavior](https://github.com/Volpestyle/clankie/blob/main/apps/discord-bridge/README.md#body-behavior) has the details; joining and leaving are conversation actions, not standalone `clankie` CLI commands. ### Consent and the transcript log Use `/discord` to choose voice consent. The default is `explicit`: each participant runs `/clankie voice-consent opt-in` in Discord for the active session. With `presence`, being in his active voice channel counts as consent. **The owner handles disclosure**: everyone in that room should know he transcribes while he is there. An explicit `/clankie voice-consent opt-out` always wins under either policy. [ADR 0071](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0071-presence-as-consent-voice-policy.md) explains that choice. The [CLI equivalent](https://docs.clankie.bot/cli/#discord-set-field-value-discord-clear-field) is `clankie discord set --voice-consent-policy explicit`; choose `presence` only for a room whose participants understand it. Full transcript logging is off by default. Enable it in `/discord`, or with `clankie discord set --voice-transcript-logging-enabled on`, then open `/vt` in the console to read it, or run `clankie discord transcripts`. It retains consented speech and Clankie's generated reply wording in a private local log, separate from the content-free receipts. Replies carry playback outcomes; interrupted text may include an unheard ending. No raw audio is saved. Turn it off with the same flag set to `off`. The [voice log reference](https://github.com/Volpestyle/clankie/blob/main/apps/discord-bridge/README.md#configure) covers its location. Check [`clankie discord status`](https://docs.clankie.bot/cli/#discord-status) for effective settings and the restart instruction. ### Who gets a shell Use `/discord` to review machine-access grants separately from room access. The [CLI fields](https://docs.clankie.bot/cli/#discord-set-field-value-discord-clear-field) are `--system-actor-user-ids`, `--system-actor-guild-ids`, and `--system-actor-channel-ids`. These grant real machine tools, including a shell running as the Clankie service user. Simply letting him read or join a room grants none of that access. An individually granted person gets machine tools in text and voice. In a shared room, that grant lasts for their turn; the next speaker does not inherit it. An official-bot DM with that person can keep a continuing work session. A trusted guild grant gives every admitted human in its scope machine access; the channel list can narrow it to selected rooms. Those rooms keep a separate, continuing work session. Everyone else stays social. See [ADR 0105](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0105-voice-is-as-capable-as-the-room-it-is-in.md) and its [lane-grant update](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0133-a-machine-grant-belongs-to-a-discord-lane.md). ### Play and share “Play Pokémon while we hang out.” Start with the [play guide](https://github.com/Volpestyle/clankie/blob/main/packages/play/README.md); [`clankie play status`](https://docs.clankie.bot/cli/#play-status) shows his current session. He needs a reachable PokeAgents world and his own [world credential](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md#world-seat), not a local emulator. The optional Discord Activity gives people a live viewer. Ask for music once he is in voice; the [media guide](https://github.com/Volpestyle/clankie/blob/main/docs/discord-media.md#youtube-music) lists the extra executables. Screen-share watching and Go Live require the separately enabled personal-lab body; the official bot cannot receive those pixels or publish Go Live. ## Then the plumbing Minecraft uses approved offline Java server profiles and shares Clankie's play body with Pokémon. Its continuous mind plays by default; explicit driver handoff lets a chosen native worker or the owner drive the same stay. Configure it through `clankie minecraft` or `/minecraft`; see the [Minecraft reference](https://github.com/Volpestyle/clankie/blob/main/docs/minecraft.md) for destination policy, action evidence, Chrome rendering and current limitations. ### Choose his models `/setup` gets the first model working. Return to `/model` to change it, `/auth` to manage provider sign-ins, and `/provider` for custom providers such as a local runtime. Use the live picker for supported models and authentication methods; provider support changes, and a subscription is not interchangeable with every API. His conversational model, image model, video model, and voice provider are separate choices. `/image-model`, `/video-model`, and `/voice` configure their own capabilities. A working chat model alone does not enable them. Credentials belong in the interactive setup flows, not in a chat message or a command flag. For scripts, [`clankie model set provider/model`](https://docs.clankie.bot/cli/#model-set-providerid-modelid) selects his conversational model. [Reasoning effort](https://docs.clankie.bot/cli/#effort-status), [routing](https://docs.clankie.bot/cli/#model-routing-status), and [compaction](https://docs.clankie.bot/cli/#model-compaction-status-model-compaction-set-tokens-model-compaction-default) have their own controls. The [model reference](https://github.com/Volpestyle/clankie/blob/main/packages/model-provider/README.md) explains custom configuration and provider resolution. ### Bring your own team “Help me implement this feature. Use Codex for the implementation and ask a second agent to review the result.” Open `/connections`; its [CLI equivalent is `clankie connections`](https://docs.clankie.bot/cli/#connections-and-runtime). Clankie's built-in service runs on [pi](https://pi.dev). Worker agents can use different supported harnesses, including Claude Code, Codex, and pi. Install and authenticate the harnesses you want on the machine that runs them. Choosing a worker harness does not replace Clankie's own model or runtime. Open `/connections` to inspect execution runtimes and accounts. Herdr supplies the native worker terminals. Clankie hires and messages supported agents through their harness channels or session APIs, including remote agents over the fleet link. See the [adapter guide](https://github.com/Volpestyle/clankie/blob/main/packages/agent-hosts/README.md) and [connection commands](https://docs.clankie.bot/cli/#connections-and-runtime). ### Give him ongoing work In the local console: ```text /goal Improve the project's onboarding guide and verify its examples /autonomy on ``` A goal gives that conversation a durable objective. Autonomy enables further turns and scheduled wakes; it does not grant new tools or access. Use `/goal` to inspect, accept a proposed goal, pause, resume, or clear the goal, and `/autonomy off` to stop new automatic continuations. Goals default to a 1,000,000 model-token budget; `/goal --tokens ` overrides it. An in-flight request or tool call may still finish. Service goals require a Pi-owned conversation; native harness seats refuse them. The [console reference](https://docs.clankie.bot/console/) owns the exact syntax. These are [console controls](https://docs.clankie.bot/cli/#console-only-not-missing), with no standalone headless goal or autonomy command. The service must remain running. For a Mac, [`clankie autostart enable`](https://docs.clankie.bot/cli/#autostart-enable-autostart-disable-autostart-status) starts it at login; it does not keep the Mac awake. ### Connect your services Use `/connect` for available account integrations; [`clankie accounts`](https://docs.clankie.bot/cli/#accounts-list-accounts-connect-provider-accounts-disconnect-provider-accounts-apps) inspects the body's GitHub, Linear and Google catalog. Google Gmail/Calendar consent is read-only; Drive uses a file picker and selected-file permissions, with reading tools. Secret entry stays in the console. Account and mailbox connections have different setup and access rules. Clankie's mailbox connection is his own address, not automatic access to your personal inbox. The [credential guide](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md) owns account identities and secret storage. Work tracking follows the project's existing convention: Linear, GitHub issues, or files. The [work-items package](https://github.com/Volpestyle/clankie/blob/main/packages/work-items/README.md) explains discovery. Following Linear notifications is a separate opt-in from connecting the account; use the [setup reference](https://docs.clankie.bot/cli/#linear-status-linear-follow-on-off). His work-tracking guidance prefers useful visual evidence: screenshots or short clips of tangible results, charts of measured data, and diagrams of systems and flows. Visuals belong on the relevant work item with captions explaining what they show; proposals and sample data are labeled, with tests and source links supporting claims about completed work. Workers do not automatically inherit every connected account. [Worker access](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md) describes explicit, restricted grants and the current isolation limits. ### Work with your computer “Find this in my browser and help me finish it.” Use `/browser` to inspect his browser settings; [`clankie browser harnesses`](https://docs.clankie.bot/cli/#browser-harnesses-browser-delegate-on-off) lists the computer-use harnesses he can hire. Clankie uses Browser Use Pi with his own browser profile for browsing tasks. Machine-authorized turns can keep JavaScript variables and helpers between browser calls; ordinary social turns have browser-only tools. Inspect them with `clankie browser tools`. Harder work in your existing apps can go to an installed computer-use harness. Native macOS control also has a Peekaboo path with documented limits. The [desktop-control reference](https://github.com/Volpestyle/clankie/blob/main/docs/desktop-control.md) distinguishes available tools from proven behavior; installing Clankie does not silently grant macOS permissions or guarantee background input isolation. ### Build on the open-source service “Help me build an integration with your service.” Start with [`clankie status`](https://docs.clankie.bot/cli/#health-status) to check the running services, or [`clankie mcp`](https://docs.clankie.bot/cli/#mcp-lane-operator-conversation-id) for an MCP client. The console is one client of the service. A headless CLI, HTTP API, and MCP projection expose configuration and authorized tools for scripts, integrations, and other agent seats. Start with the [reference index](https://docs.clankie.bot/reference/), then the [architecture](https://github.com/Volpestyle/clankie/blob/main/docs/architecture.md) and [contributor guide](https://github.com/Volpestyle/clankie/blob/main/CONTRIBUTING.md). The public service is Apache-2.0 except the separately licensed AGPL native Discord media executable. The companion app and managed service have separate, private sources. The [repository license section](https://github.com/Volpestyle/clankie#license) states the boundary. The [Linux deployment](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md) is an advanced alternative to the Mac setup, with a different capability set. --- # Reference Choose the reference for the job. [Get started](https://docs.clankie.bot/get-started/) covers setup; [using Clankie](https://docs.clankie.bot/using-clankie/) explains the everyday experience; [Customize Clankie](https://docs.clankie.bot/diy/) introduces the optional technical depth. ## Operate Clankie | I want to… | Read | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Find a console command or keyboard shortcut | [Console](https://docs.clankie.bot/console/) | | Script configuration, inspect status, or troubleshoot | [CLI](https://docs.clankie.bot/cli/) | | Install a pinned release or understand its files | [Distribution](https://github.com/Volpestyle/clankie/blob/main/docs/distribution.md) | | Understand credentials and access | [Credentials](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md) | | Inspect what he remembers | [Memory](https://github.com/Volpestyle/clankie/blob/main/docs/memory.md) | | Configure Discord voice, music, or a watch surface | [Discord media](https://github.com/Volpestyle/clankie/blob/main/docs/discord-media.md) | | Connect coding agents and other machines | [Native agent adapters and runtime connections](https://github.com/Volpestyle/clankie/blob/main/packages/agent-hosts/README.md) | | Grant a worker limited use of a connected account | [Worker access](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md) | | Run the service on Linux | [Self-hosted Linux](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md) | ## Integrate and contribute | Reference | Owns | | ------------------------------------------------------------------------------------ | --------------------------------------------------------- | | [How he works](https://docs.clankie.bot/how-it-works/) | A readable overview of the service and its connections | | [Architecture](https://github.com/Volpestyle/clankie/blob/main/docs/architecture.md) | Current system boundaries and request flows | | [HTTP API](https://docs.clankie.bot/api/) | The service's route catalog, rendered from OpenAPI | | [Public network](https://docs.clankie.bot/network/) | The gateway's allowed host routes and their authorization | | [Library index](https://github.com/Volpestyle/clankie/blob/main/docs/README.md) | Subsystem references, design proposals, and evidence | | [Contributing](https://github.com/Volpestyle/clankie/blob/main/CONTRIBUTING.md) | Source setup, checks, and repository boundaries | The CLI reference is rendered from its canonical Markdown. The console command table, HTTP catalog, and public host-route table are generated from their source registries. Generation keeps the catalogs aligned; implementation and release availability still need their own verification. ## History and machine-readable docs [Decision records](https://github.com/Volpestyle/clankie/tree/main/docs/adr) explain why boundaries changed. They include superseded designs; use the current references above for setup. [Dated verification records](https://github.com/Volpestyle/clankie/tree/main/docs/testing) say what was tested on a particular revision and environment. [llms.txt](https://docs.clankie.bot/llms.txt) indexes these docs for agents. [llms-full.txt](https://docs.clankie.bot/llms-full.txt) contains the public guides, references, and repository architecture in one Markdown document. --- # How Clankie works Clankie lives in a persistent service. The app, terminal console, and configured Discord and voice connections are ways to reach him. His machine can be one you maintain or a private hosted machine; the service owns his conversations, memory, tools, and access in either case. You can use him without knowing the pieces below. They become useful when you want to customize him, connect a team, or understand where your work goes. ## One identity, separate conversations Clankie's character belongs to the service. A chat in the app, a project in the console, and a Discord room do not each create a new personality. They do have separate conversation histories and permissions. Sharing an identity does not mean every room receives everything said elsewhere. Self-hosted Discord voice can use OpenAI/xAI realtime or an optional Claude text brain with OpenAI transcription and ElevenLabs speech. These choices share the same room permissions and turn-taking. The owner selects the stack through `/voice` or `clankie voice brain`, then restarts the active body. The Sonnet path is an experiment whose call latency and quality still need the owner's manual trial; it does not change the hosted service's provider selection. His built-in agent uses [pi](https://pi.dev) for models, sessions, tools, skills, and compaction. Clankie adds durable identity, memory, the connections around him, and the authority each caller carries. Optional [Claude](https://github.com/Volpestyle/clankie/blob/main/integrations/claude-plugin/README.md) and [Codex operator seats](https://github.com/Volpestyle/clankie/blob/main/integrations/codex-plugin/README.md) `clankie claude` and `clankie codex`, along with `clankie opencode`, use the same service through their native harnesses. All three support `--resume`, `--conversation ID`, `--dry-run`, and `--plugin-dir PATH`. Numbered Claude commands use the owner’s shell account command; `clankie codex2` selects the registered Codex account labelled `codex2`. Their setup, hook trust, delivery, and continuation limits are documented separately. Each fresh native launch gets its own workspace chat, including simultaneous launches in the same directory. Resume keeps that chat; an explicit conversation ID selects an existing one. Transcripts and wake channels follow the selected chat. `clankie grok` provides another operator seat on macOS with Grok Build 1.0.46 and an existing sign-in. Its visible native session carries the service persona, memory, operator tools and selected skill paths through leader IPC/ACP. `--dry-run` reviews the launch; `--conversation ID` selects a chat and `--resume` retains the original profile/session/chat after a confirmed exit. It does not accept `--plugin-dir`. Native permission prompts remain owner decisions. The selected chat can also be a Discord room. While the seat's channel is live, worker reports, room turns, wakes and watches reach that seat; new inputs return to the built-in agent after it leaves. Each channel or DM remains a separate conversation with its existing permissions. Workers report to the conversation that hired or subsequently adopted them, as resolved by the service. Without adoption, the actual Herdr parent receives the report through its attached conversation or native channel. If no eligible parent exists, the default conversation gets an explicitly tagged report and doctor/roster name the lead pane needing a bridge. This grants no tools or room permissions. Voice and text handoffs appear as individual threads under Clankie in the dock and app: who asked, what he is doing, and the result. Separate handoffs can run in parallel, with four active globally, two per room and a bounded waiting queue, and answers return to the asking room. Claude uses restricted native children. Codex uses native children only for the verified owner; everyone else uses the service's Pi threads with their original room grant until Codex can enforce a narrower child tool set. Approval requests still continue on the authenticated operator surface. ## History, memory, and goals These serve different purposes: | Store | What it gives you | | -------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Conversation history | The thread you return to, with messages, visible tool work, and delivered files. | | Memory | Selected experiences and facts that can inform later conversations. Notes stay until forgotten. | | Goal | An owner-approved objective that continues in a Pi-owned conversation with a finite token budget when autonomy is enabled. | Closing a client does not erase those records. Memory recall is bounded and filtered by the receiving conversation's authority; operator-private notes do not enter social Discord recall. Goals and scheduled wakes use the existing conversation and tool permissions. They do not create extra access. The [memory reference](https://github.com/Volpestyle/clankie/blob/main/docs/memory.md) and [CLI](https://docs.clankie.bot/cli/) own memory and continuation controls. ## Models, skills, and tools A model supplies reasoning. A tool performs an operation. A skill supplies instructions for using tools or approaching a task. Choosing a model does not install a browser, log in to an account, or authorize a Discord room to run a shell. The DIY setup lets you choose models and connect capabilities independently. Conversation, images, video, and voice have separate configuration. Hosted availability follows the managed service's current offering. See [Customize Clankie](https://docs.clankie.bot/diy/) for the practical setup and [clankie.bot](https://clankie.bot) for hosted availability. ## A team around him **Work stays where you track it:** Linear, GitHub, or task files in the repo. Clankie and his workers use one Linear-shaped tool surface; without a Linear connection it uses durable local storage. `clankie doctor` reports the active backend, and the existing `clankie work` commands keep working. **Herdr contains the agents:** their native interactive terminals remain yours to watch and use. Clankie sends assignments through each supported harness's message connection, without typing into your draft. If delivery is unavailable or uncertain, he reports that outcome. Native local message adapters cover Claude Code, Codex, Pi, OpenCode and Grok Build. Grok uses its interactive TUI's private leader IPC/ACP on macOS, pinned to 1.0.46; native sign-in/permission gaps refuse without terminal input. Prime Agent remains researched without a local hire adapter. Remote Claude and Codex hires use the fleet link and native channels. See the [adapter guide](https://github.com/Volpestyle/clankie/blob/main/packages/agent-hosts/README.md#tool-flow-and-current-support) for the message flow and current limits. The app presents those agents in Messages and, where execution seats exist, Commons and Terminal. A worker's contact can outlive its terminal session. Live activity and a completion claim are evidence to inspect, not substitutes for the finished result and its checks. Independent linked agents can initiate messages to Clankie through `message_clankie`. They discover and message seats in their own fleet through `list_fleet_seats` and `message_peer`, using the same native delivery and receipts. Peer messages are agent output and grant no owner authority; the owner can switch them off in `/fleet`. For a local source-checkout service, `clankie integrate` composes approved core and app commits, runs full checks in private worktrees and retains the tested commit evidence before landing. Named deploy holds block landing and runtime updates, with explicit audited owner overrides. See the [CLI reference](https://docs.clankie.bot/cli/). ## Finding your way in the console A local console opens the existing main conversation unless you select another with `--chat`. `/cd` selects a project workspace; tools use the selected conversation's directory. The TUI separates the things you can open: - `/chats`: personal and workspace chats with Clankie. - `/agents`: agents that are live now, and past ones that kept a thread. - `/rooms`: shared channels and read-only Discord inspection. - `/history`: all retained threads, including ongoing and offline ones. - `/sessions`: saved harness execution records. `/new` starts a fresh chat. `/btw` opens an ephemeral side question; `Ctrl+X` switches between it and the main thread, while `Ctrl+C` discards it. The [console reference](https://docs.clankie.bot/console/) owns commands and keys, and [product vocabulary](https://github.com/Volpestyle/clankie/blob/main/docs/product-vocabulary.md) defines the TUI terms. Other clients may organize navigation differently. ## Where the service and data live In local mode, the launcher keeps Clankie's service running after the console closes. Your Mac must remain awake and online; [`clankie awake on`](https://docs.clankie.bot/cli/#awake) can keep it awake while plugged in. In hosted mode, the console and app connect to a remote service; closing those clients leaves the remote work running, subject to the host's lifecycle and limits. The host stores service state and brokered credentials. macOS uses Keychain by default; Linux deployments use the documented private file backend. Model requests reach the configured provider or runtime, so running Clankie locally does not automatically make every model request local. See [credentials](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md) for the exact stores and exceptions. GitHub, Linear and Google account Connections use the body's credential broker too. The app and account page show identity and granted permissions and can disconnect. The result reports confirmed provider revocation or a permission-review link. Hosted GitHub disconnect removes local access; Clankie's shared developer secret never enters a customer body. GitHub authorization starts with a user code; Linear uses a browser return and PKCE. Google consent independently enables Gmail, Calendar or Drive reads. Gmail and Calendar use read-only scopes; Drive's file picker grants access to selected files. That grant permits edits, while Clankie's implemented tools only read. Its refresh and grouped revoke lifecycle stay on the body. The shared catalog shows each service's purpose, account, scopes and recovery status across app, dashboard and console. Provider tokens remain on the body, while device requests travel through the encrypted gateway. These flows require configured developer OAuth applications; hosted provisioning supplies their public client configuration. See [account connections](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0232-hosted-connections-use-the-body-broker.md). The public gateway routes encrypted device exchanges to the host. The host issues pairing offers and device grants and enforces them on requests. The gateway cannot decrypt those device payloads; it can see routing metadata, sizes, and timing. Accounts, model providers, and optional push delivery have separate data flows. The [network reference](https://docs.clankie.bot/network/) explains the transport boundary; the [privacy notice](https://clankie.bot/privacy/) covers the product's data handling. A self-hosted Mac can also advertise a direct device route on a reachable network. Direct pairing does not require a Clankie account and retains the host's pairing and device-grant checks. See [pairing](https://docs.clankie.bot/cli/#pair-json-timeout-sec-review-days-n-count-n) for supported routes and recovery. Customer support access has a separate owner-issued window of at most 72 hours. Read state can expose history and Clankie state while the grant remains live; it cannot send messages, change settings or read terminal output. Revocation or expiry closes the read device, including streams. Shell windows use the hosted service's separate enforcement and cannot mint a paired device. The owner-facing [`support` command](https://docs.clankie.bot/cli/) documents the controls; hosted availability depends on the coordinated service rollout. Pokémon play uses a game extension for its PokeAgents connector and session execution. Clankie keeps ownership, permissions, publishing destinations and evidence in his service. Minecraft and Rivals adoption of that shared extension contract remains follow-up work; their current play paths remain available. ## Go deeper The [architecture](https://github.com/Volpestyle/clankie/blob/main/docs/architecture.md) is the canonical current system diagram and request-flow reference. The [reference shelf](https://docs.clankie.bot/reference/) leads to the CLI, API, and subsystem guides. [Decision records](https://github.com/Volpestyle/clankie/tree/main/docs/adr) explain how the design changed; older records describe older systems. --- # The console The console is a full conversation with Clankie in your terminal. Ask for help, work on a project, configure his connections, or inspect an agent's progress. For the first installation, follow [Get started](https://docs.clankie.bot/get-started/); for models, skills, and worker setup, use [Customize Clankie](https://docs.clankie.bot/diy/). In local mode, `clankie` starts the service if needed and opens the console. In hosted mode it connects to your existing remote Clankie. First launch asks which mode you want. The transcript shows messages and tool work above the editor; `/` opens command suggestions and `Ctrl+/` opens the workbench. An ordinary terminal works; Herdr is optional for viewing the built-in workers. Opening or switching conversations starts at the latest messages. Scroll up to load older retained history in pages. The text you are reading stays in place, and live messages continue to arrive while older pages load. The local console uses the service's [HTTP API](https://docs.clankie.bot/api/). Hosted mode uses the paired-device transport and supports a smaller command set; see [connection modes](https://docs.clankie.bot/cli/#local-and-hosted-connection-modes). The tables below describe the local console. The [CLI](https://docs.clankie.bot/cli/) is the headless configuration and control reference, including its output formats and exceptions. ## Live agents While agents are seated, a dock below the editor shows status counts and up to three agents that want attention: blocked or with a broken bridge first, then working, then done. Idle agents are only counted. Another machine is named; this Mac is not. Press Down on an empty prompt to expand the dock in place into the whole fleet in the same order: Up/Down selects, Enter opens its conversation, and Escape or Up past the first row returns to the prompt. Typing anything else collapses the list and goes to the prompt. `Ctrl+G` opens the same list as a centered modal; its full name, harness, state, machine and distinct current step wrap below the list. Enter opens its existing conversation; Escape closes the modal and preserves your draft. An opened conversation shows its newest 20 turns, then follows live; older turns stay readable with `clankie conversations show ID`. A fixed bar above the transcript names the conversation on screen. In an agent's conversation it shows `◀ esc Clankie › name` with the agent's harness, state and machine, and the bar and the input border take the harness color (yellow for Claude, blue for others), so you can see who you are typing to. Messages use the same native delivery as `/agents`. Escape returns to the conversation you left without cancelling the worker, however the agent was opened: the dock, `Ctrl+G` or `/agents`. Composer drafts stay with their conversations. The strip disappears when no agents are live. When a harness sits in the selected conversation's seat (for example `clankie claude`), the footer names it, such as `claude seat`, in place of the configured model, because that harness takes the turns. From an expanded agent, `Ctrl+Y` focuses its exact pane in the full Herdr workspace. In an ordinary terminal it attaches a viewer to the existing workspace; inside that same workspace it only focuses the pane. A remote agent opens its selected machine and session over SSH. An unavailable connection reports an error. This action never starts a Herdr server. `/agents` still includes saved conversations under Past agents. The strip uses the service's fleet feed and works with his own workspace or your Herdr session. ## Slash commands Type `/` for the typeahead, `Ctrl+/` for the workbench, or `$` at a token boundary for the skill picker. `/skill-name task` invokes a loaded skill directly. A command typed bare opens its menu: `/project` edits names, caps, trackers, roles and workspaces in place; `/access`, `/accounts`, `/devices`, `/machines`, `/minecraft`, `/rivals` and `/update` work the same way, and `/update` shows what is running before it stages anything. Settings commands (`/autonomy`, `/awake`, `/browser`, `/compaction`, `/desktop`, `/routing`) list each setting with its current value; pick one to change it. Arguments run the same command the CLI does, and `/doctor json` keeps the full report. This table is generated from the console's own command registry. | Command | Aliases | Argument | What it does | | --- | --- | --- | --- | | `/access` | | | Inspect and revoke worker access to connected accounts | | `/accounts` | | `[codex\|claude [list \| add HOME --label LABEL \| remove LABEL]]` | Register local Claude profiles and Codex accounts/headroom | | `/activity` | `/watch` | | Show Clankie's current activity and live watch surface | | `/agents` | | | Live agents, and past ones that kept a thread | | `/auth` | | `[status \| anthropic]` | Manage API keys, subscription OAuth, and harness logins | | `/autonomy` | | `[on\|off\|clear]` | Show or switch Clankie's autonomous goal and wake runner | | `/awake` | | `[on\|off]` | Keep this Mac awake while plugged in, so Discord and the app stay reachable | | `/board` | `/herdr-lead`, `/herd-lead` | `[focus\|close]` | Open, focus, or close the herdr-lead companion board | | `/browser` | | `[record on\|off \| delegate on\|off \| harnesses]` | Recording, and the computer-use harnesses he can hire | | `/btw` | `/side` | `[]` | Ask an ephemeral side question on a fork of the current context | | `/cancel` | | | Abort the setup flow or sign-in that is waiting | | `/cd` | `/workspace` | `[]` | Work in another directory — opens that workspace's conversation | | `/chats` | `/chat`, `/conversation`, `/conversations` | `[]` | Talk with Clankie in personal and workspace chats | | `/claude` | | `[--resume] [--conversation ID] [--plugin-dir PATH] [--dry-run]` | Review a Clankie launch in claude | | `/clear` | | | Clear the screen (keeps model context) | | `/codex` | | `[--resume] [--conversation ID] [--plugin-dir PATH] [--dry-run]` | Review a Clankie launch in codex | | `/compaction` | | `[status\|set TOKENS\|default]` | Set how large a conversation grows before it compacts | | `/connect` | `/integrations` | `[accounts\|status\|linear\|email\|discord]` | Connect accounts and configure local services for Clankie | | `/connection` | `/settings` | | Choose local or hosted Clankie | | `/connections` | | `[json]` | Manage machines and accounts | | `/desktop` | | `[status \| quiet-hours START END TIME_ZONE \| quiet-hours off]` | Set desktop quiet hours | | `/devices` | | `[revoke ID]` | List paired phones and tablets, or revoke one | | `/discord` | | `[status\|invite\|rooms\|guide\|call]` | Connect a Discord server, choose Clankie’s role, fleet and project tracking | | `/doctor` | | `[json]` | Show this install's canonical doctor report | | `/effort` | `/reasoning` | `[status]` | Configure reasoning effort for Clankie's current model | | `/evaluator` | | `[status\|enable --harness codex\|claude\|disable\|open\|retry ID]` | Developer diagnostic: the independent evaluator in Herdr | | `/exit` | `/quit` | | Quit the console | | `/fleet` | | `[status\|resources\|clear]` | Edit how Clankie routes work across the agents he leads | | `/games` | `/gameplay` | `[on\|off]` | Configure Clankie's PokeAgent play | | `/goal` | | `[accept\|pause\|resume\|clear\|--tokens \|]` | Show, accept, start, pause, resume, or clear this conversation's goal | | `/grok` | | `[--resume] [--conversation ID] [--dry-run]` | Review a Clankie launch in Grok Build | | `/help` | `/h` | | Show available commands | | `/herdr` | | | Use an existing Herdr session or create one for Clankie | | `/history` | | `[]` | Browse all retained threads, including offline agents | | `/image-model` | | `[openai\|google\|xai\|status\|unset]` | Choose the model Clankie makes pictures with | | `/jump` | `/go` | `` | Focus a herdr agent by pane id or name (or click one he wrote) | | `/layout` | `/header`, `/banner` | `[status\|header on\|header off\|header toggle]` | Show or hide the Clankie header banner | | `/linear` | | | Configure Linear webhook wakes, rules and chat target | | `/login` | | | Sign in with your Clankie account (this Mac's remote access, or a hosted Clankie) | | `/machines` | | | Discover machines, connect sessions and manage workers | | `/memory` | `/memories` | `[status]` | Browse, edit, or forget Clankie's durable memory | | `/minecraft` | | `[configure play --model PROVIDER/MODEL --max-cost-usd N\|driver\|configure\|status\|join PROFILE\|leave\|cancel\|pause\|resume\|chat\|follow]` | Host, invite, administer, configure, and play in Minecraft | | `/model` | | `[status]` | Pick a model from the selected provider | | `/new` | | `[]` | Start a fresh conversation in the current workspace | | `/opencode` | | `[--resume] [--conversation ID] [--plugin-dir PATH] [--dry-run]` | Review a Clankie launch in opencode | | `/pair` | | `[--review --days N]` | Pair a phone or tablet with this machine | | `/persona` | | `[status]` | Edit Clankie's character, names, and how readily he speaks | | `/project` | `/projects` | | Create or edit projects, roles, limits and tracked work; read live membership | | `/provider` | | `[status]` | Choose which provider /model browses | | `/question` | | | Read, answer or cancel the current preference question | | `/refresh-tools` | | `[--pane PANE]` | Refresh running workers' Clankie tools in place | | `/remote-access` | `/gateway` | `[status]` | Remote access for this Mac (self-host only) | | `/reset` | | | Archive this conversation and start with fresh model context | | `/rivals` | | `[status\|connect URL\|start MODE\|objective\|observe\|share\|stop]` | Connect, play, observe, and share Spider-Man | | `/rooms` | | `[<name-or-path>]` | Group channels and Discord text or voice history | | `/routing` | | `[status\|set provider/model\|off\|escalate on\|off\|purpose NAME routine\|work\|default]` | Route everyday turns to a cheaper model | | `/runtime` | | `[list \| connect ID --session NAME \| disconnect ID]` | Manage machine connections (compatibility command) | | `/runtime-health` | | `[status\|on\|off\|set --cpu-percent N …]` | Runtime CPU and slow-health alarms, thresholds, and cooldown | | `/sessions` | | | List or read Claude/Codex/Grok/Pi sessions here or on SSH hosts | | `/setup` | `/onboard` | `[rooms]` | Model sign-in, phone pairing, then your first agent | | `/share` | | `[list \| request JSON]` | Control Activity shares | | `/skills` | | `[opinionated on\|off \| exclude NAME \| include NAME]` | Choose Clankie's bundled working skills | | `/status` | | | Show console and clankie service status | | `/support` | | `[list \| create read-state\|shell --hours 24 --ref REFERENCE \| revoke ID \| offer ID]` | View, grant or revoke time-limited support access | | `/trace` | | `[<lane>\|<guild:channel>\|all\|off]` | Watch another lane's activity (Discord servers, voice, gameplay) | | `/update` | | `[--ref REF \| status \| canary]` | Stage a runtime update or read its durable result | | `/video-model` | | `[xai\|status\|unset]` | Choose the model Clankie makes video with | | `/voice` | | `[status]` | Configure how Clankie sounds in Discord voice | | `/vt` | `/voice-log`, `/voice-transcripts` | `[off]` | Live tail of retained Discord voice transcripts | | `/work` | | `[project\|list\|init --release-source tags\|milestones\|both --release-lane NAME]` | Read project work, releases and goals; set the repo tracker | ## Keys | Key | What it does | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Ctrl+/` | Open the command workbench | | `Down` on an empty prompt | Expand the agent dock into the whole fleet; Up/Down selects, Enter opens its conversation, Escape returns | | `Ctrl+G` | Open the full live-agent modal; Up/Down selects, Enter opens its conversation, Escape closes | | `Ctrl+Y` in an expanded agent | Open that exact pane in its full Herdr workspace | | `Esc` in an expanded agent | Return to the previous conversation; leave the worker running | | `Ctrl+O` | Toggle every tool and bash block between preview and full output | | `Ctrl+Shift+F` | Search the transcript | | `Ctrl+Shift+V` | Toggle the live voice-transcript overlay (same as `/vt`) | | `Esc` | Interrupt the in-flight turn; the service aborts the model turn and settles the run as cancelled. A second `Esc`, or an older service, detaches the console instead and the turn continues | | `Ctrl+C` inside `/btw` | Discard the side conversation and restore the main transcript | | `Ctrl+X` inside `/btw` | Switch between the side conversation and the main thread, keeping both | | `!` on empty input | Open the inline shell in the conversation's directory | | `$` | Open the skill picker | | Click a tool or bash block | Toggle just that block | | Click a herdr pane id | Jump the session to that pane (same as `/jump`) | | Mouse wheel, drag | Scroll the transcript, select text | ## Workspaces Starting `clankie` opens the existing main Clankie conversation regardless of the launch directory. Use `--chat <conversationId>` to select another retained conversation, `/cd <path>` to select a project, or `/new [title]` to create a fresh conversation in the current workspace. Tools run in the selected conversation's workspace, not the directory of the launching terminal ([ADR 0111](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0111-a-console-process-starts-one-conversation.md)). Opening or switching conversations starts at the latest messages. Older history loads in bounded pages when you scroll up, keeping the visible text in place. The live tail continues independently while those pages load. `/cd <path>` moves to the newest retained conversation for another project, opening its first on first visit; `/cd` alone names the current one. The console's own `!` shell escape, path completion, footer, and `/status` follow the same directory. The process keeps its selection in memory rather than persisting a second session pointer. The service retains recent inactive conversation directories for explicit resume: at most 64 conversations, 30 days of inactivity, and 256 MiB. A conversation's public replay log retains at most 500 events. Active, newly created, and default-global conversations are protected from automatic removal. The service stays up when a console exits, so sibling Herdr panes do not disconnect each other. Logs live under `${XDG_STATE_HOME:-~/.local/state}/clankie/` rather than entering the fullscreen display. ## Operator behavior - Enter sends a prompt or steers Clankie's active Pi turn. Alt+Enter queues a separate follow-up after the current turn and any earlier queued prompts. The console keeps observing until all accepted inputs settle; Esc interrupts the current turn. Accepted local steers and follow-ups appear above the editor until their durable runs settle, with up to three previews and an overflow count. This view follows the active observation; switching rooms, detaching, or restarting clears it. The conversation log retains the messages. Acceptance is not execution: the service does not publish a separate dequeue event, so the preview says “awaiting completion.” These choices also apply to `/skill-name` prompts. - Failed sends return the message to the editor, preserving any newer draft. A failure before sending says **Message not sent**. If the send loses its acknowledgement, **Delivery unconfirmed** asks you to check the conversation before retrying. Sends never retry automatically; an accepted turn's dropped observation reconnects without resending its message. - `/chats` (aliases `/chat`, `/conversation`, `/conversations`) opens only personal and workspace chats with Clankie. `/chats <name-or-path>` switches directly. - Live agents appear below the editor in a two-line dock with status counts and a selected-agent preview. Statuses use the shell colors and machines are dim. `Ctrl+G` opens a scrolling modal of every agent; Up/Down selects, and Enter opens the existing persona conversation. Escape returns without cancelling the worker; drafts stay with their conversation. `Ctrl+Y` opens that exact agent's pane in its full Herdr workspace, using the selected connection. It attaches only, or focuses without nesting when already inside the same session. The dock disappears when empty. Escape closes the modal; the selected agent's full name, harness, status, machine and distinct current step wrap below its list. Unavailable-agent errors show a readable message instead of Herdr's JSON. - `/agents` opens the agents that are live now (a Herdr seat here or on a remote fleet). Offline agents that kept a thread sit behind one "Past agents" entry, newest first; offline agents without a thread are not listed, since there is nothing to open. - `/rooms` opens group channels and Discord text/voice rooms. A Discord channel ID also selects its room with `/rooms <id>`. - `/history` searches all retained threads, including agent threads. History includes ongoing threads; it does not mean archived, completed, or online. `/history <conversationId>` selects any retained thread directly. - `/sessions` browses saved harness sessions on local or SSH hosts; these are execution records, distinct from agent identities. Existing `/agents` session arguments remain supported. See the [product vocabulary](https://github.com/Volpestyle/clankie/blob/main/docs/product-vocabulary.md). Discord rooms are read-only inspection views, with expandable context and tool arguments/results (`Ctrl+O` toggles tools). Their transport still owns input; typing in the inspector cannot send to Discord or grant operator authority. Press `x` to close an inactive operator conversation; active, default, and transport-owned room conversations stay protected. Switching rebuilds the transcript and follows its live tail. `--chat <conversationId>` opens the same record directly; `clankie conversations list|show|tail` exposes it as JSON. - `/new [title]` starts and selects a conversation with fresh model context in the current workspace. The previous conversation remains available through `/conversation`. - `/btw [question]` (alias `/side`) opens an ephemeral side conversation from the current Pi branch on a clean screen at the fork boundary. Its inherited history is reference-only. Ctrl+X switches between the fork and the main thread while both stay alive; Ctrl+C discards the fork and restores the main transcript. The footer names the open side conversation from either side. - `/goal` shows the selected conversation's durable goal. `/goal <objective>` starts one with a 1,000,000 model-token budget; `--tokens <n>` overrides it. `/goal accept` activates Clankie's inactive proposal, and `pause|resume|clear` remain owner controls. Native harness heads refuse service goals because the service cannot enforce their continuation or usage budget. - Goal activation, acceptance, resume and `/autonomy on` use the owner credential; the captain bearer receives HTTP 403. The headless equivalents are `clankie conversations goal ID accept|resume` and `clankie conversations goal ID set --tokens N <objective>`. - `/autonomy on|off` controls autonomous goal continuations and scheduled self-wakes globally. `/autonomy clear` removes the selected conversation's pending wake without changing its goal. - `/cd` opens the conversation for another directory and moves the console's shell escape and completion with it. - Type `/skill-name` for direct skill invocation or `$` at a token boundary for the skill picker. The transcript records a compact `skill loaded` receipt. - `/activity` shows the current goal, commentary, intent, observed outcome, and the loopback watch URL without controlling the body. - `/skills` opens the working-skill picker (also in `/setup rooms`). Opinionated skills default on; product/tool skills always stay on. `/skills opinionated off` and `/skills exclude NAME` apply to new sessions and local hires. - `/accounts codex list` shows local Codex homes and observed quota headroom. `/accounts codex add HOME --label LABEL` registers an owner-signed-in home; `/accounts codex remove LABEL` forgets it without deleting credentials. - `/share [list | request JSON]` controls owner-only local Activity artifact shares; see [the CLI](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md#activity-shares). Hosted launch/admission remains pending. - `/games` opens a toggle dialog for PokeAgent play; press Enter to enable or disable it. `/games on|off` remains available for direct use. Restart Clankie to apply a change. Saves live with the world server, not here. - `/evaluator` (developer diagnostic) opens a menu for the independent evaluator, whose footer badge reads `evaluator on` while enabled: turn it on or off, switch harness, open its Herdr pane, read recent assessments, retry failures. `/evaluator status|enable --harness codex|claude|disable|open|retry ID` still work. - `/browser record on|off` saves each burst of Clankie's browsing as a WebM under `~/.clankie/runner/browser/recordings/`; `/browser` shows the setting. It applies from his next burst, without a restart. - `/memory` browses and edits episodes and permitted Discord person facts through operator-only APIs. - `/vt` (aliases `/voice-log`, `/voice-transcripts`) opens a live overlay of retained Discord voice transcripts, including Clankie's generated wording labeled with playback outcome. Cut-off text may include an unheard ending. `clankie discord transcripts` reads the same page headlessly. `Ctrl+Shift+V` toggles the same view; Esc or `/vt off` closes it. Exact speech appears only when `discord.voiceTranscriptLoggingEnabled` is on ([ADR 0121](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0121-development-voice-transcripts-are-explicit.md)); otherwise the overlay points at `/discord`. This is not `/trace`: voice lanes there are captain handoffs, not the Discord conversation. - `/status` renders `clankie status`, then adds console presence, conversation, workspace, model context, activity availability, and the Herdr pane roster. `/doctor` renders the full install report from `clankie doctor --json`. - `/awake [on|off]` is `clankie awake`: the launcher's keep-awake while plugged in, with the Mac's current power state ([always on](https://github.com/Volpestyle/clankie/blob/main/docs/always-on.md)). - `/herdr` offers **Use an existing Herdr session** or **Create a session for Clankie**, followed by **Restart now** or **Later**. `/herdr use NAME` and `/herdr create` are the direct equivalents. Clankie’s own session tracks official stable Herdr releases; staged updates apply when its server next starts, preserving active workers and their matching CLI. - `/board`, `/board focus`, and `/board close` manage the herdr-lead companion board. A seated turn receives the current agent census. - `/connect` configures Linear and email and can open Discord setup. `/discord` connects a server with Participant or Admin, fleet display on/off and a tracking level. Its invitation requests the role's permissions and its checks flag missing grants. Channel and role IDs, body diagnostics and machine grants stay under **Advanced**. Normal setup has no channel or Discord-role pickers. The explicit diagnostic CLI is `clankie discord setup test-post --channel NAME`. - `/setup` is where a new owner starts, and the console opens it on its own while Clankie cannot take a turn: how he should think (subscription, API key, local model, or a provider already signed in), then which model. It continues to phone sign-in and pairing through `/remote-access` and `/pair`, offers `/connect`, then asks for the first agent's folder and task. Send the reviewed request to Clankie or edit it in the composer; he owns the native hire. Pairing completes only when an active phone with chat access is observed. Escape or `/cancel` pauses the flow; `/setup` reads the actual state on return. `/setup rooms` lists all optional rooms and opens their existing commands ([ADR 0190](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0190-setup-asks-one-question-then-clankie-takes-over.md)). - `/auth` writes provider keys and OAuth credentials to the credential broker. `/auth status` may also report compatibility provider environment fallbacks; Discord and body credentials remain broker-only except documented operator/captain test overrides. - `/voice` selects OpenAI Realtime, Grok Voice, or OpenAI plus ElevenLabs and configures the active model, voice, xAI reasoning effort, and brokered API keys. `/voice status` shows the effective settings and environment overrides. `eleven_v4_turbo` opts into dialogue synthesis; unset keeps legacy Flash. Headless `clankie voice status`, `voice model set MODEL_ID`, and `voice model clear` inspect, select, or restore the default without restarting. - YouTube music is an ordinary prompt, not a slash command. Audible playback is on the active Discord body's Vox primary-voice role; see the [Discord media guide](https://github.com/Volpestyle/clankie/blob/main/docs/discord-media.md). - `/provider`, `/model`, and `/effort` select the captain through the launcher command modules. The header carries the effective Pi model and effort after subscription routing, effective-ref variant precedence, and model-supported clamping. `/image-model` and `/video-model` select generation models. Non-secret model configuration lives in `~/.config/clankie/clankie.json`. - `/provider` → "add a local endpoint…" declares an OpenAI-compatible local runtime (ds4, Ollama, LM Studio, vLLM) by base URL, reads its model list from `GET {baseURL}/models`, and needs no credential. The service picks the new provider up on `clankie restart captain`. Agents and scripts use the same write path through `clankie model add-local` / `clankie model set` (JSON on stdout); do not edit `clankie.json` by hand. Full contract: [`docs/cli.md`](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md). - `/layout` shows or hides the header banner. - `/jump <pane|agent>` focuses a herdr agent (`herdr agent focus`), and any pane id in the transcript — `w18:p1J`, wherever Clankie or a tool wrote it — is clickable for the same jump. Only a refusal reaches the transcript; a working jump moves the session, which says it better. `/status` lists the pane ids to aim at. - `!` on empty input opens the inline shell. `Ctrl+O` toggles tool and bash output between preview and full; clicking a block toggles just that one. Esc interrupts an in-flight turn: the service aborts Clankie's live model turn and the run settles as `cancelled` in the durable log. When the run cannot be cancelled (an older service, or it already settled) — or on a second Esc — the console detaches instead and the service continues the turn. - `clankie health` reports operator credential source and env/store consistency without fingerprints or secret values. Remove an active `CLANKIE_OPERATOR_TOKEN` override before rotating the stored credential. `/discord` connects a server with a Participant or Admin role, a fleet toggle and a tracking level. The invitation requests that role's permissions. Gateway checks flag proven missing grants as **needs** and unknown evidence as **not checked**. Participant room access follows Discord permissions; Admin controls its dedicated server except deleting it or transferring ownership. Raw IDs and machine grants live under Advanced. Opening or saving setup never posts. ## Account connections Open `/connect accounts` or `/connections` → Accounts to review the body's service catalog, identity and granted permissions. Gmail and Calendar use read-only browser consent. Drive opens Google's file picker to authorize selected files; that grant permits editing them, while Clankie's implemented tools only read. The body keeps credentials and refreshes access. The console masks the callback link. A status check verifies access, while disconnecting any Google service disables all three on this Clankie and reports whether provider revocation completed. An unconfigured service needs operator OAuth client setup. See [account setup](https://docs.clankie.bot/cli/#account-setup). ## Follow Linear Connecting an account and following its activity are separate choices. Use `/connect linear` for the account and the follow setup. Bare `/linear` opens **Follow Linear**, including **Wake rules** and the ordinary chat destination. The default chat is `global-default`; default rules wake only for James's signed comments and mentions (`volpestyle@gmail.com`). Other activity remains visible without starting a turn. Clankie can change the non-secret rules himself with `linear_wake` or `clankie linear wake set`. The [Linear reference](https://docs.clankie.bot/cli/#linear-status-linear-follow-on-off) owns webhook configuration, enabling following, status, and recovery. ## Headless Use headless commands for scripts: `clankie status`, `clankie doctor --json`, `clankie model set`, and `clankie persona set` print JSON and exit 0 or 1. Pairing, device listing and operator credential rotation default to human-readable output; pass `--json`, for example `clankie pair --json`. Other output exceptions are listed in the CLI reference. The full contract, with every flag and payload, is the [CLI reference](https://docs.clankie.bot/cli/). Secret entry stays interactive — `/auth`, `/discord`, `/connect`, `/voice` — because tokens never become flags. --- # CLI This is the command contract for agents, scripts, and people using Clankie from a terminal. For installation, use [Get started](https://docs.clankie.bot/get-started/). For console keys and slash commands, use the [console reference](https://docs.clankie.bot/console/). `clankie <noun> <verb>` exposes headless configuration and control. The CLI and local TUI share command functions and configuration writers; the TUI adds interactive forms and navigation ([ADR 0012](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0012-provider-auth-model-registry.md)). Live operator work stays on the service HTTP catalog already shared by the TUI, phone and relay: chat, play, memory, pairing, and conversations are not separate copies of the service's state. The commands below describe local mode unless noted. [Hosted mode](#local-and-hosted-connection-modes) connects to an existing remote service with a smaller supported set. `clankie help` prints the same command index. On every install the file lives at `{repoRoot}/docs/cli.md` — `clankie doctor` names `repoRoot`. ## Invocation ```bash clankie # choose mode on first run; open the selected console (TTY) clankie --version # also -V clankie --chat <conversationId> # resume a server-owned operator conversation clankie <command> # headless; no TTY clankie help # also --help, -h ``` `--chat` is stripped before headless routing. In local mode, with no command, the launcher starts the service if needed and opens the existing main **Clankie** conversation, regardless of the launch directory. It does not create a chat. Use `--chat ID` for another retained conversation, `/new` for a fresh chat, or `/cd PATH` to select a project conversation. The console opens at the latest messages; scrolling up loads older retained history in pages without moving the text you are reading. Live messages continue to arrive while older history loads. This applies to local and hosted consoles. An unknown command exits 1 without starting anything. Common near-misses name the real command: stop the service with `clankie down`, not `stop`. ## Conventions | Rule | What it means | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | One JSON document on stdout | Agents parse stdout. Progress and human narration go to stderr. | | Exit 0 or 1 | 0 is success. 1 is failure. `doctor` always exits 0 — `ok` means the card was produced. | | Secrets never as flags | No API keys, Discord tokens, or operator bearers on the command line. `/auth` and `/discord` in the console, or the credential broker. | | Fail closed, secret-free errors | Failure messages never echo tokens, pairing codes, or response bodies. | | Host | `CLANKIE_CONTROL_PLANE_URL` (default `http://127.0.0.1:4310`). `CLANKIE_CAPTAIN_URL` is a compatibility alias. | `--json` is required only where the default is human-readable (pairing QR, device and machine tables, credential-rotate sentence). Everything else is already JSON. `rivals connect --token-stdin` reads its bridge token from a pipe into the broker; the token is never an argument, settings value, or printed result. | Command | stdout | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `doctor [--machine NAME]` | Human summary; `--json` preserves the full card | | `health`, `status`, `restart`, `down`, `autostart …`, `awake` | JSON | | `model …`, `effort …`, `image-model …`, `video-model …` | JSON | | `linear …`, `persona …`, `games …`, `browser …`, `fleet …`, `herdr use/create/disable`, `workdir …`, `discord …`, `gateway …` | JSON (`herdr open` opens the terminal viewer) | | `play status` | JSON | | `send --conversation ID …` | JSON accepted-run receipt or refusal | | `file publish --conversation ID PATH …` | JSON delivered-file metadata | | `memory …`, `metrics …` | JSON | | `telemetry ship …` | One JSON line per shipping pass | | `play stop` | JSON when a session is stopping; the sentence `Nothing is playing.` when idle (still exit 0) | | `prompt …`, `memory-card …` | Plain text: the prompt or card itself, verbatim | | `seat` | Interactive (TTY); `seat --dry-run` is JSON | | `mcp` | JSON-RPC for a harness, never for people | | `pair`, `devices`, `machines`, `herdr status`, `operator-credential rotate` | Human text; pass `--json` | | `help` | This index (plain text) | | `--version` | `clankie <version>` | Do not edit `~/.config/clankie/clankie.json`, `~/.config/clankie/settings.json`, or Keychain entries by hand. ## Command index | Task | Commands | | ------------------------------------------------------ | ------------------------------------------------------- | | [Control Activity shares](#activity-shares) | `share list`, `share request JSON` | | [Diagnose the installation](#diagnostics) | `health`, `status`, `doctor` | | [Manage service lifecycle](#service-lifecycle) | `restart`, `down`, `autostart`, `awake` | | [Pair and manage devices](#device-setup) | `pair`, `devices`, `gateway` | | [Connect accounts and track work](#account-setup) | `accounts`, `work` | | [Choose working skills](#skill-setup) | `skills` | | [Choose models](#model-setup) | `model`, `effort`, `image-model`, `video-model` | | [Connect machines](#runtime-setup) | `machines`, `connections`, `runtime`, `agents`, `herdr` | | [Read and send conversations](#conversation-commands) | `conversations`, `send`, `file`, `memory` | | [Use native seats and delegated tools](#seat-commands) | `seat`, `mcp`, `access` | | [Evaluate agent work](#evaluation-commands) | `evaluator` | ## Commands <a id="diagnostics"></a> ### `health` / `status` Probe every launcher-owned service and the operator credential. `health` and `status` are the same verb. ```json { "ok": true, "status": "ready", "host": "http://127.0.0.1:4310", "owned": false, "pid": 12345, "operatorCredential": { "present": true, "source": "store", "consistency": "store_only" }, "services": [{ "id": "clankie", "state": "healthy", "owned": true }] } ``` `ok` is true only when the clankie service is healthy **and** the operator credential is present without an env/store mismatch. Exit 1 otherwise. `status` is `ready`, a service state (`unreachable`, `unhealthy`), or `operator_credential_<consistency>`. Top-level `owned` and `pid` appear when the clankie row has them. The payload never includes fingerprints or secret values. Service ids appear in dependency order: `clankie`, `relay`, `discord-bridge`, `discord-user-session`, `activity`, `tunnel`, `awake`. `awake` is the owner's keep-awake ([`awake`](#awake)); it reads healthy and "off" until they opt in. `status` and `doctor --json` include `runtimeHealth` when the service exposes it: process CPU percentage, `/health` latency, fixed CPU/health reasons, alarm state, delivery state, and the last incident duration. `/status` and `/doctor` show the same observation. Missing observations remain unknown. ### `runtime-health` `clankie runtime-health status` reads the live observation and settings from the owner API, `GET /v1/operator/runtime-health`. `on` and `off` enable or disable alarms. Change any subset with `set`: ```sh clankie runtime-health set --cpu-percent 50 --health-ms 1000 --sustained-seconds 300 \ --sample-seconds 15 --cooldown-seconds 1800 ``` These are the defaults. `/runtime-health` opens the TUI menu for every setting. Changes use revision-guarded `POST /v1/operator/runtime-health` and apply on the next sample without a restart. CPU is this service process's consumed CPU time divided by elapsed wall time (100% is one fully busy core), rather than machine load. A failed or timed-out health response also counts as slow health. CPU above its threshold or slow health must persist for the sustained duration before one alert goes to the native `global-default` conversation. Recovery reports the incident duration. A persistent incident emits no repeated alert; the cooldown bounds alarms for subsequent incidents. An unavailable native delivery retries at most once a minute, and a retained uncertain native receipt counts as accepted so it is not replayed. These observations create no service model turn. Include incident and recovery evidence in the next Linear check-in. The public `/health` observation and consented hosted `body.runtime_health` events contain fixed numeric and enum metadata only. Conversation text, worker reports, credentials, and command output never enter this projection. ### `doctor` The install card ([ADR 0142](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0142-the-install-tells-him-the-truth.md)). `clankie doctor` prints one line: `ready`, or the most important problem and its repair command. Model setup and service reachability take priority over optional integration warnings. `ready` means these probes found no repair; it does not prove a model turn or native worker tool acceptance. Use `clankie doctor --json` for the full, unchanged install card; scripts must pass this flag. `clankie doctor --machine NAME --json` preserves the machine card too. Both formats always exit 0 when the card is produced. JSON `ok` means the card was produced, not that every integration works. `/doctor` in the console continues to show the full card. The local card includes `workingPreferences`: resolved global/project values for the actual current workspace, or an explicit unavailable detail. The TUI `/doctor` displays these values; the headless command keeps its one-line summary, so use `--json` to read them. This observation never adds project or tool access. Local fleet discovery uses `<CLANKIE_STATE>/links`, defaulting to `~/.clankie/links`. Local hires carry the service's absolute state path, including into the Codex MCP bridge. Doctor and native workers select that same directory; an explicit private state directory never falls back to shared discovery. SSH fleets keep their own machine's discovery directory. `harnessBridges.linkedSession` checks Claude/Codex panes in the discovered local Herdr session, even when doctor runs outside that session. On macOS it joins the live foreground harness and bridge ancestry (or the exact dedicated Codex `--remote`/`--listen` socket) with the bridge process's `HERDR_PANE_ID` and `HERDR_SOCKET_PATH`. It returns only those identity facts, never the full process environment. The roster carries the same observation in each seat's `harnessBridge`; the console flags missing/mismatched bridges and shows the selected pane's full fix when focused with `Ctrl+G`. Roster polls reuse these bounded process observations for up to five seconds; doctor takes a fresh sample. `toolCatalogHealth` reads the service's native catalog diagnostics for current Claude and Codex panes, including the operator head. The roster carries the same `toolCatalog` verdict; a mismatch or unverified catalog appears in the agent dock and its full reason and one fixing action appear in `Ctrl+G`. `matched` means the harness actually listed the tools its Clankie bridge serves for that native session. It does not prove a tool call or message delivery. Current Claude Code's trusted plugin mod reads its actual tool list after session start (and after clear/resume/compact); MCP discovery can settle for up to 20 seconds. Clankie-managed Codex launches read `mcpServerStatus/list` for their original loaded thread. Embedded hand-started Codex has no native introspection endpoint and explicitly reports `unverified`; ask Clankie to hire a managed Codex seat with `hire_agent` to get a verified catalog. This action never recommends the shared daemon, whose pane identity inheritance can break worker bridges. Native introspection for embedded Codex remains a follow-up. Plugin hook/mod trust is required; an absent probe remains unverified. Doctor observes operator bridges separately from worker bridges; an operator bridge does not prove worker readiness. Process age is separate from transport status. `freshness: older-than-runtime` means the observed bridge started before the running service, with the remedy “seat bridge older than runtime; restart the seat”. `current` means the bridge started at least as recently as the service; `unknown` keeps unavailable timing unknown. The optional `bridgeStartedAt` and `runtimeStartedAt` fields expose the observed timestamps, not build identities. Age alone does not prove an obsolete build or successful delivery; a same-build service restart also produces this reload guidance. The roster warns seats it already lists; doctor also observes the named head's operator bridge. - `live-process`: the pane has a matching live bridge process. This does not verify the native tool catalog, a successful call, or reply delivery. - `missing`: a live native harness has no observed descendant or dedicated socket-matched bridge. For Claude, install/enable `clankie-worker@clankie` in that pane's actual profile and restart/resume it. For Codex, check its source-owned bridge registration and resume with `codex --no-daemon resume <SESSION>`. - `pane-mismatch`: the observed bridge claims another pane/socket. Check its source-owned registration and resume Codex in its own pane with `codex --no-daemon resume <SESSION>`. If a shared daemon is observed, save affected sessions and run `codex app-server daemon stop` first. Keep `daemon_auto_start=false` in the owning configuration source. - `unobserved`: foreground process or environment facts are unavailable; this is not evidence of a missing bridge. Non-macOS host observation is currently unsupported; remote fleet native bridge acceptance remains a separate check. `linkedSession.unownedBridges` names bridges on the linked socket without an observed native owner. A bridge descending from an actual `app-server-daemon` executable reports its inherited `claimedPane` and the daemon stop/resume fix. A daemon's claim alone never assigns its sessions to that pane. Hand-started `claude`/`claude2` sessions must list `message_clankie`, `clankie_tools`, and `clankie_call` after the profile fix; installation alone does not establish acceptance. ```json { "ok": true, "kind": "checkout", "version": "0.2.0", "repoRoot": "/path/to/this/install", "model": "xai/grok-4.6", "captain": { "ready": true, "model": "xai/grok-4.6", "providerId": "xai", "auth": "credential" }, "imageModel": null, "videoModel": null, "persona": { "displayName": "Clankie" }, "discord": { "activeBody": "bot", "textIngressEnabled": true, "voiceEnabled": true, "userSessionEnabled": false, "machineGrantUsers": 0, "machineGrantGuilds": 0 }, "voice": { "realtimeProvider": "openai", "ttsProvider": "openai" }, "gameplay": { "pokeagentMmoEnabled": false }, "emailConfigured": false, "mcpServers": [], "credentials": [{ "id": "openai", "type": "api" }], "commands": { "herdr": { "present": false } }, "herdrPlugin": { "bundled": true, "bundlePath": "…/integrations/herdr-plugin" }, "laneTools": { "url": "http://127.0.0.1:4310/v1/mcp", "reachable": true }, "doorway": { "state": "connected" }, "power": { "state": "sleep_allowed", "source": "battery", "sleepAfterMinutes": 1, "heldAwakeBy": [], "keepAwakeRequested": false, "advice": "On battery this Mac sleeps after 1 min idle, so Discord and the app go quiet. Plug in and run `clankie awake on` to keep it awake while plugged in, or use a hosted Clankie." }, "remediations": ["Pick a captain model with `clankie model set provider/model` or `/setup`."] } ``` `kind` is `checkout` or `release`. `captain` says whether Clankie can take a turn at all ([ADR 0190](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0190-setup-asks-one-question-then-clankie-takes-over.md)): `{ "ready": true, "model", "providerId", "auth" }` with `auth` one of `credential`, `env`, `endpoint` or `subscription`, or `{ "ready": false, "reason": "no_model" | "no_credential" }`, naming the model and provider when one is chosen. A chosen model with nothing to sign it in earns a remediation. Credential entries are ids and types, never secrets. `commands` currently probes `herdr`, `ffmpeg`, `yt-dlp` (version strings) and `herdr-lead`, `codex`, `claude` (PATH only — never execute `herdr-lead --version`). `laneTools` names the streamable-HTTP MCP route that serves a lane's tool bank ([ADR 0152](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0152-a-harness-takes-the-operator-seat.md)); `reachable` is true when it answers an unauthenticated probe with 401, so the route is served and wants a lane bearer. `doorway` is the live public doorway ([ADR 0151](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0151-the-public-doorway-routes-home.md)) in the states `clankie gateway status` reports; `sign_in_required` and `unavailable` each earn a remediation, because until they clear no app reaches him at all. `power` says whether this Mac may sleep, which drops Discord and the app while it is down ([always-on guide](https://github.com/Volpestyle/clankie/blob/main/docs/always-on.md)). `state` is `always_on` (power settings never sleep, or something holds a sleep assertion), `sleep_allowed` (with `advice`, which is also a remediation), or `unknown` (no `pmset`, as on a hosted Linux body; no warning). `source` is `ac` or `battery`, `sleepAfterMinutes` is the `pmset` idle sleep for that source (`0` never), and `heldAwakeBy` names processes holding a sleep assertion that applies on that source. `lastSleep` appears once the running service has noticed the host sleep underneath it. The same object is on the service's `/health` as `power`. <a id="service-lifecycle"></a> ### `update [--ref REF]` / `update status` / `update canary` `clankie update` fetches `origin/main` and stages its exact commit. Named branches (including `origin/BRANCH` and `refs/heads/BRANCH`) fetch that branch from origin; failed fetches refuse the update without using a cached or local tip. Use a full commit SHA, `HEAD`, or `refs/tags/TAG` for an explicit local target. The result records `resolvedRef` and `newCommit`, with `older-than-current-pin` or `diverged-from-current-pin` warnings when applicable. It installs dependencies in an independent detached worktree and schedules a fixed helper in a separate process group. The current service must be running the exact pinned checkout. Dirty tracked or untracked files in that checkout refuse the operation before any service stop. The helper installs before stopping anything, rechecks the pin, stops through the existing service supervisor, retains the previous worktree, activates the new pin and restarts the pin-dependent services. The external activity tunnel stays running under its current owner; an unowned tunnel cannot block the update. Failed new health checks trigger a confirmed-stop rollback. Unknown shutdown never authorizes a worktree move. Generated pnpm wrappers and known workspace metadata are relocated before cutover; committed source, lockfiles and global package-store files are not rewritten. After confirmed new service health, the helper refreshes existing Claude/Codex plugin links locally and on enabled SSH fleet machines through `clankie harness install --refresh-linked`. Plugin refresh failures leave the healthy service running and report `harness-refresh-incomplete`; they do not roll back the service. `harnessRefresh` in update status links the complete per-profile receipt in `harness-refresh.json` beside the transaction record. After the new service responds with its exact boot identity, a persistent post-update canary observes it for five minutes. The default budgets are 10% captain-process CPU (100% means one core) and 250 ms `/health` p95, sampled every 10 seconds. Health latency includes TCP setup and the complete response on a fresh loopback HTTP connection. Its deploy hold blocks further updates and integration landings during observation. A pass releases only that canary's hold. A regression or missing health signal records a failed canary, retains the hold, names the previous healthy commit, and attempts the runtime-health alert path. The new pin keeps running; rollback requires an explicit owner decision. Alert status distinguishes submitted, unavailable, and an uncertain claimed attempt. Submitted means the native notification path accepted the attempt; it does not claim a confirmed recipient receipt. `clankie update canary` reads the policy and last canary. Configure the next update with `--window-seconds N`, `--sample-seconds N`, `--cpu-percent N`, and `--health-ms N`; omitted fields retain their values. Policy changes do not change an in-flight observation. `/update` offers the same settings in the TUI. A restart of the observed service starts a fresh full window for its new boot identity; elapsed downtime never counts as healthy observation. The service also schedules an in-place tool refresh for running workers. Local managed Codex controllers keep their original thread and descendants, wait for idle, update only the private Clankie transport revision with a native config version check, and reload once. A lost mutation acknowledgment is held for read-only reconciliation. No turn or uncertain report is replayed. `clankie harness refresh-tools [--pane PANE]`, TUI `/refresh-tools [--pane PANE]`, and the operator tool `refresh_worker_tools` request one or all observed seats. The authenticated API is `POST /v1/fleet/worker-tool-refresh` with `{}` or `{"paneId":"PANE"}`. Each result is `refreshed`, `skipped-busy`, or `failed` with a reason. Busy requests remain pending under their original authority. Roster `workerTools` and `/doctor` show observed/expected plugin versions and whether the observed runtime revision is behind. These fields grant no access. Known native busy state also holds deployment metadata publication until idle. The current implementation cannot safely refresh remote Codex configurations or recover their original controllers after a service restart. Claude supports native list-change adoption for an already current bridge, but replacing old imported bridge code remains unverified. OpenCode verifies the original native MCP connection; its public SDK does not expose exact model-visible MCP names. These cases remain visible per-seat failures or verification gaps, never successful refresh claims. See [ADR 0235](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0235-worker-catalog-refresh-keeps-the-original-controller.md). The CLI and TUI `/update` return an accepted/pending operation, not a success claim. `clankie update status` and `/update status` read the durable old/new commit, phase, per-service receipts and exact service boot identity. `initiator` records the authenticated operator or host-admitted conversation. CLI environment seat session/conversation claims are explicitly marked as claims, not authority. Newer result evidence is tolerated by readers; damaged known fields produce a JSON reconciliation error and leave the operation and lock untouched. CLI status also reports unavailable or non-JSON server responses as JSON. Results live in private `~/.clankie/updates/<operation-id>/` directories and survive the old service exiting. A nonterminal operation or uncertain shutdown blocks another schedule; inspect/reconcile that operation rather than retrying or deleting its lock. PIDs alone are never proof that an abandoned operation is safe to repeat. The operator API is `POST /v1/runtime-update` with optional `{ "ref": "main" }` and `GET /v1/runtime-update` for status. It requires the actual operator credential; caller-supplied lane, actor and path claims confer no authority. Captain tools `update_runtime` and `runtime_update_status` exist only in host-admitted machine sessions and recheck their captured source before acceptance. Social sessions cannot gain the tool through a later permission change. Accepted host operations may finish or roll back after the original turn/service exits. Targets predating the canary coordinator are refused before installation or service shutdown (`target-runtime-canary-unsupported`); their health parser cannot complete the new observation. An owner choosing a legacy rollback must review it through the installer rather than bypassing the pending update record. `GET /v1/runtime-update/canary` returns `{policy, canary}`; `PUT /v1/runtime-update/canary` accepts a partial policy with `windowMs`, `sampleIntervalMs`, `cpuPercent`, and `healthLatencyMs` and applies it to the next canary. Both require current operator authority; policy publication rechecks it inside the lock and immediately before replacing the durable file. Revocation retains the previous policy. The local `/health` response also exposes `processHealth`: a boot UUID, PID, uptime and cumulative process CPU microseconds. Boot identity comes from the running updater's immutable identity; liveness probes do not reread update transaction files. The response carries no messages, prompts, tenant content or credentials. Deploy holds also block runtime-update admission. `update status` includes holds and holder presence. An operator may override explicitly with `--override-hold UUID --actor NAME --reason TEXT` (repeat the hold flag for every hold); the registry records the override and retains the hold. The API accepts an `overrides` array of `{holdId, actor, reason}`. See [integration](https://github.com/Volpestyle/clankie/blob/main/docs/integration.md). Supported `clankie mcp` operator bridges reinitialize after an explicit `unknown_session` rejection before tool admission and retry that rejected request once. Concurrent requests share the new session; reconnect drains old HTTP clients without closing another pending call. The refreshed tool list lets the same attached seat inspect the result. Protected `message_seat` and `hire_agent` calls receive a `deliveryId` or `hireId` before dispatch, carried in MCP `_meta["clankie/seat-call"]`. The service persists the scoped receipt before the native effect. A lost result returns typed uncertainty with that original ID; reconnect never replays the pending action. Use the read-only `reconcile_seat_call({deliveryId})` or `reconcile_seat_call({hireId})` in the owning operator conversation to inspect its original receipt, without sending again or launching a replacement. A settled call receipt returns the original tool result, not proof of task completion. The journal keeps the latest 1,000 settled result bodies; older IDs remain non-replayable and report an expired result, while uncertain originals remain retained. Receipts survive restart within these retention bounds. See [ADR 0207](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0207-work-records-and-native-agent-delivery.md#mcp-reconnect-and-native-call-receipts-vuh-1638). Fleet bridges retain their separate exact-link refresh and durable receipt rules. Already-loaded older operator bridges need their MCP process refreshed to gain these protocols; refreshing only the fleet mailbox is insufficient. The native seat need not be restarted for a supported bridge. `clankie hire-receipt settle ORIGINAL_NATIVE_HIRE_UUID` calls the operator-only `settle_hire_receipt` operation. This is the native hire receipt UUID, not an MCP call UUID or a `seat-…` message acknowledgement. The service takes a fresh census through the original configured SSH host/session and closes its guarded host reservation. Only a reservation whose entire window precedes every pane, process, session or resume/send effect can become `settled-not-launched`. Evidence records the original key/fingerprint, target, host identity, interval and census digest. An irreversible service launch flag and the host's exclusive launch/seal transition prevent a late dispatch or a reset host journal from granting settlement. Legacy receipts without that recorded window, already allocated panes, attempted launches, incomplete census, changed connection or revoked authority are refused. Current absence cannot reconstruct history. Receipts and evidence are retained; the original key stays blocked permanently and no request is resent. `clankie hire-receipt settle seat-ORIGINAL_UUID delivered` records historical native insertion through the same authenticated host census. The service resolves that exact event in its canonical mailbox journal and uniquely links its fingerprint to the allocated remote hire. It requires the original native channel event ID, recipient conversation, complete body hash, canonical cwd, native channel-origin metadata, and session UUID derived from both the confined transcript filename and metadata. A legacy event without a stored recipient binding also needs its exact retained bridge acknowledgement. The evidence explicitly labels the historical binding reconstruction; it does not adopt the old session or claim work completed. Ambiguous, forged, truncated, redirected or changed histories refuse. `clankie hire-receipt settle ORIGINAL_NATIVE_HIRE_UUID abandoned` records the operator's explicit disposition of a legacy allocation with current authenticated pane/process/session census. It preserves uncertainty about prior launches and keeps the original key blocked permanently. It does not close panes. Close an old allocation only with owner authorization and fresh proof of its exact ownership, idle state and empty draft; otherwise list it for the owner. New acceptance work needs separately authorized fresh intent, not a replay or an automatic key change. All original receipts, bridge acknowledgements and host recovery history remain retained. Neither recovery command sends, relaunches or adopts an original. An abandoned host-operation lock is a refusal, never an invitation to delete it. The host OS and configured SSH principal are trusted; a compromised host cannot attest its own history. The journal covers service-authorized effects, not arbitrary programs launched outside Clankie's controlled hire path. It adds no fleet tool, worker authority, account setup or TUI setting. `clankie hire-receipt fresh --json-stdin` admits separately authorized new remote work after a retained settlement. Supply an existing hiring conversation, a new brief and `seat.freshIntent` through the public `spawn_seat` request: ```json { "conversationId": "conv-YOUR-HIRING-CONVERSATION", "seat": { "schemaVersion": 1, "fleet": "pc", "harness": "codex", "title": "Ada", "role": "tester", "workingDirectory": "C:\\work\\approved-repo", "freshIntent": { "id": "f219ef86-91ba-4697-8e2e-91fc9416e72c", "afterReceiptId": "9ef6657c-2d09-4b15-85b4-04608168a532" } }, "brief": "The owner authorized this independent new task. Complete its bounded acceptance check." } ``` Save the request before calling `clankie hire-receipt fresh --json-stdin < request.json`. Choose one new lowercase UUID for that intent and retain it. `afterReceiptId` is the exact **native hire** UUID of the settled original, including when delivery recovery used a `seat-…` message ID. Use the original fleet, harness and exact working-directory value; its configured host/session must still match. Resume, an unresolved sibling, changed authority or replay of any retained brief refuses. The service records the captured owner, resolved project/launch settings and brief fingerprint before any new effect. A reused UUID with different scope, owner or brief refuses. An exact retry only inspects the original native binding; it never launches or sends again. A completed fresh UUID stays fenced permanently: use its existing seat for follow-up. Both original settlement evidence and fresh receipts remain retained, including across restart and age pruning. Older service versions may refuse this journal; upgrade forward rather than editing its records. The native `hire_agent` tool accepts the same optional `freshIntent`. This adds no account change, fleet alias or TUI setting. Exit 0 from the fresh CLI means the service returned `spawned`; completion still needs the matching native event. ### `integrate` ```bash clankie integrate CORE_SHA... [--app APP_SHA]... [--push] [--id UUID] [--no-wait] clankie integrate status UUID clankie integrate push UUID [--override-hold UUID --actor NAME --reason TEXT] clankie integrate revert PASSED_BATCH_UUID [--push] clankie integrate holds clankie integrate hold --holder NAME --reason TEXT [--pane ID|--seat ID] [--id UUID] clankie integrate release UUID --actor NAME --reason TEXT ``` An ordered approved batch composes on fresh origin in independent throwaway core/app worktrees, performs real installs and full checks with private home, state, credentials and package stores, and records tested HEAD and exit codes durably. It only lands a clean exact HEAD with a recorded pass. Core lands first; app rejection retains a partial record and retries skip already landed core. Revert creates a new commit restoring a passed tree. Named holds block push and deploy; explicit owner overrides name the hold, actor and reason and are audited. Requires a local source-checkout service. See [integration](https://github.com/Volpestyle/clankie/blob/main/docs/integration.md) for evidence paths, isolation limits, uncertain sends and crash recovery. ### `restart [service]` Restart launcher-owned services in dependency order ([ADR 0055](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0055-launcher-owned-local-services.md)). Default target is `all`. Progress lines go to stderr; stdout is JSON: ```json { "ok": true, "status": "ready", "target": "clankie", "host": "http://127.0.0.1:4310", "owned": true, "services": [{ "id": "clankie", "ok": true }] } ``` Naming a service restarts it **and** anything that holds a live claim against it. `clankie` (`captain`) also restarts `relay` and the Discord body, because those processes cache presence and bearer state from this service instance. Stopping is different: `down` names one service and stops only that service. Local HTTP services check listeners on their configured port (`PORT` for Clankie, `CLANKIE_RELAY_PORT` for the relay, and both `CLANKIE_ACTIVITY_PORT` and `CLANKIE_ACTIVITY_PRODUCER_PORT` for the activity). A scratch instance on other ports does not block them. This uses `lsof`, supplied by macOS and required in Linux installations for this check; if inspection fails, the launcher conservatively refuses matching unowned processes. Named activity tunnels check their configured tunnel name. Foreign processes are never signalled. Use plain `clankie restart` for a normal restart. The optional service target is only needed to select a narrower dependency set. Clankie can run this from his own bash in the console or an authorized Discord text/voice turn. The launcher detaches a helper and waits for the current turn to settle. Stdout reports `"status": "scheduled"` with `afterRun` (console) or `afterSession` (native Pi room), plus `logPath` for the helper's output. This is success (exit 0), not proof of recovery: finish the reply, then check the log and `clankie status`, including the Discord bridge. The helper cancels if the turn has not settled within ten minutes. No hired worker or custom sleep script is needed; a hired worker's backend may share the service's process lifetime. New service processes clear inherited pnpm lifecycle and Pi session markers. Otherwise a restart launched from a running package script can be mistaken for a recursive `start` and skipped by pnpm, leaving all stopped dependents offline. ### `down [service]` Stop in reverse dependency order. Default `all`. Same stdout shape as restart, with `"status": "stopped"` on success. ### `autostart enable` / `autostart disable` / `autostart status` Start Clankie when you log in. `enable` writes the user LaunchAgent `~/Library/LaunchAgents/bot.clankie.autostart.plist` and loads it into your `gui` domain. At login it runs this install's launcher as `clankie restart clankie`, so the service, the relay, and the selected Discord body start in dependency order and the launcher's supervision owns them from there. launchd launches it once (`RunAtLoad`, no `KeepAlive`), and only inside a logged-in session: a Mac waiting at the login window starts nothing. On a release install the agent records the `current` launcher path, so upgrades need no re-enable. It also records your `PATH`, `XDG_CONFIG_HOME`, and `XDG_STATE_HOME` as they were when you enabled it; run `enable` again after changing them. `enable` is idempotent (a loaded agent is booted out first) and `disable` unloads and removes the agent. ```json { "ok": true, "status": "enabled", "label": "bot.clankie.autostart", "plist": "/Users/me/Library/LaunchAgents/bot.clankie.autostart.plist", "loaded": true, "command": ["/Users/me/.local/share/clankie/current/bin/clankie", "restart", "clankie"], "log": "/Users/me/.local/state/clankie/autostart.log" } ``` `status` is `enabled`, `disabled`, or `stale` (the agent file and launchd disagree; run `enable`). The job's own output lands in `log`; the services keep their usual per-process logs. <a id="awake"></a> ### `awake [status|on|off]` Keep this Mac awake while it is plugged in, so Discord and the app stay reachable ([always-on guide](https://github.com/Volpestyle/clankie/blob/main/docs/always-on.md), [ADR 0203](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0203-clankie-keeps-what-better-models-cannot-absorb.md)). `on` stores the opt-in (`host.keepAwake` in settings) and starts a launcher-supervised `caffeinate -s` now; `off` clears it and stops that process. macOS holds a `-s` assertion only on AC power, so unplugging lets the Mac sleep as its own settings say. The opt-in survives a restart: the launcher restarts `awake` with the clankie service, so [`autostart`](#service-lifecycle) brings it back at login. It never runs `pmset` with anything but `-g` and never changes a power setting. Local only; hosted mode and non-macOS hosts refuse `on`. The console has the same command as `/awake [on|off]`. ```json { "ok": true, "keepAwake": true, "service": { "state": "healthy", "detail": "holding this Mac awake while plugged in", "pid": 4242 }, "power": { "state": "always_on", "source": "ac", "sleepAfterMinutes": 10, "heldAwakeBy": ["caffeinate"], "keepAwakeRequested": true }, "note": "Keeps this Mac awake only while it is plugged in; …" } ``` `service.state` is `healthy` when off or holding, and `unreachable` when requested with no `caffeinate` running (`clankie restart awake`). A `caffeinate` you started yourself is never touched and never counted as the launcher's. <a id="device-setup"></a> ### `pair [--json] [--timeout SEC] [--review --days N [--count N]]` Mint a one-time pairing offer (QR + code + deep link) for the phone/desktop app. Pairing reuses a healthy app relay or starts a stopped one before minting an offer. If the relay cannot start, no offer is minted. A configured public doorway carries the offer, so when this Mac has no live connection to it — `doorway.state` anything but `connected` — pairing fails `unavailable` rather than handing out a code the phone can only report as unrecognized, unless a direct route is configured: then the offer carries that route alone (ADR 0204). `--timeout` covers startup and minting together and defaults to 30 seconds; an ordinary offer lives five minutes. A remote `CLANKIE_CONTROL_PLANE_URL` fails with `unavailable`: run pairing on that host so its launcher can verify the relay. The console's `/pair` runs this same command and accepts the same flags. On a self-hosted Mac, `--local-companion` writes a single-use offer to the owner-private `~/.clankie/companion/companion-offer.json` (`CLANKIE_STATE` overrides the root), for the locally installed companion to redeem. Output contains only the handoff path. Re-running it reuses the active companion's device identity; it cannot be combined with review offers. See [the local companion handoff](https://github.com/Volpestyle/clankie/blob/main/docs/local-companion.md) for the typed API and security contract. Signed app distribution and the installer's call remain separate work. Public-gateway pairing uses a secure QR or full pasted link; the encryption credential is in its fragment. Short codes are for direct private connections. An ordinary offer also returns `localCode`, the offer's own short code, even when `code` is the gateway link; human output shows it as `On this Mac code` for the Mac app's **On this Mac** pairing. Only the authenticated operator's offer response carries it, and review offers never do. One link carries every route the Mac has (ADR 0204): the gateway fragment when remote access is on, and `direct=<origin>` when `clankie gateway direct` has configured a device-reachable control origin. With a direct route, the App Store app pairs without a Clankie account. It uses the gateway first when both are present. Human output ends with `Routes: remote access (gateway) + direct (<origin>)`, one of them, or `Route: this Mac only`, which pairs only a source build. It warns when the App Store app cannot reach the direct origin: plain HTTP works only for `.local`, single-label and private IP addresses, so serve tailnet names over HTTPS (`tailscale serve --https`). The service reaches the LAN only through the opt-in device doorway (`CLANKIE_DEVICE_HOST`, `CLANKIE_DEVICE_PORT`, default 4311), which serves device routes alone. Human mode writes the QR and code/link to stdout. Those values are secret-bearing display data — never log or persist them. `--json` is the agent form; `routes` is omitted when the link carries neither route: ```json { "ok": true, "code": "ABCD-EFGH", "localCode": "ABCD-EFGH", "deepLink": "clankie://connect?v=1&offer=…&direct=…", "expiresAt": "2026-08-30T12:00:00.000Z", "routes": { "gateway": false, "direct": "http://my-mac.local:4311" } } ``` `--review --days N` mints a review offer for App Review or a TestFlight tester who will pair hours or days later: `--count` (default 3, max 10) independent single-use offers that each live `N` days (max 31, the public gateway's route window) and survive a Clankie restart. Human output is headed `REVIEW OFFER` and lists `Code 1…N`; `--json` is `{ "ok": true, "review": true, "expiresAt": "…", "offers": [ { "code", "deepLink", "expiresAt" } ] }`. For public pairing, send each offer's secure QR or full link, not its displayed short code. The `Code 1…N` labels are CLI display labels; short codes remain usable only over direct private connections. Mint review offers only after the public gateway release that accepts them; an older gateway drops the Mac connection on the first review route. Failure with `--json`: `{ "ok": false, "status": "unavailable"|"unauthorized"|"expired"|"malformed"|"interrupted", "error": "…" }`. Without `--json`, the same message goes to stderr and stdout stays empty when no offers were minted. If a review batch fails after minting some offers, the command still exits 1 and displays those live offers: JSON adds `partial: true`, `review: true`, and `offers`; terminal output starts with `PARTIAL`. Each offer remains usable until consumed or expired through its supported pairing path. ### `devices [--json]` List paired devices. Human mode is a table whose `SOURCE` column reads `review` for devices paired through a review offer (revoke those after the review) and `pair` otherwise; `--json` is `{ "ok": true, "devices": [ … ] }` with `"review": true` on those rows. Empty human output is `No paired devices.` The `PUSH` column shows `enabled` for an active device with chat access and an enabled delivery reference, otherwise `off`. This is the host's registration state, not proof that Apple delivered a notification. JSON includes the optional `push` object with `registrationId`, `sequence`, and `enabled`; disabled records retain their last version. The phone authorizes delivery and controls notification permission. The hosted gateway holds the APNs signing key and delivery registrations. ### `devices revoke <id> [--json]` Revoke one device. Human: `Revoked <id> (<name>).` JSON: `{ "ok": true, "device": { … } }`. ### `gateway [status]` / `gateway set --url URL --host-id ID` / `gateway disable` `clankie gateway rotate-encryption-key` replaces the broker key wrapping device tickets. It reports the required captain restart without performing it. Coordinate that restart, then re-pair every device. The `/gateway` menu exposes the same action. [Encryption contract](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0173-the-gateway-cannot-read-device-traffic.md). Read the public doorway binding or disable it. JSON includes `publicGateway`, the derived `hostId`, `credentialPresent`, `enabled`, `settingsFile`, the restart command, and `doorway` — the running captain's own view, read over loopback, because stored settings never prove the socket is up. Its `state` is `connected`, `connecting`, `sign_in_required` (with the `since` timestamp; no app reaches this Mac until someone signs it back in), `unavailable` (configured, but this Clankie holds no connector at all), `disabled`, or `unreachable` when the captain does not answer. Use the interactive TUI `/gateway` wizard to sign in with an invited email and one-time code; the rotating account credential goes to Keychain and the wizard restarts Clankie automatically. `disable` signs this Mac out and removes its installation binding. After sleep or an offline startup, the account connector backs off until the account endpoint responds to a harmless reachability probe. Lost refresh replies get up to three quick retries with the same token inside the rotation grace window. Network failures keep the doorway `connecting`; an explicit credential rejection parks it at `sign_in_required`, visible in `/gateway` and `doctor`, until the owner signs in again. Sleeping through the entire grace window cannot recover a replacement token whose reply was lost. `set --url URL --host-id ID` remains only for legacy static-bearer migration and local verification. It never accepts a secret as a flag. ### `linear post comment|issue --json-stdin` Publish through the operator tool bank as an existing worker persona using the connected Linear app. Input is JSON with `personaId`; comments also need `issueId` and `body`, issues need `teamId` (UUID) and `title`. The service derives the name and colored Clankie portrait from the fleet. Output includes the MCP result and `ok`; provider/tool rejection sets `ok: false` and exits nonzero. See [worker posts](https://github.com/Volpestyle/clankie/blob/main/docs/linear-worker-posts.md) for examples, grants and limitations. ### `linear budget` `clankie linear budget` reads `/v1/linear/request-budget` without calling Linear. It reports each connected actor's actual HTTP attempts in the last hour across MCP, the API tracker, pagination, and worker publishing. The same workspace and actor share one budget across OAuth audiences. Counters reset on service restart; provider remaining/reset headers account for usage by other clients after the next provider response. No credentials or request bodies appear in the report. At 50% of the 5,000-request hourly budget, Clankie shows `warning` in `/doctor` and submits one warning through native runtime alerts. A refused, thrown or rejected admission remains pending and retries after 60 seconds, including when provider requests stop or the hard budget refuses them. Only one admission can be in flight per actor. Native acceptance stops retries even if its original receipt is unconfirmed; acceptance does not prove the recipient read the alert. Boot-time warnings await the native handler's result. At 80%, device Work refreshes and explicitly marked background reads share a one-minute minimum interval per actor; excess calls are refused before dispatch with a retry time. Existing issue-list caching continues to apply. Ordinary owner/lead reads, writes and webhook context reads retain priority. Every request remains subject to the hard budget, which leaves one request below the cap. Provider headers can lower the effective limit. The warning rearms after usage falls below 50%. `doctor --json` and `/doctor json` include `linearRequestBudget`; unavailable observations remain explicit. These fixed limits need no owner setup. ### `linear read TOOL --json-stdin [--background]` Read through the connected Linear tool bank using JSON arguments on stdin. Use `--background` for automated polling; owner reads default to interactive priority. The fleet equivalent is `clankie_call({name, arguments, background: true})`. Background markers do not grant authority or downgrade writes. Each logical read gets its own admission and may finish its provider pages. For example: ```sh printf '%s' '{"team":"VUH"}' | clankie linear read list_issues --json-stdin --background ``` Initial account setup and OAuth token endpoint calls are outside the connected Linear request counter; GraphQL identity verification during a connected app's credential refresh is counted. ### `linear status` / `linear follow on|off` A verified Linear webhook stores a compact **External activity** message in one ordinary Clankie chat. `linearWebhook.wakeConversationId` selects that chat; `global-default`, the lead conversation, is the default. Open it with `clankie --chat global-default`, or use the configured ID. A chat named for Linear has the same conversation controls and history as any other chat. Following is off by default. With following on, a new signed event that passes `linearWebhook.wake` wakes this chat. The lead decides what to do and delegates from there. Events arriving within a 1.5-second burst window coalesce into one wake containing issue identifiers and titles, what changed, who acted, and links. Events that do not pass the rules remain visible as external context without starting a turn. Exact own-write echoes are suppressed; Clankie's connected account and attributed workers never wake him. Unknown or ambiguous actors stay quiet. Production wakes also require the connected Linear account identity to match the signed event's workspace. An unavailable identity or workspace mismatch keeps activity passive, logged as `identity_unavailable` or `account_workspace_mismatch`; local `active` readiness alone does not prove this identity lookup succeeded. No connected-account notification poll, separate inbox, read/ack protocol, or issue-owner route participates in delivery. A Comment webhook may carry only `issueId`, without an issue title. Missing display context is filled from retained signed Issue history or a native connected `get_issue` lookup bounded to one second. That read supplies the identifier/title only; signed actor, resource and changes remain the authority for wake rules. If title lookup is unavailable, the compact event says `Title unavailable` and keeps its signed issue UUID and link rather than dropping the event. | Following | Chat history | Automatic model turns | | ------------- | ------------------------------ | -------------------------------------- | | Off (default) | Accepted events remain visible | None from incoming events | | On | Accepted events remain visible | One coalesced wake for eligible events | The default rules wake only for comments or mentions by James, identified by the signed user email `volpestyle@gmail.com`. Other actors and other changes stay quiet. The connected tracker account remains Clankie's and his fleet's publishing identity; it does not become the human owner. Display names and notification subtitles do not prove authorship. A wake supplies context, never new permission. [ADR 0214](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0214-linear-wakes-require-attribution-and-rules.md) records the attribution and routing decision. `clankie linear follow on|off` applies live. Turning it off suppresses new and queued event turns; an already-running turn can finish. Turning it on does not replay passive history. `clankie linear status` reports `following`, `active`, `webhookConfigured`, `reason`, `missingWebhook`, `detail`, `wakeConversationId`, `wakeWarning`, and `settingsFile`. Following requires both the registered webhook URL and its broker-held signing secret. Enabling without them leaves the switch unchanged, returns `ok: false`, `error: "linear_webhook_required"`, and exits nonzero. `missingWebhook` names `url`, `secret`, or both. If a prerequisite is removed after enabling, status reports `following: true`, `active: false`, and the same reason. Turning following off remains available. Configure the webhook from `/connect linear` → **Follow Linear** → **Configure webhook**. The flow stores the registered public URL in `linearWebhook.url` and the signing secret in the credential broker (`linear-webhook`). Select all available activity events in Linear's webhook settings. Setup does not enable following; **Start following** / **Stop following** is separate. Existing setups that stored only a secret can record their registered URL with `clankie linear webhook set --url URL`. `clankie linear webhook clear` removes the stored URL and leaves requested following visibly blocked. The secret remains broker-owned and is never a CLI flag. Readiness checks local configuration, not delivery health or the webhook's current status in Linear. The signed public ingress remains `POST /v1/hooks/linear`. It verifies the raw body's signature and timestamp before parsing, deduplicates accepted deliveries, and retains replay safety across restart. The consumer accepts signed `create`, `update`, and `remove` activity. A matching revision of Clankie's own MCP write is dropped at ingress; delegated worker events retain their provenance but stay quiet. For issue write receipts, display-label `id` values such as `VUH-1641` require a valid returned `uuid`; UUID-valued `id` is also accepted. Conflicting or missing identity proof never suppresses an event. Workspace, actor, resource, revision time, and saved-field matching still apply to exact echoes. The old `linear-inbox` conversation and notification checkpoint are retired. On upgrade, retained unread inbox activity is dropped once with a service log entry; it is not replayed into a fresh wake. Historical records in Linear remain available through Clankie's connected `linear_*` tools. The authenticated local operator API exposes `GET /v1/linear/follow` and `PUT /v1/linear/follow` with `{ "following": true | false }`. Both report the configured `wakeConversationId`; PUT without webhook prerequisites returns 409. Changing the local switch does not change which events Linear sends. #### `linear target show|set CONVERSATION_ID` ```sh clankie linear target show clankie linear target set global-default ``` The target is one non-secret setting, `linearWebhook.wakeConversationId`. Select an existing ordinary global chat the owner can open; an attached native operator seat can drive it. To use a new chat, create it with `/new` in the TUI, name it Linear, and find its stable ID with `clankie conversations list` before setting the target. `set` does not create a chat. The change applies live to new activity; it does not move history or replay earlier events. The authenticated API is `GET /v1/linear/target` and `PUT /v1/linear/target` with `{ "conversationId": "global-default" }`. #### `linear wake [show|set …]` Bare `/linear` opens **Follow Linear**, including **Wake rules**. Rules live in `linearWebhook.wake`. Clankie can inspect or change these non-secret settings himself through the operator-only `linear_wake` tool or authenticated CLI; following must still be active. ```sh clankie linear wake show clankie linear wake set --owner-user-emails volpestyle@gmail.com --actors owner clankie linear wake set --types issueNewComment,issueCommentMention,issueMention clankie linear wake set --exclude-types issueSubscribed ``` `set` flags patch only the specified fields. Lists are comma-separated; `none` clears one. `--json-stdin` replaces the whole rule object with defaults for omitted fields. Malformed rules fail without writing. The result contains `ok`, `wake`, and `settingsFile`. | Flag | JSON field | Meaning / default | | --------------------- | --------------------------- | --------------------------------------------------------- | | `--owner-user-ids` | `ownerUserIds` | Additional explicit owner Linear IDs; initially empty | | `--owner-user-emails` | `ownerUserEmails` | Signed owner user emails; default `volpestyle@gmail.com` | | `--actors` | `actors` | Any of `owner`, `human`, `self`, `users`; default `owner` | | `--user-ids` | `userIds` | Exact IDs selected by `users`; initially empty | | `--types` | `notificationTypes` | Included activity types; empty allows all | | `--exclude-types` | `excludedNotificationTypes` | Exclusions always win; default `issueSubscribed` | The default included types are `issueNewComment`, `issueCommentMention`, `issueMention`, `projectUpdateNewComment`, `projectUpdateMention`, `initiativeUpdateNewComment`, `initiativeUpdateMention`, `documentNewComment`, and `documentMention`. The existing VUH-1549 rule engine classifies signed webhook activity using these types. A newly added Linear issue/profile/resource link in signed `body`, `description`, or `content` counts as a mention. Legacy owner-only filters migrate to the new comment/mention defaults when `ownerUserEmails` is absent and the saved filters match the old defaults: `actors: ["owner"]`, `userIds: []`, `notificationTypes: []`, and `excludedNotificationTypes: ["issueSubscribed"]`. Configured `ownerUserIds` are preserved as identity setup. Edited selectors, included types, or exclusions remain unchanged. Once `ownerUserEmails` is persisted, an intentionally empty `notificationTypes` list remains all-types; `clankie linear wake set --types none` can select that behavior after upgrade. `owner` requires a signed human actor matching an owner ID or email. `human` requires signed user identity and excludes the connected account and workers. `users` matches exact IDs. Selectors are ORed, but own-write suppression remains in force. Find Linear IDs through the connected `linear_get_user` tool rather than inferring them from a name or an app account. The operator-only `linear_wake({ action: "show" })` returns the rules and target. `linear_wake({ action: "set", wake: {…}, conversationId: "global-default" })` patches the supplied rule fields and optionally changes the target. No secret or owner-console wizard is needed for these settings. `GET /v1/linear/wake` returns `{ "schemaVersion": 1, "wake": {…} }`. `PUT /v1/linear/wake` replaces the rule object, filling omitted fields with schema defaults. Both require the operator bearer; invalid rules return 400. API, CLI, tool, and TUI edits apply to the next event without restart. They never promote old passive history. Use `follow off` to stop queued turns. The Claude seat denies the inherited `linear-server` MCP server with Claude Code’s server-prefix permission rule. It uses Clankie’s connected `linear_*` tools as the owner-connected account. Whatever tracker identity the owner connects is the identity of Clankie and every worker he hires, across Claude, Codex and pi. No email, display name or installation-specific user ID selects that identity. Worker tracker writes use his granted broker connection; without a grant, the worker asks the lead to write rather than using an independent harness account. The remaining automatic worker-isolation work is specified in [worker tracker identity](https://github.com/Volpestyle/clankie/blob/main/docs/worker-tracker-identity.md). <a id="account-setup"></a> ### `accounts codex [list | add HOME --label LABEL | remove LABEL]` Register the owner's extra Codex homes, without copying or inspecting credentials: ```sh clankie accounts codex add ~/.codex-second --label second clankie accounts codex list clankie accounts codex remove second ``` The TUI accepts the same arguments under `/accounts codex`. The owner signs in and approves hooks in each home through Codex itself. Registration stores only a canonical home path and label; `authPresent` checks file existence, not whether the login is valid. `default` is implicit (`CODEX_HOME`, otherwise `~/.codex`). Removing a registration never deletes its home or credentials. Account reads also report `hookTrust`: `ready`, `review_required`, or `unknown`, from Codex’s read-only `hooks/list` query for that home. Unsupported or failed queries stay unknown. This checks home hooks, not trust for a future repository. Selection still follows headroom; review stale hooks in the selected account’s native Codex UI. No hook hashes or trust approvals are written by this check. If a hired Codex TUI is waiting on hook or folder trust, the hire reports `trust_required` when the prompt is visible. Its pane and app-server stay alive, and its original brief continues once the owner completes review. A slow startup without a recognized prompt reports `start_unconfirmed` with the same pending explanation. Do not repeat the hire; inspect the existing pane. Closing that pane cancels its pending startup. Local Codex hires choose the greatest minimum remaining fraction across the windows Codex reports (some plans report only a weekly window). The read-only `account/rateLimits/read` query uses each home without starting a model turn. If unavailable after ten seconds, recent rollout `rate_limits` provide a fallback. Missing usage or fallback observations older than 24 hours are unknown, not free quota. Known positive headroom wins over unknown; unknown wins over exhausted accounts; registration order breaks ties. Passed reset times restore the corresponding window. If all accounts are exhausted the least constrained one is returned; Codex still enforces its quota. No credentials present means the hire fails. `hire_agent`'s `account: "second"` pins a registered label (including `default`), even when it has less headroom. Overrides on remote or non-Codex hires fail. The hire result and fleet roster carry `seat.account: {label, home}`; the app shows the label. Existing seats keep their account. New registrations apply without a service restart. The owner-authorized API offers `GET /v1/accounts/codex` and `POST /v1/accounts/codex` with `{op:"add", home, label}` or `{op:"remove", label}`. Local transcript discovery, `clankie agents`, resumed sessions and follow-up queue delivery use the account's home; seat-sync uses the hook's transcript path. ### `accounts [list]` / `accounts connect PROVIDER` / `accounts disconnect PROVIDER` / `accounts apps` The owner's own GitHub, Linear and Google accounts, linked to this body ([ADR 0232](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0232-hosted-connections-use-the-body-broker.md)). The service runs each flow and keeps the token in the credential broker (`github`, `linear-api` for registered Linear API OAuth); nothing here prints a token. `accounts` lists each provider's `status`, account, scopes and where to manage it. The body supplies the catalog's name, purpose, permission disclosure and read-only flag. App, account dashboard and `/connect accounts` (also `/connections` → Accounts) use that same catalog. Google rows can report `awaiting_consent`, `expired`, `reconnect_required`, `unavailable` or `disconnected`, together with the last check and pending revocation. An unconfigured row means an operator has not configured the developer OAuth client. `accounts connect github` prints the code to type at GitHub on stderr, polls at GitHub's interval, and returns the connection. `accounts start github` and `accounts poll github --flow-id ID` expose the same flow as separate steps for interactive clients. `accounts disconnect PROVIDER` revokes at the provider when it can and always deletes the local token; `revoked: false` comes with the `manageUrl` to revoke by hand. Disconnecting Linear clears its API and legacy MCP/app lanes and pending flows. The app's Connections settings and the account page use the same encrypted lifecycle. `/connections` exposes account identity, granted scopes, connect and disconnect beside machines in the console. `accounts connect google-gmail|google-calendar|google-drive` (or `start` with the same provider) returns a browser consent URL, single-use state and expiry. `accounts complete google-gmail|google-calendar|google-drive --json-stdin` consumes `{state,code}` for Gmail/Calendar or `{state,code,pickedFileIds}` for Drive; the provider comes from the command selector. Google's file picker returns selected IDs in `picked_file_ids`; clients validate and forward them as the `pickedFileIds` array. `accounts check google-gmail|google-calendar|google-drive` verifies the selected authorized access. Google refresh, token exchange and revoke run on the body; the console accepts the `clankie://accounts/google/callback` link through a masked prompt. Gmail requests `gmail.readonly`; Calendar requests `calendar.calendarlist.readonly` and `calendar.events.readonly`; Drive requests only `drive.file` through Google's file picker, with no other scope combined. The body reads files selected in that flow. Google's selected-file permission also permits editing those files; Clankie's implemented Drive tools only read. Gmail and Calendar consent also request `openid email` to verify identity. Drive identity is verified through the Drive API. No mail-send or calendar-write scope is granted, and the shipped Google tools expose no writes. The [Google Picker guide](https://developers.google.com/workspace/drive/picker/guides/desktop-mobile-picker) describes the selected-file consent flow. Google grants share an application and account lifecycle. Disconnecting any Google row disables all three Google connections on this body. If the provider cannot confirm revocation, local access stays disabled and the catalog reports pending revocation; retry disconnect or review the grant at Google's management URL. A connection is never reported as revoked until Google confirms it. Real Google access requires developer client registration and the owner's browser consent; fixture checks do not establish a consented production read. `accounts connect linear` returns the registered app's authorize URL, single-use state and expiry. The body retains the S256 verifier and exchanges the callback code. `accounts complete linear --json-stdin` consumes `{state,code}` from stdin; codes do not belong in argv or logs. The console accepts the Open Clankie callback link through a masked prompt. The separately configured Mac `/connect linear` MCP connection remains available. For worker names and portraits, use a workspace-owned app: `accounts connect linear-app --client-id ID --secret-stdin`. The secret enters through stdin and is verified and stored by the service, never returned. `/connect linear` also offers **Connect a Clankie app**. `accounts list` reports the verified `actor` and `workspace`. This updates the legacy MCP/app lane; the registered API connection remains separate and takes precedence when present. Changing the active app identity requires new worker grants. Setup and scope: [worker posts](https://github.com/Volpestyle/clankie/blob/main/docs/linear-worker-posts.md). `accounts apps [set|clear] [--github-client-id ID] [--linear-client-id ID] [--linear-redirect-uri URL] [--google-client-id ID] [--google-redirect-uri URL]` reads or writes the public OAuth client settings (`oauthApps` in `settings.json`); they apply without a restart. `CLANKIE_GITHUB_OAUTH_CLIENT_ID`, `CLANKIE_LINEAR_OAUTH_CLIENT_ID` and `CLANKIE_LINEAR_OAUTH_REDIRECT_URI` override them, which is how a hosted body is configured. An owner-run self-hosted body may revoke its own GitHub token using its own OAuth app's secret as broker entry `github-oauth-app`. Explicit owner provisioning on that self-hosted body uses `accounts apps github-secret --client-id ID --secret-stdin`; it stores the secret only in the broker and returns a closed outcome. It requires operator access and refuses hosted bodies. Clankie's shared developer secret is never delivered to customer bodies. Hosted GitHub disconnect removes local access and returns the GitHub permission-management URL with `revoked: false`. Self-hosted revocation deletes only the selected token, preserving other body tokens. Hosted public app IDs and the exact gateway `/account/connections/callback` arrive through body bootstrap; developer secrets are excluded. Provider app registration and terms acceptance remain owner actions. For a local development Google web OAuth client, set its public client ID and registered redirect URI through `accounts apps set`. The callback path is `/account/connections/google/callback`; HTTPS is required except for local loopback HTTP development. Store the matching developer secret with `accounts apps google-secret --client-id ID --secret-stdin`. This is a local operator command, writes broker entry `google-oauth-app` with its client ID, and refuses hosted bodies and remote transports. The secret never enters argv, environment variables, settings, output or device responses. Google public settings also support `CLANKIE_GOOGLE_OAUTH_CLIENT_ID` and `CLANKIE_GOOGLE_OAUTH_REDIRECT_URI` overrides; neither variable accepts a secret. <a id="voice-status-voice-model-set-model-id-voice-model-clear"></a> ### `voice [status]` / `voice brain set PROVIDER [MODEL_ID]` / `voice model set MODEL_ID` The headless launcher inspects voice settings, selects the voice brain, and changes an already configured ElevenLabs speech model. These commands store public settings locally; they never make a model call or restart a service. `voice status` returns `voice` (stored), `effectiveVoice`, `overriddenByEnvironment` (environment variable names), `settingsFile`, and `restart`. No credential is returned. `voice model set/clear` changes only the ElevenLabs model, preserving the voice ID, realtime provider, consent and all other settings. Select an ElevenLabs voice ID with the console's `/voice` first. `voice brain set openai|xai|anthropic [MODEL_ID]` selects the conversation brain. Omitting the model keeps that provider's prior model or its runtime default; `voice brain model clear` restores the selected brain's runtime default. The OpenAI and xAI models, voices, and inactive ElevenLabs configuration remain stored when switching. xAI selects its native speech output; OpenAI keeps the currently selected speech output. Anthropic selects ElevenLabs and refuses to save until an ElevenLabs voice ID is configured. Anthropic's default is `claude-sonnet-5-5`. It receives attributed transcript text and conversation context; OpenAI transcribes consented audio and ElevenLabs synthesizes Clankie's chosen words. The active Discord body needs separate brokered API credentials under `anthropic`, `openai`, and `elevenlabs`. Use `/voice` or `/auth` to store those keys; environment credentials and Claude subscription tokens are refused. `/voice status` shows all three key checks. ```bash clankie voice status clankie voice brain set anthropic claude-sonnet-5-5 clankie voice model set eleven_v4_turbo # After reviewing settings and arranging an interruption of active calls/work: clankie restart clankie ``` `eleven_v4_turbo` selects Text to Dialogue multi-context WebSocket synthesis. An unset model retains `eleven_flash_v2_5` on the legacy TTS transport. To roll back an originally unset model, use `clankie voice model clear`, then the same restart. If a model was explicitly set, restore it with `model set ORIGINAL_ID`. To return to the prior brain, use `voice brain set ORIGINAL_PROVIDER` with its retained model. If its speech output was native OpenAI, select that stack again with `/voice`; returning from Anthropic to OpenAI preserves ElevenLabs output. Then use the same restart after arranging an interruption of active calls. Environment overrides still win: check `effectiveVoice` before restarting. This command is local-only; hosted mode refuses it. See the [voice operating guide](https://github.com/Volpestyle/clankie/blob/main/apps/discord-bridge/README.md) for verification limits. ### `work [status]` / `work init` / `work list|show|create|update|close|attach|write|receipt` Tracks work where the repo already does ([ADR 0191](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0191-work-is-tracked-where-the-repo-tracks-it.md)): its Linear team (through the Linear account connected to Clankie), its GitHub issues (through the owner's `gh` login), its own one-file-per-item Markdown directory, or `.clankie/work/` when it has none. Every command runs against the git repo containing the current directory, or `--repo PATH`, and prints JSON. It is a compatibility CLI over the same Linear-shaped tracker tools Clankie and workers discover as `linear_*` ([ADR 0226](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0226-one-tracker-tool-surface.md)). Issue reads and searches, patch edits, labels, relations, comments and replies, projects and project status updates use the same input shapes with connected Linear or durable local storage. `clankie doctor` reports the active backend and selection reason. An explicit `repo` tool argument selects the repo's recorded GitHub or Markdown adapter. - `clankie work` (or `work status`, `work discover`) reports the repo's signals, its recorded convention if any, and a `question` when discovery found more than one tracker or only a single `TODO.md`. Answer it once with `work init`. - `clankie work init` records what discovery found; `work init --backend default|markdown|github|linear [--directory D] [--github-repo OWNER/NAME] [--linear-team KEY] [--linear-project NAME] [--linear-label LABEL] [--release-source tags|milestones|both] [--release-lane NAME] [--note TEXT]` records the owner's choice. The answer is written to `.clankie/tracking.json` in the repo; nothing else is added to a repo that tracks work elsewhere. `--linear-label` saves an existing Linear label as `linear.label`, scoping this repo's board within its team/project and adding the label to new items. It requires a Linear convention with a team; blank, multiline or over-64-character labels are refused. Omit it to keep the team/project-wide board. The HTTP init parameter and the device write's init parameter are `linearLabel`. - `clankie work project` (also `/work project` in the TUI) returns planned milestones, shipped `v*` versions and Linear initiative goals for the saved tracker. `work init --release-source tags|milestones|both --release-lane NAME` changes the release selection without changing an existing tracker. Source defaults to `both`; lane defaults to `repository` until explicitly named. Separate mobile/macOS repos can name their own lanes. Dates say whether they came from a publication, annotated tag or lightweight tag's commit. Missing store builds and release membership are not inferred. Markdown has no planned milestone collection; GitHub and Markdown return no initiative goals. Failed/unsupported sections carry explicit `unavailable` entries. - Work statuses are `backlog`, `todo`, `in_progress`, `in_review`, `done`, and `canceled`. Linear backlog/triage, GitHub `status: backlog`, and Markdown `status: backlog` stay distinct from todo. Items may carry a native milestone id/name; Markdown uses both `milestone_id` and `milestone_name` front matter. Device `work_items` requests opt into these facts with `statusVersion: 2`; older requests receive backlog as todo and omit the new milestone field. `work_project` uses the same registered repo ids and device authority as `work_items`. Metadata reads share connected-account snapshots and pagination, preserving the poller's provider budget. - `clankie work repos` lists the repos registered on this machine. A repo is registered the first time a local command names it; only registered repos are readable from a paired device. - `clankie work list [--status todo,in_progress] [--owner NAME] [--label L]`, `work show ID`. `--label` keeps items carrying that label, matched case-insensitively; it is how a role station reads its backlog ([ADR 0208](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0208-agents-carry-a-role-the-world-reads-it.md)). Items carry `labels` from the backend: Linear labels, GitHub labels (without the `status: …` labels this backend writes), or a Markdown item's `labels:` front matter (`[a, b]`, `a, b`, or a YAML block list). On a scoped Linear board, `--label` intersects the saved repo label along with status/owner filters; it does not replace the scope. `show ID` remains a direct known-item read. Board scope does not grant or restrict tool authority. - `clankie work create TITLE [--summary S] [--owner NAME] [--criterion C]... [--status S] [--priority 0..4]`. - `clankie work update ID [--status S] [--owner NAME | --no-owner] [--title T] [--priority 0..4] [--check N]... [--uncheck N]... [--add-criterion C]...`; criterion numbers are 1-based and may be comma-separated. - `clankie work close ID [--canceled]` sets `done` (or `canceled`). - `clankie work attach ID --url URL --caption TEXT [--kind image|video|log|link]` appends evidence; the kind is inferred from the URL when omitted. `clankie work write ID --owner NAME|--no-owner|--add-label ROLE|--remove-label ROLE|--add-blocker ID [--request-id UUID]` performs exactly one owner-authorized change to an existing item. `--owner` sets its work metadata, not a provider user assignee. The CLI allocates an ID before dispatch when omitted; retain that ID from the JSON result. `clankie work receipt ID --request-id UUID` reads the original result. Both accept a registered or project repository ID in `--repo`; a local path resolves to an already registered repository. Use `work status` to register it before writing. Project references require their local saved tracker workspace. GitHub writes require its connected account. The JSON result includes `requestId`, `outcome` (`applied`, `refused`, `uncertain`), a plain `message`, and optionally the refreshed `item`. Repeating an ID reads its receipt after checking the original owner, item, binding and command; it never writes again. If the response is lost, inspect the receipt and tracker rather than sending the same change with a new ID. Labels and blockers merge into freshly read state, preserving unrelated labels and prerequisites. GitHub status labels are reserved. `parent` is optional read metadata, separate from blockers. Paired devices with `terminalControl` use the owner-preserving operator ops `work_item_write` (`request: {repoId, itemId, requestId, command}`) and `work_item_write_receipt` (`repoId`, `itemId`, `requestId`). Commands are `{action:"assign", owner:NAME|null}`, `{action:"add_label", label:ROLE}`, `{action:"remove_label", label:ROLE}`, or `{action:"add_dependency", id:ID}`. The host checks owner authority again at publication and audits each write; chat and execution credentials cannot authorize it. Local HTTP uses `POST /v1/work` with `{action:"write", request:...}` or `{action:"write_receipt", request:...}` and the operator bearer. Statuses are `todo`, `in_progress`, `in_review`, `done` and `canceled`, projected onto each backend's own states. Priority is `0` (none), `1` (Urgent), `2` (High), `3` (Medium), `4` (Low); open work sorts Urgent through Low, with unprioritized work last, before limits. When Linear is disconnected the common surface uses local storage; a connected failure never replays a write locally. Connected Linear issue lists share a sorted snapshot for up to 60 seconds, including their cursor pages and concurrent readers. Writes and verified webhooks invalidate it; an expired or invalidated cursor requires restarting the listing. Failed list reads wait at least 30 seconds before another provider scan. Direct issue reads remain fresh for read-before-write checks. The service's `mcp.host.call` log includes `trackerRead.providerPages`, so a cache hit records zero provider requests rather than looking like another Linear scan. Local records persist across restart and do not automatically migrate on connection. Other unavailable repo providers answer `backend_unavailable`. The HTTP form is `POST /v1/work` with the operator bearer and `{ "action": ... }`. ### `operator-credential rotate [--json]` Mint a new local operator bearer. Existing operator sessions are invalid immediately. JSON: `{ "ok": true, "status": "rotated", "source": "store" }`. The new secret is not printed. ### `play status` The live embodiment session (`GET /v1/embodiment/sessions/live`). JSON `{ "session": … }` or `{ "session": null }`. Requires an operator credential; start the clankie service once if none exists. ### `play stop` Operator kill-switch (`POST /v1/embodiment/sessions/live/stop`). The play host winds down at the next turn boundary — this is not a process kill. A live session returns JSON. Idle is the sentence `Nothing is playing.` (exit 0, not JSON). ### `play guide TEXT --conversation CONVERSATION_ID` Suggest an objective or approach to the live Pokémon mind. The authenticated `POST /v1/embodiment/sessions/live/guide` accepts `{text, conversationId}` and checks the selected conversation's play ownership again before queueing it. Clankie can use `pokeagent_guide` from that conversation; the mind still chooses its objective and actions. This does not start a sitting or affect another game. ### `rivals` `rivals connect URL [--token-stdin]` / `disconnect` configure the Rivals Agent origin live; its token is broker-owned under `rivals-agent` (`/auth rivals-agent`). `rivals status` reads the current sitting. `rivals start autonomous|combat|disengage [NOTE]` starts a bounded sitting. `rivals objective SESSION MODE [NOTE]`, `observe SESSION`, `share SESSION [GUILD CHANNEL]`, and `stop SESSION` require its observed ID. All return JSON; a refusal exits 1. `/rivals` exposes the same commands in the TUI. Notes are context, not instructions the current scripted policy understands. See [Rivals setup and verification](https://github.com/Volpestyle/clankie/blob/main/docs/rivals.md). ### `minecraft` `minecraft host status|start|stop|restart|backup` manages the integration-owned Paper server. Hosting is off by default, stops after 15 minutes with no players, and has a six-hour maximum requested-run uptime. `host configure` reads its settings; `host configure JSON` updates stopped-server resource, backup and idle/uptime settings. The `backend` field selects `{"kind":"local"}` or the pre-provisioned AWS EC2 backend (account, instance and region); see [AWS setup and cost controls](https://github.com/Volpestyle/clankie/blob/main/docs/minecraft.md#aws-hosting-and-cost-controls). AWS starts use a scoped broker credential and SSM, and stop must confirm the instance is stopped. `host admin JSON` accepts typed administration; `host approve USERNAME` approves an existing verified Discord request. No op, raw RCON or account-secret arguments are accepted. `host tunnel claim` starts a background agent build/claim job and returns quickly with its phase. `host tunnel status` reads its phase and the approval URL when ready. The integration polls playit every three seconds and sends browser approval straight to the broker even after the CLI exits; `host tunnel complete` also reads status. This Mac builds pinned playit source during setup and requires Cargo. `minecraft configure PROFILE HOST --version VERSION [--port PORT] [--username NAME]` adds an offline Java server profile. `configure` shows settings; `configure remove PROFILE` and `configure allow-public|revoke-public HOST [PORT]` manage destinations. DNS/SRV targets are resolved and checked before dial; public endpoints require an owner allowlist. Clankie’s setup tools can configure profiles for owners/individual operators; gameplay tools select approved profile ids. `minecraft configure play` reads the default play loop settings. Flags update them without changing profiles or destinations: ```sh clankie minecraft configure play --model openai/gpt-4.1-mini --max-tokens 100000 --max-cost-usd 1 clankie minecraft configure play --turn-interval-ms 2000 --idle-backoff-ms 15000 --idle-stop-ms 900000 clankie minecraft configure play --enabled off ``` Play is enabled by default with the model and limits shown above. The token and reported cost ceilings are per mind run; reaching either stops further decision calls. Model, budget and pacing changes apply on the next join or handoff back to the mind. Disabling play quiesces the current mind. The existing authenticated `GET`/`PUT /v1/minecraft/configuration` API carries these same fields in `play` alongside the full profiles and allowlist configuration. `minecraft driver` reads who currently drives the session. `driver mind` returns it to Clankie's play loop; `driver owner` takes direct control for the owning conversation; `driver worker fleet:FLEET:pane:SEAT` hands it to that exact hired native seat. Direct actions require the selected owner or worker driver. In the TUI, `/minecraft driver` shows the current driver and a selector with a worker principal prompt; `/minecraft configure play` exposes the same settings flags. `minecraft status|profiles|join PROFILE|leave|cancel [ACTION]|pause|resume|observe` manages the session. `chat TEXT`, `follow PLAYER [DISTANCE]`, `goto X Y Z`, `dig X Y Z`, `place X Y Z ITEM`, `craft ITEM COUNT`, and `action JSON` return prompt action handles; `action-status ACTION` separates settlement from server evidence. All return JSON. Use `--conversation ID` to select an existing owning conversation. `/minecraft` exposes the same controls and settings in the TUI. Minecraft and Pokémon share one play lease, released only after confirmed disconnect. See [Minecraft setup and limitations](https://github.com/Volpestyle/clankie/blob/main/docs/minecraft.md). <a id="model-setup"></a> ### `model [status]` Paired apps, including hosted bodies without a terminal, use the same catalog, credential broker and captain selection through the [owner model-key API](https://github.com/Volpestyle/clankie/blob/main/docs/model-keys.md). Captain model and every config-declared provider. JSON: ```json { "ok": true, "model": "ds4/deepseek-v4-flash", "effort": "high", "providers": { "ds4": { "baseURL": "http://127.0.0.1:8000/v1", "models": ["deepseek-v4-flash"] } }, "restart": "clankie restart captain" } ``` `ok` is false and exit 1 when `clankie.json` has load issues (`issues` is then present). `model` and `effort` are `null` when unset. The running service does not pick up a write until `clankie restart captain`. ### `model add-local --id ID --base-url URL [--context N] [--models id,id] [--set]` Declare a credential-less OpenAI-compatible local runtime (ds4, Ollama, LM Studio, vLLM, llama.cpp) into global `clankie.json`. The TUI `/provider` → “add a local endpoint…” flow uses the same writer. | Flag | Meaning | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `--id` | Provider id. Lowercased. Letters, digits, `.`, `_`, `-`; no slashes. | | `--base-url` | `http://` or `https://`. A bare origin is rewritten to `/v1` (`http://127.0.0.1:8000` → `http://127.0.0.1:8000/v1`). Trailing slashes are stripped. | | `--context` | Fallback context window in tokens when the probe does not report one. Default `32768`. Must be a positive integer. | | `--models` | Comma-separated model ids used when the probe returns nothing. | | `--set` | Select the first listed model as captain (`providerId/firstId`). | The probe is `GET {normalizedBaseURL}/models` with a 3-second timeout. Local runtimes are unknown to models.dev, so the endpoint itself is the catalog. ```bash clankie model add-local --id ds4 --base-url http://127.0.0.1:8000 --set ``` ```json { "ok": true, "providerId": "ds4", "baseURL": "http://127.0.0.1:8000/v1", "models": ["deepseek-v4-flash", "deepseek-v4-pro"], "model": "ds4/deepseek-v4-flash", "restart": "clankie restart captain" } ``` If the probe fails and `--models` was given, the write still succeeds and the payload includes `probeError`. If the probe fails or lists nothing and `--models` was omitted, exit 1 with `{ "ok": false, "error": "…" }`. The local runtime is **not** a launcher-owned service. Start ds4, Ollama, or LM Studio yourself; `clankie restart captain` only reloads Clankie's config. ### `model set providerId/modelId` Select the captain. The ref splits on the **first** slash (model ids may contain slashes). JSON: `{ "ok": true, "model": "xai/grok-4.6", "restart": "clankie restart captain" }`. ### `model refresh` Refresh the available model catalog. Use this when a newly released model is missing: the captain, gameplay, and commentary otherwise read the installed or cached catalog. The TUI's `/model` and `/provider` offer the same refresh. Restart the captain afterward with `clankie restart captain`. JSON contains `ok`, `source` (`network`, `cache`, or `bundled`), `updated`, provider and model counts, and the restart command. A successful network refresh exits 0. If the network fails or fetching is disabled, the existing catalog remains usable, but refresh returns `ok: false` and exits 1. `CLANKIE_DISABLE_MODELS_FETCH` and an explicit `CLANKIE_MODELS_PATH` skip fetching. For Astra, run `clankie model refresh`, then `clankie model set openai-codex/gpt-6-astra` and `clankie effort set high`. The supported efforts are `low`, `medium`, `high`, `xhigh`, and `max`. Captain, gameplay, and commentary use the selected model; voice and image/video generation keep their separate selections. Verified transport settings (2026-09-04): | Provider/model | Transport | Configured context / maximum output | | -------------------------- | ---------------- | ----------------------------------- | | `openai-codex/gpt-6-astra` | Codex Responses | 400,000 / 128,000 tokens | | `openai/gpt-6-astra` | OpenAI Responses | 1,050,000 / 128,000 tokens | The subscription context value is conservative, not a measured backend ceiling. A local/self-hosted `openai` selection uses the subscription when available; disable the `openai-codex` provider to select the metered API transport explicitly. In a checkout, `pnpm --filter @clankie/clankie verify-model provider/model@effort` checks a captain tool-and-image turn, a gameplay action, and commentary using isolated settings. It makes live provider requests. Add `--metered` for the API transport or `--json` for a machine-readable receipt. A provider error fails that path's check with the provider's message, and any failed check exits 1. `--config-home PATH` checks the selection previously written by the CLI under that configuration home. The owner's live selection remains unchanged. ### `model routing [status]` Task-based model routing ([ADR 0192](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0192-model-routing-by-kind-of-task.md)). Everyday turns run on a cheap routine model; real work stays on the captain model. Off until a routine model is set; off, every turn runs on `model` as before. JSON: ```json { "ok": true, "enabled": true, "routineModel": "openai/routine-model", "workModel": "openai/gpt-6-astra", "escalate": true, "escalationModel": "openai/gpt-6-astra", "routineTurnLimit": 12, "purposes": { "operator": { "tier": "work", "model": "openai/gpt-6-astra" }, "discord_social": { "tier": "routine", "model": "openai/routine-model", "escalatesTo": "openai/gpt-6-astra" }, "discord_granted": { "tier": "work", "model": "openai/gpt-6-astra" }, "gameplay": { "tier": "work", "model": "openai/gpt-6-astra" } } } ``` | Purpose | Which calls | Default tier | | ----------------- | ------------------------------------------------------------------- | ------------ | | `operator` | Operator conversations, their wakes, watches and side conversations | `work` | | `discord_social` | Discord text or voice turns without machine tools | `routine` | | `discord_granted` | Discord turns holding machine tools, and the Herdr watches they arm | `work` | | `gameplay` | The play mind and its commentary | `work` | Every verb prints the status above after writing. Writes take effect on the next turn (the next play session for `gameplay`); no restart is needed. | Command | Effect | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `model routing set providerId/modelId` | Choose the routine model; turns routing on | | `model routing off` | Remove the routine model; every purpose returns to `model`. Other routing settings are kept | | `model routing escalate on\|off [--model providerId/modelId]` | Let a turn move to the escalation model once per turn (routine turns default to `model`) | | `model routing purpose PURPOSE routine\|work\|default` | Override one purpose's tier | | `model routing turn-limit N\|default` | Model calls a routine turn may make before it escalates as looping (default 12) | With escalation on, a routine turn moves to the escalation model for the rest of that turn when he calls `escalate`, when it reaches the turn limit, or when the routine model fails with an error the runtime retries (the retry runs on the escalation model). A permanent error does not escalate. A routine model that cannot be served fails the turn by name; it never falls back to the work model. A work turn (operator, granted Discord, gameplay) escalates too when escalation is on and `--model` names a model other than the captain model, but only when he calls `escalate`: never on the turn limit or a provider error, since long work is normal there. This works without a routine model. Effort is per model ref, so `clankie effort set LEVEL --model REF` sets the routine model's effort. Hosted bodies receive routing from the fleet at start. The console's `/routing` takes the same arguments. ### `model compaction [status]` / `model compaction set TOKENS` / `model compaction default` When a long captain session compacts ([ADR 0195](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0195-hosted-requests-fit-the-model-proxy.md)). Unset, included usage (the hosted `clankie/*` models) compacts at 250,000 tokens and every other model at its own context window. Set, the threshold applies to every model (at least 16,384 tokens, Pi's reserve). A live session picks the change up on its next turn. JSON: ```json { "ok": true, "compactAtTokens": null, "includedUsageDefault": 250000, "appliesTo": "included usage" } ``` The console's `/compaction` takes the same arguments. ### `effort [status]` Read the current captain model's stored effort override. JSON: `{ "ok": true, "model": "xai/grok-4.6", "effort": "high", "restart": "clankie restart captain" }`. `effort` is `null` when Pi uses its model-supported default. ### `effort set LEVEL [--model provider/model]` / `effort clear [--model provider/model]` Set or remove the variant for the named model. Without `--model`, the currently configured captain model is the target. The TUI `/effort` modal obtains the supported levels from Pi and calls this writer. Paired apps read and set the running model's effort through the [owner model-key API](https://github.com/Volpestyle/clankie/blob/main/docs/model-keys.md). The writer saves the requested effort. At execution, an unsupported effort is refused by name with the supported ladder, consistently across captain, gameplay, and commentary; it is never silently downgraded. ### `image-model [status]` / `image-model set provider/model` / `image-model clear` Read, set, or clear the image generation model. JSON is `{ "ok": true, "imageModel": "openai/gpt-image-2" }`; the value is `null` when unset. Media generation loads this config per request, so no restart is needed. The TUI `/image-model` command calls the same functions. ### `video-model [status]` / `video-model set provider/model` / `video-model clear` The same contract for video generation, with a `videoModel` result field. The TUI `/video-model` command calls the same functions. ### `persona [status]` Return the complete owner-authored persona plus `settingsFile` and the restart command. Character configuration grants no authority. ### `persona images status|set <folder>|clear` Select an owner-authored image/video folder with `persona images set ~/Pictures/clankie-vibe`. `status` previews filenames, roles, counts, source/base64 sizes, dimensions, video durations, sample timestamps and viewable cached `sheetPath` paths, skips and load errors; it never emits image bytes. `clear` clears the setting without deleting files. Restart Clankie to apply changes (the command prints the reminder). Top-level files are vibe; put physical character references in `appearance/`. Only appearance references feed self-portraits. PNG/JPEG/WebP sources may be up to 10 MiB; MOV/MP4/WebM up to 256 MiB / ten minutes. Videos require ffmpeg and ffprobe and become one 5×2 contact sheet of ten evenly spaced samples each; audio is ignored. Read sheets left to right, then top to bottom. Appearance loads first, then vibe, filename-sorted: eight source slots and eight references total. Stills fit within a 1024-pixel edge, sheets within 2000×800; both cap base64 at 128 KiB. The TUI `/persona` → Persona images uses the same writer. The authenticated `/v1/operator/persona` API accepts `imagesDir`; an empty string clears it. Hosted paths name folders on the body. See [persona images](https://github.com/Volpestyle/clankie/blob/main/docs/persona-images.md) for caching, voice, model support and A/B evaluation. ### `persona set [flags]` Update one or more persona fields atomically: | Flag | Value | | ----------------------- | ---------------------------------------------------------------------------------------- | | `--display-name` | 1–64 characters | | `--aliases` | Comma-separated names; `none` clears | | `--character-notes` | Up to 4,000 characters | | `--chattiness` | `quiet`, `balanced`, or `chatty`; shapes Discord and stream rooms, not the operator lane | | `--reply-policy` | `addressed` or `all` | | `--live-message-window` | Whole number from 0 through 100 | JSON contains `{ "ok": true, "persona": { … }, "settingsFile": "…", "restart": "clankie restart captain" }`. The TUI `/persona` modal calls this same writer. ### `desktop [status]` / `desktop quiet-hours START END TIME_ZONE|off` Read or set desktop quiet hours. Times use `HH:mm` and an IANA time zone, for example `clankie desktop quiet-hours 22:00 07:00 America/Chicago`. Overnight ranges are supported; the start is inclusive and the end exclusive. Equal start and end times are rejected. `clankie desktop quiet-hours off` removes the range. The TUI `/desktop` takes the same arguments. Changes apply immediately without restarting. JSON returns `desktop` and `settingsFile`. The captain's `desktop` tool can emote, move within the current display using normalized coordinates, or show a short bubble. Expressions carry a unique ID and expiry in `presence`; they last five seconds by default, up to thirty. Quiet hours suppress them without changing the source-derived mood. Desktop clients also honor macOS Focus, discard expired expressions, and show them without taking keyboard focus. Publishing does not confirm a client displayed it. ### `games [status]` / `games set on|off` / `games budget` Read or set whether the PokeAgent MMO body is available. JSON contains the `games.pokeagentMmoEnabled`, optional `games.pokemonBudget`, `settingsFile`, and `"restart": "clankie restart"`. The TUI `/games` exposes availability and token/cost caps using the same writer. Restart to apply defaults to subsequent sittings. ```sh clankie games budget max-tokens 250000 clankie games budget max-cost-usd 1 clankie games budget max-cost-usd default ``` `max-turns` and `max-duration-ms` accept positive integers too. `default` removes an override. Pokémon defaults to 250,000 charged model tokens, including commentary and interrupted decisions; turn/duration and dollar caps are optional. The authenticated `GET`/`PUT /v1/games/configuration` reads/replaces this gameplay configuration. Embodiment start intents can override these defaults with `budget`. Journals record input/output tokens, charged tokens, model calls and estimated USD on each turn and the summary. A metered call without usage reserves 16,000 charged tokens and marks cost unknown; a dollar-capped session then stops. Caps are checked between calls, so the last call can exceed a threshold. Cost is an estimate from registry prices, not an invoice. Terminal receipts name `budget_exhausted`, `mind_unavailable`, `world_ended` or `stopped`. ### `browser tools` / `browser call TOOL JSON` Inspect or call Clankie's Browser Use Pi tools with the operator credential: ```sh clankie browser tools clankie browser call browser_use_open '{"url":"https://example.com"}' --conversation CONVERSATION_ID clankie browser call browser_use_javascript '{"code":"console.log(await page.info())"}' --conversation CONVERSATION_ID clankie browser call browser_use_close '{}' --conversation CONVERSATION_ID ``` The same catalog and call contract are available at `GET /v1/browser/tools` and `POST /v1/browser/call`. Native JavaScript requires machine authority; native captain calls carry their admitted conversation binding. Direct operator calls require `--conversation ID` (HTTP: `x-clankie-conversation-id`) naming a runnable conversation. A bearer and arbitrary ID cannot create authority. JavaScript variables persist within a browsing burst; mode changes, idle close and worker timeouts reset them. The SDK uses Clankie's private profile and workspace under `~/.clankie/runner/browser/`. It discovers installed Chrome; set `CLANKIE_BROWSER_EXECUTABLE` to use a particular Chrome/Chromium executable. `CLANKIE_AGENT_BROWSER_EXECUTABLE` no longer applies. No browser model key is needed: Clankie's existing model writes the code and the SDK executes it. ### `body status` / `body request JSON` Inspect who holds Clankie's Discord mouth, voice/Go Live, browser, or play body: ```sh clankie body status clankie body request '{"action":"queue","resource":"browser","conversationId":"CONVERSATION_ID","request":"Notify me when the browser is free","ttlMs":300000}' clankie body request '{"action":"ask","resource":"voice","conversationId":"CONVERSATION_ID","request":"Can you finish this voice stay?","ttlMs":300000}' ``` `computer` ownership is exposed separately through `clankie computer request` and `/v1/computer`; the legacy body status resource set stays unchanged. `GET /v1/body-leases` returns `{leases:[...]}` with resource, owning stable conversation ID, expiry and `active`/`recovery_required` state. It accepts the operator or an active paired device's existing observe grant. It exposes no incarnation tokens, actor details, room text or request messages. The relay forwards the same read with the original device bearer. `POST /v1/body-leases` uses the strict JSON request above and operator authority bound to the selected runnable conversation. Busy results name the holder and retain typed `queue`/`ask` options. Queue wakes the requester after release; ask delivers only the supplied text to the captured owner. Both expire, refresh source and destination authority, and perform no body effect. Unknown legacy owner routes cannot be redirected to a default room. A social request stays social even if its actor later gains machine authority. Acquire returns a private incarnation for renew/release. Ordinary release is owner-only and confirms actual session termination; a token is not a stop receipt. Explicit operator `recover` can stop a different owner's resource, using the current private host claim. Expiry, restart, a stop request, or a failed response does not imply termination. Uncertain operations remain held until exact delivery or termination evidence resolves them. Recovery never silently retries a Discord send. ### `browser [status]` / `browser record on|off` Read or set `browser.recordSessions`. When on, each burst of Clankie's browsing is saved as a WebM under `~/.clankie/runner/browser/recordings/`: recording samples the current tab every 750 ms and stops after 60 seconds without one; the newest 50 are kept. The browser then closes its tabs/windows while keeping its private profile and persistent logins, even with recording off. Browsing defaults to headless; explicit `headed: true` takeover lasts for that burst, and the next burst starts headless. Changing modes saves the previous recording before starting another. Off by default, because videos capture every page he opens, signed-in ones included. JSON contains `browser.recordSessions`, `settingsFile`, and `"appliesTo": "next_browsing_burst"` — no restart is needed. The TUI `/browser` command calls this same writer. ### `browser harnesses` / `browser delegate on|off` The configured computer-use harnesses here and on linked Windows fleets for hard computer and browser work ([ADR 0199](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0199-hard-computer-work-goes-to-a-computer-use-harness.md)). `harnesses` asks the service (`GET /v1/browser/harnesses`, operator bearer), which re-probes on every read: `codex login status` and `codex features list` plus Codex's plugin config for Codex computer use and Chrome, and `claude auth status` plus `~/.claude.json` and Chrome's native host for Claude in Chrome. Nothing is started or driven. JSON contains `detected` (false on a hosted body, where owner-machine probes are not configured), `harnesses` (each with `harness`, `signedIn`, `surfaces` of `desktop` and/or `chrome`, `chromeNeedsHireFlag`, and `missing` saying what the owner does when it is not ready), optional `platform` and fleet `machineId`, and `harnessDelegation`. Windows probes read Codex login, flags and its installed Windows plugin; they report signed-out and disabled installs. App grants and successful input remain separate live proof. `delegate on|off` sets `browser.harnessDelegation` (default on): whether the ready harnesses appear in the `reach` section of his prompt, on lanes with machine access only. Turn it off to keep him from spending those plans. His own browser is unaffected. JSON is the `browser status` shape with `"appliesTo": "next_session"`. `/browser harnesses` and `/browser delegate on|off` in the TUI call the same code. A listed harness is hired with `hire_agent`; `chrome: true` starts claude with `--chrome`. <a id="fleet-resource-governor"></a> ### `heavy [--seat LABEL] -- COMMAND [ARGS...]` / `fleet resources` / `simulator` `heavy` runs a local command inside the shared OS-account resource governor. It preserves child arguments, exit status and signals. Keep installs, compilers, test suites, builds and entire owned runtime lifetimes inside the wrapper, and serialize multi-package compilers with `--workspace-concurrency=1`. Native local hire briefs include this contract automatically. Nested verified commands reuse the same permit; surviving descendants retain it after a wrapper exits. `fleet resources` returns current capacity, pressure, holders and queue as JSON. The operator fleet snapshot carries the cached `resources` field; the TUI `/status` and `/doctor` show holders by seat label or actual PID. Sampling does not run on `/health`. Resource metadata contains no arguments or credentials. The owner sets `fleet.resources` with these flags or the TUI `/fleet resources`: | Flag | Default | Meaning | | ----------------------------------------- | ------- | ----------------------------------------------------------------------------------- | | `--heavy-slots auto` or `--heavy-slots N` | `auto` | Shared capacity, 1–64; auto is min(floor(cores/8), floor(RAM GiB/24)), at least one | | `--simulator-slots N` | `1` | Simulator ceiling, 0–64; each consumes a shared slot | | `--simulator-idle-seconds N` | `600` | Lease heartbeat timeout, 1–86400 seconds | | `--max-load-ratio N` | `1.5` | Maximum load average per core, greater than zero and at most 16 | | `--minimum-free-memory-mb N` | `4096` | Minimum OS available memory, 0–1048576 MiB | CLI edits update the journal immediately; API edits are reconciled by the body within its five-second refresh. High pressure delays queued heavy work and refuses new local hires with a reason. Existing accepted agents keep running. Missing Python 3, helper or pressure observations refuse resource admission while the body remains available. The canonical registry is the OS user's `~/.clankie/fleet-resources`; worker environment and settings-path overrides do not create independent capacity. See the [shipped skill](https://github.com/Volpestyle/clankie/blob/main/.agents/skills/fleet-resources/SKILL.md). `simulator acquire JSON` accepts `seatId`, optional `fleet`, `deviceType` and `runtime`. The host proves the current local seat and occupant, creates a new device, records its exact UUID and boots it. `simulator touch JSON` and `simulator release JSON` accept `seatId`, optional `fleet` and lease `id`. `simulator status` lists receipts. The operator credential is required; native occupant, process proof and binding fields are rejected as caller input. Idle expiry or proven seat exit cleans up only the exact created device. External booted devices count toward the ceiling. Unknown receipts remain held for review; observer shutdown does not release them. Owner HTTP routes are `GET /v1/operator/fleet-resources` and `GET|POST /v1/operator/fleet-resources/simulators`. POST uses the same strict JSON with `action: acquire|touch|release`, a 16 KiB limit and fresh authority checks before native effects. Unavailable resource status is 503; rejected mutations are 409. These routes retain the existing operator owner boundary. The manual `pnpm check:resources -- --run` proof starts an isolated Captain and service embedding plus ten bounded command processes. Run its whole lifetime through the active fleet limiter. It checks a two-slot pool, actual queueing, process cleanup, service CPU and 250 ms health p95. Its fixed ten-by-two-second workload must finish within 30 seconds, with the empty-pool first start within five seconds; these are fixture regression budgets. It runs no coding model or CoreSimulator; the VUH-1706 release gate remains the worker-bridge load proof. The command is excluded from `pnpm check` and push, PR and scheduled CI. <a id="fleet-status-fleet-set-notes-text-size-size-models-mode-fleet-clear"></a> ### `fleet [status]` / `fleet set [--notes TEXT] [--size SIZE] [--models MODE] [--closure lead|owner] [--machine-setup lead|owner] [--commit lead|owner] [--push lead|owner] [--release lead|owner|time_rule --release-rule TEXT] [--verification review_and_seal|change_run_read] [--report-style TEXT] [--tools connected|off] [--peer-messages on|off] [--hire-profile FILE.json]` / `fleet clear` Read, set, or clear how the owner wants work routed across the agents Clankie leads — which harness is the workhorse, which one reviews, what never goes to which (up to 4,000 characters of free text) — and the budget he sizes the fleet to, plus work closure, machine setup, working preferences and the fleet connected-tool and peer-message switches. `set` takes any combination of the flags; what is left out keeps its value. `clear` restores every default, including tools `connected`. `--tools off` stops new standing tool admissions; manual grants keep working. A call already past its last asynchronous check can still dispatch after the change; there is no proven global concurrency or cancellation bound. That is the chosen contract: the switch stops new calls ([ADR 0217](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0217-fleet-membership-gets-connected-tools.md), VUH-1585). `--tools connected` restores standing access to verified accounts through `clankie_tools` and `clankie_call`. `--peer-messages off` stops new messages between fleet workers independently of connected tools. It hides `list_fleet_seats` and `message_peer` from current worker catalogs and the service refuses sends from stale catalogs too. Existing receipts remain readable for reconciliation; a message already handed to a native receiver cannot be recalled. `--peer-messages on` restores the capability, which defaults to on. Workers still need proven native pane/process and matching session identity; a fleet bearer alone cannot send. See [worker peer messages](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md#messages-between-workers) and [ADR 0213](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0213-clankie-retires-swarm.md#direct-peer-messages-vuh-1608). `--closure` and `--machine-setup` both default to `lead`: | Setting | `lead` (default) | `owner` | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | `closure` | The lead closes tracked work to Done after it has landed, relevant checks pass, and evidence is attached. | Park completed work In Review for the owner to close. | | `machineSetup` | The lead and workers may install, refresh or prepare Clankie's own harness plugins, bridges and worker setup on already-linked machines. | Ask the owner before those setup actions. | Under lead closure, workers report to the lead without parking for owner acceptance. Genuine owner-only gates (payments, evals or sign-ups on owner accounts) get linked follow-ups without holding otherwise delivered work open; missing implementation or verification is never a pass. The owner may reopen work under either closure mode. Worker reports and native delivery receipts alone do not establish acceptance or landing. Each project may override each leaf independently with `project settings`, below. The CLI presents the logical fields as `fleet.closure` and `fleet.machineSetup`; owner settings store them under `autonomy.fleet`. These policies apply without a service restart. Setup still requires an existing authorized route, preserves source-owned configuration, and never restarts or steers existing lanes. Sign-ins, codes, CAPTCHAs, payments, account or credential changes, and destructive actions outside fleet workspaces remain owner decisions. The same `autonomy.fleet` block holds working preferences. `--commit` and `--push` use `lead` for without asking (default) or `owner` for ask first. `--release owner` asks before official tags, packages or store submissions (default); `lead` permits them after relevant checks, and `time_rule` requires `--release-rule TEXT`. A rule is owner-authored guidance to verify against current evidence, not a scheduler. `--verification review_and_seal` asks for independent review, addressed findings and sealing the reviewed revision with evidence; `change_run_read` (default) asks for focused checks and reading their results. `--report-style TEXT` sets reporting guidance (default "Short and plain."). Explicit task and integrator gates take precedence, and these preferences grant no additional account, tool or workspace authority. `fleet status`, `doctor --json` and the TUI `/doctor` expose the resolved preferences for the actual current workspace through the verified service context, including the project ID or global inheritance. Agents launched independently can read this same context. Unavailable, ambiguous or unverified context is reported explicitly. Global fields remain visible separately, so project overrides are apparent. Every hire receives resolved preferences in its native brief, even when no task brief is supplied; machine-bearing "Your fleet" prompts refresh them each turn. Ask Clankie to view or change a preference and he uses these same CLI/API tools. Legacy settings migration seeds the weekly release rule only on an existing owner project with ID `clankie` that has no release override: the last `v*` tag must be more than one week old and `main` must have user-visible changes worth shipping. It creates no project, workspace or grant. Persisted global working preferences mark the migration complete; clearing that project override then stays cleared after restart. An unregistered project inherits global ask-first releases. See [ADR 0230](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0230-fleet-responsibility-is-owner-settings.md). `--hire-profile FILE.json` retains the global launch defaults for harness, model, effort, native subagents, delegation, account and placement. Project role defaults and explicit per-hire choices keep their existing precedence. **The budget is two targets, never caps.** Nothing counts seats against them; the leadership skill (`lead`) and his prompt use them to aim. An owner who wants a thousand agents picks `max` or says so in the notes. | `--size` | Fits | Aims for | | --------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | `max` (default) | several top-tier plans, e.g. four or five $200/month subscriptions | maximum bandwidth: one worker per separable deliverable plus independent reviewers, as far as the work and machines can use them; no ceiling | | `large` | one or two top-tier plans | around six concurrent workers, reviewers included | | `small` | one mid-tier plan, about $100/month | one or two workers at a time; the rest sequenced | | `solo` | pay-per-token API use | no standing workers: he works himself or through short native subagents, and asks before a long or parallel run | | `--models` | Picks per job | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `optimal` (default) | the strongest model and the effort the job needs; cost is not a reason to downgrade | | `efficient` | the smallest model and lowest effort that still meet the job's acceptance; the top model stays on consequential boundaries (safety, data integrity, live surfaces, a disputed review) | **The default notes are empty**, and empty means he picks a harness per job on his own. Nothing here ships with an opinion; this is where you add one. It is free text rather than a table of roles because an enum of `reviewer`/`implementer` only covers the situations someone enumerated, and the useful ones are conditional ("never codex on Swift", "grok for a hostile read on work that already passed review"). The thing reading it is a model. The notes reach him as the `fleet` prompt section, and only on lanes that hold a shell — a room that cannot dispatch would carry the section for nothing. They are preference, not authority: the section says plainly that he still reads the work and decides, and a note here can no more widen his reach than a warmer persona can. The section carries the effective fleet size, model mode and autonomy policy on machine-authorized lanes, including the default `lead` responsibilities. JSON includes the global `fleet` projection and a separate workspace `workingPreferences` report, with either available resolved values or an unavailable detail. The TUI `/fleet` command opens the same editor (size, models, connected tools, peer messages, closure, machine setup, working preferences, then notes) and `/fleet status` prints the same values. ```bash clankie fleet set --notes "codex is the workhorse. claude when it needs skills or long context. grok for a hostile read on work that already passed review. never codex on Swift." clankie fleet set --size small --models efficient clankie fleet set --peer-messages off clankie fleet set --closure owner --machine-setup owner clankie fleet set --commit lead --push lead --release owner clankie fleet set --verification review_and_seal --report-style "Short and plain." ``` <a id="runtime-setup"></a> ### `machines [list|discover] [--json]` A machine is where agents run; a device is a paired phone or desktop portal. `machines` prints one row per machine: Herdr sessions, state and worker count (`?` when unavailable). `--json` returns `{ observedAt, machines }` from `GET /v1/machines`. Discovery reads local Herdr sessions and literal aliases in the owner's SSH config (including bounded `Include` expansion). Probes use BatchMode and strict known-host checking, never prompt, start a server or install remote software. Four probes run at most concurrently, with a three-second probe deadline and a 6.5-second listing budget. Unreachable and still-discovering candidates remain visible. Results are cached for fifteen seconds; `discover` refreshes them. ```bash clankie machines add pc --ssh my-pc --shell powershell clankie machines sessions pc clankie machines sessions pc --connect work --id pc-work clankie machines remove pc ``` Adding registers transcript access immediately and lists available sessions. Connecting names an existing Herdr session; it does not start one. Removal unregisters that machine's connections without stopping workers. Named and SSH connections apply live to census, hires and watches. Default workspace changes still require `clankie restart captain` (ADR 0172). Named local workspace connections support Codex structured hires and resume on their pinned socket. Claude structured hires on a named local workspace currently return `harness_unavailable`: its worker channel is not configured for that socket. They never launch through the default workspace or inherit its fleet grant. Removing a connection releases cached control without stopping its native workers; retained controllers cannot send or interrupt after removal or same-ID replacement. Existing `herdr add/remove/fleets`, `runtime connect` and `agents hosts` remain aliases. Machine records own transport; old connection IDs, transcript host aliases, exact-directory grants and saved seat IDs survive migration. Devices continue to use `pair` and `devices`. The paired operator `connections` operation uses the existing `steer` grant for `discover`, `add_machine` (`id`, `ssh`, optional `shell`), `remove_machine` and `connect_runtime` (`id`, `session`, optional `machine`, default `local`). Its inventory includes `machines` and a `machine` ID on every runtime row. Paired metadata omits local socket paths. ### `connections` and `runtime` `clankie connections` combines execution runtime health and the recorded Linear account identity as JSON. Its operator API is `GET /v1/connections`; the companion app shows it under Settings, where it can also connect a local Herdr session by name. In the TUI, `/connections` links to `/machines` and accounts. `/machines` shows machine state and agent counts, discovered candidates before the typed-name fallback, and each machine's connected and discoverable Herdr sessions. Connect or disconnect named sessions live; retry disabled or unreachable connections while keeping their saved session, transport, workspace grants and capacity. Native worker harnesses are chosen per hire. `/runtime` with no argument opens Machines, as does `/sessions`; saved transcripts open inside each machine. Both retain their arguments, and `/connections json` prints the raw inventory. Onboarding starts with how he thinks, then guides phone pairing and a first agent request. `/setup rooms` offers the Herdr workspace choice only when doctor finds installed Herdr with running sessions. His own workspace is recommended; leading your session lets him see and message every pane in it. `/herdr` default workspace changes still offer Restart now / Later. ```sh clankie runtime list clankie runtime connect build --session workers clankie runtime connect review --socket /absolute/herdr.sock clankie herdr --connection review agent list clankie herdr --connection review open clankie runtime disconnect review ``` `/runtime` accepts the same list/connect/disconnect commands. Connections pin an ID to a verified socket and session label; use a new ID for a different endpoint. Disconnect disables routing and retains identity without stopping any worker. An unavailable named connection never selects another session. Native Herdr commands/viewers require a local Clankie service. These managed launch routes share the service's filesystem and executable paths. A Herdr fleet on another machine is an ssh connection (`herdr add`, below); its agents reach Clankie through that fleet's link. For custom capacity/capabilities, `runtime connect CONNECTION.json` accepts `{ "id": "build", "session": "workers", "capacity": 2, "capabilities": ["code"] }`. A socket can replace `session`. `default` is reserved for the existing fleet; `runtime:` capability names are reserved for explicit routing. Up to 15 named connections are stored under `execution.connections`. The operator API is GET/POST `/v1/runtime-connections` and DELETE `/v1/runtime-connections/ID`; GET `/v1/herdr?connection=ID` resolves a live binding. Approve additional execution locations for the default or a named runtime through the operator API (the same commands work as `/runtime` in the TUI): ```sh clankie runtime workspaces default --repo /absolute/project --dir /absolute/scratch clankie runtime workspaces build --repo /absolute/project clankie runtime workspaces build --clear ``` Runtime capacity defaults to 16. `clankie runtime capacity ID N` changes it; `--clear` selects unlimited and `0` pauses new admission. The operator endpoint owns these settings. `runtime status` reports the effective value and source. Each call **replaces** that runtime's extra approvals; `--clear` restores the conversation-directory-only default. `runtime list` shows the stored policy. `--repo` pins the canonical Git common directory and accepts that repository's currently registered checkouts, including newly created linked worktrees outside the original directory. Stale approvals grant nothing; rejected requests identify them as `stale_workspace` with `staleWorkspaces` details. `--dir` permits only that canonical directory, never its children. Paths must be absolute and exist on the service host. The runtime retains one capacity pool. An ssh fleet is the exception: its grants are exact `--dir` paths on that machine (a drive path such as `C:\src\rivals` for a Windows fleet), stored as written, because this host can neither resolve nor stat them. The operator-only POST `/v1/runtime-connections` accepts `{ "action": "workspaces", "id": "default", "workspaces": [{ "kind": "repository", "path": "/absolute/project" }] }`. Named `connect` JSON also accepts `workspaces`; captain/Discord credentials cannot call this endpoint. Existing operator-machine shell authority remains unchanged. Use the actual approved checkout as `hire_agent.workingDirectory`. A remote hire needs an exact remote directory grant. See [ADR 0193](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0193-runtime-workspaces-are-owner-approved.md). ### `agents [list]` / `agents read` / `agents resume` / `agents hosts` Clankie reads any Claude Code, Codex, Grok or Pi session from the agent's own transcript, on this machine or an owner-configured SSH host. No terminal host is involved: a session in Herdr, tmux, or a bare PowerShell tab reads the same way ([ADR 0189](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0189-agent-sessions-read-from-their-transcripts.md)). ```sh clankie agents hosts add pc --ssh volpe@supedupsilly --shell powershell clankie agents # every host, newest first clankie agents list --host pc --limit 5 clankie agents read pc:01a0da31 --tail 20 clankie agents read pc:01a0da31 --after CURSOR clankie agents resume local:SESSION_ID clankie agents resume pc:SESSION_ID --fleet pc --brief "Continue the task" clankie agents hosts remove pc ``` A remote host needs only sshd and its default shell; nothing is installed there. Authentication is the owner's SSH configuration (keys, `~/.ssh/config` aliases). Reads are confined to `~/.claude/projects`, `~/.codex/sessions`, `~/.grok/sessions` and `~/.pi/agent/sessions` on that host and capped at 4 MiB per call. `local` is always present. Hosts are stored under `agentHosts.connections` (up to 15). `list` reports `ref` (`host:sessionId`), harness, size and `modifiedAt`; a recent write means recently active, not that a process is running. `--limit` is 1–100 per host (default 20). Hosts that fail are reported under `errors` instead of failing the listing. `read` takes a ref or any unique prefix of its session id, resolved among that host's 200 most recently written transcripts; older sessions are not reachable by ref. It returns normalized, redacted messages and tool calls plus an opaque `cursor`. Passing it back as `--after` returns only what was appended. A cursor is bound to its session; `reset: true` means the transcript was replaced and the page restarted from its tail, and `skippedBytes` means one record was too large for a single read and was stepped over. `/sessions` in the TUI takes the same arguments, or opens the saved sessions menu with none. `clankie sessions` is also an alias for these CLI commands. Existing `/agents` session arguments remain supported, but `/agents` without arguments now opens the agents that are live, with offline agents that kept a thread behind one "Past agents" entry. `clankie agents contacts` returns every known identity, live or not, through the existing fleet API. `clankie agents role NAME|PERSONA_ID ROLE|none [--project PROJECT]` assigns a current member's role in the selected project. Omit `--project` for the default project. The host verifies the agent's current native seat and project membership through its original hire assignment or, for agents Clankie did not start, verified cwd; offline agents, unknown membership and members of a different project are refused. The assignment changes the project's semantic role, preserving the live harness and its launch profile ([ADR 0208](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0208-agents-carry-a-role-the-world-reads-it.md)). The built-ins `planner`, `designer`, `builder`, `tester`, `reviewer` and `researcher` are suggestions; a custom role is 1–24 letters, digits, spaces and hyphens. Quote a role with spaces: `clankie agents role Smith "sound designer"`. The role is the last positional argument and everything before it names the agent. A name must match exactly one agent, case-insensitively; otherwise pass the persona id from `agents contacts`. Roles are trimmed, inner whitespace collapses, and a built-in in any casing is stored lowercase. A custom role keeps the casing you typed and compares case-insensitively, so `Sound Designer` and `sound designer` are one role. `none` clears it in the selected project. It prints the updated persona with that project's role. For example, `clankie agents role "Pixel Smith" tester --project clankie` updates a Clankie project member. Existing contacts' `role` remains the default-project compatibility view; the confirmed project membership snapshot carries the selected project's current saved role, including a cleared role. `clankie agents role ROLE --project PROJECT` edits a project hire profile through its revision-bearing owner API. Set any of `--harness`, `--model`, `--effort`, `--subagent-model`, `--subagent-effort`, `--delegation native-first|panes`, `--account LABEL`, `--placement new-tab|split`, `--cap N` and `--naming TEXT`. `inherit` clears one preference; omitted fields remain unchanged. The console's `/agents roles` menu sets the same fields. ```sh clankie agents role implementer --project clankie --harness codex --model "sol 6.1" --effort xhigh --subagent-model "sol 6.1" --subagent-effort medium --delegation native-first --placement new-tab ``` Explicit hire fields expressing the owner's words win over the role, then `fleet.hire` defaults, then the harness default. Omit fields to inherit; a model family override includes its harness (for example `claude` / `Opus`) and `subagents: null` clears incompatible inherited children for that hire. Friendly names resolve to exact IDs against the current model registry. Missing, retired, or incompatible models refuse instead of silently selecting a replacement. Subagent settings inherit independently and travel in the first native brief; the worker passes them to its harness's native spawn calls. A `native-first` hire supplies a stable `deliverable` key, such as its issue ID. All slices keep that key. Another pane for that project/deliverable is refused while the original hire is live, starting or uncertain; message that worker and use its native children. `panes` assigns independent slices to separate hires. Closing a pane releases its admission only after successful inventory confirms it absent. Retry reconciliation retains the original profile. New hires use one Herdr workspace per repository on the selected fleet, even when the lead or another client is focused elsewhere. Herdr's observed Git identity groups linked worktrees of the same repo; equal directory names do not group unrelated repos. A workspace is created and named from the repo once, with a separate root tab reserved as `Clankie`. Existing labels are preserved. An unmarked hand-created workspace is reused only when all its observed pane directories belong to that repo. Mixed legacy workspaces stay untouched. Non-Git directories use their exact working directory instead. `new-tab` is the normal placement: a solo worker gets a `Name · role` tab. A deliberate pipeline supplies a per-hire `pipeline` name, for example `"VUH-1550 design → implement → review"`. Its first hire opens that named tab; later hires use `placement: "split"` with the same pipeline and split its last stage, preserving focus. `split` without a pipeline refuses. A same-named tab with unmarked panes refuses instead of appending to an unrelated lane. Pipeline names belong to the hire, not a blanket role or fleet default. Prepared initial-command Pi/OpenCode/Grok hires can create the first pipeline tab but cannot yet split into an existing one; they refuse rather than rebuild it. This policy allocates new panes only. Resuming an already live native session keeps its existing pane; a saved-session resume that needs a new pane uses the same repo/tab rule. An explicitly requested move re-hires in a solo tab at the destination, carrying its human name and known role; there is no automatic migration, rename or cleanup of older workspaces, tabs or panes. The protocol `spawn_seat` request and `hire_agent` accept the same optional `pipeline` field alongside `placement`. Local Codex accounts use the registered account labels and homes; local Claude accounts use `claudeAccounts` entries (`{label, home}`) plus the implicit `default` profile. The owner registers their existing alternate directory with `clankie accounts claude add /absolute/config/home --label second` (also `/accounts claude` in the console); no login or profile path is guessed. Remote account overrides remain unsupported. Profile selection confers no grants. `clankie fleet set --hire-profile FILE.json` sets fleet hire defaults with the same profile keys (`subagents` is `{model, effort}`); `fleet status` includes the defaults and effective project role profiles. The hire result's `profile` shows the effective launch preferences. These settings affect new hires, not running agents. James's global agent instructions remain owner-authored. `clankie agents rename NAME|PERSONA_ID NEW_NAME` changes an agent's saved display name. Quote names containing spaces. `/agents rename NAME "NEW NAME"` is the same TUI action. It uses the existing `update_persona` operation with only the name; omitted appearance stays unchanged. Names support any language and the existing 1–80 character/Discord webhook rules. Rename keeps the persona, conversation, native seat and project assignment, including after a refresh or resume. The app offers the same action in an agent's tray card. Appearance and avatar updates may omit `name`; the service keeps the current saved name, so an avatar finishing later cannot undo a completed rename. `clankie agents roles` lists the built-ins (always, with counts), then custom roles personas hold, most held first, each as `{ role, builtIn, count }`. Counts include offline personas in the default-project compatibility view. The role is semantic, unlike the cosmetic `appearance.accessory`, and persists as a project association across seats. The same settings are the `set_persona_role` operator op (`{ personaId, role: ROLE | null, projectId?: PROJECT }`, steer grant; omitted project defaults to `default` and requires the same current membership check), the `roles` op (read), and `hire_agent`'s and `spawn_seat`'s `role` (required in the model-facing hire tool, optional for older API clients). In the TUI, `/agents role NAME "ROLE"` and `/agents roles` opens the project hire-profile editor. The `/agents` picker shows each live agent's role. The TUI separates `/chats` (personal/workspace chats with Clankie), `/agents` (known identities), `/rooms` (group channels and Discord inspection), and `/history` (all retained threads, including ongoing ones). `/conversation`, `/conversations`, and `/chat` alias `/chats`; use `/history ID` for any retained thread. See [product vocabulary](https://github.com/Volpestyle/clankie/blob/main/docs/product-vocabulary.md). `resume` continues a saved session as an ordinary hired seat in its native TUI. It resolves fresh transcript metadata, reuses the exact live session when found, or opens that session in Herdr. A remote resume needs an existing registered Herdr fleet with the same SSH target and shell as the transcript source, plus a grant for its recorded working directory; matching friendly ids alone are insufficient. `--fleet` selects among several matching fleets. A local Codex resume uses the original registered account that owns its transcript, rather than choosing another account by headroom. The TUI's saved-session actions offer **Resume in native TUI**. An optional `--brief` is sent through native control when reusing a live seat; an uncontrolled live seat must be messaged explicitly through its existing lane. Incomplete fleet discovery refuses a new start. `delivery_unconfirmed` or `start_unconfirmed` means inspect the named pane before retrying: it may already have taken the work. A failed resumed start keeps its pane visible. The inventory covers configured Herdr servers; it cannot prove that a separate unregistered terminal is not holding the same history. Close that terminal before resuming. A transcript's age is never used as proof of absence. The operator API for saved sessions is GET `/v1/agent-sessions?host=&limit=`, GET `/v1/agent-sessions/read?ref=&tail=|after=`, POST `/v1/agent-sessions/resume` `{ ref, conversationId, fleet?, brief? }`, GET/POST `/v1/agent-hosts`, and DELETE `/v1/agent-hosts/ID`. The resume route delegates to the existing `spawn_seat` service operation. Both require an explicitly selected existing operator conversation; inspection of a room is insufficient. Use `clankie agents resume HOST:SESSION --conversation ID [--fleet ID] [--brief TEXT]`. A missing `spawn_seat.conversationId` returns `not_ready` without launching. After an authorized same-thread native reattach, use `clankie agents readopt SEAT_ID --conversation ID` (or the lead's `readopt_seat` tool) to rebind the existing owner. The host proves the current native thread and original owning conversation again; a different thread or owner is refused. A same-thread `hire_agent` resume performs this rebinding under the admitted hiring authority. Remote-attached local Codex panes without a Herdr session hook are discovered from their exact foreground socket/thread and retained private server lifetime; labels alone never establish a binding. Doctor reports `linkedSession.nativeBindings` as observed, recovered, or missing. Hired workers retain their original host-persisted owner through restart and movement. Saved sessions without exact persisted ownership cannot be reclaimed by inferring a persona or default conversation. Completion and escalation wake only their owner, with current route grants checked again; there is no default room or persona fallback. It has no separate runner or run store. Clankie's own tools are `agent_sessions` and `agent_session_read`, available where he has machine access. `hire_agent` accepts `resume: "host:sessionId"` with the recorded harness and workingDirectory; follow-ups use `message_seat`. Reading never starts or resumes a harness. Workers retain their native identity and ownership; a native resume does not enroll or replace them. Headless continuation remains retired (ADR 0203). ### `agents efficiency` / `agents tidy-worktrees` `clankie agents efficiency --conversation ID` returns `{ conversationId, seats }` for every seat that conversation leads, including linked fleets. Each existing roster seat may carry `efficiency`: observed assignment, native model and effort, context occupancy, progress and reporting evidence, and plain-text `flags`. Context percentage is the latest native Codex model-input snapshot and may age between responses. Claude context/effort and OpenCode or remote telemetry remain unknown. Original report acceptance or a reporting attempt remains progress after a later acknowledgment; acknowledgment creates no new progress. The TUI shows these flags in the agent dock. Every watch wake calls for reviewing all owned seats. Periodic checks default to every 30 minutes; unchanged, unflagged evidence skips a model turn, and pending reviews coalesce. Wake prompts carry bounded summaries; use `fleet_efficiency` or the CLI to inspect the full owned roster. Clankie chooses interventions using the `lead` skill and his existing native worker tools. Automatic commit evidence requires an advanced descendant HEAD on the seat's captured branch in an exclusive linked worktree. Primary checkouts, shared worktrees and commits predating admission do not establish that seat's progress. Native transcript snapshots are cached by file identity, size and modification time; unchanged branch HEADs reuse Git evidence. Discovery runs once in the background instead of walking transcript directories during roster refreshes. Concurrent roster reads share one refresh and may reuse a completed result for one second while its change cursor is unchanged. Codex child discovery caches the parent rollout and discovery-directory stats and uses asynchronous reads; unchanged sessions avoid another tree walk. Authority-sensitive and post-mutation reads force fresh observation; the display cache grants no native control. To change an external worker's effort, ask that worker or re-hire with a retained handoff; captain model settings do not change the worker. After inspecting a worker's actual assignment, tracker status or progress evidence, record the finding with: ```bash clankie agents efficiency review SEAT --conversation ID --json-stdin < review.json ``` The JSON object requires `evidence` (1–2048 trimmed characters). Optional fields are `offScope` (boolean), `assignmentStatus` (`active`, `paused`, `canceled` or `done`), `deliverable` (1–512 trimmed characters), and `progressAt` (a UTC ISO 8601 timestamp for the substantive finding or commit). The CLI supplies the action, seat ID and conversation; extra JSON keys are refused. A review applies to that exact owned native session and records inspected evidence. It does not change tracker state, harness settings, ownership or report receipts. The authenticated operator API is POST `/v1/fleet/efficiency` with `{ action: "show", conversationId }`, or the review fields plus `{ action: "review", conversationId, seatId }`. The model's `fleet_efficiency` tool uses the current leading conversation. Ownership changes or unavailable evidence refuse a review; there is no default-conversation fallback. `clankie agents tidy-worktrees --repo /canonical/repository/path [--merged-into REF]` lists linked worktrees that are clean and merged into the locally saved ref (`origin/main` by default). The API is POST `/v1/fleet/tidy-worktrees` with `{ repository, mergedInto? }`, and the lead tool is `list_tidy_worktrees({ repository, mergedInto? })`. All return `{ outcome, mergedInto, candidates, excluded }`; candidates contain `path`, optional `branch`, and `sha`, while excluded entries contain `path` and a reason. Listing is read-only. It excludes the main checkout, locked or prunable entries, dirty or unmerged worktrees, and directories occupied by any observed local pane, including idle agents and shells. Unknown or changing pane inventory returns `outcome: "unavailable"` with no candidates. The command does not fetch refs or prove ownership. Use the `tidy` skill to harvest results, verify ownership and fresh merge/clean evidence, then remove finished owned worktrees after landing. ### `herdr` / `herdr status [--json]` / `herdr use NAME` Bare `clankie herdr` attaches the full workspace, as `clankie-herdr` does. `herdr status` prints machine rows; `--json` includes those rows plus the configured and active default-binding details. `herdr help` prints Clankie's commands. The TUI `/status` shows the active fleet binding and `/herdr` shows both the configured and active sessions. The binding is re-read at start, after `/herdr`, and on `/status`. Routine fleet status stays out of the conversation footer. In the TUI, `/herdr` opens a modal menu showing configured and active sessions. Choose **Use an existing Herdr session** to pick a saved session (running ones first), or **Create a session for Clankie** for a separate worker fleet. Creating reuses Clankie’s own retained session if it already exists. **Open active session** opens its viewer. After saving, choose **Restart now** to apply the binding or **Later** to keep it pending. **Apply saved changes** restarts Clankie, relay and Discord from the menu. Either restart then shows the binding he actually landed on, and warns when the saved session did not answer. Existing Herdr panes stay open. The same choices are available as `/herdr use NAME` and `/herdr create`. The older `set --session NAME` and `set --runtime auto|bundled|external|disabled` forms remain compatible for scripts; the TUI does not ask users to choose a runtime. The binding is resolved at every service start and never written back ([ADR 0181](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0181-clankie-is-independent-of-his-connections.md)). He leads the session or socket the owner explicitly named; failing that, his own private bundled Herdr ([ADR 0164](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0164-the-fleet-is-its-own-session.md)). A candidate that does not answer is stepped over. If the owned runtime also cannot start, Clankie continues with Herdr unavailable. A bound external session that stops stays unavailable until restart; it never redirects existing work to a replacement fleet. `clankie herdr disable` (also `/herdr disable`, or **Run without Herdr** in the TUI menu) saves `runtime: disabled`. Apply with `clankie restart captain`. Clankie starts without probing, downloading or starting Herdr. Conversations, connected services and conversations remain available. Terminal actions for that default fleet report unavailable. Named execution connections remain independent; existing workers are not stopped. Use `herdr use NAME` or `herdr create` and restart to enable execution again. The invoking terminal's Herdr session never selects the fleet. `create` (the compatible `set --runtime bundled` setting) selects the owned runtime directly. It follows official stable Herdr releases, checking at startup and every six hours. Downloads must match the official SHA-256 checksum. Updates are staged separately; active workers retain their matching executable until their session ends. The next Clankie start without a live fleet server uses the staged release. Existing sessions selected with `use NAME` keep their owner's installation and update policy. `pnpm herdr:build` prepares the pinned official offline fallback for a checkout. Panes in the bundled fleet start the owner's login shell with the owner's environment: the private XDG roots that isolate that Herdr never reach an agent, so `gh`, `git`, `mise` and the rest behave as in any terminal. macOS permissions (screen recording, accessibility) follow the process that started the service, so a fleet descending from a terminal carries that terminal's grants; one started by the login-time autostart job may prompt for them once. `set --session NAME` selects external mode and resolves that named session on restart; `set --runtime external` keeps whichever session name is already saved. External mode never starts or stops the owner's server. `set --runtime auto` clears the named session and selects the bundled default. Apply changes with `clankie restart captain`. `clankie herdr status --json` reports configured `herdr`, `settingsFile`, `restart`, and the running service's `active` binding (or `unavailable`). Settings hold the owner's intent and `active` holds what is live; the two differ whenever a named session is down. The authenticated operator endpoint `GET /v1/herdr` returns the running binding while it is available; pending settings do not redirect clients. `/health` reports Herdr's state independently of service liveness: disabled, unavailable or recovering execution does not make the captain unhealthy. `/v1/herdr` returns 503 when no active binding is available. `clankie-herdr` with no arguments is the shortcut for `clankie herdr open`. It attaches a native viewer to the selected, already-running local server. With arguments it is the fleet's own Herdr CLI: `status`, `set`, `use`, `create`, `disable`, and `open` stay Clankie's, and every other verb is forwarded to the runtime he is bound to, with its binary, its socket, and its configuration. So `clankie-herdr pane list` reads the fleet, and `clankie-herdr server stop` ends a bundled fleet that outlives the service (ADR 0164). Running a bare `herdr` instead reaches whatever build is on PATH, which for a bundled fleet answers a protocol mismatch on a socket it cannot see. Use **Ctrl+B, then Q** to detach with the default bindings. Closing the viewer leaves Clankie and his workers running. The TUI's `/herdr open` opens the same viewer and returns to the conversation after detach. Native viewing requires a local service. Every TUI reads the service's fleet, including from ordinary terminals and unrelated Herdr sessions. `/jump`, clickable pane IDs, and the optional herdr-lead `/board` commands target that fleet. Use the viewer to see a focused worker. The optional board requires herdr-lead installed and linked in the selected runtime. Pane-scoped messages and `clankie stance` carry the source socket in `x-clankie-herdr-socket`: unrelated pane IDs cannot attach to or change a worker with the same ID in another session. ### `herdr fleets` / `herdr add NAME --ssh HOST` / `herdr remove NAME` / `herdr prepare NAME` A **machine** can expose one or more Herdr sessions, called fleets internally ([ADR 0184](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0184-clankie-leads-more-than-one-fleet.md)): a named runtime connection whose transport is the owner's own ssh. ```sh clankie herdr add pc --ssh volpe@supedupsilly --session default --shell powershell clankie runtime workspaces pc --dir 'C:\src\rivals' clankie herdr fleets clankie herdr remove pc ``` `add` is `runtime connect NAME --ssh HOST --session SESSION [--shell posix|powershell]`. `HOST` is a host or alias from the owner's ssh configuration; keys and host trust stay there (`BatchMode`, so an unknown host key or a locked key fails instead of prompting). `--shell powershell` is for a Windows host whose sshd default shell is PowerShell. The session must already be running there: adding checks `herdr --session SESSION api snapshot` over ssh and refuses otherwise. `remove` unregisters the machine and its connections without stopping workers. Use `runtime disconnect ID` to disable only one connection while keeping its identity. Named machine connections reach the captain immediately; only default workspace changes require `clankie restart captain`. `prepare NAME [--codex-source-setup ABSOLUTE_REMOTE_SCRIPT]` readies the machine's Claude and Codex workers; running it is the owner's approval. It ships this Clankie's own worker bundle to `~/.clankie/claude-plugin`, installs/enables Claude in each discovered profile, and approves its worker channel in managed policy (`C:\Program Files\ClaudeCode\managed-settings.json` on Windows), keeping existing entries. Codex uses its native `clankie-worker@clankie-fleet` plugin. A source-managed Codex config requires its source manager, selected with the remote setup option; Clankie never writes through its symlink. See [linking native fleet harnesses](#linking-native-fleet-harnesses) for the setup contract and dotfiles example. Preparation reports incomplete native worker checks as failure, even if a legacy Codex MCP registration is present. Policy is machine-wide, so the SSH account must be that machine's administrator. Rerun preparation after an update to ship the matching plugin. Its API is the operator-only `POST /v1/runtime-connections/NAME/prepare`. What crosses the link, and what cannot: - Every call runs `herdr --session SESSION <verb> …` on the remote host with an exact argv (a Windows command line is built for `CommandLineToArgvW` and handed to `ProcessStartInfo`, so PowerShell never parses it). One multiplexed ssh connection per fleet carries them through service-owned control sockets under `~/.clankie/ssh/` (`ControlPersist=600`). New service lifetimes and connections older than ten minutes use a fresh socket. A failure before the remote program starts retries once with a fresh login environment; failures from an already running program are reported without replaying the command. Resident fleet relays also refresh every ten minutes: the replacement becomes ready before the old relay drains accepted requests and proof commands, so routine renewal keeps link status ready. Retired masters retain existing clients and expire when idle, leaving other SSH sessions intact. PowerShell progress is suppressed and serialized errors are decoded before appearing in link status and logs. - Only read and pane verbs pass: `agent list|get|read|wait|prompt|send-keys|start`, `pane list|get|read|send-text|send-keys|close|process-info|layout`, `tab|workspace create|list`, `api snapshot`, `session list`. Nothing that launches, attaches, stops, updates or reconfigures a server can be sent, and the Herdr CLI never starts a server for a subcommand. The remote server stays the one its owner started. Agent seats may run in a service session; desktop-bound work uses that machine's separately authorized desktop bridge. - A remote pane's ids carry the fleet: `pc/w2:p1J`, `pc/term_…`. Local ids stay bare. The census Clankie reads lists each fleet under its own `HERDR FLEET` heading, and the roster carries remote seats with `fleet` set. - He can watch (`herdr_watch pc/w2:p1J`), message and hire there (`hire_agent` with `fleet: "pc"` and a granted `workingDirectory`). A watch is persisted under its qualified id, so it resumes after a service restart. Remote panes are observed by polling one shared `pane list` every three seconds rather than holding a wait open per pane. - A remote Codex hire gets the same native channel a local one does (VUH-1527). Clankie starts a dedicated `codex app-server` on that machine, detached and listening on its loopback only, reaches it through his own `ssh -L` forward, and the Codex TUI in the remote pane attaches to it with `--remote`. The brief, later messages and completion go through that server; nothing is typed into the pane. Its inherited Linear connectors are switched off from that machine's own Codex configuration. On Windows, launch arguments that `cmd.exe` would reinterpret are refused rather than altered. - A remote Claude hire uses the `clankie-worker` plugin there, as a local one does: its brief and messages arrive on the plugin's channel and its hooks report each settled turn. Both travel over the fleet's **link**, which the service keeps up for every ssh fleet: an `ssh -R` forward from that machine's loopback to a listener here that answers only the fleet seat routes, and a token in `~/.clankie/link.json` there (owner-only) that reaches only that fleet's panes. The operator credential never leaves this Mac. Tracker isolation reads that machine's own `~/.claude.json`, and the launch settings are written there as a file. Until `herdr prepare` has run, a briefed remote Claude hire fails typed with the fix. - Any agent in a pane on a linked machine (or on this Mac) can write to Clankie with the plugin's `message_clankie` tool, hired or not. It wakes him as that agent's output, not the owner's instruction; he answers with `message_seat`, which reaches a session that loaded `--channels plugin:clankie-worker@clankie`. Its receipt reports `stored` only after durable conversation acceptance. The roster exposes `workerReportBridge` separately from tool health: the last `stored`, `uncertain`, `rejected` or `unavailable` outcome, observation time, fixed safe reason, and last confirmed stored time when observed. `doctor` and the console show it. A hired seat held idle or done for 15 minutes without a stored report since its last brief carries `finished, unreported`. Three distinct failed seats within ten minutes produce one native alert to their owning lead, rearmed after recovery; raising it spends no service-model turn. Both `mcp --seat` and `mcp --fleet` preserve an uncertain original across bridge/service replacement. Calling again reconciles that exact ID through a read; it never resends it. A different follow-up during reconciliation remains unsent. Missing or mismatched evidence stays blocked; do not delete the receipt files or switch bridges to bypass it. Older inbound writers without a delivery ID are rejected before dispatch. - Other remote seats' replies are read with `herdr agent read`. Terminal observe/control is not wired for ssh fleets yet. - An unreachable fleet is a state. `herdr fleets` (and `runtime list`) report `state: "unreachable"` with `lastSeenAt`; other fleets answer normally. ### `workdir [status]` / `workdir set PATH` / `workdir clear` The captain's working directory — where his shell and sessions run when a conversation names no workspace. Unset (the default) means the operator's home directory. `set` expands a leading `~` and stores the absolute path. JSON contains `workingDirectory` (the configured value or `null`), `effective` (what the captain runs in after a restart), `settingsFile`, and `"restart": "clankie restart captain"`. ### `reset --conversation ID` Archive an idle service-owned global or workspace conversation and start fresh model context under the same ID and title. For the root conversation: ```bash clankie reset --conversation global-default ``` The TUI's `/reset` resets the selected conversation. `/clear` only clears the screen; `/new` creates another conversation. Reset preserves persona, settings, and durable memory, and clears the conversation's pending goals and watches. The transcript and Pi session remain in `conversation-archives/reset-UUID`, beside the service's `conversations` directory. JSON returns the fresh `conversation` and `archiveId`. Reset requires an idle conversation with no open side conversations. A root bound to an external seat refuses reset: end that seat first because its model context belongs to the external harness. The API's `reset` operation requires `expectedRevision`; stale requests refuse without changing history. <a id="conversation-commands"></a> ### `conversations list | show ID | tail ID | goal ID` Recent-history reads include native agent seats. Backward replay returns a bounded window and an exclusive `previousCursor`; native cursors are opaque identities, so pass them back unchanged when loading older messages. Inspect the same conversations as the TUI picker, including Discord text/voice rooms, operator chats, fleet agents, and channels. `conversation` is an alias. ```bash clankie conversations list clankie conversations show 1551975693582336060 clankie conversations show ROOM_ID --cursor 000000000100 --limit 100 clankie conversations tail ROOM_ID --cursor 000000000100 clankie conversations goal global-default accept clankie conversations goal global-default set --tokens 1000000 "Finish the checked task" clankie conversations goal global-default pause clankie conversations goal global-default resume ``` `list` returns JSON metadata. `show` returns metadata plus one replay page of messages, tools, and lifecycle events; follow `nextCursor` while `hasMore` is true. `tail` streams newline-delimited JSON events, live drafts, and explicit cursor-recovery notices. `--limit` is 1–100 (default 100). A selector is a conversation id, exact title, or an unambiguous Discord channel/target id. Native `claude`, `codex`, and `opencode` launches accept the same selectors with `--conversation`; use the stable ID when room names are ambiguous. Room names include the server and channel, or the DM peer, when the Discord transport provides them; retained rooms without names display their target IDs. Discord room records are read-only: use Discord to send messages. Their transcripts include model-visible context and bounded, redacted tool details; source-session entries identify the original local Pi journals for deeper inspection. Voice rooms contain captain handoffs, not unrecorded ambient voice. The existing authenticated conversation API provides these same list/get/replay/tail operations. See [ADR 0176](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0176-every-room-is-an-inspectable-conversation.md). Room handoffs also appear as separate child records with `roomHandoff` metadata and in the fleet snapshot's `roomHandoffs` array. Use their child conversation ID with `show` or `tail`; the original `roomConversationId` identifies the asking room and its delivery evidence. The inline TUI dock shows active jobs above fleet seats; `Ctrl+G` retains finished jobs and their results in its picker. The app collapses finished jobs behind an explicit expansion control. The recorded `host` is the actual executor: all non-owner work under a Codex head runs on Pi with the original room authority and grant. Only the verified owner's work uses native Codex children. Completed delivery retries return the saved result. See [ADR 0229](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0229-room-handoffs-are-visible-parallel-threads.md). ### `send --conversation ID [--delivery steer|queue] [--attach PATH]... (MESSAGE | --stdin)` Send to an existing operator conversation through the shared service API. The default `steer` joins Clankie's active Pi turn at its next input boundary; `queue` waits for a separate turn after earlier queued work. Either starts a turn when idle. Channel rounds and external seats keep their own delivery behavior ([ADR 0091](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0091-a-mid-turn-message-steers-the-turn.md)). ```bash clankie send --conversation global-default "Focus on the failing test first" clankie send --conversation global-default --delivery queue "Then update the docs" cat notes.md | clankie send --conversation global-default --stdin ``` `--stdin` reads the message from standard input. Interior newlines are preserved; surrounding whitespace is trimmed by the shared message schema. Passing both `MESSAGE` and `--stdin` is refused. The command reads the current revision, submits once, and prints the JSON receipt including `runId`; it does not wait for a reply. Exit 0 means accepted. A revision conflict or offline seat returns its JSON refusal and exit 1; inspect the conversation before resubmitting. Observe replies with `clankie --chat ID` or the conversation API. The running service and a local captain credential are required. Service preparation and execution have a five-minute inactivity watchdog, including cold startup before a Pi session exists. Host-observed preparation progress and Pi events renew it. The watchdog is suspended while one or more Pi tools execute; tools retain their own timeout and cancellation behavior. A full five-minute idle window resumes after the last tool ends. Before execution starts, or with no active tool and no preparation or streamed progress, inactivity still times out after five minutes. Healthy work has no total duration cap, and queued runs do not consume the timeout while waiting. A stalled stored run fails with `conversation_turn_stalled`; the service log names its conversation, run ID and stalled phase. The host releases its admission so later inputs can proceed, but its original receipt remains and the request is never replayed. Earlier effects may have an unknown outcome; inspect the original run before retrying. See [ADR 0218](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0218-native-seats-drive-their-attached-conversation.md#stalled-service-preparation-and-execution-vuh-1613). `--attach PATH` (repeatable, at most eight) sends images or video with the message: PNG, JPEG, HEIC/HEIF, GIF and WebP up to 20 MiB, and MP4 or MOV up to 200 MiB. The message may then be empty. Each file is uploaded through the `upload_begin`, `upload_chunk` and `upload_commit` conversation ops in 512 KiB chunks and verified by SHA-256 before the send. Clankie sees images as images and video as keyframes. A local agent seat receives copies under `.clankie/inbox/<message>/` in its working directory (git-ignored by the inbox's own `.gitignore`), with keyframes beside a video when ffmpeg is installed, and a message listing their paths. An agent on another machine cannot receive files: that send is refused as `seat_undelivered` and nothing is delivered. See [ADR 0209](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0209-owner-attachments-reach-agents-as-files.md). ```bash clankie send --conversation global-default --attach ~/Desktop/bug.png "What is wrong here?" clankie send --conversation CONVERSATION_ID --attach repro.mov --attach crash.heic ``` ### `file publish --conversation ID PATH [--name FILE] [--type MEDIA_TYPE]` Publish one finished regular file from the conversation's working directory. `PATH` may be relative to that directory or an absolute path inside it. Realpath containment rejects symlink and parent-directory escapes; files larger than 15 MiB are refused. `--name` changes only the safe delivered filename and `--type` overrides extension-based content-type detection. ```bash clankie file publish --conversation global-default build/report.pdf clankie file publish --conversation global-default dist/site.zip --name launch-site.zip ``` The JSON result contains the opaque artifact id, filename, content type, byte count, and SHA-256. The same metadata appears as a durable file event in the conversation. The command is local-only because it accepts a host path; paired devices may retrieve published bytes with their chat grant but cannot publish a path on the Mac. Files share the conversation's retention and are removed when that conversation resets, closes, or ages out. See [ADR 0174](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0174-finished-files-belong-to-conversations.md). ### `prompt [--lane LANE] [--sections identity,persona,reach,fleet,address,model] [--conversation ID] [--harness claude]` The system prompt that lane's session starts from, printed verbatim as plain text. The intended consumer is a seat launcher in another harness, which reads it once at startup so the seat begins from the same words the service lanes do. `LANE` is `operator` (the default), `discord_voice`, `discord_presence`, or `gameplay`, and must be the lane the bearer speaks for. The operator bearer comes from the credential broker, so this reads the operator lane. Sections default to the five a session is built with, joined by one blank line: | Section | What it is | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identity` | `instructions.md` — who he is, his trust boundaries and where things live | | `persona` | The owner-authored character configuration | | `reach` | The machine-access or this-room paragraph for that lane; with machine access, the ready computer-use harnesses (`browser harnesses`) unless delegation is off; in Discord lanes, how a reply carries media | | `fleet` | Current fleet budget, effective project closure and machine setup responsibility, and optional routing notes; machine-holding lanes | | `address` | His own mailbox, when one is connected | | `model` | The card naming the model the service lanes run on (ask for it by name) | A seat that carries the identity some other way asks for the rest: `clankie prompt --sections persona,reach,address`. With a selected conversation (`--conversation` or `CLANKIE_CONVERSATION_ID`), the prompt ends with that workspace's project instructions. `--harness claude` leaves out what Claude Code loads itself: every `CLAUDE.md`, and any `AGENTS.md` with a `CLAUDE.md` beside it. An `AGENTS.md` that stands alone is kept, since Claude Code never reads it. The Claude seat's hook also leaves out `fleet`: the lead skills read it from `clankie fleet status` when they need it. ### `memory [status] | search <terms...> | forget <episodeId> | correct <episodeId> --summary TEXT` Inspect and curate notes through the operator API. Output is JSON; success exits 0 and failure exits 1. `status` shows the newest 20 notes, including private notes. `search` matches all supplied terms against the note, source lane, and room, returning up to 20 newest matches and the total matched count. Quote a correction's summary as one shell argument. Notes stay until forgotten, without a retention flag or count quota. `correct` replaces the note while preserving its source and date. `forget` deletes the note. `/memory` exposes the same controls in the console. See [Memory](https://github.com/Volpestyle/clankie/blob/main/docs/memory.md) for lane privacy and migration behavior. ### `metrics --fleet` Read operator-only `GET /v1/fleet/metrics` for proof attempts and refusals, worker report attempts and failures, and fixed native/transport reason counters. The five- and sixty-minute windows show failure fractions and failures per minute; counters contain no process IDs, paths, argv, report bodies or credentials. Doctor includes the same windows. A live seat with more than 1% terminal proof refusals in five minutes produces a native alert to its current owning lead, with a five-minute cooldown; native retries are counted separately from terminal refusals. Metrics restart with the service and state their coverage start. ### `metrics --issues [--issue ID] [--worker ID] [--since ISO] [--until ISO]` Per-issue and per-worker measurements from the service's existing records, through the operator-only `GET /v1/captain/issue-metrics` route. `--issue` or `--worker` also selects this mode. Worker matches an exact label, terminal ID, or retained native session reference. `--since` and `--until` are ISO timestamps; the default window is the last 24 hours, with an exclusive end and a maximum of 366 days. The window selects approval time for accepted episodes, or the latest observation for unfinished episodes; totals cover the whole selected episode. Turn-mode `--run` / `--limit` cannot be combined with issue mode. ```sh clankie metrics --issue VUH-1608 --since 2026-10-04T00:00:00Z --until 2026-10-05T00:00:00Z clankie metrics --issues --worker Noor ``` The JSON `report` contains `issues`, `workers`, `window`, and explicit `coverage`: - `reportedTokens` sums provider-reported native worker responses, including cached input. Codex response IDs and Claude message IDs are deduplicated; cumulative Codex token-count events are not added again. Native child sessions and legacy/missing usage are not inferred. Unknown totals are `null`. - `wallTimeMs` is elapsed time from the owner's assignment to an explicit approval in native user messages, including waits. The issue value is the envelope across its observed workers; worker totals can overlap. - `fullCheckRuns` counts recognized worker `pnpm check` invocations in native tool command records (shell and literal Python subprocess forms). Lead batch checks and unrecognized wrappers are outside this partial count. - `reviewRounds` counts explicit fix requests and approvals; `reworkRounds` counts fix requests. Native prompt wording is the evidence, not Linear's Done status. Unrecognized wording remains unknown. - `leadReportedTokens` separately sums settled report-handling turns whose retained inbound acceptance names exactly one issue. Mixed/ambiguous inbound issue references are excluded; other work in a turn’s context is unknown. This is not all lead work on that issue. - Worker `seatSettlements` and `unresolvedHireReceipt` expose ledger edges and pending receipts. A passed/ship edge is never issue acceptance; missing receipts do not establish historical delivery. Ledger edges match the preferred retained seat ID. Older seat aliases have no saved association intervals, so their ledger rows are excluded from that worker's settlement totals. Retained exact local bindings come from conversation metadata, the persisted hire-owner journal, and archived pane-tidy entries. This includes workers whose conversation metadata predates `nativeSource` and panes that have been closed. Matching session IDs and transcript paths are combined before counting, so the same native history is counted once across these records. Retained labels, seat IDs, session IDs, and transcript paths can select that worker with `--worker`. These historical bindings provide attribution; they grant no current pane control or delivery authority and cannot establish issue approval. Missing, remote, malformed, conflicting, unreadable, or over-64-MiB sources appear in `coverage.warnings`. Unbound session directories are not searched for issue mentions. Separate files claiming the same native session are ambiguous and excluded, including when `--worker` selects only one of their aliases. Reads also have a 256-MiB total request budget, with skipped sources reported. There is no fuzzy pane attribution or new metrics ledger. Known totals are partial when other sources are unavailable. No transcript, command, tool output, or credential appears in the response. This read neither launches checks nor queries models. Include the report and its coverage in an issue handoff. ### `metrics [--run ID] [--limit N]` Recent settled captain turns, newest first, from the durable `~/.clankie/captain/turn-settled.jsonl` the service already appends. Reads through the operator API (`GET /v1/captain/turn-metrics`), so the CLI and the route answer the same rows. `--limit` is 1–100 and defaults to 20; `--run` narrows to one run id. Each item carries the turn's counters — outcome, per-tool counts, first mutating tool, context occupancy — plus: - `execution`: the `model`, `provider`, and `effort` that actually ran the turn, captured as it executed. A `/model` or `/effort` change under a live conversation belongs to the next turn to execute, not to the one in flight. - `usage`: `totalTokens` summed over the assistant messages the provider reported for this turn, and `reports`, how many reports contributed. Both are `null` when unknown, and unknown is said out loud rather than defaulted. `execution` is null for turns settled before the capture existed or when the session had no model bound; `usage` is null when nothing was reported — never zero, which would read as a free turn. `contextTokensStart`/`contextTokensEnd` are context occupancy, not usage and not a charge; no dollar figure is inferred anywhere. No transcript, tool argument, tool output, or credential appears in the output. ```json { "ok": true, "items": [ { "schemaVersion": 1, "type": "captain.turn.settled", "conversationId": "…", "lane": "operator", "runId": "…", "outcome": "completed", "toolCount": { "bash": 6, "read": 2 }, "mutatingCount": 1, "contextTokensStart": 21000, "contextTokensEnd": 48000, "execution": { "model": "gpt-6-astra", "provider": "openai-codex", "effort": "high" }, "usage": { "totalTokens": 41200, "reports": 3 } } ] } ``` ### `memory-card [--lane LANE] [--hook]` The memory card that lane's next run injects, printed verbatim as plain text. The intended consumer is a per-turn hook, so a seat in another harness carries the same selected memories his own sessions do. `--hook` reads Claude hook JSON on stdin. On `UserPromptSubmit` it prints the whole card the first time a `session_id` asks, then only the notes that session has not seen yet, under a short "Newer notes" header. Unchanged turns, and notes that merely age out of the card, add nothing to the conversation. `SessionStart` prints nothing and re-arms the session, so the prompt after startup, resume, `/clear`, or compaction injects it again. Input without a usable `session_id` prints the card every time. Filtered by lane exactly as the session's own injection is: operator-private notes reach only the operator lane. An empty store still returns a labeled card. An unchanged hook turn can print nothing, which is not an error. ### `support [list | create read-state|shell --hours 1..72 --ref REFERENCE | revoke ID | offer ID]` Manage a customer-issued support grant through the owner-authenticated body API. `list` (the default) returns active and terminal grants. `create` requires a support reference, defaults to 24 hours and accepts at most 72 hours. Read state permits a support device to inspect conversation history and Clankie state; it cannot change settings, send commands or read terminal output. Shell permits commands and the content those commands can read during the grant window. `offer ID` requires a Read state grant and returns a short-lived, read-only pairing offer attached to it. The resulting device loses access on grant expiry or revocation. Shell grants refuse pairing with `support_pairing_requires_read_state` and authorize only the hosted Systems Manager `StartSession` path. `revoke ID` closes the grant. Responses are JSON. This command requires the operator credential; a captain bearer cannot issue support access. `/support` exposes the same command in the console. The hosted app and web account page provide the customer controls without requiring a CLI. ### `telemetry ship --spool DIR --cursor FILE --log-group NAME [--audit-log-group NAME] [--once] [--interval SECONDS]` Hosted infrastructure only. Ships a body's metadata telemetry spool (what `CLANKIE_BODY_TELEMETRY_DIR` collects) to a CloudWatch Logs group, stream `<tenantId>/<instanceId>`, each event at its own time. It must run on the EC2 host with instance metadata reachable, not inside the body: the tenant and instance ids and the credentials come from the instance, never from the spool. Every line is parsed against the event schema again before it leaves; anything else is counted as `dropped`. The cursor file records how far each spool file has shipped and advances only after CloudWatch accepts. Support grants and accesses use the mandatory `support-audit/` child spool, independent of diagnostic consent and diagnostic pruning. Hosted installations pass `--audit-log-group clankie-obs-<stage>-audit`: each support record goes to both the body and audit groups, with a separate acknowledgement cursor for each destination. A failed destination retries without suppressing the other. The host needs a writable mount for the support child directory so it can remove completed prior-hour files after both groups accept them. Unacknowledged support records remain; failed local audit persistence refuses support access. If an outage exceeds CloudWatch's event-age limit, the log timestamp is the ingestion time and the payload retains the original `atMs`. `--interval` is 10–3600 seconds (default 60). Without `--once` it runs until `SIGTERM`, printing `{"ok":true,"shipped":N,"dropped":N,"files":N}` per pass and `{"ok":false,"error":…}` on stderr when a pass fails; a failed pass is retried from the same cursor. See [hosted bodies](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md#body-telemetry). <a id="skill-setup"></a> ### `skills [opinionated on|off | exclude NAME | include NAME]` The selected `tidy` skill exposes `/tidy` in the console. It starts an ordinary visible, stoppable Clankie turn to inspect, harvest and close finished hired panes with reasons. Output and saved reports remain in roster history, with a five-minute reopen/resume Undo. Optional context can be passed as `/tidy selection=w1:p1`. See [bundled skill declarations](https://github.com/Volpestyle/clankie/blob/main/docs/bundled-skills.md#quick-action-declarations). List the bundled skill catalog as JSON, with `class` (`product` or `opinionated`) and `included` for each skill. `clankie doctor` includes the same selection. ```bash clankie skills clankie skills opinionated off clankie skills opinionated on clankie skills exclude reflect clankie skills include reflect ``` `skills.opinionated` defaults to `true`; `skills.exclude` defaults to `[]`. Product/tool and repo-authored skills always stay on; excluding one is refused. `include` removes an exclusion and leaves the class switch unchanged. The console has the same controls in `/skills` and `/setup rooms` → Working skills. Changes apply to new service sessions, local hires and Claude seats; existing context is not erased. Reset a service conversation or start a fresh seat after changing the selection, and reopen the console for its initial autocomplete. No service restart is needed for selection changes once this code is running. Existing service conversations discover added, changed or removed skill files and workspace instructions before their next turn, keeping their history and selected skill exclusions. Native seats keep their harness's own resource-loading behavior. `hire_agent` accepts `skills: "bundled" | "plain"` for one local Claude, Pi or Codex hire; omission follows the owner setting. `bundled` still honors exclusions. The result records the condition and supplied names. Unsupported/remote routes cannot honor an explicit override and refuse it. Independent global or project skills can still be discovered by Claude/Codex; this switch does not rewrite owner-global selection. See [the full bundle and A/B limits](https://github.com/Volpestyle/clankie/blob/main/docs/bundled-skills.md). Local briefed Codex hires use a dedicated app-server with a native Codex TUI in Herdr. Briefs and `message_seat` use protocol receipts; completion comes from turn events. The owner can type into the same session, whose identity and transcript stay visible. Existing unmanaged seats can use a supported native queue or channel. Automated messages never fall back to terminal typing. A `steered` receipt means guidance reached the active Codex turn, not an after-turn queue. When a hired Codex seat asks through native `request_user_input` or `request_user_input_async`, its question text, question IDs, and request ID reach the hiring conversation as worker output. The roster summary shows “Waiting on a question” and the request ID while it is pending. Async request IDs are the tool's `call_id`, rather than an app-server request number. The lead answers the existing prompt with `message_seat`, omitting `message`: ```json { "seat": "term_worker", "questionAnswer": { "requestId": "observed-request-id", "answers": { "scope": { "answers": ["Change the core package only."] } } } } ``` Use the observed request ID exactly (including its string or numeric type) and answer every question ID. This uses the seat's native control channel without interrupting the turn or typing into the terminal. Blocking sync questions hold ordinary follow-up messages; native async questions remain nonblocking. The owner can still answer in the pane. For sync questions, Codex takes the first answer and `status: answered` requires the matching winning native tool-output record. For async questions, Clankie sends the same attributed user-input envelope as the Codex 0.160 TUI: it steers the active turn, or starts the reply turn if the question's turn has already completed. Its receipt requires the exact native user-message client ID and content. Async answers have no upstream atomic first-answer arbitration; simultaneous owner and lead replies can both reach Codex. Observed answered requests are refused, and an uncertain answer is never sent twice. If no exact native receipt is visible, the result is `unconfirmed`; inspect the session instead of resending. Approvals and folder-trust decisions remain with the owner. Hand-started Codex panes without this controller do not gain a prompt-answer channel. If the confirming native snapshot also records another client answering the same async question IDs, the result is `unconfirmed` with `answered_concurrently_by_owner`. It cannot be reported as a clean answer. Cold history reads do not re-notify unanswered async questions from older completed turns; the latest completed question remains discoverable. An interrupted or failed native turn releases its active-turn marker and stale questions. If only an idle notification arrives, Clankie reads the native thread to verify that the exact active turn ended before releasing dispatch. A late completion from an older turn cannot release a newer one. Async questions survive normal completion until they are answered. A supplied `hire_agent` brief goes through the harness's own interface when a seat adapter drives that harness locally ([ADR 0187](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0187-clankie-hires-his-own-seats.md), VUH-1458). A Claude hire starts the real interactive TUI with the `clankie-worker@clankie` plugin enabled for that session: the brief and later `message_seat` messages arrive as channel notifications, a message counts as delivered only once it appears whole in the native transcript, and the plugin's Stop and StopFailure hooks settle `herdr_watch` with Claude's own final text. The worker is never swapped for a headless process; the owner can type into its pane at any time. This needs the owner's one-time consent: the plugin installed and disabled, and its channel approved in managed settings (see the [plugin README](https://github.com/Volpestyle/clankie/blob/main/integrations/claude-plugin/README.md#worker-channel-plugin-clankie-worker)). Until then the hire reports unavailable control with `consent_required` and the owner's fix. It does not launch a second worker or type the brief into the pane. Nothing accepts the development-channel warning on the owner's behalf. Local OpenCode workers use `hire_agent` with `harness: "opencode"` on macOS and a direct native **1.18.18** executable. Optional models use `provider/model`; `effort` requires an explicit model and a variant it supports. Account, skill, Chrome and extra-argv overrides are unavailable. The same native TUI stays in Herdr; its original process/socket, cwd and displayed session are checked before SDKv2 delivery. Owner questions and permissions hold sends. Native queue acceptance does not prove attention or completion, and uncertain delivery is never retried. Registered dedicated worker SQLite history supports `clankie agents list` and `clankie agents read`. `clankie agents resume … --conversation ID` needs the same live controller and hiring conversation; saved metadata cannot start another process. Remote control, general profile discovery, restart reattachment and new-process resume remain unavailable. Exact-session interrupt is supported. `close_seat` asks an owned live worker's original TUI to exit and succeeds only after its terminal disappears. Cold, replaced or switched sessions refuse; there is no unconditional physical pane-close fallback. See the [worker checkpoint and live limits](https://github.com/Volpestyle/clankie/blob/main/docs/testing/2026-10-04-opencode-workers/README.md). Linked Mac POSIX OpenCode hires use `harness: "opencode"` and the configured fleet ID. Their original native controller is carried over private loopback SSH with fresh remote process/socket/Herdr proofs, no local fallback and no automatic reconnection. `clankie agents list --host FLEET` and `clankie agents read FLEET:ses_…` read only registered dedicated worker SQLite history. Resume uses the original live controller on that exact SSH target. The remote Mac needs Node 24+, Python 3, Herdr, native OpenCode 1.18.18 and its existing Clankie fleet link. Windows is unsupported. Remote live acceptance remains open; [fixture evidence and limits](https://github.com/Volpestyle/clankie/blob/main/docs/testing/2026-10-05-remote-opencode/README.md). Every hire logs its selected lane and reason. The result carries `control.mode`: `channel` for the Claude worker channel, `adapter` for Codex or OpenCode, `terminal` for an unbriefed native launch, or `unavailable` with `control.reason` explaining missing structured control. Registered remote fleets do not change the control lane of a local hire. Native questions remain visible in the pane; approvals and folder-trust prompts remain owner decisions. Failed startups log `hire_agent.startup_failed`, with its session ID, resolved transcript path (null when no file is found), and rejecting rule. Claude receipt failures also log `hire_agent.receipt_rejected`, distinguishing `transcript_unavailable`, `no_new_operator_message`, `complete_body_mismatch`, and `mailbox_not_delivered`, without logging the brief. A mailbox delivery alone is not proof that Claude recorded it. Only the selected bridge polls: a globally registered `clankie-seat` stays inactive when the worker plugin is selected. An uncertain start or brief delivery retains its pane for inspection and reports uncertainty. The turn may already have started; reconcile its native session before retrying. `message_seat` distinguishes confirmed delivery, unconfirmed delivery, and unavailable control. External Codex messages first try the selected machine's existing app-server connection. On Windows, control supports an existing dedicated loopback backend and a TUI attached with `--remote`, preserving their private environment, cwd, configuration and MCP bridge. This adapter does not establish automatic supervision of new Windows launches; that launcher path still needs native verification. Existing embedded `--no-daemon` sessions and explicit named profiles retain that launch mode. Steering refuses when no private endpoint can be proven. An embedded TUI may queue through the existing SSH CLI only when fresh kernel observations prove that its home is the SSH account's canonical default `~/.codex` and the CLI inherits that same home. The native projection must positively prove a standalone TUI; an absent or rejected remote endpoint is insufficient. The pane/session and home proof are repeated after preparation. Caller authority is checked again after the final observation, with a 250 ms deadline immediately before sending. A timeout reports undelivered and never sends on late approval. Private, changed or unproved homes refuse the fallback; a private native receipt cannot authorize a second send through CLI. Clankie checks the current pane/session, native process lifetimes and ancestry, private-home consistency, listener and actual connected TCP owner before steering through the fleet's native SSH forwarding. Private queues reach the same proven backend and remain pending until its active turn settles, preserving custom `CODEX_HOME` sessions. These observations do not widen tool grants. `state: steered` confirms the exact active turn; `state: queued` and `status: queued_until_turn_end` mean native queue acceptance, not that the agent saw the message. A goal may hold it until the whole goal ends. No new setting or daemon is enabled. An unavailable connection does not promise an automatic retry. The current Codex adapter's control map is in memory, so a saved session reference alone does not reattach after a service restart. Sends target the bound Codex thread even if the owner switches the TUI to another thread; they do not follow terminal focus. Explicit owner terminal control remains available; normal agent messages do not use it. See [ADR 0207](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0207-work-records-and-native-agent-delivery.md). <a id="seat-commands"></a> ### `claude[N] | codex[N] | opencode [--resume] [--conversation ID] [--plugin-dir PATH] [--dry-run]` Open Clankie in the selected native harness ([ADR 0152](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0152-a-harness-takes-the-operator-seat.md)). `clankie claude` opens this seat with `claude`; `clankie claude2` uses your `claude2` account command. Numbered Claude commands are resolved through your interactive `$SHELL`, including shell aliases and functions. The same seat flags work with either command. Each numbered command keeps its own resume record. `clankie codex` and `clankie opencode` open the corresponding native harness with the same flags. `clankie codex2` selects the registered account labelled exactly `codex2`: `clankie accounts codex add /absolute/CODEX_HOME --label codex2` registers it. The number is part of the label, never an account-list position. Unknown labels fail without selecting another account. The launcher captures the canonical home for native discovery, the app-server and TUI. Numbered commands keep separate resume records and refuse to resume after their label is rebound to another home. Plain `clankie codex` retains the current `CODEX_HOME` behavior. OpenCode has no numbered account command. For numbered accounts, set `CODEX_HOME` to that registered home in the environment of native plugin installation commands and the Codex session used to review `/plugins` and `/hooks`. Setup under a different home does not prepare this account. Claude launches need a TTY and the selected Claude command available. The launcher projects the bundled plugin (or `--plugin-dir` source) into a private launch directory with only the selected skills. Identity, hooks, and MCP are retained. It passes the permission allowlist for `clankie` commands, disables an older installed `clankie@clankie` for this session, and enables `clankie@inline` with the development channel flag for that same identity. This also prevents a stale marketplace copy from restoring pruned or disabled skills. Keep any marketplace seat plugin disabled globally, since its forced output style makes every session answer as him when enabled there. With `--conversation global-default`, inside the service's herdr fleet it names that pane `clankie` once Claude Code is detected there, which binds the pane to his own persona rather than a fleet contact; a second pane claiming the name stays an ordinary fleet agent and is told so on stderr. The pane is un-named again when the session ends. Every fresh Claude launch starts a new Claude Code session under a recorded id and creates a separate workspace chat through `POST /v1/captain/seat-context`, rooted at the launch directory. Multiple launches in the same directory or account each get their own chat, transcript, tool context and wake channel. The chat is available in the app and `clankie conversations list`. A running service and operator credential are required; failure to create the chat stops the launch. `--resume` reopens the last seat for that Claude command and its chat. The conversation selection is retained on resume, and a different `--conversation` is refused. Skill selection is reapplied at launch, but resumed history can still contain previously loaded guidance. `--conversation ID` selects an existing global/workspace service conversation or Discord text/voice room, resolves its cwd through `/v1/captain/seat-context`, and opens the selected harness there. That workspace must exist on the native host. The prompt includes its agent instructions and the owner's persona/fleet preferences. The MCP bank and channel share its conversation. Inherited worker capabilities and conversation selections do not select the seat. Use `--conversation global-default` to select the shared global chat. Workspace seats do not rename themselves as the global Herdr head. While its channel is live, the seat receives that conversation's worker reports, escalations, wakes and watches instead of starting a service model turn. Closing the seat returns new inputs to the service runner. A turn already accepted by either destination keeps that destination; uncertain native delivery is never replayed automatically. Service goals require a Pi-owned conversation. Native harness MCP seats refuse `create_goal` with `native_goal_unsupported`; owner activation or resume also refuses while a native head owns the conversation. Queued or restored service goals pause on finding a native head, so they cannot start another Pi lead alongside the seat. Internal self-wakes, watch notifications and worker messages also keep the native receiver while its polling channel is offline; they do not start a Pi lead. Failed self-wakes remain scheduled and retry after 5 seconds, doubling to a maximum interval of 5 minutes. Each chat has its own retry delay, and a replacement wake starts with a fresh delay. `/autonomy clear` cancels the selected chat's scheduled wake; it does not cancel an already running turn. Worker reports retain their original delivery IDs and require explicit read acknowledgment after delivery. Model calls in Pi create inactive proposals; `/goal accept` confirms one. `/goal <objective>` creates an active goal directly. Starting, accepting and resuming a goal, and `/autonomy on`, require the owner/device credential; the shared captain bearer receives HTTP 403 `goal_owner_required`. The console uses its owner transport, and headless owners can use `clankie conversations goal ID accept|resume` or `clankie conversations goal ID set [--tokens N] <objective>`. An omitted action reads status; `pause|clear` and status use the captain transport. This raises the activation bar, but a same-UID shell can still read Keychain credentials or the device signing key; it does not provide OS-level owner isolation (ADR 0130). Every service goal defaults to a 1,000,000 model-token budget, overridden with `/goal --tokens <n> <objective>`. Recorded usage survives budget migration for older goals, and exhausted goals stop before another provider request. Stalled service preparation releases its admission so the attached seat can take later queued inputs. Native delivery keeps its existing acknowledgment deadlines and ten-minute escalation reply wait; it has no new five-minute reply cutoff. Selecting `global-default` affects only that chat. To drive a Discord room, select its conversation; replies return through the original room delivery and authority checks. Rooms remain read-only to ordinary `send` and `reset` commands. Room attachment adds no machine grants: its cached MCP bank has social tools and no generic operator body identity. It cannot inherit an actor's grants from a later room message. See [ADR 0218](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0218-native-seats-drive-their-attached-conversation.md). `--dry-run` prints the launch plan without creating a chat or launching: ```json { "ok": true, "command": "claude", "args": [ "--name", "Clankie", "--settings", "{…}", "--plugin-dir", "…/skill-projections/launch-…", "--dangerously-load-development-channels", "plugin:clankie@inline", "--session-id", "…" ], "plugin": { "source": "plugin-dir", "path": "…/skill-projections/launch-…" }, "channel": true, "sessionId": "…", "resumed": false, "cwd": "/Users/me/dev/project", "newConversation": { "op": "create", "schemaVersion": 1, "scope": { "kind": "workspace", "workspaceId": "/Users/me/dev/project" }, "title": "Clankie claude · project · …" } } ``` `plugin.source` is `plugin-dir`; the projected skill catalog is also in the plan. The [plugin README](https://github.com/Volpestyle/clankie/blob/main/integrations/claude-plugin/README.md) describes the component source and session-only channel identity. `clankie codex` opens the real Codex TUI on its own app-server thread. Install the [Codex seat plugin](https://github.com/Volpestyle/clankie/blob/main/integrations/codex-plugin/README.md) first. The launch plan includes a typed `hook_trust_required` owner step: review the plugin in Codex's `/hooks`, then exit and launch the seat again. The launcher never bypasses hook trust. Wakes bind only after trusted session hooks succeed. `--resume` retains the last Codex thread and conversation independently of the Claude seat. Fresh Codex launches also create their own workspace chat. Both harnesses use the same service prompt, memory card, tool bank, redacted transcript endpoint and conversation outbox. ### `mcp [--lane operator] [--conversation ID]` The seat's stdio side: an MCP server on stdin/stdout that re-serves the service's lane tool bank (`/v1/mcp`), resolving the operator bearer from the credential broker so no secret lands in a harness config. The plugin's `.mcp.json` names it; a Codex MCP config names the same command. Only the operator lane has a bearer on this side. stdout is the wire: progress goes to stderr, and the process ends when the harness closes stdin. `--conversation ID` or the launcher-set `CLANKIE_CONVERSATION_ID` binds tools, polls and replies to one service conversation; the API rejects a changed binding within an MCP session. It is also his channel. While it runs it long-polls `/v1/seat/events` and pushes worker reports, self-wakes, herdr completion watches and room escalations into the session as `<channel source="clankie" kind="message|wake|watch|escalation" conversation="…" event_id="…">`; that polling is what binds the seat as his head, and with no bridge polling the same turns run the pi operator lane. A `reply` tool answers an escalation by `event_id`; the reply lands in the escalating conversation as his own message. Claude Code loads the channel only when `clankie claude` passes its development flag; without it the tools still work without consuming events, leaving those turns with the service. Worker `message_clankie` reports project as `kind="message"`, framed as untrusted agent output, never an owner instruction. Completion harvests remain `kind="watch"`, and self-wakes remain `kind="wake"`. These tags do not change the service-owned lead route or delivery receipts. A room-owned worker message still uses `reply` with its `event_id` for the correlated room reply; the original actor, route and mouth checks remain in force. ### `mcp --seat` For connected-account tools in a worker, use `mcp --grant FILE` instead; see below. A fleet pane's stdio MCP server: no tools, only the channel. A message to that agent (a DM from the app, or a group-chat turn) arrives as `<channel source="clankie" kind="message" conversation="…" event_id="…">` instead of being typed into the pane. The bridge polls only when the parent `claude` argv loaded `server:clankie-seat` as a channel; otherwise it serves empty and does not bind. Claude Code binds that channel when the server is in the harness MCP config (`claude mcp add -s user clankie-seat -- clankie mcp --seat`) and the session is started with `--dangerously-load-development-channels server:clankie-seat`. `--channels server:clankie-seat` starts without the development-channels dialog but then rejects `server:` as not on the approved allowlist. The service's hire path persists the server and passes the dangerous flag for a claude seat. <a id="mcp-fleet-fleet-connected-tools"></a> ### `mcp --fleet`: fleet tools and native worker messages Register `clankie mcp --fleet` in Codex with `env_vars = ["HERDR_PANE_ID", "HERDR_SOCKET_PATH"]`, or install the `clankie-worker@clankie` Claude plugin. Preserve generated/symlinked harness configuration: inspect `doctor.harnessBridges.codex.configSource` and edit its owning source. Admitted fleet panes receive every connected MCP tool whose account is verified, except persona-bound Linear worker-publishing tools. Admission uses the pinned local Herdr socket, a live remote relay stream, or a remote fleet link bearer. A bearer proves only its fleet, with no verified pane or mailbox authority. No project grant, native session or workspace proof is needed for these tools. Projects retain roles, caps, hiring and tracker binding. The connected-tool service lists `clankie_tools` and `clankie_call`; the shared worker bridge adds `message_clankie` and, for a proven native sender while peer messages are on, `list_fleet_seats` and `message_peer`. Search connected tools with `{query}` for at most 20 names/descriptions, or `{names}` for up to 10 input schemas, then call with `{name, arguments}`. `message_clankie` reports to the conversation that hired the worker. A host-admitted `message_seat` from another conversation adopts that worker, so future reports and completion watches follow the new lead. The worker does not choose the destination. Worker reports fall back to `global-default` when that conversation has been removed; a retained room with revoked grants is refused. Local and fleet-qualified remote workers follow the same persisted ownership proof and delivery receipts. Worker output remains in the owning conversation independently of a worker pane. `clankie agents reports --conversation ID [--limit N]` (or `worker_reports`) returns the oldest unread reports with their original delivery IDs and exact text. Reading does not mark them read. After reviewing every offered report, run `clankie agents reports ack DELIVERY_ID... --conversation ID` (or `acknowledge_worker_reports`). Only fully offered IDs can be acknowledged. You can pass the unmodified returned page on standard input with `clankie agents reports ack --json-stdin --conversation ID`; the page must name the same conversation. A page holds at most 100 IDs. For reviewed, retained history, the owner can run `clankie agents reports ack-history DELIVERY_ID... --conversation ID` to acknowledge up to 1,000 explicitly selected IDs, including migrated receipts that were never offered by the current runtime. This requires operator authentication; captain and paired-device credentials cannot use it. Unknown IDs or IDs from another conversation reject the entire operation. New reports and reports outside the selected IDs remain unread. The roster exposes per-worker unread receipts and the fleet retains report rows for disappeared panes. A finished worker with pending or uncertain output shows “done, report not delivered”; confirmed transport alone still shows “report unread”. The console also lists retained reports under the agent dock. New definitely queued or refused-before-handoff reports keep their original target and may resume when its verified receiver returns. Crash-interrupted attempts and legacy receipts remain uncertain and readable; they are never blindly resent. A matching original thread may retain its output while requiring re-adoption; that retention grants no control or dispatch until the owner repairs the binding. The API uses authenticated operator dispatch operations `readopt_seat`, `worker_reports`, `acknowledge_worker_reports`, and the owner-only `acknowledge_worker_report_history`, each naming the exact owning `conversationId`. Credential and conversation authority are checked again at admission. Without persisted adoption, the host reads the actual census parent/launcher edge and routes to that exact native lead or its attached conversation. Explicit adoption wins; tabs, titles and report text establish no ownership. The parent needs an exact-session native mailbox, authenticated hook or existing harness control/queue path. A room still needs its original Discord admission and grants. If no eligible parent exists, the report falls back to `global-default` with `workerReportRouting.source: "unadopted"` on its durable accepted turn and a reason (`no_parent`, `parent_unavailable`, `parent_unlinked`, or `owner_removed`). The fleet roster exposes the same diagnostic and parent pane/seat when known. Observed launcher edges are retained by native child and parent thread identity. After a Herdr reset, a missing edge is recovered only while one exact original child and parent are live on the same fleet. A new actual ancestry supersedes history; a changed, missing, or ambiguous parent cannot inherit it. Older launches with no retained edge keep the explicit `no_parent` diagnostic. An authority or occupant mismatch is `source: "refused"` with `reason: "authority_unavailable"`; it does not admit a default fallback. `clankie doctor` includes `linkedSession.parentLeads` and names lead panes whose bridges are missing or unobserved, including their child panes. These process observations do not prove native delivery or grant tools. Reconcile the original report ID after uncertainty; restarting or adopting a worker never resends an already accepted report to another conversation. Only definite pre-handoff recovery reuses its original target. `clankie fleet set --tools off` stops new standing tool admissions. Each call rechecks live admission, account binding and settings, but a call already past its last asynchronous check can still reach a provider after tools-off or admission loss. The strict refusal guarantee is not met; see [ADR 0217](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0217-fleet-membership-gets-connected-tools.md) and VUH-1585. Manual grants keep their existing restrictions. Use `list_fleet_seats({})` to discover seats in the sender's own fleet, then pass the returned recipient `seatId` as `seat` to `message_peer({seat, text})`. The bridge obtains the sender and recipient bindings; workers do not supply them. Both the Claude worker plugin and `clankie mcp --fleet` use `runSeatChannel` for this path. The server requires the caller's proven native pane process and matching session, confines recipients to the same fleet and checks their current binding. Peer content reaches the existing `message_seat` native channel/session delivery path as agent output, never an owner instruction. It records a server audit and an agent-role message in Clankie's default transcript. Native channel events carry `source: peer`; the exchange does not wake him or create an owner turn. An uncertain peer send keeps its original receipt. Reconcile that ID through `GET /v1/fleet/seats/{paneId}/peer-messages/{id}`; do not issue another POST, delete receipt state or switch bridges to replay it. Discovery uses `GET /v1/fleet/seats/{paneId}/peers`; new sends use `POST /v1/fleet/seats/{paneId}/peer-messages`. These worker routes derive authority from the admitted identity, not caller-supplied pane or fleet fields. Receipt reads remain available with peer messages off. A lost recipient binding terminates reconciliation as `recipient_gone` with outcome `unconfirmed`: delivery stays unknown, the original is never resent, and fresh messages are allowed. The service keeps full bodies for the latest 100 settled messages and all unresolved originals; older settled bodies become exact compact receipts that still prevent ID replay. See [worker access](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md#messages-between-workers). `doctor.harnessBridges` reports installation, registration and invoking-process membership separately. Remote project `eligibility: unsupported` does not mean fleet tools are denied; `nativeTools: not-verified` still requires an actual native catalog/call check. MCP sessions bind to fleet/pane (fleet only for bearer links) and expire after 15 minutes idle. See [worker access](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md). New Claude/Codex hires require the connected-tool wrapper pair and, when `fleet.peerMessages` is on, `list_fleet_seats` and `message_peer` before starting their brief, including native-first hires without a project allocation. The wrapper catalog depends on fleet settings and admission; provider account and native peer proofs are checked when invoking a tool. A temporary provider or discovery failure keeps previously verified schemas in the native catalog. Explicit settings changes remove disabled tools. Each worker connected-tool request has a thirty-second total budget covering initialization, catalog/account reads, remote invocation and the response body. Timeouts report a reason and never replay an uncertain mutation. Concurrent requests share one completed MCP handshake. Replacing a service provider connection lets already dispatched calls settle on their original connection. HTTP refusals retain the service's reason, including fleet admission errors; cached schemas do not authorize a refused call. A `No durable native binding` message receipt means the bridge could not prove its delivery binding and sent no new message; inspect the pane's native binding before retrying delivery. Connected reads do not run optional native write-attribution proof. Ordinary connected writes bound that attribution separately while preserving fleet, account, configuration and publication checks. Request cancellation follows the local native proof queue, so an expired worker call does not keep consuming it. An older running worker bridge can keep an exact terminal inbound receipt unresolved even after the service and plugin files are updated. Version 0.6.2 accepted only positive stored receipts; current code also accepts the service's matching `definitive: not_sent` fence. Refresh the MCP process that owns the call, preserving the original thread and receipt file. The deployed operator outbox pump fix does not reload a worker's already-imported parser. For a Clankie-managed Codex seat with its original dedicated controller and isolated copied config, the controller replaces only Clankie's connection by updating `mcp_servers.clankie.env.CLANKIE_CATALOG_REVISION` with `config/value/write`, then calling `config/mcpServer/reload`. The next model step uses the refreshed connection on the same loaded thread. Do not edit the owner's config, restart a shared daemon, fork the thread or delete a claim. An embedded or remote session without that controller needs its owner's exact-session reconnect after the old runtime unloads. After refresh, invoke `message_clankie` once to read the original receipt. A matching positive stored result or terminal `definitive: not_sent` result settles the retained claim; that invocation still sends no replacement. Invoke again deliberately to send the later report. A timeout, unauthenticated or mismatched lookup stays uncertain. See [the worker fleet regression evidence](https://github.com/Volpestyle/clankie/blob/main/docs/testing/2026-10-05-worker-fleet-tools/README.md). ### `project create PROJECT --settings FILE.json --revision REVISION` Requires a service build containing the local project-creation route; a source landing does not update an installed CLI or the running service. Read `clankie project list`, then review a proposal file and submit it with the returned revision. `/project create` uses the same owner-authenticated API. Ordinary preference answers do not authorize creation. The file requires `name` and `workspacePath`; `PROJECT` and `REVISION` are explicit arguments and cannot be supplied or overridden by the file. Optional `roles`, `workerCap`, `trackerRef`, `trackerSetup` and `fleet` use the existing project policy vocabulary. Role and project caps may be `null` to inherit; zero stays an explicit zero. Fleet size and model preferences do not imply numeric caps or new hiring guidance. Creation accepts one existing canonical workspace on the service's local machine. Its workspace ID is `primary`. Existing project IDs, path overlaps, changed directory identity, changed authority and stale settings fail without an automatic retry. Remote enrollment, linked roots, assignments and grants are not part of this command; existing `project add` semantics are unchanged. The pre-rename authority, directory, tracker and revision guards are asynchronous observations, not cross-process compare-and-swap or atomic authority checks. An optional tracker binding must be `{"workspaceId":"primary","path":".clankie/tracking.json"}` and the valid saved convention must already exist unless `trackerSetup` is supplied. For a workspace without a saved convention, `trackerSetup` includes an explicit `backend` (`default`, `markdown`, `github`, `linear`) and the existing work-init inputs: `directory`, `githubRepo`, `linearTeam`, `linearProject`, `linearLabel`, `decisions` and `note`. It requires that tracker binding and writes the same `.clankie/tracking.json` as `clankie work init`, within the reviewed CREATE. An existing convention or changed workspace, parent directory, owner authority or project revision refuses initialization. It chooses no account and creates no provider project or label. In an unassigned owner workspace conversation, Clankie receives the onboarding opportunity and can read the repo, ask about tracking, propose useful roles and ask about fleet size through the existing dialog questions. Answers are context; `propose_project_create` puts their complete configuration into the existing explicit CREATE review. Confirmation consumes that proposal once. Tracker and project settings are two file writes: a failure after tracker save can leave only the tracker. An uncertain confirmation stays uncertain and is checked with the original proposal target, never replayed. See the [closure evidence](https://github.com/Volpestyle/clankie/blob/main/docs/testing/2026-10-05-project-onboarding/README.md) for source checks and the remaining live acceptance. Project village visuals belong to VUH-1710. ### `project list`, `project settings` and `project update` `clankie project list` reads the current project settings and their revision. `clankie project update PROJECT --changes FILE.json --revision REVISION` submits reviewed changes for an existing project through the authenticated service API. The console exposes the same verbs through `/project`. The changes file may contain `name`, `roles`, `workerCap`, `trackerRef` and `autonomy`. Omitted fields remain unchanged; `null` removes a worker cap or tracker binding. An empty roles list inherits the six built-in roles; an explicit list defines the available roles and may set their whole hire profile (harness, model, effort, subagents, delegation, account, placement), naming rule and concurrency cap. Zero prevents new hires, while an absent cap adds no limit. These settings affect new hire admission, not the configuration of already running agents. The service validates the whole resulting project settings document, preserving workspaces, roots, assignments, grants, label mappings and unrelated projects. Stale revisions or removal of an in-use role fail without overwriting the saved settings. Read the settings again and review the changes before retrying. `clankie project settings PROJECT` prints stored autonomy overrides and their current effective values. Add `--closure lead|owner|inherit` or `--machine-setup lead|owner|inherit`, `--commit lead|owner|inherit`, `--push lead|owner|inherit`, `--release lead|owner|time_rule|inherit` (with `--release-rule TEXT` for `time_rule`), `--verification review_and_seal|change_run_read|inherit`, or `--report-style TEXT|inherit` to change only that leaf through the current revision-bearing owner API. Missing leaves inherit the global setting independently; `inherit` removes the selected override without changing its sibling. The console accepts the same syntax as `/project settings PROJECT ...`. ```sh clankie project settings garden --closure owner clankie project settings garden --machine-setup lead clankie project settings garden --closure inherit clankie project settings clankie --release time_rule --release-rule "Release without asking when the last v* tag is more than one week old and main has user-visible changes worth shipping." clankie project settings garden --commit owner --push inherit clankie project settings garden --report-style "Short and plain." ``` For a reviewed JSON update, `autonomy: { "fleet": { "closure": "owner" } }` sets one override; `autonomy: { "fleet": { "closure": null } }` clears it. No defaults are copied into project overrides. API readers request `?includeAutonomy=true` to receive project autonomy and `autonomyDefaults`; the default response preserves the older project snapshot shape. New fleet/context and autonomy-aware project responses advertise `workingPreferences:true`; an older response keeps new leaves absent. The app hides unadvertised working preference controls while retaining existing closure/machine-setup controls. Release mode and rule are replaced or inherited together. These settings grant no workspace, machine or tool authority. Machine setup derives its project from the actual canonical caller workspace; an explicit `--project` must match that context and cannot select a more permissive override. `trackerRef` selects an existing project workspace and the fixed path `.clankie/tracking.json`. It does not initialize a tracker, select an account or register a repo. The app's existing work reader receives a read-only virtual repo for the binding. Only an exact canonical workspace on the current local machine is readable; remote, missing or changed sources report unavailable. Existing registered repos remain independent. Project label mappings are preserved but this editor does not apply them to station placement. ### `project add NAME --workspace PATH` The owner can approve one local project workspace with `clankie project add NAME --workspace /absolute/canonical/path`. This local settings command requires the canonical broker operator credential and an existing directory with exact canonical spelling. A new project ID creates a project; an existing ID appends one workspace while preserving its name, roles, caps, tracker, grants and assignments. Duplicate or nested-overlapping local workspaces are rejected across all projects, including the same project. Appended workspace IDs are derived deterministically from the machine, platform and canonical path. It creates no roles, assignments or tool grants. Fleet connected-tool access is independent of these project approvals. ### `access` and `mcp --grant FILE` `clankie access linear [verify]` reads or verifies the connected account. `access list`, `access issue REQUEST.json --out GRANT.json` and `access revoke ID` manage individual worker grants. The private file feeds `clankie mcp --grant FILE`, which serves only granted tools and loads no operator bearer or seat channel. Tokens expire after at most 15 minutes and require explicit reissue. `access project NAME SERVER [--tool NAME]...` retains legacy project-grant records with no bearer delivery; they no longer gate fleet tools. `access fleet` remains retired. Use `fleet set --tools off` to disable standing fleet tools. `/access` exposes status, verification and revocation; issue from the terminal. See [worker access](https://github.com/Volpestyle/clankie/blob/main/docs/worker-access.md) for restrictions and account bindings. ### `stance <working|thinking|stuck|hauling|resting|celebrate> [--activity KIND] [--note TEXT] [--for SECONDS]` For agents, not for people ([ADR 0148](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0148-an-agent-moves-its-own-figure.md)). Say what you are doing with your own figure in the commons; the operator's app poses it and moves it accordingly, and prints your note on your Messages row. Takes no seat argument by design: the seat is resolved from `HERDR_PANE_ID` in the caller's own environment against the live Herdr census, so this can only ever move the figure the caller is sitting in. `--for` defaults to 15 minutes and is capped at one hour — a stance is a live statement, and once it lapses the figure goes back to being posed by what its pane is observed to be doing. ```json { "outcome": "stated", "seatId": "…", "personaId": "…", "stance": { "pose": "stuck", "note": "waiting on the build", "statedAt": "…", "expiresAt": "…" } } ``` `{"outcome":"unseated"}` means the pane holds no fleet seat — normal in a plain shell pane, and not an error. `--activity reading|editing|testing|planning|waiting` explicitly states the kind of work for the World activity bubble. For example, before a generic shell test run: `clankie stance working --activity testing --for 60`. The roster/fleet read returns `seat.activity` with its kind and `source: stated`, or `native_tool` when a fresh outstanding known native tool establishes it. Unknown kinds are refused; notes and shell arguments are never classified. Omitting `--activity` on the next stance clears it. The statement expires, belongs to this exact occupying session, and is absent for idle/offline seats. Unsupported native telemetry can still carry a live explicit statement; absence means unknown. ### `discord [status]` Return stored and effective non-secret Discord configuration: ```json { "ok": true, "discord": { "activeBody": "bot", "systemActorUserIds": ["12345"] }, "effectiveDiscord": { "activeBody": "bot", "systemActorUserIds": ["12345"] }, "overriddenByEnvironment": [], "settingsFile": "/Users/me/.config/clankie/settings.json", "restart": "clankie restart" } ``` `discord` is the stored value. `effectiveDiscord` includes environment overrides, whose variable names appear in `overriddenByEnvironment`. ### `discord directory [servers|channels|roles|people] [--server ID] [--limit N] [--after ID]` Read the names, IDs and kinds the active Discord account can see. The default lists servers; channels, roles and people require `--server`. Pages contain at most 200 entries (default 100); pass the returned `nextCursor` as `--after`. `state` and `reason` distinguish a disconnected runtime, partial cache and failed read from a complete empty list. People and channel/thread coverage may be partial. No account is connected or configured by this command. Requires operator authentication. See [the directory contract](https://github.com/Volpestyle/clankie/blob/main/docs/discord-rooms.md#discord-directory-for-settings-pickers). Hosted bodies obtain this view from the managed provider, restricted to their bound server and current installation, without a local Discord control port. ### `discord definition` Read the host's shared Discord setup definition, check kinds, Advanced groups, choice labels and role-correct invitation URL as JSON. Requires operator authentication, like `discord rooms`. The host supplies its computer name. See [Discord settings](https://github.com/Volpestyle/clankie/blob/main/docs/discord-rooms.md) and [ADR 0227](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0227-discord-connects-a-server-with-a-role.md). ### `discord setup` Read the connected server, Clankie's role, fleet toggle, tracking level and setup checks. TUI `/discord` uses the same host definition and revision-fenced writer. The server is chosen by name; channel and Discord-role pickers do not appear in normal setup. Raw IDs and machine-access grants live under Advanced. ```sh clankie discord setup choices connect clankie discord setup invite --role participant clankie discord setup connect --server Studio --role participant clankie discord setup connect --server Oathkeeper --role admin clankie discord setup fleet --enabled on clankie discord setup fleet --enabled off clankie discord setup tracking --level project_updates clankie discord setup tracking --level project_activity clankie discord setup tracking --level all_issues clankie discord setup tracking --level off clankie discord setup check ``` Participant uses Discord's own permissions to decide which rooms Clankie can read and speak in. Admin is for a dedicated server: the invitation requests Administrator and Clankie controls channels, categories, roles, webhooks and members. Server deletion and ownership transfer are always refused. Selecting a role never grants access to the operator's computer. Fleet display and tracking are independent. Participant projection messages use the designated `fleetChannelId` under Advanced; no channels or webhooks are created. Admin can create fleet channels and mirror tracked projects as channels or forums, with one thread/post per issue. Tracking levels are **Off**, **Project updates only**, **Project activity** (status changes, milestones, new/finished issues) and **Every issue notification**. Disabling display or tracking preserves retained mappings and connections. The invitation requests the selected role's grants. Setup checks the connected body's gateway evidence: proven denials say **needs** and missing evidence says **not checked**. Admin requires Administrator; Participant checks its normal text, thread and voice grants. Channel overwrites still control Participant's actual access. Reading setup and saving controls never post to Discord. Each control saves through the authenticated host API. A stale edit fails rather than overwriting someone else's changes. Hosted consoles and CLI use their existing encrypted transport. Raw local fields and credentials are not written through a hosted connection. Body settings retain their existing restart requirement; a save does not claim the running gateway has applied it. Managed edge policy synchronization is reported in `managedPolicy`: a saved body revision can be pending while the edge retries. Only `synced` identifies the revision the edge acknowledged. The hosted dashboard edits the same role model using a Discord-only signed owner bridge; disconnect/reinstall revokes that account connection grant and refuses later admissions. The explicit diagnostic `clankie discord setup test-post --channel general` remains available. It requires settings-level operator authority, a current revision, exact account identity and verified Send Messages. Missing native receipts return `unconfirmed`; inspect the room before deliberately trying again. It is never part of opening or saving setup. ### `discord transcripts [--cursor CURSOR] [--limit N]` Read the private retained voice log through the authenticated service API. The default page contains the newest 100 entries; `--limit` accepts 1–200. Pass `nextCursor` to read later entries and follow `hasMore` when paging. Logging must be enabled through `discord set --voice-transcript-logging-enabled on` and activated by restarting the relevant services; otherwise the page is empty with `enabled: false`. `/vt` shows the same entries in the console. Human entries retain consented final recognition. Clankie's entries have `role: assistant`, generated `text`, provider `itemId`, and playback outcomes (`played`, `interrupted`, `suppressed`, `failed`, `truncated`). An interrupted or failed reply may include words that never played: the exact audible word cutoff is unknown. No raw audio is saved. See [ADR 0121](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0121-development-voice-transcripts-are-explicit.md). ### `discord set --field value […]` / `discord clear --field […]` Set several fields atomically, or reset fields to their schema defaults. Field flags are the `settings.json` camel-case names in kebab-case. Lists are comma-separated (`none` clears); booleans accept `on|off`, `true|false`, or `enabled|disabled`; integer fields require whole numbers. Zod validates the completed settings document and the settings writer rejects token-shaped values. | Group | Fields | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Application and roles | `application-id`, `guild-id`, `swarm-guild-id`, `ambient-role-ids`, `ambient-user-ids`, `approval-role-ids`, `owner-user-id` | | Machine grants | `system-actor-user-ids`, `system-actor-guild-ids`, `system-actor-channel-ids` | | Text and presence | `text-ingress-enabled`, `ingress-guild-ids`, `ingress-channel-ids`, `ingress-dm-policy`, `ingress-dm-user-ids`, `ingress-context-messages`, `tool-progress-channel-ids`, `presence-guild-ids`, `presence-channel-ids` | | Voice | `voice-enabled`, `voice-guild-ids`, `voice-channel-ids`, `voice-channel-id`, `voice-join-policy`, `voice-consent-policy`, `voice-transcript-logging-enabled` | | Body selection and lab | `active-body`, `user-session-enabled`, `user-session-guild-ids`, `user-session-channel-ids`, `user-session-voice-enabled`, `user-session-voice-channel-ids`, `user-session-dm-policy`, `user-session-dm-user-ids` | | Activity | `activity-application-id-gba`, `activity-tunnel-name`, `activity-tunnel-hostname` | `active-body` is `bot` or `user_session`. These commands never accept Discord tokens and do not perform the lab-user ToS opt-in. The main TUI `/discord` flow uses the shared host definition and revision-fenced API writer. Advanced keeps the existing local credential and opt-in flows on the broker and service HTTP catalog; its raw field editor uses host revision checks. ### External native agent chats Herdr discovery provides agent identity, routing and status. It does not import external conversations or create chat threads. Opening a persona chat and using the existing `replay`/`tail` operations reads the harness session on demand, including messages, tools, typing state and contained images. Native cursors are opaque; clients follow the returned recovery cursor after a session or history change. The host persists the source locator, not a second native transcript. Explicit app sends and native messages remain durable host communications. An unadopted worker report can create its linked native parent's seat thread to retain the report's delivery receipt; discovery alone still creates no thread. Clankie can inspect panes and arm completion watches independently of chat views. See [the native chat decision](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0188-native-agent-chats-read-their-own-history.md). <a id="evaluation-commands"></a> ### Independent evaluator ```sh clankie evaluator enable --harness codex clankie evaluator enable --harness claude clankie evaluator status clankie evaluator open clankie evaluator disable clankie evaluator retry EVALUATION_UUID ``` The local operator credential authorizes `GET /v1/captain/evaluator` and `POST /v1/captain/evaluator`. POST accepts `{ "action": "enable", "harness": "codex" }`, `disable`, `open`, or `{ "action": "retry", "id": "<UUID>" }`; an omitted harness preserves the selection. In the TUI, bare `/evaluator` opens a menu that toggles the evaluator, switches its harness, opens its pane, shows recent assessments, and retries failed jobs; `/evaluator` also accepts the same arguments and renders the queue, recent assessments, linked issues/MRs and errors. `open` focuses the evaluator in the service's active Herdr session. CLI success is `{ ok: true, evaluator: ... }`, with exit 0; transport, authentication or command errors exit 1. Invalid API commands return 400, missing operator authority 401/503, and conflicting commands 409. The status includes `enabled`, `harness`, evidence `directory`, optional `paneId` and `error`, `queued`, and up to 50 recent `jobs`. An enabled evaluator can report an operational error (missing harness, blocked startup, unavailable Herdr); inspect `error` and the pane. The evaluator is a developer diagnostic, not a user feature. It defaults off and captures only Clankie’s Pi turns and native head-seat replies while enabled; the console footer shows `evaluator on · HARNESS` for as long as it is. Other Herdr agents do not trigger assessments. Enabling creates its own pane and starts a harness; new work uses fresh agent context. Captures coalesce for a quiet minute, with fifteen-minute checkpoints for continuing activity. Only a schema-valid report from a settled agent completes an assessment. Restart resumes inspection of the existing assignment; uncertain failures block every retry until the original delivery is reconciled. A busy evaluator pane keeps new work queued rather than failing it. The pane is recognized by its Herdr name, or by its terminal plus harness session or process once a harness clears that name; a pane it can no longer prove is its own is left open and a fresh one is started. The service interrupts assessments after thirty minutes. Disable stops new capture and dispatch; in-flight work finishes. It does not change Linear following, merge changes or close review panes. Evidence and queue state live under `~/.clankie/captain/evaluator/`, outside conversation pruning. Goal identity groups continuations; otherwise captures are conversation checkpoints and do not assert a completed task. Pi transcript excerpts are bounded to 512 KiB and declare truncation; native projections retain their existing bounded entries. Gameplay journals are not separate triggers. Raw evidence remains local; findings carry redacted excerpts to Linear. See [the evaluator decision](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0178-the-evaluator-has-its-own-seat.md) for scope and limits. ### Hired seat lifecycle hooks `clankie seat-hook` is the `clankie-worker` plugin's hook (VUH-1458). Inside a pane Clankie hired (`HERDR_PANE_ID` set), it reads Claude's `SessionStart`, `UserPromptSubmit`, `Stop` or `StopFailure` JSON on stdin and posts `{ event, sessionId, lastMessage?, error? }` to `/v1/fleet/seats/{paneId}/hook` with the operator credential. The final text is the hook's `last_assistant_message`, or the transcript's last reply when the hook omits it. The service records it only when herdr reports that Claude session in that pane; anywhere else the command does nothing. The worker plugin uses the matching fleet link when available. Only transport proof of the current native pane/session can register or drain its next-turn mailbox; a bearer-only lifecycle report cannot. Without a live channel, `message_seat` reports `deliveryStage: stored` for an observed receiver and holds the reply up to 24 hours. The synchronous `UserPromptSubmit` hook writes it as additional context once and acknowledges its message IDs after stdout succeeds. That receipt means bridge delivery, not model consumption. A lost handoff remains `uncertain` and is never automatically replayed. Sessions without an observed receiver remain unavailable. ### Native seat transcript sync `clankie seat-sync` consumes Claude or Codex hook JSON on stdin. The `clankie claude`, `clankie codex`, and `clankie opencode` launchers set their selected `CLANKIE_CONVERSATION_ID`. Claude supplies `CLANKIE_SEAT_SESSION_ID`; Codex’s trusted hook supplies it from the captured native binding. OpenCode sends transcripts through its per-launch bridge. Unlaunched plugin use and hooks for another session are ignored. The plugin invokes sync at session start/end, prompt submission, stop/failure and before compaction. Claude and Codex also upload on asynchronous `PostToolUse` hooks, throttled to one attempt per two seconds. Progress appears as tools finish; a long tool or a text-only stretch waits for the next hook. Codex requires native hook review again when its hook definitions change. The CLI reads the matching native transcript locally, redacts display records, and posts bounded message/tool batches to `/v1/seat/transcript` with the operator credential. No host file path is read by the service. The session is pinned to its conversation; retries and resume retain the same native entry identities. The next hook retries retained records after a transport failure. The final page carries `responding` at prompt submission or tool progress and `waiting` at session start/end or stop/failure; compaction leaves activity unchanged. Empty transcripts still carry lifecycle activity. These are display signals, not service-run completion or ownership. Reset retires that conversation's native sessions; launch a new seat afterward so old history cannot repopulate the cleared conversation. Sync failures never instruct the harness to continue or block a stop. The current 9,000-entry display tail is the replay bound. Image files use `clankie file publish` separately. A service restart interrupts an unanswered seat run with `failed` and `reasonCode: service_restarted`; it does not prove that the independent native seat stopped. Escalation reply waiters do not survive restart. A later `reply` returns an explicit target-gone error instead of claiming the answer was sent. An uncertain delivery remains fenced until its exact receipt is reconciled; restarting or reconnecting never replays the request. ## Services | Name on the CLI | Process | Aliases | | --------------- | ------------------------------------ | ---------------------------------------------------------------------- | | `all` | every service, in order | (default) | | `clankie` | captain + HTTP API on :4310 | `captain`, `captain-eve`, `eve`, `control-plane`, `controlplane`, `cp` | | `relay` | remote operator relay | `app-relay`, `phone` | | `discord` | official bot | `discord-bridge`, `bridge` | | `user-session` | personal-lab Discord body | `discord-user-session`, `lab` | | `activity` | watch-me-play surface | `watch`, `viewer` | | `tunnel` | cloudflared in front of the activity | `cloudflared` | | `awake` | owner's keep-awake (`caffeinate -s`) | `keep-awake`, `caffeinate` | Unknown names fail closed without signalling any process. ## Environment | Variable | Role | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `CLANKIE_CONTROL_PLANE_URL` | Service origin for probes, pairing, devices, play. Default `http://127.0.0.1:4310`. | | `CLANKIE_CAPTAIN_URL` | Compatibility alias for the same origin. | | `CLANKIE_OPERATOR_TOKEN` | Test/CI override for the operator bearer. An env/store mismatch makes `health` fail. Remove it before rotating. | | `CLANKIE_LAUNCHER_PATH` | Path used to spawn a deferred self-restart; `autostart enable` records it as the login agent's program. | | `XDG_CONFIG_HOME` | Config root. Model/provider config is `$XDG_CONFIG_HOME/clankie/clankie.json` (default `~/.config/clankie/clankie.json`). | | `XDG_STATE_HOME` | Process records and logs (`$XDG_STATE_HOME/clankie/`). | ## Console-only, not missing These carry secrets, external consent, or live session chrome, so entry stays interactive in the console. The capability exists — only the flag does not: - `/setup` — first-run sign-in and model choice → phone sign-in and `/pair` → optional `/connect` → first agent's folder/task and a reviewed request through Clankie's normal conversation. An active phone with chat access is required to mark pairing complete; a minted QR is only an offer. `/setup rooms` opens the other settings checklist. Escape or `/cancel` stops the current step; re-entry reads live device/roster state. `doctor --json`'s `captain` field is its headless model readiness - `/auth` and `/connect` secret entry — provider keys, OAuth, Linear (MCP token and webhook signing secret), and email - `/discord` secret entry and lab-user ToS opt-in — Discord tokens never become flags - `/voice` — realtime/TTS provider and brokered credentials - `/btw`, `/board`, `/jump`, `/conversation`, `/goal`, `/layout` — live console state There is no `clankie start`, `clankie up`, or `clankie auth`. Local model servers are not supervised. ### Where a provider key lives `/auth anthropic` opens API-key entry directly. Clankie no longer offers Claude subscription login or refresh; replace any legacy token with an Anthropic API key (or remove it through `/auth`). Doctor/setup do not count that legacy OAuth entry as usable authentication. Native Claude Code and Codex retain their own login. Hosted Clankie/Pi refuses ChatGPT subscription login and token forwarding pending OpenAI approval, before opening a browser or requesting a device code. Use a provider API key or included model usage. Local/self-hosted ChatGPT login remains available. Approval and waitlist submission are owner actions; see [ADR 0052](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0052-subscription-precedence-over-metered-api-key.md). One credential store backs both surfaces: `/auth <providerId>` writes it, and every service this CLI starts reads it. Provider config in `clankie.json` never holds a secret — the schema rejects secret-shaped keys — so an endpoint that wants a bearer gets it from the store, keyed by the same provider id as the model ref. A local endpoint that checks a key therefore needs two things, not one: ```sh clankie model add-local --id ds4 --base-url http://127.0.0.1:8000 --models <id> # then, in the console: /auth ds4 ``` `--models` is required there because the add-local probe is unauthenticated: a keyed endpoint answers its `GET {baseURL}/models` with 401 and the probe reports `Could not list models`. A genuinely keyless local runtime needs no `/auth` step — it is served a placeholder bearer it ignores. ### Pointing the captain at a local model Start to finish, with the runtime already serving: ```sh curl -s -H "authorization: Bearer $KEY" http://127.0.0.1:8000/v1/models # the real ids clankie model add-local --id ds4 --base-url http://127.0.0.1:8000 --models <id> # console: /auth ds4 (only if the endpoint checks a key) clankie model set ds4/<id> clankie restart captain ``` Model ids come from the endpoint, never from a guess: a runtime that serves from a directory names the model after that directory, so `ds4/deepseek-v4-flash` is a 404 where the served id is `DeepSeek-V4-Flash-0731-2.4bit-mixed`. Two things decide whether a local captain is usable, and neither shows up in `clankie doctor`: - **Decode speed.** A large model whose weights get paged out runs one or two tokens a second regardless of the hardware's rating. Check `sysctl vm.swapusage` on the host before blaming the captain. - **Prefill.** Every turn re-sends the system prompt and the tool schemas, so time-to-first-token at 8k-32k context is paid on each one, not once. A model that chats acceptably can still be unusable in a tool loop. Revert with `clankie model set <provider>/<model>` and another `clankie restart captain`; nothing about the switch is one-way. ## Related - [Operator console](https://github.com/Volpestyle/clankie/blob/main/apps/tui/README.md) — TUI, workspaces, slash commands - [Distribution](https://github.com/Volpestyle/clankie/blob/main/docs/distribution.md) — install layout and `clankie doctor` on a release - [Credentials](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md) — bot vs user vs internal tokens - [Architecture canonical homes](https://github.com/Volpestyle/clankie/blob/main/docs/architecture.md#canonical-homes) Managed pi workers use OpenRouter `moonshotai/kimi-k3`; the runtime harness selector chooses that explicit model instead of inheriting pi’s personal default. ## Local and hosted connection modes A fresh `clankie` launch offers **This Mac** or a hosted connection before starting a service. Existing installations remain local. Hosted startup never starts a local body. Its footer identifies **Hosted · <machine>** and reports Connected, Asleep/Waking, Sign-in expired, Access revoked, or Unavailable. ```sh clankie login clankie login --email you@example.com --code-stdin clankie whoami clankie conversations list clankie --chat global-default clankie logout ``` `login` is the one account sign-in (email code). If the account has a hosted Clankie it lists the account's hosted machines and pairs a revocable device; otherwise it signs **this Mac** in for remote access, which is also how a signed-out Mac signs back in (restart the captain afterwards; the JSON output says so). `connect hosted` asks for a hosted body specifically, so an account without one gets an explanation instead of a doorway. `logout` only forgets the hosted client; on a Mac that is not a hosted client it changes nothing and points at `clankie remote-access off`. The current fleet model permits one machine per account, so it is selected automatically. The list contract supports a picker if that changes; headless selection uses `--machine ID`. `--url ORIGIN` selects a compatible deployment (HTTPS, or loopback HTTP for development). Account credentials exist only during login; the broker retains the device session, encryption material and a separate device wake key. `whoami` shows the selected machine and checks access without revealing secrets. `connect hosted` and `disconnect` remain aliases for `login` and `logout`. `/connection`, `/settings` and local `/setup` expose the mode choice. Reopen the console after changing modes. Hosted `/conversation [ID]` selects a retained thread; `/reconnect` retries the saved selection. `/persona`, `/model set provider/model`, `/keys [PROVIDER]` and `/connect github|linear` change the hosted body. `/fleet` and `/terminal` show its fleet and terminal catalog. Headless `conversations`, `send`, `model`, `persona`, `accounts`, `fleet`, `terminal` and `keys` address the selected machine. `keys set PROVIDER --key-stdin` accepts a secret only on stdin. `fleet spawn|move|close --json-stdin` and `terminal tail|control|input --json-stdin` accept the corresponding protocol request fields from stdin; the verb fixes the operation. Terminal input keeps the existing exclusive-control lease validation. Unknown operations fail closed. The single hosted-device authority policy allows chat, fleet, terminal, model, keys, persona and connections. **Restart, reset and deprovision require the account page/control plane**, including when attempted through the old device relay. The API policy does not inspect shell commands typed under terminal control. Local lifecycle, autostart, sockets, native harness commands (`claude[N]`, `codex[N]`, `opencode`), `mcp` and shell escapes refuse in hosted mode. Closing the client leaves accepted work running. `logout` forgets this Mac's device credential and wake key and selects This Mac for the next launch; account-side device revocation invalidates a lost Mac. Expired or revoked access requires `login`. After pairing, an asleep host is woken using the app's device-signed P-256 challenge protocol; status shows the wait and wake failures. First login can wake the selected machine using the signed-in account. A legacy body without wake registration needs account-page wake and a fresh login. There is no local fallback. `/remote-access` means **Remote access for this Mac**, for self-hosted use only; `/gateway` remains its alias. When the Mac is signed out, its menu opens on **Sign this Mac back in** (email + one-time code) and its status text starts with that step; the same menu reads **Sign this Mac in to enable remote access** before first setup. Headless: `clankie remote-access [status]`, `on [--email EMAIL --code-stdin]` (the same sign-in as `login`, pinned to this Mac), `off`, `rotate-key` and `direct --control-plane-url URL --relay-url URL`; `gateway` and the older `disable` / `rotate-encryption-key` spellings still work. `clankie status` also carries what `whoami` reports (`connection`), the live `doorway`, and one `nextStep` line for phone access; `clankie doctor` carries the same `nextStep`. `whoami` keeps working. In the console, `/login` is the account sign-in (it skips the `/remote-access` menu; the model-provider `/auth` no longer answers to `/login`), `/pair` adds a sign-in note when a code lacks the gateway route because remote access is signed out, and `/devices` lists or revokes paired devices and opens an empty list on `/pair`. `clankie pair` never prints a code the doorway cannot carry. When the service refuses an offer because remote access is signed out, it exits 1 with "No pairing code was made" and names `/remote-access` → **Sign this Mac back in** as the fix (JSON `status: "unavailable"`, same text in `error`). It detects an existing hosted tenant and offers connection instead of creating another doorway. Matching fleet, body and relay deployments plus a real Mac/phone rehearsal remain separate from source verification. ## Bundled working skills Clankie's seats include his process and leadership skill bundle. Local hired Claude, Codex and Pi workers receive it through their launch configuration; a plain harness keeps the owner's independent global selection. See [bundled working skills](https://github.com/Volpestyle/clankie/blob/main/docs/bundled-skills.md) for the inventory, source revisions, per-harness mechanisms, deployment gate and remote limitations. ### Current agent assignment From a local Herdr agent pane, `clankie work-on "Objective"` states what that native session is working on. Add `--repo REPO_ID --issue ISSUE_ID` to link an existing Work item, or run `clankie work-on clear` to remove the assignment. Repo IDs come from `clankie work repos`. The authenticated `state_work` dispatch resolves the caller's pane in its source Herdr session; devices cannot submit it. Assignments persist across service restarts and follow the same native session between panes. They do not change tracker status or ownership. The fleet also projects local Codex goals from its native goal store and Clankie's conversation goals. Native goal state remains separate from turn activity. ### OpenCode operator seat `clankie opencode --conversation ID --dry-run` reviews the native launch, installed version, skill selection and required owner steps. Remove `--dry-run` to launch; `--resume` uses the exact recorded session and chat. Without `--conversation ID`, each fresh launch creates a separate workspace chat; dry-run creates none. `/opencode` in the console reviews the same plan. Installation, per-launch settings, removal, native delivery semantics and current verification limits are in the [OpenCode seat guide](https://github.com/Volpestyle/clankie/blob/main/integrations/opencode-plugin/README.md). ### Grok Build worker and operator seats `clankie grok --dry-run` or `clankie seat --harness grok --dry-run` reviews the native launch, profile, selected skills and conversation. Remove `--dry-run` to open the interactive Grok Build TUI. This adapter requires macOS and verified Grok Build 1.0.46 on PATH, an existing Grok sign-in, and Clankie's operator credential. It uses the current `GROK_HOME` (otherwise `~/.grok`); it never changes accounts or signs in. `/grok` reviews the same plan in the console. Each fresh launch creates a separate workspace chat. `--conversation ID` selects an existing conversation; `--resume` retains its exact native session, profile and chat after a confirmed exit. An uncertain prior exit or delivery refuses another launch until the original TUI and receipts are inspected. `--plugin-dir` and numbered account commands are unsupported. Persona and the memory card come from the selected service conversation; selected skills are provided as paths to their `SKILL.md` files. Native transcripts and service wakes follow that conversation through the existing operator API/outbox. `hire_agent` with `harness: "grok"` creates a visible worker in its own repo tab. The brief and `message_seat` follow-ups use leader IPC/ACP on that exact TUI session. Explicit model/effort choices must match the native registry; unavailable choices refuse. The worker gets the fleet meta tools and `message_clankie`. Queue consumption is a delivery receipt, not a completed reply. A saved Grok transcript without its original live controller cannot be resumed as a hire. Pipeline splitting and control adoption after a service restart are unsupported. Native permissions remain owner decisions. Grok leader mode ignores CLI `--allow`/`--deny`; this launcher does not claim they isolate tools. An observed enabled direct Linear MCP endpoint refuses before the worker brief: disable it in that Grok profile and start a fresh seat. A missing native catalog, changed session or process, unavailable sign-in, or uncertain acknowledgment retains the original evidence and names the refusal; no headless or terminal-input fallback runs. See [ADR 0224](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0224-grok-build-shares-the-visible-native-session.md). ### Delivery receipt stages Delivery results add `deliveryStage` while retaining native outcome, queue state, and detail. `stored` means service retention; `delivered` means the bridge has acknowledged it; `consumed` means native queue/turn acceptance; `responded` means a correlated reply or turn outcome, including silence. A native queue is consumed even while waiting for the current turn or goal. None of these receipts proves the model read the message or that requested work succeeded. `unavailable`, `uncertain`, `expired`, and `rejected` are distinct stops. Every retry of an uncertain delivery is blocked until its original receipt is reconciled, including explicit retries and retries after service or launcher restart. An exact late bridge acknowledgment or original-session native transcript receipt can reconcile it without dispatching a replacement. Missing or corrupt evidence remains blocked; changing channels is not a repair. ### `conversations head OWNER HEAD|none` Set or remove an explicit escalation head using operator authentication: ```sh clankie conversations head ROOM_OR_CONVERSATION_ID HEAD_CONVERSATION_ID clankie conversations head ROOM_OR_CONVERSATION_ID none ``` Use the existing conversation listing to select exact IDs. The owner can be a host-created room; the head must be a writable global/workspace conversation. The TUI command `/conversation head HEAD|none` applies to the selected thread. `POST /v1/conversation-heads` accepts only `{conversationId,headConversationId}`, where `null` removes the designation. Observe and captain credentials cannot change it. Self references and cycles are refused; there is no default head. Explicit asks and asynchronous results try the owner first and may use its head only after a definite refusal before acceptance. Revoked source authority or presence, a changed designation, and uncertain dispatch cannot trigger fallback. This forwards the explicit request/result only and grants no room privileges. Discord room inspection, private next-turn guidance, delivery health and exact-stay voice controls are documented in [Discord rooms](https://github.com/Volpestyle/clankie/blob/main/docs/discord-rooms.md). Use `clankie discord rooms`, `clankie discord guide CONVERSATION_ID TEXT`, and `clankie discord call`. ### Project workspace removal `clankie project remove-workspace NAME --workspace PATH` removes the exact owner registration, including a folder that no longer exists. It preserves project policy, grants and assignments and refuses to orphan a tracker binding. Both `project add` and `project remove-workspace` accept `--machine ID --platform windows|posix` for an explicit remote registration. Remote paths are normalized absolute paths on that machine; registration does not replace live native process/canonical filesystem proof or grant any tools. The owner API exposes `GET /v1/operator/projects` and revision-guarded `POST /v1/operator/projects/remove-workspace` (`projectId`, `workspaceId`, `expectedRevision`). ### Linking native fleet harnesses `clankie harness install [--refresh-linked | --codex-source-setup /absolute/script] [--project PROJECT] [--approve]` reads the current effective `machineSetup` policy and existing local link through the authenticated service. Under `lead`, it runs without a terminal or another approval on an already-linked machine. New setup is limited to the caller's existing `CLAUDE_CONFIG_DIR` (otherwise `~/.claude`) and `CODEX_HOME` (otherwise `~/.codex`); sibling Claude account profiles are skipped. Under `owner`, use an interactive terminal for per-profile consent or supply the owner's explicit `--approve` and confirm in an interactive terminal for the selected profiles. Headless `--approve` is refused. Declining consent changes no registration. `clankie harness install --refresh-linked [--project PROJECT] [--approve]` maintains existing links and returns JSON receipts with a failing exit code for incomplete installations. It includes remembered custom profiles and registered Codex account homes; enabled fleet aliases sharing one SSH destination refresh once. The CLI rechecks current project policy and target linkage before each profile or remote destination. Under `owner`, refresh requires the owner's interactive confirmation with `--approve`; under `lead`, automatic refresh requires an already-linked target. Unlinked profiles and existing Claude channel policy stay unchanged. An explicitly disabled Codex plugin reports `declined`: its native installer would enable it, so refreshing that profile requires a reviewed install. Checkout and release installers maintain existing links as part of the owner-authorized update, then offer new linking interactively. Native clients reporting an older worker version receive a durable, display-only `clankie-plugin` pane flag with a save/restart/resume prompt, once per native occupant/process and expected version. No harness or pane is restarted. The flag clears when a current native client connects. A release installer announces its worker version to an already-running service; unavailable notification is reported as `notices.state: deferred` until the updated service connects. The operator API exposes `POST /v1/harness-refresh` for the same maintenance, with a required `workingDirectory`, optional matching `projectId`, and `ownerApproved: true` for caller-reported owner approval. Its receipt marks this as `ownerApproval: "claimed"`; the server cannot verify human confirmation. It checks current policy, workspace membership, target linkage and operator authority before setup. It exposes `POST /v1/harness-plugin-version` with `{ "version": "0.6.2" }` for that announcement. Codex uses the native `clankie-worker@clankie-fleet` plugin for project-scoped bridge tools and packaged skills. It does not load the operator-seat plugin. Symlinked or marked generated Codex configuration is not rewritten. Use the owning source/setup; `--codex-source-setup /absolute/script` runs an explicitly selected source setup and checks that the link is preserved. A new source setup always needs interactive owner consent, including under `lead`; automatic setup may use the native plugin manager or an exact, already-remembered source setup. Setup completion still needs doctor verification; no hook trust record is written. Successful owner-approved source setup is remembered for that exact config source and profile, so subsequent updates reuse it. A changed config source or a legacy bridge without a recorded source setup reports `source-manager-required`; select the source-owned script through the supported install/prepare command first. `clankie herdr prepare NAME [--codex-source-setup ABSOLUTE_REMOTE_SCRIPT] [--project PROJECT] [--approve]` (also `clankie runtime prepare`) prepares the configured remote machine. Under `lead`, it needs an existing healthy link and no fresh approval; under `owner`, the owner must review the setup and confirm `--approve` in an interactive terminal. Headless self-approval is refused. A newly selected source setup script requires the same owner confirmation under either policy. The CLI reads current policy before dispatch, and the service independently checks policy, canonical source workspace, project context and operator authentication again. The CLI retains the named target revision across terminal confirmation and sends it to the service; changing its SSH target or session refuses dispatch until reviewed. An existing canonical workspace with no project uses global policy. Missing, ambiguous, mismatched or unverified worktree context refuses. It enables Claude in each discovered profile. An already enabled, installed Claude profile whose settings symlink points to another discovered unmanaged profile can update its own plugin cache without installing, enabling, or changing the shared settings. Generated sources, disabled/missing plugins, and unknown targets still require their source manager. Refusals include each profile's setup result. Native enable's exact "already enabled at user scope" result (with the observed Windows `×` or macOS `✘` marker) is successful only when a fresh read of that same regular profile confirms the plugin is enabled; other native errors still fail. Preparation uses native plugin installation for Codex, preserves managed Codex configuration, and compares installed Claude versions with the service bundle. A source-managed Codex config needs the owning setup script on that remote machine. Select it explicitly; Clankie never writes through the config symlink: ```sh clankie herdr prepare pc --codex-source-setup 'C:\Users\volpe\dotfiles\scripts\codex-worker-setup.py' ``` For owner mode or a new source setup, add `--approve` and confirm interactively. The owner API requires `workingDirectory` in the JSON body of `POST /v1/runtime-connections/NAME/prepare`, accepts a matching `projectId`, and uses `ownerApproved: true` only as a caller claim, shown as `ownerApproval: "claimed"` in the receipt; it cannot verify a human confirmation. Optional `expectedMachineRevision` binds the claim to the exact target revision returned by the context route; the CLI always sends it. Without it, the claim refers to the current named alias. It accepts the same remote script path as `codexSourceSetup`. Node (`.js`/`.mjs`), Python (`.py`, Python 3.11+ for the dotfiles setup), Windows PowerShell (`.ps1`), and directly executable source scripts run as argument vectors. The source hook receives `CODEX_HOME`, `CLANKIE_CODEX_WORKER_MARKETPLACE`, and `CLANKIE_CODEX_NATIVE_EXECUTABLE` for this approved installation. The dotfiles-owned script uses a temporary regular config and the real native plugin cache, then renders only worker selection into its own generated source, preserving unrelated settings and the runtime link. No credentials are copied. When Codex is installed, preparation fails with HTTP 409 and `fleet_prepare_failed` if its worker version, activation, bridge, identity forwarding or packaged skill is missing. The error names the missing checks and native setup result; a legacy MCP registration cannot mark preparation complete. An absent Codex executable remains an absent harness. After updating the owning source setup and completing preparation, verify `clankie doctor --machine NAME`. Do not restart unrelated panes; installation alone cannot prove a live receiver. `clankie doctor` reports local profiles and connected remote fleets, including their `linkState` and decoded failure reason. The human `/doctor` checklist also shows each observed fleet link's state and reason. `clankie doctor --machine NAME` inspects one registered fleet through `GET /v1/runtime-connections/NAME/harnesses`, `GET /v1/runtime-connections/NAME/membership`, and the connection inventory at `GET /v1/runtime-connections`. Its `linkState` remains visible even when the native harness diagnostics answer successfully. The membership card reads native process and actual cwd observations for at most 64 panes, with two concurrent inspections. It distinguishes missing proof, unsupported harnesses, pending native sessions, stale hires, and project eligibility. Changed observations are discarded. `nativeTools: "not-verified"` means the card has not tested that pane's bridge socket, catalog or reply delivery; use a native tool call to verify those. Doctor and roster additionally show `workerTools` for an authenticated served or bridge-reported catalog: `pending`, `ready`, `missing`, `stalled`, or `not-observed`, with the reason and observation time. An idle worker remains ready; an unobserved catalog is unknown. Catalog reads do not erase a tool-call timeout; a successful connected-tool call clears it. These diagnostics grant no account, project or native peer authority. Unregistered or disconnected machines never supply an arbitrary SSH target. On Windows, Codex detection resolves a unique installed native executable from PATH or the fixed npm package layouts, including the per-user npm root when SSH omits it from PATH. It does not execute command shims or dotfiles launchers and refuses ambiguous installations. Existing legacy Node bridge registrations remain visible; no generated config rewrite is required merely to inspect them. Reports separate executable presence, version, activation, bridge, hooks, and the `clankie` skill. Static files never prove a live receiver or project membership. OpenCode and Pi automatic plugin installation remains unsupported and appears explicitly in doctor; use their native setup. Existing native sessions may retain their loaded plugins; verify the actual catalog after setup. These setup commands never restart the service or existing lanes. No launcher flags, project approvals, grants or owner credentials are changed by linking a plugin. Fleet plugin membership grants connected MCP tools, not operator CLI authority. A native PC worker's fleet proof alone cannot call the operator setup/context routes or supply a service-host canonical workspace. An authorized operator on the service host can prepare its linked PC through the existing remote route; no owner credentials are copied to make a worker's local CLI an operator. ### Repository-bound linked worktree roots Enroll a dedicated directory for future linked worktrees of an already approved repository with `clankie project add NAME --worktree-root ROOT --repo APPROVED_REPO`. `--machine ID --platform windows|posix` selects a registered remote machine; the service must observe that machine's real filesystem and Git state. The CLI uses the owner API and does not treat a supplied path as filesystem evidence. The root must exist at its exact canonical path. Filesystem roots, home/repository ancestors, aliases, and namespaces overlapping another project are refused. `APPROVED_REPO` must exactly match an existing workspace of the same project and machine. Enrollment records its canonical Git common directory and grants no tools. A native agent qualifies only when its actual canonical cwd lies in a real linked worktree strictly inside that root. Clankie checks the linked Git admin directory under the enrolled repository's `worktrees` metadata, the `.git` backlink, and the repository's current `git worktree list`. A plain folder, copied `.git` pointer, foreign repository, alias, or changed repository identity does not qualify. Missing or invalid roots deny their own matches; unrelated registrations continue working. Remove an enrollment with `clankie project remove-worktree-root NAME --worktree-root ROOT` (and the same remote machine/platform flags if needed). Remove a repo's enrolled roots before removing its ordinary workspace approval. Removal does not delete worktrees, change repository files, or alter grants. The owner endpoints are `POST /v1/operator/projects/add-worktree-root` (`projectId`, `machineId`, `platform`, `path`, `repoPath`, `expectedRevision`) and `POST /v1/operator/projects/remove-worktree-root` (`projectId`, `rootId`, `expectedRevision`). Read the current revision from `GET /v1/operator/projects`. Both writes recheck owner authority and settings immediately before persistence; enrollment also re-observes the native root/repo. ### Present tense `clankie status` and the TUI `/status` include the service's `presence` snapshot when it answers. The operator `presence` read accepts a cursor and `waitMs` up to 30000 ms; callers with the current cursor wait for a projected change. Mood priority is needs_you, thinking, in_voice, playing, leading, idle. Thinking includes background Discord captain turns. `activeSeats` counts all live registered fleet seats, including those waiting between turns. Optional `nativeSubagents` counts only running native children of the live local Clankie captain session, never workers or their children. It is absent when the parent or transcript is unavailable, and zero when a readable parent has no running children. Child and parent-session changes wake the presence poll independently of fleet-seat changes. The oldest unanswered owner preference appears as `pendingOwnerItem`, with the conversation and question IDs needed to open it. `since` is a source start timestamp, or null when that source has no known start. An unreachable service has no mood; clients show that connection failure separately. The same read passes through the relay and hosted paired-device authority seam. The opt-in `includeFace: true` read adds an optional `face` field that drives the desktop pet's screen independently of his body animation. Its priority is `needs_you`, `error`, `new_message`, then `working` (thinking or leading) or `voice` (in voice). An observed working native captain or running native child also selects `working` without changing the mood. Otherwise idle and play have no override. A newly committed captain reply in an owner conversation shows `new_message` for ten seconds; a failed owner turn shows `error` for thirty seconds, or until that conversation completes a successful turn. Replayed history, worker threads, Discord rooms and forks do not raise these faces. The face and its expiry participate in the presence cursor. Reduce Motion holds a distinct static face, and an unreachable pet uses his offline art. Legacy reads omit the field. The desktop client retries without the opt-in when an older service rejects it, checking again after one minute. Desktop consumers may separately request `includeBeats: true`. Its optional `beats` array contains only an ID, `hire` or `worker_report` kind, and source timestamp; at most the latest completed hire and confirmed report accepted within ten seconds. Quiet hours suppress them. Expiry changes the opted-in cursor. Legacy requests omit the field and keep their existing cursor. A resumed seat, failed hire or uncertain report never creates a beat. The desktop client consumes IDs once and skips history and bursts; this metadata does not contain worker output or identify a private conversation. See [ADR 0220](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0220-clankie-has-one-present-tense.md). ## Computer body ```sh clankie computer request '{"conversationId":"global-default","command":{"action":"status"}}' clankie computer request '{"conversationId":"global-default","command":{"action":"acquire"}}' clankie computer request '{"conversationId":"global-default","command":{"action":"inventory","leaseId":"LEASE_UUID"}}' clankie computer request '{"conversationId":"global-default","command":{"action":"capture","leaseId":"LEASE_UUID","target":{"appId":"PID:123","windowId":"456"}}}' clankie computer request '{"conversationId":"global-default","command":{"action":"frame","leaseId":"LEASE_UUID","screenshotId":"SCREENSHOT_UUID"}}' --image-path /tmp/clankie-frame-unique.png clankie computer request '{"conversationId":"global-default","command":{"action":"input","leaseId":"LEASE_UUID","screenshotId":"SCREENSHOT_UUID","requestId":"REQUEST_UUID","inputs":[{"kind":"click","at":{"x":100,"y":80}}]}}' ``` Replace IDs with current receipts and a new UUID for each intended batch. The command uses operator authority and `POST /v1/computer`, bound to the selected runnable conversation. `frame --image-path NEW_PNG_PATH` saves a private PNG and prints its metadata; read that image before deciding inputs. It refuses an existing destination. Without the flag, `frame` returns base64 PNG media. Receipts contain no pixels. Further actions are `renew` (`leaseId`, optional `ttlMs`), `release` (`leaseId`), `revoke` (`leaseId`) and `recover` (operator stop-proof recovery). Never retry uncertain input with a new request UUID. A busy body does not transfer ownership. `revoke` quarantines the current driver even during an input batch; its next input refuses. Recovery still needs host stop proof. The service registers a macOS Peekaboo adapter. A native Windows Codex `node_repl` can explicitly attach the Windows computer host; the same command targets it through `CLANKIE_CONTROL_PLANE_URL` (loopback or an SSH forward). Every attachment defaults to read-only, even with the full `sky` client. An owner explicitly sets `allowInput: true` on the native host before acquiring a new input lease. Lease and status record `allowInput`; status also reports current `inputReady`. With that opt-in the full native client supports coordinate click, type, key, scroll and drag, one primitive per capture. Windows input requires `foreground: true` and `expect: {"field":"document_text","equals":"EXPECTED_RESULT"}` (or `tree`, `focused_element`, `selected_text`): the observed field must change to that exact value in a fresh same-window observation. Read the capture's `accessibility` fields and its actual PNG before choosing the action. Scroll also requires `at` in image pixels; its `amount` is a native logical-pixel delta. Typing requires verified focus; clear-and-type and guessed element IDs refuse. Dispatch or a changed PNG alone cannot confirm an effect. Windows input rechecks host Win32 person activity before each dispatch, requires a two-second quiet margin and refuses shell/system targets and system-switching shortcuts. Any native error retires the host. Release remains gated on James's W8 live stop evidence; fixtures do not prove native interrupt behavior. Windows setup is in [desktop control](https://github.com/Volpestyle/clankie/blob/main/docs/desktop-control.md#windows-observation-host). A host without an attached adapter returns `computer_body_unavailable`. The attached native harness supplies its own app grants and turn stops; no second reasoning loop starts. Hosted displays are not implemented. [Desktop control](https://github.com/Volpestyle/clankie/blob/main/docs/desktop-control.md#shared-computer-body) explains capture freshness, coordinate mapping and the provider's recovery limitation. ## Activity shares `clankie share [list | request JSON]` and `/share` control Activity shares on the current connection. Local commands use the owner operator bearer; hosted commands use the existing encrypted paired-device transport, including the hosted console. Requests use `POST /v1/activity/shares`. The typed API client exposes `activityShares(request)` with the same canonical request/response schemas. Output retains the service's session and launch/stop receipt. Share the current authorized game producer with `sourceId:"play"`: ```bash clankie share request '{"action":"start","sourceId":"play","guildId":"GUILD_ID","channelId":"CHANNEL_ID"}' clankie share list ``` For an image, first publish it through the existing conversation file contract; the share request selects its exact conversation and artifact ID: ```bash clankie file publish --conversation CONVERSATION_ID image.png clankie share request '{"action":"image","conversationId":"CONVERSATION_ID","artifactId":"ARTIFACT_ID","guildId":"GUILD_ID","channelId":"CHANNEL_ID"}' ``` An existing delivered GIF, MP4, WAV or MP3 uses its registered artifact source ID: `artifact:CONVERSATION_UUID:ARTIFACT_ID`. GIF is an animation; MP4 is a finite demo. PNG is also accepted through this source form. Files are checked against the stored digest; animations/demos are bounded to 32 MiB and 120 seconds. ```bash clankie share request '{"action":"start","sourceId":"artifact:CONVERSATION_UUID:ARTIFACT_ID","guildId":"GUILD_ID","channelId":"CHANNEL_ID"}' clankie share request '{"action":"switch","shareId":"SHARE_ID","generation":1,"sourceId":"play"}' clankie share request '{"action":"switch","shareId":"SHARE_ID","generation":2,"conversationId":"CONVERSATION_ID","artifactId":"OTHER_ARTIFACT_ID"}' clankie share request '{"action":"stop","shareId":"SHARE_ID","generation":3}' ``` IDs above are placeholders; guild/channel IDs must be Discord snowflakes. A switch chooses exactly one registered source or conversation/artifact pair. No source URL, filesystem path or capture permission is accepted. The service assigns tenant/installation scope and checks destination authority. Start/image accept optional `ttlMs` (default 30 minutes, at most two hours). Start and switch return `{session,receipt?}`, list returns `{sessions}`, and stop returns `{stopped:true,receipt?}`. Use the returned generation on later controls. A hosted launch/stop receipt reports `outcome:"confirmed"|"refused"|"uncertain"`, a request-bound `receiptId`, its exact session, and an invite URL when confirmed. An unavailable or lost external reply stays uncertain. Commands never replay an uncertain effect; use list and the receipt to reconcile before deciding another action. Self-hosted sessions without a configured official launch adapter can stream media but do not claim a Discord launch receipt. The official hosted viewer performs Discord's SDK ready/authorize/authenticate handshake and obtains scoped admission from the server. SDK query parameters and URL fragments do not choose a tenant or room. Admission/configuration failure opens no media socket; there is no anonymous legacy fallback. Audience access is revalidated during viewing and revocation is terminal. Hosted customers do not configure applications, bot credentials or tunnels. For local self-hosted delegated viewing, `action:"grant"` returns `{grant,expiresAt}`. On the configured viewer origin use `/#share=SHARE_ID&grant=GRANT`; keep the read-only grant in the fragment and never put an operator/producer bearer in a viewer URL. Grants are delegated access, not proof of Discord membership, and expire for existing viewers too after at most five minutes. Switch clears media/text/audio, advances generation and revokes old grants for later joins; current viewers follow until their original grant expires. Stop, share expiry or producer loss is terminal. Private scoped media never appears on the self-hosted public legacy stream. See [Activity sharing](https://github.com/Volpestyle/clankie/blob/main/docs/activity.md) and the [wire reference](https://github.com/Volpestyle/clankie/blob/main/apps/discord-activity/README.md#scoped-general-media-core). --- # HTTP API Local `apps/clankie` service on `http://127.0.0.1:4310`. There is no HTML index: `/` is 404 and `GET /health` is the public probe. Most other routes take an `Authorization: Bearer <token>` header. Pairing redeem/complete use their one-time offer secrets instead. Persistent tokens live in the credential broker (Keychain service `bot.clankie.credentials`): | Principal | Keychain account | Typical use | | --- | --- | --- | | Operator | `clankie_operator` | TUI operator reads/writes, pairing, devices, play stop | | Captain | `clankie_captain` (also `CLANKIE_CAPTAIN_TOKEN`) | `/operator/v1/dispatch`, `/captain/v1/lanes` | | Discord text | `clankie_discord_bridge` | Discord channel turns, presence, person memory | | Discord voice | `clankie_discord_voice_bridge` | Voice briefing; voice-lane channel turns | | Discord user text | `clankie_discord_user_bridge` | Lab-user channel turns and presence | | Discord user voice | `clankie_discord_user_voice_bridge` | Lab-user voice-lane turns | | Device | minted at pairing complete | `/v1/devices/self*` | Discord-lane routes 403 if the bearer is the console captain token (steer source `api`). Import this file in Yaak (Settings → Import/Export) or run `apps/clankie/scripts/setup-yaak.py`. Base URL `http://127.0.0.1:4310` · version 0.2.0 · the raw document is [openapi.yaml](https://docs.clankie.bot/api/openapi.yaml). > This is the service contract on Clankie’s host. The [public network surface](https://docs.clankie.bot/network/) exposes a bounded subset through `api.clankie.bot`; the host still decides every device grant. Hosted account and billing endpoints are separate contracts. ## Bearers | Bearer | Where it lives | | --- | --- | | worker | Individual short-lived grant issued by POST /v1/worker-grants/; never an operator or provider credential | | operator | Keychain `clankie_operator` | | captain | Keychain `clankie_captain` / `CLANKIE_CAPTAIN_TOKEN` | | discord text | Keychain `clankie_discord_bridge` or `clankie_discord_user_bridge` | | discord voice | Keychain `clankie_discord_voice_bridge` or `clankie_discord_user_voice_bridge` | | device | Device session token from pairing complete | ## Routes | Method | Route | Summary | Bearer | | --- | --- | --- | --- | | POST | [`/v1/activity/shares`](#post-v1-activity-shares-control-owner-authorized-activity-media-shares) | Control owner-authorized Activity media shares | operator | | POST | [`/v1/integrate`](#post-v1-integrate-compose-gate-and-land-an-ordered-approved-commit-batch) | Compose, gate and land an ordered approved commit batch | operator | | POST | [`/v1/activity/viewer`](#post-v1-activity-viewer-read-hosted-activity-media-with-a-live-audience-authorization) | Read hosted Activity media with a live audience authorization | none | | GET | [`/v1/runtime-update`](#get-v1-runtime-update-read-runtime-update-status-and-current-deploy-holds) | Read runtime-update status and current deploy holds | operator | | POST | [`/v1/runtime-update`](#post-v1-runtime-update-admit-a-local-pinned-runtime-deployment-after-the-deploy-hold-check) | Admit a local pinned-runtime deployment after the deploy-hold check | operator | | GET | [`/v1/discord/settings`](#get-v1-discord-settings-read-the-server-role-fleet-and-tracking-setup-with-advanced-settings) | Read the server role, fleet and tracking setup with Advanced settings | operator | | POST | [`/v1/discord/settings`](#post-v1-discord-settings-save-revision-fenced-non-secret-discord-settings) | Save revision-fenced non-secret Discord settings | operator | | POST | [`/v1/discord/setup/test-post`](#post-v1-discord-setup-test-post-explicitly-send-one-owner-requested-setup-message-to-a-text-room) | Explicitly send one owner-requested setup message to a text room | operator | | POST | [`/v1/discord/ingress`](#post-v1-discord-ingress-admit-an-encrypted-discord-turn-or-scoped-voice-callback) | Admit an encrypted Discord turn or scoped voice callback | none | | GET | [`/v1/composer/transcription/status`](#get-v1-composer-transcription-status-read-local-or-managed-availability-and-included-allowance) | Read local or managed availability and included allowance | device | | POST | [`/v1/composer/transcription/begin`](#post-v1-composer-transcription-begin-begin-one-bounded-composer-recording) | Begin one bounded composer recording | device | | POST | [`/v1/composer/transcription/chunk`](#post-v1-composer-transcription-chunk-append-one-recording-chunk-at-an-exact-byte-offset) | Append one recording chunk at an exact byte offset | device | | POST | [`/v1/composer/transcription/commit`](#post-v1-composer-transcription-commit-transcribe-a-completed-recording-once) | Transcribe a completed recording once | device | | POST | [`/v1/composer/transcription/cancel`](#post-v1-composer-transcription-cancel-discard-a-recording-and-prevent-late-draft-delivery) | Discard a recording and prevent late draft delivery | device | | POST | [`/v1/composer/transcription/receipt`](#post-v1-composer-transcription-receipt-reconcile-the-same-recording-without-repeating-provider-dispatch) | Reconcile the same recording without repeating provider dispatch | device | | GET | [`/v1/model-keys`](#get-v1-model-keys-list-supported-providers-models-stored-key-status-and-captain-selection) | List supported providers, models, stored-key status and captain selection | operator or device | | GET | [`/v1/model-keys/subscriptions`](#get-v1-model-keys-subscriptions-list-providers-signed-in-through-an-account-oauth-or-subscription-instead-of-an-api-key) | List providers signed in through an account (OAuth or subscription) instead of an API key | operator or device | | GET | [`/v1/model-keys/subscriptions/methods`](#get-v1-model-keys-subscriptions-methods-list-currently-supported-provider-subscription-sign-in-methods) | List currently supported provider subscription sign-in methods | operator or device | | POST | [`/v1/model-keys/subscriptions/start`](#post-v1-model-keys-subscriptions-start-start-a-provider-sign-in-for-the-authenticated-initiating-principal) | Start a provider sign-in for the authenticated initiating principal | operator or device | | POST | [`/v1/model-keys/subscriptions/status`](#post-v1-model-keys-subscriptions-status-read-the-initiating-principal-s-sign-in-interaction) | Read the initiating principal's sign-in interaction | operator or device | | POST | [`/v1/model-keys/subscriptions/cancel`](#post-v1-model-keys-subscriptions-cancel-cancel-a-pending-sign-in-an-admitted-credential-write-finishes-as-committing) | Cancel a pending sign-in; an admitted credential write finishes as committing | operator or device | | GET | [`/v1/model-keys/options`](#get-v1-model-keys-options-list-providers-usable-without-key-entry-and-the-running-model-s-reasoning-effort) | List providers usable without key entry and the running model's reasoning effort | operator or device | | POST | [`/v1/model-keys/effort`](#post-v1-model-keys-effort-set-or-clear-the-running-model-s-reasoning-effort-for-the-next-turn) | Set or clear the running model's reasoning effort for the next turn | operator or device | | POST | [`/v1/model-keys/set`](#post-v1-model-keys-set-store-or-replace-a-write-only-provider-api-key) | Store or replace a write-only provider API key | operator or device | | POST | [`/v1/model-keys/validate`](#post-v1-model-keys-validate-validate-the-stored-key-with-a-bounded-provider-call) | Validate the stored key with a bounded provider call | operator or device | | POST | [`/v1/model-keys/select`](#post-v1-model-keys-select-select-the-captain-model-for-the-next-turn) | Select the captain model for the next turn | operator or device | | POST | [`/v1/model-keys/remove`](#post-v1-model-keys-remove-remove-a-stored-api-key-without-removing-oauth-or-environment-fallback) | Remove a stored API key without removing OAuth or environment fallback | operator or device | | GET | [`/v1/accounts/codex`](#get-v1-accounts-codex-list-registered-local-codex-homes-and-observed-headroom) | List registered local Codex homes and observed headroom | operator or device | | POST | [`/v1/accounts/codex`](#post-v1-accounts-codex-register-or-remove-an-owner-s-local-codex-home) | Register or remove an owner's local Codex home | operator or device | | GET | [`/v1/accounts`](#get-v1-accounts-read-the-body-owned-github-linear-and-google-account-catalog) | Read the body-owned GitHub, Linear and Google account catalog | operator or device | | POST | [`/v1/accounts/github/start`](#post-v1-accounts-github-start-start-a-github-device-flow-return-the-user-code-and-verification-url) | Start a GitHub device flow; return the user code and verification URL | operator or device | | POST | [`/v1/accounts/github/poll`](#post-v1-accounts-github-poll-poll-a-github-device-flow-returns-pending-connected-or-a-closed-error) | Poll a GitHub device flow; returns pending, connected or a closed error | operator or device | | POST | [`/v1/accounts/linear/start`](#post-v1-accounts-linear-start-start-registered-linear-app-oauth-with-s256-pkce-return-the-authorize-url) | Start registered Linear app OAuth with S256 PKCE; return the authorize URL | operator or device | | POST | [`/v1/accounts/linear/complete`](#post-v1-accounts-linear-complete-hand-back-linear-s-redirect-code-the-body-exchanges-it-with-its-verifier) | Hand back Linear's redirect code; the body exchanges it with its verifier | operator or device | | POST | [`/v1/accounts/linear/app`](#post-v1-accounts-linear-app-connect-a-workspace-owned-linear-app-for-worker-names-and-portraits) | Connect a workspace-owned Linear app for worker names and portraits | operator or device | | POST | [`/v1/accounts/google/start`](#post-v1-accounts-google-start-start-body-owned-google-consent-with-state-and-s256-pkce) | Start body-owned Google consent with state and S256 PKCE | operator or device | | POST | [`/v1/accounts/google/complete`](#post-v1-accounts-google-complete-exchange-the-one-time-google-code-on-the-tenant-body) | Exchange the one-time Google code on the tenant body | operator or device | | POST | [`/v1/accounts/google/check`](#post-v1-accounts-google-check-refresh-and-verify-the-selected-google-account-s-authorized-access) | Refresh and verify the selected Google account's authorized access | operator or device | | POST | [`/v1/accounts/disconnect`](#post-v1-accounts-disconnect-disconnect-locally-and-report-actual-provider-revocation) | Disconnect locally and report actual provider revocation | operator or device | | GET | [`/v1/connections`](#get-v1-connections-inspect-execution-runtimes-and-verified-linear-identity) | Inspect execution runtimes and verified Linear identity | operator | | GET | [`/v1/runtime-connections`](#get-v1-runtime-connections-inspect-the-default-fleet-and-named-execution-connections) | Inspect the default fleet and named execution connections | operator | | POST | [`/v1/runtime-connections`](#post-v1-runtime-connections-connect-a-herdr-runtime-or-configure-owner-execution-policy) | Connect a Herdr runtime or configure owner execution policy | operator | | DELETE | [`/v1/runtime-connections/{id}`](#delete-v1-runtime-connections-id-disable-a-named-execution-connection-while-preserving-workers-and-route-identity) | Disable a named execution connection while preserving workers and route identity | operator | | POST | [`/v1/work`](#post-v1-work-read-and-write-work-items-in-the-repo-s-own-tracking-convention) | Read and write work items in the repo's own tracking convention | operator | | GET | [`/v1/linear/target`](#get-v1-linear-target-inspect-the-ordinary-chat-selected-for-linear-activity) | Inspect the ordinary chat selected for Linear activity | operator | | PUT | [`/v1/linear/target`](#put-v1-linear-target-select-an-existing-ordinary-global-chat-without-restarting) | Select an existing ordinary global chat without restarting | operator | | GET | [`/v1/linear/request-budget`](#get-v1-linear-request-budget-read-rolling-provider-request-usage-and-background-read-throttling) | Read rolling provider request usage and background read throttling | operator | | GET | [`/v1/linear/wake`](#get-v1-linear-wake-inspect-actor-and-activity-type-wake-rules) | Inspect actor and activity type wake rules | operator | | PUT | [`/v1/linear/wake`](#put-v1-linear-wake-replace-wake-rules-without-restarting) | Replace wake rules without restarting | operator | | GET | [`/v1/linear/follow`](#get-v1-linear-follow-inspect-linear-activity-following-and-required-webhook-readiness) | Inspect Linear activity following and required webhook readiness | operator | | PUT | [`/v1/linear/follow`](#put-v1-linear-follow-enable-or-disable-rule-matched-turns-from-signed-linear-webhooks) | Enable or disable rule-matched turns from signed Linear webhooks | operator | | GET | [`/v1/worker-grants/`](#get-v1-worker-grants-list-worker-grant-records-without-bearer-tokens) | List worker grant records without bearer tokens | operator | | POST | [`/v1/worker-grants/`](#post-v1-worker-grants-issue-a-scoped-grant-for-one-verified-connected-account) | Issue a scoped grant for one verified connected account | operator | | DELETE | [`/v1/worker-grants/{id}`](#delete-v1-worker-grants-id-durably-revoke-one-worker-grant-and-close-its-sessions) | Durably revoke one worker grant and close its sessions | operator | | GET | [`/v1/worker-grants/linear/account`](#get-v1-worker-grants-linear-account-inspect-disconnected-unverified-or-verified-linear-account-identity) | Inspect disconnected, unverified or verified Linear account identity | operator | | POST | [`/v1/worker-grants/linear/account`](#post-v1-worker-grants-linear-account-verify-the-stored-linear-account-s-provider-user-and-workspace) | Verify the stored Linear account's provider user and workspace | operator | | GET | [`/v1/worker-mcp`](#get-v1-worker-mcp-mcp-session-stream) | MCP session stream | worker | | POST | [`/v1/worker-mcp`](#post-v1-worker-mcp-initialize-a-worker-mcp-session-or-list-call-its-granted-tools) | Initialize a worker MCP session or list/call its granted tools | worker | | DELETE | [`/v1/worker-mcp`](#delete-v1-worker-mcp-close-an-authenticated-worker-mcp-session) | Close an authenticated worker MCP session | worker | | POST | [`/v1/hosted/operator`](#post-v1-hosted-operator-dispatch-as-an-account-paired-hosted-operator-device) | Dispatch as an account-paired hosted operator device | device | | POST | [`/v1/runtime-connections/{id}/prepare`](#post-v1-runtime-connections-id-prepare-prepare-clankie-s-own-harness-setup-on-an-existing-machine) | Prepare Clankie's own harness setup on an existing machine | operator | | GET | [`/v1/operator/fleet-settings`](#get-v1-operator-fleet-settings-read-fleet-size-model-mode-and-owner-working-preferences) | Read fleet size, model mode and owner working preferences | operator or device | | POST | [`/v1/operator/fleet-settings`](#post-v1-operator-fleet-settings-update-fleet-budget-and-working-preferences-with-revision-fencing) | Update fleet budget and working preferences with revision fencing | operator or device | | GET | [`/v1/operator/fleet-settings/context`](#get-v1-operator-fleet-settings-context-resolve-current-project-policy-and-already-linked-setup-target) | Resolve current project policy and already-linked setup target | operator or device | | POST | [`/v1/operator/projects/update`](#post-v1-operator-projects-update-update-project-policy-while-preserving-independent-inheritance) | Update project policy while preserving independent inheritance | operator or device | | GET | [`/v1/operator/projects`](#get-v1-operator-projects-read-owner-project-settings-and-their-current-revision) | Read owner project settings and their current revision | operator or device | | POST | [`/v1/operator/projects/create`](#post-v1-operator-projects-create-create-a-reviewed-project-with-one-canonical-local-workspace) | Create a reviewed project with one canonical local workspace | operator | | POST | [`/v1/operator/projects/remove-workspace`](#post-v1-operator-projects-remove-workspace-remove-one-exact-owner-approved-workspace-registration) | Remove one exact owner-approved workspace registration | operator | | POST | [`/v1/harness-refresh`](#post-v1-harness-refresh-refresh-existing-native-worker-plugin-links-locally-and-on-enabled-fleet-machines) | Refresh existing native worker plugin links locally and on enabled fleet machines | operator | | POST | [`/v1/harness-plugin-version`](#post-v1-harness-plugin-version-tell-a-running-service-which-worker-version-the-installer-shipped) | Tell a running service which worker version the installer shipped | operator | | GET | [`/v1/runtime-connections/{id}/harnesses`](#get-v1-runtime-connections-id-harnesses-inspect-native-harness-registration-on-one-registered-remote-fleet) | Inspect native harness registration on one registered remote fleet | operator | | GET | [`/v1/runtime-connections/{id}/membership`](#get-v1-runtime-connections-id-membership-inspect-host-observed-project-eligibility-on-one-registered-remote-fleet) | Inspect host-observed project eligibility on one registered remote fleet | operator | | GET | [`/v1/operator/persona`](#get-v1-operator-persona-read-the-hosted-or-local-owner-authored-persona) | Read the hosted or local owner-authored persona | operator | | POST | [`/v1/operator/persona`](#post-v1-operator-persona-apply-a-validated-partial-persona-update) | Apply a validated partial persona update | operator | | GET | [`/v1/operator/runtime-health`](#get-v1-operator-runtime-health-read-runtime-cpu-and-health-alarm-settings-and-metadata) | Read runtime CPU and health alarm settings and metadata | operator or device | | POST | [`/v1/operator/runtime-health`](#post-v1-operator-runtime-health-change-runtime-health-alarm-thresholds-without-restarting) | Change runtime health alarm thresholds without restarting | operator or device | | GET | [`/v1/operator/voice`](#get-v1-operator-voice-read-the-owner-authored-voice-settings) | Read the owner-authored voice settings | operator | | POST | [`/v1/operator/voice`](#post-v1-operator-voice-apply-a-validated-partial-voice-settings-update) | Apply a validated partial voice settings update | operator | | GET | [`/health`](#get-health-public-liveness) | Public liveness | none | | GET | [`/v1/herdr`](#get-v1-herdr-running-service-s-worker-runtime-binding) | Running service's worker-runtime binding | operator | | GET | [`/v1/discord/presence-status`](#get-v1-discord-presence-status-operator-presence-snapshot) | Operator presence snapshot | operator | | GET | [`/v1/discord/user-session/opt-in`](#get-v1-discord-user-session-opt-in-current-user-session-opt-in) | Current user-session opt-in | captain or operator | | POST | [`/v1/discord/user-session/opt-in`](#post-v1-discord-user-session-opt-in-record-user-session-tos-acceptance) | Record user-session ToS acceptance | operator | | DELETE | [`/v1/discord/user-session/opt-in`](#delete-v1-discord-user-session-opt-in-revoke-the-active-user-session-opt-in) | Revoke the active user-session opt-in | operator | | GET | [`/v1/discord/stream-watch`](#get-v1-discord-stream-watch-latest-observed-discord-screen-shares) | Latest observed Discord screen shares | captain or operator | | POST | [`/v1/discord/stream-watch`](#post-v1-discord-stream-watch-bridge-report-of-shares-and-optional-stills) | Bridge report of shares and optional stills | captain | | GET | [`/v1/discord/readiness`](#get-v1-discord-readiness-discord-captain-readiness) | Discord captain readiness | discord text or discord voice | | POST | [`/v1/discord/voice-briefing`](#post-v1-discord-voice-briefing-compose-the-realtime-voice-briefing) | Compose the realtime voice briefing | discord voice | | POST | [`/v1/discord/presence-session-events`](#post-v1-discord-presence-session-events-apply-a-discord-presence-phase-event) | Apply a Discord presence phase event | discord text or discord voice | | GET | [`/v1/discord/presence-sessions`](#get-v1-discord-presence-sessions-full-presence-session-records) | Full presence session records | captain | | GET | [`/v1/discord/voice-history`](#get-v1-discord-voice-history-completed-voice-stays) | Completed voice stays | captain | | GET | [`/v1/discord/voice-transcripts`](#get-v1-discord-voice-transcripts-page-through-retained-discord-voice-transcripts) | Page through retained Discord voice transcripts | captain | | POST | [`/v1/discord/presence-actions`](#post-v1-discord-presence-actions-execute-a-policy-gated-discord-action) | Execute a policy-gated Discord action | discord text or discord voice | | POST | [`/v1/captain/channel-turns`](#post-v1-captain-channel-turns-one-discord-message-becomes-one-captain-turn) | One Discord message becomes one captain turn | discord text or discord voice | | GET | [`/v1/captain/channel-turns/{deliveryId}`](#get-v1-captain-channel-turns-deliveryid-poll-a-discord-turn-by-its-stable-delivery-id) | Poll a Discord turn by its stable delivery ID | discord text or discord voice | | POST | [`/v1/captain/presence`](#post-v1-captain-presence-report-captain-lease-heartbeat) | Report captain lease heartbeat | captain | | POST | [`/operator/v1/dispatch`](#post-operator-v1-dispatch-operator-conversation-contract) | Operator conversation contract | captain or operator or device | | GET | [`/captain/v1/lanes`](#get-captain-v1-lanes-observable-captain-lanes) | Observable captain lanes | captain | | GET | [`/v1/captain/evaluator`](#get-v1-captain-evaluator-independent-evaluator-status-queue-and-recent-reports) | Independent evaluator status, queue and recent reports | operator | | POST | [`/v1/captain/evaluator`](#post-v1-captain-evaluator-enable-disable-focus-or-retry-the-independent-evaluator) | Enable, disable, focus or retry the independent evaluator | operator | | GET | [`/v1/captain/issue-metrics`](#get-v1-captain-issue-metrics-observed-issue-and-worker-tokens-elapsed-time-checks-and-reviews) | Observed issue and worker tokens, elapsed time, checks and reviews | operator | | GET | [`/v1/captain/turn-metrics`](#get-v1-captain-turn-metrics-recent-settled-captain-turns) | Recent settled captain turns | operator | | GET | [`/v1/captain/seat-context`](#get-v1-captain-seat-context-resolve-an-operator-seat-to-its-service-conversation-and-workspace) | Resolve an operator seat to its service conversation and workspace | operator or captain | | POST | [`/v1/captain/seat-context`](#post-v1-captain-seat-context-create-a-separate-workspace-chat-for-a-new-native-operator-seat) | Create a separate workspace chat for a new native operator seat | operator or captain | | POST | [`/v1/seat/transcript`](#post-v1-seat-transcript-append-redacted-native-seat-display-records-to-its-selected-conversation) | Append redacted native seat display records to its selected conversation | operator or captain | | GET | [`/v1/captain/prompt`](#get-v1-captain-prompt-the-system-prompt-a-lane-s-session-starts-from) | The system prompt a lane's session starts from | operator or captain | | GET | [`/v1/captain/memory-card`](#get-v1-captain-memory-card-the-memory-card-that-lane-s-next-run-injects) | The memory card that lane's next run injects | operator or captain | | GET | [`/v1/mcp`](#get-v1-mcp-server-to-client-notification-stream-for-an-open-session) | Server-to-client notification stream for an open session | operator or captain or discord text or discord voice | | POST | [`/v1/mcp`](#post-v1-mcp-a-lane-s-tool-bank-over-streamable-http-mcp) | A lane's tool bank over streamable-HTTP MCP | operator or captain or discord text or discord voice | | DELETE | [`/v1/mcp`](#delete-v1-mcp-end-an-mcp-session) | End an MCP session | operator or captain or discord text or discord voice | | GET | [`/v1/seat/events`](#get-v1-seat-events-long-poll-the-seat-s-outbox) | Long-poll the seat's outbox | operator or captain | | POST | [`/v1/seat/events/{id}/reply`](#post-v1-seat-events-id-reply-answer-an-escalation-from-the-seat) | Answer an escalation from the seat | operator or captain | | GET | [`/v1/fleet/metrics`](#get-v1-fleet-metrics-read-fixed-reason-fleet-proof-and-report-counters) | Read fixed-reason fleet proof and report counters | operator | | POST | [`/v1/fleet/seats/{paneId}/messages/health`](#post-v1-fleet-seats-paneid-messages-health-record-a-native-worker-s-content-free-receipt-health) | Record a native worker's content-free receipt health | operator | | POST | [`/v1/fleet/efficiency`](#post-v1-fleet-efficiency-inspect-owned-seats-or-record-a-worker-review) | Inspect owned seats or record a worker review | operator | | POST | [`/v1/fleet/tidy-worktrees`](#post-v1-fleet-tidy-worktrees-list-merged-clean-worktree-cleanup-candidates) | List merged, clean worktree cleanup candidates | operator | | GET | [`/v1/fleet/seats/{paneId}/events`](#get-v1-fleet-seats-paneid-events-long-poll-a-fleet-seat-s-mailbox) | Long-poll a fleet seat's mailbox | operator or captain | | GET | [`/v1/fleet/seats/{paneId}/peers`](#get-v1-fleet-seats-paneid-peers-list-native-seats-in-the-calling-worker-s-fleet) | List native seats in the calling worker's fleet | none | | POST | [`/v1/fleet/seats/{paneId}/peer-messages`](#post-v1-fleet-seats-paneid-peer-messages-send-one-same-fleet-peer-message-through-native-seat-delivery) | Send one same-fleet peer message through native seat delivery | none | | GET | [`/v1/fleet/seats/{paneId}/peer-messages/{id}`](#get-v1-fleet-seats-paneid-peer-messages-id-inspect-the-original-peer-delivery-receipt-without-resending) | Inspect the original peer delivery receipt without resending | none | | POST | [`/v1/fleet/seats/{paneId}/hook`](#post-v1-fleet-seats-paneid-hook-report-a-hired-seat-s-lifecycle-hook) | Report a hired seat's lifecycle hook | operator or captain | | POST | [`/v1/fleet/seats/{paneId}/tool-catalog`](#post-v1-fleet-seats-paneid-tool-catalog-report-the-occupying-native-harness-s-accepted-clankie-tools) | Report the occupying native harness's accepted Clankie tools | operator or captain | | GET | [`/v1/fleet/tool-catalog-health`](#get-v1-fleet-tool-catalog-health-read-native-catalog-health-for-every-current-claude-and-codex-seat) | Read native catalog health for every current Claude and Codex seat | operator | | POST | [`/v1/operator/projects/add-worktree-root`](#post-v1-operator-projects-add-worktree-root-enroll-a-repository-bound-linked-worktree-root) | Enroll a repository-bound linked-worktree root | operator | | POST | [`/v1/operator/projects/remove-worktree-root`](#post-v1-operator-projects-remove-worktree-root-remove-a-linked-worktree-root-enrollment) | Remove a linked-worktree root enrollment | operator | | GET | [`/v1/memory`](#get-v1-memory-browse-every-memory-note-and-discord-person-fact) | Browse every memory note and Discord person fact | operator | | POST | [`/v1/memory/discord-people/proposals`](#post-v1-memory-discord-people-proposals-upsert-a-discord-person-fact) | Upsert a Discord person fact | discord text or discord voice | | GET | [`/v1/memory/discord-people/{guildId}/{userId}`](#get-v1-memory-discord-people-guildid-userid-recall-discord-person-facts) | Recall Discord person facts | discord text or discord voice | | DELETE | [`/v1/memory/discord-people/{guildId}/{userId}`](#delete-v1-memory-discord-people-guildid-userid-delete-every-fact-for-a-discord-person) | Delete every fact for a Discord person | operator | | GET | [`/v1/memory/discord-people/{guildId}/{userId}/export`](#get-v1-memory-discord-people-guildid-userid-export-operator-export-of-a-discord-person) | Operator export of a Discord person | operator | | PATCH | [`/v1/memory/discord-people/{guildId}/{userId}/{factId}`](#patch-v1-memory-discord-people-guildid-userid-factid-edit-one-discord-person-fact-without-changing-its-identity-or-provenance) | Edit one Discord person fact without changing its identity or provenance | operator | | DELETE | [`/v1/memory/discord-people/{guildId}/{userId}/{factId}`](#delete-v1-memory-discord-people-guildid-userid-factid-forget-one-discord-person-fact) | Forget one Discord person fact | operator | | GET | [`/v1/memory/captain-episodes`](#get-v1-memory-captain-episodes-recall-memory-notes-for-a-lane) | Recall memory notes for a lane | captain | | POST | [`/v1/memory/captain-episodes`](#post-v1-memory-captain-episodes-record-a-self-authored-memory-note) | Record a self-authored memory note | captain | | PATCH | [`/v1/memory/captain-episodes/{lane}/{episodeId}`](#patch-v1-memory-captain-episodes-lane-episodeid-edit-one-memory-note-or-its-visibility) | Edit one memory note or its visibility | operator | | DELETE | [`/v1/memory/captain-episodes/{lane}/{episodeId}`](#delete-v1-memory-captain-episodes-lane-episodeid-forget-one-memory-note) | Forget one memory note | operator | | GET | [`/v1/minecraft`](#get-v1-minecraft-read-the-current-minecraft-body-session) | Read the current Minecraft body session | operator | | POST | [`/v1/minecraft`](#post-v1-minecraft-join-choose-a-driver-act-inspect-pause-cancel-or-leave-minecraft) | Join, choose a driver, act, inspect, pause, cancel or leave Minecraft | operator | | POST | [`/v1/minecraft/host`](#post-v1-minecraft-host-configure-start-administer-or-back-up-clankie-s-own-minecraft-server) | Configure, start, administer or back up Clankie's own Minecraft server | operator | | GET | [`/v1/minecraft/configuration`](#get-v1-minecraft-configuration-read-owner-minecraft-profiles-destination-allowlist-and-play-settings) | Read owner Minecraft profiles, destination allowlist and play settings | operator | | PUT | [`/v1/minecraft/configuration`](#put-v1-minecraft-configuration-replace-offline-minecraft-profiles-public-destination-grants-and-play-settings) | Replace offline Minecraft profiles, public destination grants and play settings | operator | | GET | [`/v1/games/configuration`](#get-v1-games-configuration-pok-mon-play-defaults) | Pokémon play defaults | operator or captain | | PUT | [`/v1/games/configuration`](#put-v1-games-configuration-replace-gameplay-configuration-for-subsequent-sittings) | Replace gameplay configuration for subsequent sittings | operator or device | | POST | [`/v1/embodiment/intents`](#post-v1-embodiment-intents-ask-to-start-or-stop-play) | Ask to start or stop play | operator | | GET | [`/v1/embodiment/sessions/live`](#get-v1-embodiment-sessions-live-live-play-session) | Live play session | operator or captain | | POST | [`/v1/embodiment/sessions/live/stop`](#post-v1-embodiment-sessions-live-stop-operator-kill-switch-ordinary-stop-intent) | Operator kill-switch (ordinary stop intent) | operator | | POST | [`/v1/embodiment/sessions/live/guide`](#post-v1-embodiment-sessions-live-guide-suggest-an-objective-or-approach-to-the-owning-pok-mon-mind) | Suggest an objective or approach to the owning Pokémon mind | operator | | GET | [`/v1/embodiment/sessions/live/activity`](#get-v1-embodiment-sessions-live-activity-present-tense-self-observation) | Present-tense self-observation | operator or captain | | GET | [`/v1/embodiment/sessions/live/still`](#get-v1-embodiment-sessions-live-still-one-still-of-the-live-play-screen) | One still of the live play screen | operator or captain | | GET | [`/v1/embodiment/sessions/live/story`](#get-v1-embodiment-sessions-live-story-bounded-story-of-this-playthrough) | Bounded story of this playthrough | operator or captain | | GET | [`/v1/embodiment/sessions/{id}`](#get-v1-embodiment-sessions-id-one-embodiment-session) | One embodiment session | captain | | POST | [`/v1/conversation-heads`](#post-v1-conversation-heads-designate-or-clear-an-explicit-escalation-head) | Designate or clear an explicit escalation head | operator | | GET | [`/v1/body-leases`](#get-v1-body-leases-conversation-ownership-of-clankie-s-body-resources) | Conversation ownership of Clankie's body resources | operator or device | | POST | [`/v1/body-leases`](#post-v1-body-leases-explicitly-acquire-renew-release-queue-ask-or-recover-a-body-resource) | Explicitly acquire, renew, release, queue, ask or recover a body resource | operator | | POST | [`/v1/computer/authority`](#post-v1-computer-authority-recheck-the-owning-conversation-s-computer-authority) | Recheck the owning conversation's computer authority | operator | | POST | [`/v1/computer`](#post-v1-computer-drive-the-conversation-leased-computer-body) | Drive the conversation-leased computer body | operator | | GET | [`/v1/browser/harnesses`](#get-v1-browser-harnesses-computer-use-harnesses-here-and-on-linked-windows-fleets) | Computer-use harnesses here and on linked Windows fleets | operator | | GET | [`/v1/browser/tools`](#get-v1-browser-tools-live-browser-tool-catalog) | Live browser tool catalog | operator or captain | | POST | [`/v1/browser/call`](#post-v1-browser-call-call-a-browser-tool) | Call a browser tool | operator or captain | | POST | [`/v1/media/images`](#post-v1-media-images-generate-or-edit-an-image) | Generate or edit an image | operator or captain | | POST | [`/v1/media/videos`](#post-v1-media-videos-start-or-resume-a-video-render) | Start or resume a video render | operator or captain | | GET | [`/v1/support/grants`](#get-v1-support-grants-read-owner-issued-support-grants) | Read owner-issued support grants | operator or device | | POST | [`/v1/support/grants`](#post-v1-support-grants-create-a-support-window-of-at-most-72-hours) | Create a support window of at most 72 hours | operator or device | | POST | [`/v1/support/grants/{id}/revoke`](#post-v1-support-grants-id-revoke-revoke-support-access) | Revoke support access | operator or device | | POST | [`/v1/support/grants/{id}/pairing-offer`](#post-v1-support-grants-id-pairing-offer-offer-pairing-for-an-active-read-state-support-grant) | Offer pairing for an active read-state support grant | operator or device | | POST | [`/v1/hosted/support`](#post-v1-hosted-support-apply-an-exact-command-authorized-by-a-hosted-account-ticket) | Apply an exact command authorized by a hosted account ticket | none | | POST | [`/v1/pairing/local/offer`](#post-v1-pairing-local-offer-mint-the-same-uid-mac-companion-handoff-through-owner-private-ipc) | Mint the same-UID Mac companion handoff through owner-private IPC | operator | | POST | [`/v1/pairing/local/redeem`](#post-v1-pairing-local-redeem-redeem-the-owner-local-handoff-once-over-native-loopback) | Redeem the owner-local handoff once over native loopback | none | | POST | [`/v1/pairing/offer`](#post-v1-pairing-offer-mint-a-one-time-pairing-offer) | Mint a one-time pairing offer | operator | | POST | [`/v1/pairing/redeem`](#post-v1-pairing-redeem-redeem-an-offer-into-a-pending-device) | Redeem an offer into a pending device | none | | POST | [`/v1/pairing/complete`](#post-v1-pairing-complete-activate-a-pending-device) | Activate a pending device | none | | GET | [`/v1/captain/readiness`](#get-v1-captain-readiness-read-secret-free-chat-setup-readiness) | Read secret-free chat setup readiness | operator or device | | GET | [`/v1/devices/self/diagnostics-default`](#get-v1-devices-self-diagnostics-default-read-the-account-diagnostics-default) | Read the account diagnostics default | operator or device | | GET | [`/v1/devices`](#get-v1-devices-list-paired-devices) | List paired devices | operator or device | | POST | [`/v1/devices/{id}/revoke`](#post-v1-devices-id-revoke-revoke-a-device) | Revoke a device | operator or device | | GET | [`/v1/devices/self`](#get-v1-devices-self-device-reads-its-own-registration) | Device reads its own registration | device | | POST | [`/v1/hosted/pair-offer`](#post-v1-hosted-pair-offer-mint-an-authenticated-encrypted-offer-for-the-hosted-account-page) | Mint an authenticated encrypted offer for the hosted account page | none | | POST | [`/v1/devices/wake-key`](#post-v1-devices-wake-key-register-a-paired-device-wake-public-key-on-a-managed-body) | Register a paired device wake public key on a managed body | device | | POST | [`/v1/devices/self/push`](#post-v1-devices-self-push-device-records-or-clears-its-push-delivery-reference) | Device records or clears its push delivery reference | device | | POST | [`/v1/devices/self/session/refresh`](#post-v1-devices-self-session-refresh-renew-a-device-session-token) | Renew a device session token | device | ## Health ### `GET /health` — Public liveness Optional Herdr, gateway, host-power, runtime identity and runtimeHealth findings do not change service liveness. `runtimeHealth` contains fixed CPU percentage, health latency, duration, reason/alarm/delivery enums and incident timestamps; no conversation content. `power` says whether this host may sleep and when it last did (`@clankie/protocol/host-power`; docs/always-on.md). When available, `runtime` exposes only this service boot's pid, commit, root and instanceId; update transaction details are omitted. **Bearer:** none | Status | Meaning | | --- | --- | | 200 | Service is up (`application/json`) | ## Operator ### `POST /v1/integrate` — Compose, gate and land an ordered approved commit batch Source-checkout service only. Run returns a durable batch immediately; poll status. Independent clones and detached core/app sibling worktrees install real packages and run pnpm check with private home, state, credentials and caches. Only the recorded zero-exit exact HEAD may land. Core pushes first; app failure retains partial core-landed/app-pending evidence. Revert selects a passed batch via restore and creates a new commit restoring its tree. No force push or automatic push retry. Holds never expire; explicit overrides name every hold, actor and reason and are durably audited. Only the authenticated operator can invoke this route. **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Durable batch or holds; inspect ok and batch.state for failures/partial landing (`application/json`) | | 400 | Malformed request | | 403 | Actual operator required | | 409 | Unknown record | | 503 | Repository integration unavailable | ### `GET /v1/runtime-update` — Read runtime-update status and current deploy holds **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Runtime boot identity | | 403 | Operator required | | 503 | Runtime updater unavailable | ### `POST /v1/runtime-update` — Admit a local pinned-runtime deployment after the deploy-hold check **Bearer:** operator **Request body** (optional JSON) A JSON object. | Status | Meaning | | --- | --- | | 202 | Accepted durable update | | 400 | Invalid update request | | 403 | Operator required or revoked | | 409 | Named deploy hold or other refusal; detail describes the blocker | | 503 | Runtime updater unavailable | ### `GET /v1/model-keys` — List supported providers, models, stored-key status and captain selection Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. Shares the CLI model catalog and config. Returns no credentials or endpoint headers. Cache-Control no-store. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Secret-free model catalog and selection (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `GET /v1/model-keys/subscriptions` — List providers signed in through an account (OAuth or subscription) instead of an API key Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. Returns provider ids and names only; no tokens, expiry or account metadata. A separate read so clients parsing the strict model catalog never meet a new field. Cache-Control no-store. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Providers signed in through an account (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `GET /v1/model-keys/subscriptions/methods` — List currently supported provider subscription sign-in methods **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Policy-filtered providers and browser or device methods (`application/json`) | | 401 | authentication_required | | 403 | forbidden | | 503 | unavailable | ### `POST /v1/model-keys/subscriptions/start` — Start a provider sign-in for the authenticated initiating principal Reuses console sign-in helpers. Poll status for the URL and optional code, then open it in the initiating client's browser. Browser callbacks reach this Mac; phones use the device method. Credentials and the selected catalog model are committed together after a fresh authority check. Claude subscription login remains unsupported. No-store; no raw provider errors. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | New pending interaction (`application/json`) | | 400 | malformed or unsupported provider/model | | 401 | authentication_required | | 403 | forbidden | | 409 | busy | | 503 | unavailable | ### `POST /v1/model-keys/subscriptions/status` — Read the initiating principal's sign-in interaction **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Pending URL/code, committing write, or terminal result (`application/json`) | | 400 | malformed | | 401 | authentication_required | | 403 | forbidden | | 404 | session_not_found | | 503 | unavailable | ### `POST /v1/model-keys/subscriptions/cancel` — Cancel a pending sign-in; an admitted credential write finishes as committing **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Cancelled or already committing/terminal interaction (`application/json`) | | 400 | malformed | | 401 | authentication_required | | 403 | forbidden | | 404 | session_not_found | | 503 | unavailable | ### `GET /v1/model-keys/options` — List providers usable without key entry and the running model's reasoning effort Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. Returns provider ids and effort levels only; no credentials. A separate read so clients parsing the strict model catalog never meet a new field. Cache-Control no-store. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Usable providers and the running model's effort (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/model-keys/effort` — Set or clear the running model's reasoning effort for the next turn Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. Writes the same config as `clankie effort set`; null returns the model to its default. An effort the model does not accept, or no running model, is unsupported_model. Cache-Control no-store. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | Malformed input, or an effort the running model does not accept (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 413 | malformed — body exceeds 16 KiB | | 503 | unavailable | ### `POST /v1/model-keys/set` — Store or replace a write-only provider API key On a self-hosted Mac, device key entry returns forbidden when shared Clankie readiness is true. Operator key management and hosted behavior remain unchanged. The write rechecks readiness and authority. Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. No key, provider error text, or generated text is returned or logged. Cache-Control no-store. Validation uses a fixed prompt, 16 output tokens, no retries and a 15-second deadline; the provider may bill this request. Set does not validate implicitly. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | Malformed input, unsupported provider/model, missing key, failed or timed-out validation (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 413 | malformed — body exceeds 16 KiB | | 503 | unavailable | ### `POST /v1/model-keys/validate` — Validate the stored key with a bounded provider call Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. No key, provider error text, or generated text is returned or logged. Cache-Control no-store. Validation uses a fixed prompt, 16 output tokens, no retries and a 15-second deadline; the provider may bill this request. Set does not validate implicitly. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | Malformed input, unsupported provider/model, missing key, failed or timed-out validation (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 413 | malformed — body exceeds 16 KiB | | 503 | unavailable | ### `POST /v1/model-keys/select` — Select the captain model for the next turn Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. No key, provider error text, or generated text is returned or logged. Cache-Control no-store. Validation uses a fixed prompt, 16 output tokens, no retries and a 15-second deadline; the provider may bill this request. Set does not validate implicitly. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | Malformed input, unsupported provider/model, missing key, failed or timed-out validation (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 413 | malformed — body exceeds 16 KiB | | 503 | unavailable | ### `POST /v1/model-keys/remove` — Remove a stored API key without removing OAuth or environment fallback Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. No key, provider error text, or generated text is returned or logged. Cache-Control no-store. Validation uses a fixed prompt, 16 output tokens, no retries and a 15-second deadline; the provider may bill this request. Set does not validate implicitly. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | Malformed input, unsupported provider/model, missing key, failed or timed-out validation (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 413 | malformed — body exceeds 16 KiB | | 503 | unavailable | ### `GET /v1/accounts/codex` — List registered local Codex homes and observed headroom Owner-authorized. Reports credential-file presence, never credential contents. Queries current quota without a model turn, with recent rollouts as fallback. Missing or stale usage is unknown. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Default and registered account labels, paths, rate windows and headroom | | 401 | Authentication required | | 403 | Owner authority required | ### `POST /v1/accounts/codex` — Register or remove an owner's local Codex home **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Registration updated; credentials and home are unchanged | | 400 | Invalid or duplicate account | | 401 | Authentication required | | 403 | Owner authority required | ### `GET /v1/accounts` — Read the body-owned GitHub, Linear and Google account catalog Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. Includes service purpose, permission disclosure, account identity, granted scopes, recovery status and selected Drive file IDs. An unconfigured Google row needs operator OAuth client setup; it does not request a customer credential. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/github/start` — Start a GitHub device flow; return the user code and verification URL Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. The device code stays on the body. Unconfigured without a GitHub OAuth client ID. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | A closed error code (malformed, unconfigured, unknown_flow, expired, denied, provider_rejected) (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/github/poll` — Poll a GitHub device flow; returns pending, connected or a closed error Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. Polls faster than GitHub's interval are answered by the body without calling GitHub. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | A closed error code (malformed, unconfigured, unknown_flow, expired, denied, provider_rejected) (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/linear/start` — Start registered Linear app OAuth with S256 PKCE; return the authorize URL Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. The PKCE verifier stays on the body. Tokens use the separate linear-api broker entry and GraphQL tracker, never MCP. Unconfigured without a redirect URI. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | A closed error code (malformed, unconfigured, unknown_flow, expired, denied, provider_rejected) (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/linear/complete` — Hand back Linear's redirect code; the body exchanges it with its verifier Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. state is single use. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | A closed error code (malformed, unconfigured, unknown_flow, expired, denied, provider_rejected) (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/linear/app` — Connect a workspace-owned Linear app for worker names and portraits Verifies an app actor and workspace before replacing the Linear credential. Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. Client credentials and tokens stay in the broker and are never returned or logged. Cache-Control no-store. Enable client credentials tokens on the workspace's Linear OAuth application. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Verified connection with actor and workspace; no credentials (`application/json`) | | 400 | malformed or provider_rejected; previous connection retained (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `POST /v1/accounts/google/start` — Start body-owned Google consent with state and S256 PKCE Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. The body retains the verifier and keeps all client secrets and tokens in its broker. Cache-Control no-store. Gmail requests gmail.readonly plus openid email. Calendar requests calendar.calendarlist.readonly and calendar.events.readonly plus openid email. Drive requests only drive.file and opens Google Picker with prompt consent, trigger_onepick true and include_granted_scopes false. Google's selected-file grant permits editing; Clankie's implemented tools only read selected files. All flows request offline access. Client setup and browser consent are required. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | One-time state, registered redirect, consent URL and expiry; no token or verifier (`application/json`) | | 400 | Closed failure code, including malformed, unconfigured or unavailable (`application/json`) | | 401 | authentication_required (`application/json`) | | 403 | forbidden — terminalControl required (`application/json`) | | 413 | malformed — body exceeds 16 KiB (`application/json`) | | 503 | unavailable — body, broker or current owner authority could not be verified (`application/json`) | ### `POST /v1/accounts/google/complete` — Exchange the one-time Google code on the tenant body Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. State is single use, bound to this body's pending provider, OAuth application and expiry. The body verifies provider identity and granted scopes before storing credentials; no tokens or provider errors escape. Gmail and Calendar accept provider, state and code only. Drive also requires a nonempty unique pickedFileIds array, validated from Google Picker's picked_file_ids callback. These IDs bound the files the implemented Drive read tools can access. Cache-Control no-store. Authorization codes belong in the JSON body, never API request URLs or logs. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Verified account connection and granted permissions; no credential (`application/json`) | | 400 | Closed failure code, including malformed, unknown_flow, expired, unconfigured, provider_rejected or unavailable (`application/json`) | | 401 | authentication_required (`application/json`) | | 403 | forbidden — terminalControl required (`application/json`) | | 413 | malformed — body exceeds 32 KiB (including selected file IDs) (`application/json`) | | 503 | unavailable — body, broker or current owner authority could not be verified (`application/json`) | ### `POST /v1/accounts/google/check` — Refresh and verify the selected Google account's authorized access Owner operator bearer or active device with terminalControl only. Public gateway calls require the encrypted envelope. The body refreshes the selected grant under the credential-store lock and verifies its identity, scopes and disconnect epoch. Returns catalog metadata and last-check status without exposing credentials. Invalid grants require renewed consent; provider outages do not imply revocation. Cache-Control no-store. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Current verified account connection and check metadata; no credential (`application/json`) | | 400 | Closed failure code, including malformed, unconfigured, denied, provider_rejected or unavailable; list accounts for recovery status (`application/json`) | | 401 | authentication_required (`application/json`) | | 403 | forbidden — terminalControl required (`application/json`) | | 413 | malformed — body exceeds 16 KiB (`application/json`) | | 503 | unavailable — body, broker or current owner authority could not be verified (`application/json`) | ### `POST /v1/accounts/disconnect` — Disconnect locally and report actual provider revocation Owner operator bearer or active device with terminalControl only (ADR 0232). Public gateway calls require the encrypted envelope. Never returns or logs a token or provider error text. Cache-Control no-store. revoked is false when the provider could not confirm revocation; local access is disabled either way and manageUrl names where to review the grant. Disconnecting any Google provider disables all three Google connections on this body. Its broker may retain a refresh credential solely for bounded revocation retries; catalog revocationPending remains true until Google confirms revocation. Hosted GitHub disconnect never uses a developer app secret and reports revoked false. Owner-run self-hosted GitHub revocation deletes only the stored token, never an app grant. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Success (`application/json`) | | 400 | A closed error code (malformed, unconfigured, unknown_flow, expired, denied, provider_rejected) (`application/json`) | | 401 | authentication_required | | 403 | forbidden — terminalControl required | | 503 | unavailable | ### `GET /v1/connections` — Inspect execution runtimes and verified Linear identity **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Current runtime health and recorded account identity | | 401 | Operator authentication required | | 503 | Operator authentication unavailable | ### `GET /v1/runtime-connections` — Inspect the default fleet and named execution connections **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Configured endpoints and freshly probed health | | 401 | Operator authentication required | | 503 | Operator authentication unavailable | ### `POST /v1/runtime-connections` — Connect a Herdr runtime or configure owner execution policy Resolves an explicit session or socket and pins that endpoint to the ID. Reconnection cannot retarget an existing ID. action=workspaces replaces additional repository/directory approvals for a named or default runtime; it never starts or stops workers. action=capacity sets one runtime limit; Unset limits default to 16; null clears a limit to unlimited; zero pauses new admission. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Persisted connection | | 400 | Invalid connection | | 401 | Operator authentication required | | 409 | Endpoint unavailable, identity conflict or coordinator synchronization failed; inspect state before retrying | | 503 | Execution management or authentication unavailable | ### `DELETE /v1/runtime-connections/{id}` — Disable a named execution connection while preserving workers and route identity **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Connection disabled and coordinator routes synchronized | | 401 | Operator authentication required | | 409 | Unknown connection or synchronization unconfirmed; inspect state before retrying | | 503 | Execution management or authentication unavailable | ### `POST /v1/work` — Read and write work items in the repo's own tracking convention One contract over Linear, GitHub issues, the repo's own Markdown directory, or .clankie/work/ (ADR 0191). `action` is repos, discover, init, list, show, project, create, update, attach, write or write_receipt. The two narrow owner operations take request {repoId,itemId,requestId,command}; receipt reads omit command. The command assigns work-metadata owner, adds/removes a role label or appends a blocker. Their applied/refused/uncertain receipt always carries the UUID; repeated IDs and receipt reads never replay a write. A path names and registers a local repo; registered repos are readable from paired devices through the work_repos, work_items and work_project operator ops. work_items statusVersion=2 opts into backlog and native milestone metadata; older requests receive backlog as todo. project reads planned milestones, shipped v* tags/published versions and Linear initiative goals. init accepts releaseSource (tags, milestones, both; default both) and releaseLane (default repository). Unavailable facts are explicit; store builds are not inferred. list takes optional status, owner and label (case-insensitive) filters; items carry the backend's labels (ADR 0208). **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | The repos | | 400 | Invalid request | | 401 | Operator authentication required | | 404 | Unknown repo or work item | | 409 | The repo needs its convention decided | | 502 | The backend refused or failed the request | ### `GET /v1/linear/target` — Inspect the ordinary chat selected for Linear activity **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Current live wake target (`application/json`) | | 401 | Operator authentication required | | 503 | Operator authentication unavailable | ### `PUT /v1/linear/target` — Select an existing ordinary global chat without restarting Sets linearWebhook.wakeConversationId, default global-default. The target must be an existing ordinary global chat; its attached native operator seat may drive it. Changing the target applies to new activity without moving or replaying history. Selecting a target grants no additional authority. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Persisted live wake target (`application/json`) | | 400 | Invalid target | | 401 | Operator authentication required | | 409 | Target is not an available ordinary global chat | | 503 | Settings or operator authentication unavailable | ### `GET /v1/linear/request-budget` — Read rolling provider request usage and background read throttling Counts actual HTTP attempts across MCP and the API tracker per workspace and actor. The default 5000/hour cap reserves one request; provider remaining/reset headers can lower available headroom. At 50% a native runtime warning is emitted once until usage recovers; at 80% background reads share a one-minute minimum interval. Writes and webhook context retain priority within the hard cap. This diagnostic makes no provider calls. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Service-local rolling counts and available provider rate-limit observations (`application/json`) | | 401 | Operator authentication required | | 503 | Budget or operator authentication unavailable | ### `GET /v1/linear/wake` — Inspect actor and activity type wake rules **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Current live wake rules (`application/json`) | | 401 | Operator authentication required | | 503 | Operator authentication unavailable | ### `PUT /v1/linear/wake` — Replace wake rules without restarting Missing fields take defaults. Defaults select signed comments and mentions by the owner user email volpestyle@gmail.com and exclude issueSubscribed. Owner IDs may also be configured. Unknown or ambiguous authors and own writes never wake. Actor selectors are ORed; exclusions always win. Empty notificationTypes allows all types. Rules apply to new signed webhook activity without replaying history. The follow switch still gates wakes; passive activity remains visible in the selected ordinary chat. **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Persisted live wake rules (`application/json`) | | 400 | Malformed rules | | 401 | Operator authentication required | | 503 | Settings or operator authentication unavailable | ### `GET /v1/linear/follow` — Inspect Linear activity following and required webhook readiness **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Following switch | | 401 | Operator authentication required | ### `PUT /v1/linear/follow` — Enable or disable rule-matched turns from signed Linear webhooks Enabling requires a stored webhook URL and signing secret. It never promotes passive backlog. Disabling suppresses new and queued wakes while leaving ordinary chat history delivery enabled. Status distinguishes the requested following switch from active readiness, with linear_webhook_required and missingWebhook when setup is removed. Bursts coalesce into one wake for the selected ordinary global chat. There is no notification poll. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Following switch | | 400 | Invalid request | | 401 | Operator authentication required | | 409 | Following requires the registered webhook URL and broker-held signing secret; the switch is unchanged. (`application/json`) | ### `GET /v1/worker-grants/` — List worker grant records without bearer tokens **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Grant records including account identity and revocation status | | 401 | Operator authentication required | | 503 | Worker access service unavailable | ### `POST /v1/worker-grants/` — Issue a scoped grant for one verified connected account Work ID is provenance. Exact top-level argument restrictions enforce resource boundaries; omitted arguments remain unrestricted. A fleet grant binds access to an authenticated fleet link until revoked; a manual grant requires private bearer delivery. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 201 | Grant record and private worker bearer token; Cache-Control no-store | | 400 | Invalid grant request | | 401 | Operator authentication required | | 409 | Account unverified or tool unavailable | | 503 | Worker access service unavailable | ### `DELETE /v1/worker-grants/{id}` — Durably revoke one worker grant and close its sessions **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Revoked grant record | | 401 | Operator authentication required | | 404 | Grant unavailable | ### `GET /v1/worker-grants/linear/account` — Inspect disconnected, unverified or verified Linear account identity **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Account status and verified identity when available | | 401 | Operator authentication required | ### `POST /v1/worker-grants/linear/account` — Verify the stored Linear account's provider user and workspace Verifies API keys via GraphQL and OAuth via Linear's official MCP identity tools. Credentials are never migrated automatically. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Verified account identity | | 401 | Operator authentication required | | 409 | Verification unavailable or failed | ### `POST /v1/hosted/operator` — Dispatch as an account-paired hosted operator device Encrypted gateway carrier required remotely. Checks active device session, server-recorded operator pairing purpose and Take Control grant. No operator bearer is exported. The shared hosted-device allowlist permits chat, fleet, terminal, model, keys, persona and connections. Restart, reset and deprovision require account authority, including through the legacy device relay. The inner route keeps its own validation. Revocation is rechecked before returning a parked page. **Bearer:** device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Inner operator route response | | 400 | Invalid inner request or forbidden route | | 401 | Missing | | 403 | Not an account-paired operator device | ### `POST /v1/runtime-connections/{id}/prepare` — Prepare Clankie's own harness setup on an existing machine Resolves policy from the verified service-local source workspace and checks the target's current link independently. Lead setup requires an already-linked target. Owner policy or a newly supplied source script requires the caller's ownerApproved consent claim. The service records that claim without verifying human confirmation. expectedMachineRevision pins the exact registered target shown to the caller; omission claims the alias's current target. This retains operator authority and never restarts or steers existing lanes. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | Existing runtime connection ID | **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Harness preparation result and the caller's consent claim (`application/json`) | | 400 | Malformed request | | 401 | Operator authentication required | | 403 | Owner approval or existing linked target required | | 409 | Preparation or context verification failed | ### `GET /v1/operator/fleet-settings` — Read fleet size, model mode and owner working preferences Closure, machineSetup, commit and push default to lead; official releases default to owner, verification to change_run_read, reporting to Short and plain. workingPreferences=true advertises the five new controls; old responses may omit them. Existing owner or Take Control device authority is required; preferences confer no credentials or new access. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Current fleet settings and SHA-256 revision (`application/json`) | | 401 | Owner authentication required | | 403 | Caller lacks owner authority | ### `POST /v1/operator/fleet-settings` — Update fleet budget and working preferences with revision fencing Omitted leaves retain their value. A null autonomy leaf restores its global default. Release mode and rule are replaced together. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated fleet snapshot and revision | | 400 | Malformed request | | 401 | Owner authentication required | | 403 | Caller lacks owner authority | | 409 | Settings or owner authority changed before commit | ### `GET /v1/operator/fleet-settings/context` — Resolve current project policy and already-linked setup target Verifies a local source directory and Git worktree separately from the registered target machine. Returns a nonsecret targetRevision hashing the resolved execution descriptor, including transport and session, so consent can be pinned across requests. Machine aliases hash their registration set rather than selecting an arbitrary runtime. Invalid or ambiguous project evidence fails closed. This filesystem route is excluded from hosted-device forwarding. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `workingDirectory` | yes | string | Existing absolute source directory on the service host | | query | `machine` | yes | string | Existing runtime or machine ID | | query | `projectId` | no | string | Optional project assertion; must match verified directory membership | | Status | Meaning | | --- | --- | | 200 | Effective policy, matched project and current target observation (`application/json`) | | 400 | Malformed query | | 401 | Owner authentication required | | 403 | Caller lacks owner authority | | 409 | Context could not be verified or settings changed | ### `POST /v1/operator/projects/update` — Update project policy while preserving independent inheritance Each autonomy.fleet leaf overrides its global default independently; null clears only that override and omission retains it. Release mode and rule are one atomic leaf. Responses omit autonomy fields unless includeAutonomy=true; workingPreferences=true then advertises support for the new controls. The expectedRevision fences full project settings even for legacy projections. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `includeAutonomy` | no | string, one of `true` | Opt into project autonomy overrides and global autonomyDefaults | **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated project snapshot and revision | | 400 | Malformed request | | 401 | Owner authentication required | | 403 | Caller lacks owner authority | | 409 | Project settings or owner authority changed | ### `GET /v1/operator/projects` — Read owner project settings and their current revision Legacy responses omit autonomy fields. includeAutonomy=true adds optional project autonomy.fleet overrides, global autonomyDefaults and workingPreferences=true without changing the full settings revision. New working-preference leaves stay absent on an older service. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `includeAutonomy` | no | string, one of `true` | Opt into project autonomy overrides and global autonomyDefaults | | Status | Meaning | | --- | --- | | 200 | Project settings and SHA-256 revision | | 401 | Owner authentication required | | 403 | Caller is not the owner | ### `POST /v1/operator/projects/create` — Create a reviewed project with one canonical local workspace Owner only. Strict fields are projectId, name, workspacePath, expectedRevision, roles, workerCap, trackerRef, trackerSetup and fleet. The service supplies primary/local workspace identity and its platform. Nullable caps inherit; zero is preserved. Fleet preferences are not numeric caps. Optional trackerRef must name primary and .clankie/tracking.json, an existing valid canonical convention unless trackerSetup supplies reviewed work-init inputs for a missing convention. Tracker save precedes settings save; a partial failure can leave only the convention. No automatic retry, account choice, provider project/label creation, remote enrollment, assignments, hires or grants. Rechecks canonical directory/tracker identity, settings revision and owner authority before the existing SettingsStore rename. These asynchronous observations are not cross-process compare-and-swap or atomic authority/filesystem checks. This documents the source contract, not an installed CLI or running-service update. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 201 | Saved project settings and current revision | | 400 | Malformed or unsupported creation fields | | 401 | Owner authentication required | | 403 | Caller is not the owner | | 409 | project_create_conflict or project_tracker_unavailable; no automatic retry | | 413 | Request exceeds 16 KiB | | 503 | Settings writes unavailable | ### `POST /v1/operator/projects/remove-workspace` — Remove one exact owner-approved workspace registration Preserves roles, grants and assignments. Refuses tracker-bound workspaces, stale revisions, changed owner authority and concurrent settings changes. Does not delete a directory. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated project settings and revision | | 400 | Malformed request | | 401 | Owner authentication required | | 403 | Caller is not the owner | | 409 | Removal conflicts with current settings or authority | ### `POST /v1/harness-refresh` — Refresh existing native worker plugin links locally and on enabled fleet machines Uses the native harness installer without enrolling new profiles, changing channel policy or restarting panes. The canonical source workspace selects current machineSetup policy; owner mode requires the caller's ownerApproved consent claim, and automatic lead refresh requires an already-linked target. The service records the claim without verifying human confirmation. Current operator authority, source context and each captured fleet destination are revalidated before refresh. Returns per-profile and per-fleet outcomes; ok is false for incomplete installation, missing source setup or unreachable machines. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Plugin refresh receipts, including incomplete outcomes (`application/json`) | | 400 | Invalid refresh context request | | 403 | Operator required or revoked | | 409 | Source context | | 503 | Harness refresh or policy context unavailable | ### `POST /v1/harness-plugin-version` — Tell a running service which worker version the installer shipped Updates display-only restart flags at the next admitted native client request. Client version reports confer no tool authority, and nothing is restarted. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Announcement accepted for the next native client request | | 400 | Invalid plugin version | | 403 | Operator required or revoked | | 503 | Plugin notices unavailable | ### `GET /v1/runtime-connections/{id}/harnesses` — Inspect native harness registration on one registered remote fleet Reports profile activation, service versus installed version, bridge, hooks and skill presence. Static registration never establishes live process membership or receiver proof. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Per-machine and per-profile harness diagnostics | | 401 | Owner authentication required | | 403 | Caller is not the owner | | 503 | Fleet unavailable or inspector not yet prepared | ### `GET /v1/runtime-connections/{id}/membership` — Inspect host-observed project eligibility on one registered remote fleet Reports bounded per-pane process, native session, hire and actual cwd observations. Host eligibility does not verify a bridge socket, native tool catalog or delivery. Rechecks the configured fleet, settings, hire, native observations and caller authority before returning. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Per-pane host observations with native tools explicitly unverified | | 401 | Current owner authentication required | | 403 | Caller lacks owner control | | 503 | Fleet or native observations unavailable | ### `GET /v1/operator/persona` — Read the hosted or local owner-authored persona Includes persona image diagnostics (files, roles, counts, sizes, video timestamps, viewable cached sheet paths, load errors), never image bytes. Status previews the current folder; the running image snapshot changes on restart. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Persona settings | | 401 | Operator authentication required | ### `POST /v1/operator/persona` — Apply a validated partial persona update Accepts imagesDir as a folder on the service host (empty string clears). Top-level images and sampled videos are vibe; appearance/ holds physical character references. Returns image status and a restart reminder. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated persona settings | | 400 | Invalid persona patch | | 401 | Operator authentication required | | 503 | Settings persistence unavailable | ### `GET /v1/operator/runtime-health` — Read runtime CPU and health alarm settings and metadata Owner or Take Control device authority. Returns schemaVersion=1, a SHA-256 settings revision, settings, and the fixed RuntimeHealthObservation from packages/protocol/src/runtime-health.ts. Process CPU is wall-time-normalized process.cpuUsage; 100% is one fully busy core. No conversation content. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Current settings revision and live observation | | 401 | Authentication required | | 403 | Owner authority required | ### `POST /v1/operator/runtime-health` — Change runtime health alarm thresholds without restarting Accepts schemaVersion=1, expectedRevision and a strict partial changes object. Defaults are enabled=true, cpuPercent=50, healthLatencyMs=1000, sustainedMs=300000, sampleIntervalMs=15000 and cooldownMs=1800000. Settings apply on the next sample. Native global-default delivery emits one alarm and one recovery with incident duration; it creates no service model turn. The schema and bounded ranges live in @clankie/protocol. **Bearer:** operator or device **Request body** (required JSON) ```json { "schemaVersion": 1, "expectedRevision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "changes": { "cpuPercent": 50, "healthLatencyMs": 1000, "sustainedMs": 300000 } } ``` | Status | Meaning | | --- | --- | | 200 | Saved revision and live observation | | 400 | Malformed or out-of-range settings | | 401 | Authentication required | | 403 | Owner authority required | | 409 | Settings revision or owner authority changed | | 503 | Settings unavailable | ### `GET /v1/operator/voice` — Read the owner-authored voice settings Native owner/self-hosted voice control; this route is not admitted by the paired hosted-device bridge. Returns stored provider, model and public voice identifiers with schema defaults. Environment overrides, provider credentials and other owner settings are omitted. Does not start voice or contact providers. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Stored voice settings; Cache-Control is no-store (`application/json`) | | 401 | Operator authentication required | | 503 | Operator authentication or settings read unavailable | ### `POST /v1/operator/voice` — Apply a validated partial voice settings update Native owner/self-hosted voice control; this route is not admitted by the paired hosted-device bridge. Merges a strict patch into the current settings under the atomic settings store. The merged configuration must satisfy VoiceSettings, including Anthropic's ElevenLabs voice requirement. Preserves inactive provider models and unrelated settings. Unknown fields and token-shaped values are refused; provider keys belong in the credential broker. Owner authority is rechecked before persistence. Changes stored settings only and requires an active Discord body restart to apply; does not restart a body or contact providers. **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Updated stored voice settings; Cache-Control is no-store (`application/json`) | | 400 | Malformed | | 401 | Operator authentication required or revoked before persistence | | 503 | Operator authentication or settings persistence unavailable | ### `GET /v1/herdr` — Running service's worker-runtime binding Without a connection ID, pending default-fleet settings apply on service restart. Named connections resolve their pinned endpoint and current health. No unavailable connection falls back to the default fleet. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `connection` | no | string | Named runtime connection, or default | | Status | Meaning | | --- | --- | | 200 | Selected local worker runtime (`application/json`) | | 401 | Operator authentication required | | 503 | Operator authentication or runtime binding unavailable | ### `GET /v1/discord/presence-status` — Operator presence snapshot Phase and counts only. What `clankie status` reads. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Present-tense presence (`application/json`) | ### `POST /v1/discord/user-session/opt-in` — Record user-session ToS acceptance **Bearer:** operator **Request body** (required JSON) ```json { "schemaVersion": 1, "characterId": "clankie", "acknowledgement": "I accept Discord user-session transport risk on my account.", "guildIds": [ "123456789012345678" ], "channelIds": [ "123456789012345678" ], "dmPolicy": "deny" } ``` | Status | Meaning | | --- | --- | | 201 | Recorded | ### `DELETE /v1/discord/user-session/opt-in` — Revoke the active user-session opt-in **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Revoked | | 409 | Nothing active to revoke | ### `GET /v1/discord/stream-watch` — Latest observed Discord screen shares Metadata plus up to four chronological stills. Raw video is never stored. **Bearer:** captain or operator | Status | Meaning | | --- | --- | | 200 | Current share projection | ### `GET /v1/discord/voice-transcripts` — Page through retained Discord voice transcripts Captain bearer only. Exact speech is returned only while `discord.voiceTranscriptLoggingEnabled` is enabled; otherwise the route returns an empty page with `enabled: false`. Includes legacy human recognition and assistant generated wording (`role: assistant`, `speakerId: clankie`, `itemId`, optional `playbackId`/`responseId`, `textSource`, `textComplete`, `audioStarted`, `playbackMs`, `outcome`). Outcomes are played, interrupted, suppressed, failed, or truncated. Generated text may include an unheard ending after a cutoff; no raw audio or word-aligned audible transcript is retained. **Bearer:** captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `cursor` | no | string, matching `^[0-9]{12}$` | Exclusive cursor from a previous page. | | query | `limit` | no | integer, 1–200, default 100 | | | Status | Meaning | | --- | --- | | 200 | Bounded transcript page (`application/json`) | ### `GET /v1/fleet/metrics` — Read fixed-reason fleet proof and report counters Operator-only, content-free counts since service start and five/sixty-minute rates. Proof terminal admissions/refusals, receipt observations, native retries and transport failures are separate. No PIDs, paths, argv, pane IDs or report bodies. Contract: FleetHealthMetricsSnapshotSchema in packages/protocol/src/fleet-health-metrics.ts. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Current no-store metrics snapshot | | 401 | Operator authentication required | | 503 | Authentication or fleet metrics unavailable | ### `POST /v1/fleet/seats/{paneId}/messages/health` — Record a native worker's content-free receipt health Requires the current admitted native session. This observation does not send or replay a report and does not grant tool authority. Unknown panes, changing native bindings and fields beyond the strict status are refused. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 202 | Observation recorded independently of tool readiness | | 400 | Invalid strict status | | 401 | Authentication required | | 403 | Current native session required | | 503 | Worker health observation unavailable | ### `POST /v1/fleet/efficiency` — Inspect owned seats or record a worker review `show` returns every seat led by the exact conversation, including linked fleets, with optional factual efficiency evidence. `review` records inspected scope, tracker status or substantive progress for one exact owned native session. It changes no tracker state, harness settings, ownership or report receipts. Unknown telemetry is omitted, never treated as healthy. Flags are observations for the lead skill; they do not prescribe or perform worker actions. Every watch wake calls for reviewing all owned seats; periodic lead rounds default to 30 minutes and coalesce while a review turn is outstanding. Prompts carry bounded summaries; use show for full details and every-owned inspection. Original report acceptance or attempt remains progress after acknowledgment; acknowledgment creates no new progress. Context percentage is the latest native Codex model-input snapshot and may age between responses. Claude context/effort and OpenCode or remote telemetry remain unknown. **Bearer:** operator **Request body** (required JSON) *show* ```json { "action": "show", "conversationId": "global-default" } ``` *review* ```json { "action": "review", "conversationId": "global-default", "seatId": "worker-seat", "assignmentStatus": "active", "deliverable": "VUH-1662", "evidence": "Reviewed the assigned issue and current worker report." } ``` | Status | Meaning | | --- | --- | | 200 | Current owned roster, after recording the review when requested (`application/json`) | | 400 | Invalid strict request (invalid_request) | | 401 | Operator authentication required (operator_authentication_required) | | 409 | Conversation, native ownership or review observation unavailable (refused) | | 503 | Fleet efficiency service unavailable (fleet_efficiency_unavailable) | ### `POST /v1/fleet/tidy-worktrees` — List merged, clean worktree cleanup candidates Read-only listing from the exact canonical repository root and locally saved merge ref (origin/main by default); it never fetches refs or removes worktrees. Main, locked, prunable, dirty, unmerged and live-pane worktrees are excluded. Idle agents and shells also protect their directories. Unverified paths, unavailable or changing refs, and an incomplete or changing local pane inventory return outcome unavailable with no candidates. Candidate status does not prove ownership; the tidy skill verifies ownership, preserved results and fresh landing evidence before any separately authorized removal. **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Read-only candidates and exclusions, or an unavailable listing with no candidates (`application/json`) | | 400 | Invalid strict request (invalid_request) | | 401 | Operator authentication required (operator_authentication_required) | | 503 | Worktree listing service unavailable (tidy_worktrees_unavailable) | ### `GET /v1/fleet/tool-catalog-health` — Read native catalog health for every current Claude and Codex seat Includes the operator head. Reports from a previous occupant are never reused. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Session-bound catalog health and remediation in seats | | 401 | Operator authentication required | ### `POST /v1/operator/projects/add-worktree-root` — Enroll a repository-bound linked-worktree root Owner only. Requires projectId, machineId, platform, path, repoPath and expectedRevision. The service observes canonical root/repository paths and Git common-directory identity, then rechecks native facts, authority and revision before saving. The repo must exactly match an approved workspace in the project. This confers no tool grant. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Project snapshot and new revision | | 400 | Malformed request | | 403 | Owner authority required | | 409 | Unverified root, namespace conflict, or changed authority/settings | ### `POST /v1/operator/projects/remove-worktree-root` — Remove a linked-worktree root enrollment Owner only. Requires projectId, rootId and expectedRevision. Rechecks authority and revision before persistence. Deletes no filesystem content. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Project snapshot and new revision | | 400 | Malformed request | | 403 | Owner authority required | | 409 | Unknown root or changed authority/settings | ### `GET /v1/minecraft` — Read the current Minecraft body session Operator-authenticated read of the approved offline Java body. Account material and raw endpoints are excluded. x-clankie-conversation-id selects an existing owning conversation. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Current exact session and bounded action state | | 403 | Operator authority required | | 503 | Minecraft unavailable | ### `POST /v1/minecraft` — Join, choose a driver, act, inspect, pause, cancel or leave Minecraft Joining starts the configured continuous play mind. Use driver to hand off to owner, mind or a chosen admitted worker; it quiesces prior motor work without transferring the conversation play lease. Direct act requires owner driver. Long work returns a handle. The shared play lease is retained until exact confirmed disconnect. Completion is distinct from server-verified effects. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Session or action handle with settlement and evidence | | 400 | Invalid typed command | | 403 | Operator authority required | | 409 | Busy play lease | | 503 | Motor unavailable | ### `POST /v1/minecraft/host` — Configure, start, administer or back up Clankie's own Minecraft server Integration-owned on-demand Paper host and broker-backed playit claim. claim starts background agent preparation and returns preparing immediately; claim_status reads preparation progress and returns the official approval URL when pending and claim_complete polls once and stores an approved secret only in the broker. Tunnel claiming requires owner/admin authority. Hosting is off by default, idle-stops after at most 15 minutes, and has a maximum uptime watchdog. x-clankie-conversation-id selects an existing operator conversation. Typed administration has no op, raw RCON or secret arguments. Enrollment approval uses a previously verified Discord requester; operator arguments cannot select a Discord subject. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Safe status | | 400 | Invalid typed hosting command | | 403 | Operator authority required | | 503 | Hosting adapter unavailable | ### `GET /v1/minecraft/configuration` — Read owner Minecraft profiles, destination allowlist and play settings Offline host, port, version, username and explicit public destination grants. This operator configuration does not appear in model tools. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Offline profiles and public allowlist | | 403 | Operator authority required | ### `PUT /v1/minecraft/configuration` — Replace offline Minecraft profiles, public destination grants and play settings Owner-only settings write. DNS and SRV destinations are checked again before dial; model calls cannot supply raw host or authentication arguments. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated offline profiles and public allowlist | | 400 | Invalid settings | | 403 | Operator authority required | | 503 | Settings unavailable | ### `POST /v1/pairing/local/offer` — Mint the same-UID Mac companion handoff through owner-private IPC Self-hosted macOS only. The CLI uses the protected Unix socket, never unverified TCP. Local native transport and operator bearer are required. The five-minute offer is not published to the gateway or generic store. A new mint invalidates its predecessor. No-store; secrets never enter logs. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Single-use offer for the private handoff file (`application/json`) | | 401 | operator_authentication_required | | 403 | native_loopback_required | | 404 | not_found | | 503 | device_authentication_unavailable | ### `POST /v1/pairing/local/redeem` — Redeem the owner-local handoff once over native loopback Refuses browser pages, forwarded requests, the gateway and the LAN device doorway. Reuses the active companion's durable device ID and grants on reinstall. Revoked identities never revive. Session uses the existing device signer and includes host, grants and loopback control/relay routes. **Bearer:** none **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Existing device-session fields plus host name and loopback directRoute (`application/json`) | | 400 | malformed | | 403 | native_loopback_required or revoked | | 404 | not_found | | 409 | consumed | | 410 | expired | | 503 | device_authentication_unavailable | ## Captain ### `POST /v1/captain/channel-turns` — One Discord message becomes one captain turn **Bearer:** discord text or discord voice | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | header | `Prefer` | no | string, one of `respond-async` | Return 202 while the turn runs, then poll the delivery ID. Without this header, the request waits for the result. | **Request body** (required JSON) ```json { "schemaVersion": 1, "deliveryId": "yaak-delivery-1", "identity": { "presenceSessionId": "replace-me", "correlationId": "yaak-1", "profileHash": "unversioned", "characterId": "clankie", "credentialRef": "discord_bot", "transportKind": "bot" }, "trigger": { "kind": "message", "id": "1", "channelId": "123456789012345679", "actorId": "123456789012345680", "body": "hello from Yaak" } } ``` | Status | Meaning | | --- | --- | | 200 | Settled turn | | 202 | Turn accepted and still running | ### `GET /v1/captain/channel-turns/{deliveryId}` — Poll a Discord turn by its stable delivery ID **Bearer:** discord text or discord voice | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `deliveryId` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Settled turn | | 202 | Turn still running | | 404 | Turn unavailable after a service restart; resubmit the original delivery | ### `POST /v1/captain/presence` — Report captain lease heartbeat **Bearer:** captain **Request body** (required JSON) ```json { "schemaVersion": 1, "type": "captain.heartbeat", "eventId": "yaak-hb-1", "leaseId": "replace-me", "generationId": "replace-me", "occurredAt": "2026-08-15T20:00:00.000Z" } ``` | Status | Meaning | | --- | --- | | 202 | Accepted | ### `POST /operator/v1/dispatch` — Operator conversation contract One route, discriminated `op`: `list`, `roster`, `get`, `create`, `fork`, `close`, `send`, `cancel`, `replay`, `subagent_replay`, `tail`, `autonomy`, `terminal_tail`, `readopt_seat`, `worker_reports`, and `acknowledge_worker_reports`. Worker ownership/report operations require current operator authority and an exact conversationId. Re-adoption proves the original owner and same native thread. Worker reads return complete bounded unread payloads and ackDeliveryIds without acknowledging; only fully offered IDs may be marked read. Roster/fleet report summaries survive disappeared panes, and delivery receipts never establish reading. The TUI and relay both speak this. The `autonomy` command `set_goal` activates an owner-authored goal; `accept_goal` confirms an inactive model proposal. Service goals default to a finite 1,000,000-token budget and refuse activation for native heads. Starting, accepting or resuming a goal, and enabling autonomy, require an operator credential or active terminalControl device authority. A captain bearer alone receives 403 goal_owner_required. Seat scopes identify Herdr direct-send/projection threads. `cancel` interrupts one accepted run: the captain aborts the live model turn and the durable log settles that run as `cancelled`. `fork` clones the current Pi leaf into an ephemeral child conversation. Pane-scoped `send` and `state_stance` and `state_work` requests qualify their pane ID with the source socket. A mismatched `send` loses its pane association; a mismatched stance returns 409 without changing a worker. Seat-control operations return 503 with herdr_unavailable when the default runtime is unavailable; ordinary conversations and their durable records remain accessible. `presence` returns a strict snapshot with mood (`needs_you`, `thinking`, `in_voice`, `playing`, `leading`, `idle`, in that precedence), detail, since (source start timestamp or null when unknown), activeSeats (all live registered fleet seats), and optional pendingOwnerItem (oldest unanswered owner preference: conversationId, questionId, title, since). It uses optional cursor and waitMs (0–30000); an absent or changed cursor returns immediately. A current cursor parks until the projected sources change, sampled within 500 ms, or the wait expires. Unreachable is a client transport state, never a mood. Paired hosted devices may read it. includeFace true opts in to an optional face (legacy reads omit it). It drives the desktop screen separately from mood/body: needs_you, error, new_message, working or voice, in that precedence. Working follows thinking/leading or observed native captain/child work; voice follows in_voice. Otherwise idle/play omit the override. Live committed captain replies in owner conversations show new_message for 10s; failed owner turns show error for 30s or until the same conversation completes successfully. History, room, worker and fork activity do not raise these transient faces. Face expiry changes the cursor; clients honor Reduce Motion with a static face. Optional expression is a transient desktop action with UUID id and ISO expiresAt: kind emote adds animation (a hero sprite tag), kind say adds text (1–200 characters), and kind move adds x/y (normalized 0–1 on the current display, left-to-right/top-to-bottom). Expiry or owner quiet hours removes it and changes the cursor without changing the mood. Clients discard expired actions even while disconnected, honor Focus, and never take keyboard focus to show an expression. `fleet` returns one cursor-coherent snapshot. Optional `view: home` omits historical personas that are neither seated contacts nor channel participants. Seats, channels, tallies and edges are unchanged. Omitting the selector preserves the full directory; `personas` and conversation IDs still address retained history. `set_persona_role` (`personaId`, `role`: a built-in such as planner, designer, builder, tester, reviewer or researcher, or a custom role of 1-24 letters, digits, spaces and hyphens; null clears) returns the updated persona and rides the steer grant (ADR 0208). Roles are trimmed and whitespace-collapsed, built-ins fold to lowercase, and custom roles keep their casing and compare case-insensitively. `roles` returns the built-ins, then custom roles in use, as `{ role, builtIn, count }`. Fleet seats may carry `subagents` (running count and up to eight recent labels) for local Claude, Codex and registered OpenCode seats that were hired or whose chat is open. Recent entries optionally carry native `id`, `startedAt` and `endedAt`; absent means unknown. `work_items` takes an optional case-insensitive `label`; items may carry optional backend-native parent metadata. `work_item_write` requires the original owner with terminalControl and request {repoId,itemId, requestId UUID,command}. Commands assign work-metadata owner, add/remove a role label, or append a dependency. `work_item_write_receipt` takes repoId,itemId,requestId and reads the original scoped result. Both return an accepted envelope with receipt {requestId,outcome:applied|refused|uncertain, message,item?}. Repeated IDs and receipt reads never replay a write. `subagent_replay` takes `replay` with the parent's existing seat/persona `conversationId`, a native `subagentId`, `surfaceClientId` and the ordinary replay paging options. It needs only the chat grant and returns `subagentId` plus the ordinary normalized replay result. It reads a bounded local child window through the parent's native source without creating a conversation or granting send, resume or terminal control. Missing locators, changed parents and remote children return typed recovery. Older hosts may reject this operation while their fleet remains readable. `replay` optionally takes `direction: backward`, `turnLimit` (1..40, default 20) and `limit` (1..500, default 500 for backward). Omit `cursor` to open the newest window; pass the page's `previousCursor` as the next exclusive upper bound to load older history. Events remain chronological. `hasOlder` and `hasMore` say whether more retained history precedes the backward page. `nextCursor` is still the newest event in that page and can resume an ordinary forward `tail`. The event ceiling wins when one turn exceeds 500 events. Omitted direction preserves forward replay and tail behavior. Clients keep distinct older-history and live-tail cursors. `terminal_catalog` lists up to 48 panes per execution connection (16 connections). Each row carries runtime identity separately from Herdr workspace/tab IDs. Named terminal addresses pin their endpoint; tail, control and input refuse disabled or changed connections with a typed unavailable result, without falling back to the default fleet. Terminal catalog/tail require the relay's `terminalObserve` grant; control/input require `terminalControl`. `connections` reads bounded runtime and account metadata or manages named runtime connections. Commands are `list`, `connect_runtime` (id, session), `reconnect_runtime`, `disconnect_runtime` (id). It requires the operator captain lane; the device relay requires `steer`. No provider credential, socket path or session capability is returned. **Bearer:** captain or operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | header | `x-clankie-herdr-socket` | no | string | Caller Herdr session's exact socket path, required for pane identity. | **Request body** (required JSON) *List conversations* ```json { "op": "list", "schemaVersion": 1 } ``` *List live Herdr seats* ```json { "op": "roster", "schemaVersion": 1 } ``` *Create a global conversation* ```json { "op": "create", "schemaVersion": 1, "scope": { "kind": "global" }, "title": "Yaak probe" } ``` *Get one conversation* ```json { "op": "get", "schemaVersion": 1, "conversationId": "replace-me" } ``` *Fork an ephemeral side conversation* ```json { "op": "fork", "schemaVersion": 1, "parentConversationId": "replace-me" } ``` *Close a non-default conversation or discard a side fork* ```json { "op": "close", "schemaVersion": 1, "conversationId": "replace-me" } ``` *Send a message turn* ```json { "op": "send", "schemaVersion": 1, "turn": { "schemaVersion": 1, "kind": "message", "conversationId": "replace-me", "surfaceClientId": "yaak", "expectedRevision": 0, "message": "ping from Yaak" } } ``` *Replay a page* ```json { "op": "replay", "schemaVersion": 1, "replay": { "schemaVersion": 1, "conversationId": "replace-me", "surfaceClientId": "yaak", "limit": 50 } } ``` *Open the newest 20 turns* ```json { "op": "replay", "schemaVersion": 1, "replay": { "schemaVersion": 1, "conversationId": "replace-me", "surfaceClientId": "yaak", "direction": "backward", "turnLimit": 20, "limit": 500 } } ``` *Read autonomy state for one conversation* ```json { "op": "autonomy", "schemaVersion": 1, "conversationId": "replace-me", "command": { "action": "status" } } ``` | Status | Meaning | | --- | --- | | 200 | Discriminated result | | 403 | goal_owner_required for activation without owner/device authority | ### `GET /captain/v1/lanes` — Observable captain lanes **Bearer:** captain | Status | Meaning | | --- | --- | | 200 | Lane listing | ### `GET /v1/captain/evaluator` — Independent evaluator status, queue and recent reports **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Enabled state, harness, pane, errors, queue count and up to 50 recent jobs | | 401 | Operator authentication required | ### `POST /v1/captain/evaluator` — Enable, disable, focus or retry the independent evaluator **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Current evaluator status; operational startup errors appear in error | | 400 | Invalid command | | 401 | Operator authentication required | | 409 | Conflicting command or unavailable pane | ### `GET /v1/captain/issue-metrics` — Observed issue and worker tokens, elapsed time, checks and reviews Read-only projection of retained exact local native bindings, native transcripts, settled turn metrics, seat ledger edges and unresolved hire receipts. Unknown totals are null and partial/unsupported sources are explicit in coverage. No transcript, command or credential is returned. Window selects approval time or latest unfinished observation; totals cover the selected episode. Passed seat edges never establish approval. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `issue` | no | string, matching `^[A-Z][A-Z0-9]*-[0-9]+$` | | | query | `worker` | no | string | Exact worker label, terminal ID or native session reference. | | query | `since` | no | string | ISO timestamp; defaults to 24 hours before until. | | query | `until` | no | string | Exclusive ISO end; defaults to now. Maximum window 366 days. | | Status | Meaning | | --- | --- | | 200 | schemaVersion, window, issues, workers and coverage. | | 400 | Invalid filter or time window. | | 401 | Operator authentication required. | | 503 | Operator authentication unavailable. | ### `GET /v1/captain/turn-metrics` — Recent settled captain turns Counters, execution identity and reported usage for recent settled operator and Discord turns, newest first, from the durable `turn-settled.jsonl` the captain appends. Never a transcript, tool argument, tool output or credential. `execution` is the model, provider and effort that actually ran the turn, captured as it executed rather than read back from configuration. `usage` is `totalTokens` summed over the assistant messages the provider reported plus `reports`, the number that contributed. Both are `null` when unknown — a turn settled before the capture existed, or a provider that reported nothing. Null never means zero, and `contextTokensStart` / `contextTokensEnd` are context occupancy, never usage or a charge. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `limit` | no | integer, 1–100, default 20 | 1–100; clamped, never refused. Defaults to 20. | | query | `runId` | no | string | Narrow to one run. | | Status | Meaning | | --- | --- | | 200 | Bounded settled-turn metrics, newest first | | 401 | Operator authentication required | ### `GET /v1/captain/seat-context` — Resolve an operator seat to its service conversation and workspace **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `conversationId` | no | string | Existing global or workspace conversation; defaults to the global head. | | Status | Meaning | | --- | --- | | 200 | The service-owned conversationId and cwd (`application/json`) | | 400 | Invalid conversation ID | | 403 | Operator lane required | | 404 | Unknown conversation or a scope that does not run Clankie | ### `POST /v1/captain/seat-context` — Create a separate workspace chat for a new native operator seat **Bearer:** operator or captain **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 201 | A fresh service-owned conversationId and cwd; repeated launches create separate chats (`application/json`) | | 400 | Invalid workspace conversation creation request | | 401 | Authentication required | | 403 | Operator lane required | ### `POST /v1/seat/transcript` — Append redacted native seat display records to its selected conversation **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `conversationId` | no | string | Existing global or workspace conversation; defaults to the global head. | **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Accepted; repeated native entry IDs do not append duplicate events (`application/json`) | | 400 | Invalid transcript or conversation ID | | 401 | Authentication required | | 403 | Operator lane required | | 404 | Unknown captain conversation | | 409 | Native session is pinned to another conversation or retired by reset | | 413 | Request exceeds 1 MiB | ### `GET /v1/captain/prompt` — The system prompt a lane's session starts from Plain text, for a seat launcher in another harness. `sections` is a comma-separated subset of `identity`, `persona`, `reach`, `fleet`, `address`, `model`; omitted, it is the session's standard sections (`model` is the per-run card, asked for by name). The lane must be the bearer's own — the operator credential and the console captain token are `operator`, a Discord bridge bearer is the lane it serves. An explicit conversationId adds that workspace's agent instructions with source paths. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `conversationId` | no | string | Existing operator global/workspace conversation; omitted selects the global head. | | query | `lane` | no | string, one of `operator`, `discord_voice`, `discord_presence`, `gameplay` | Defaults to the bearer's own lane. | | query | `sections` | no | string, e.g. `persona,model` | | | Status | Meaning | | --- | --- | | 200 | The assembled prompt (`text/plain`) | | 400 | Unknown lane or section name | | 403 | Lane is not the bearer's own | ### `GET /v1/captain/memory-card` — The memory card that lane's next run injects Plain text, for a per-turn hook. Filtered by lane the same way the pi session's own injection is: operator-private notes reach only the operator lane. An empty store still returns a labeled card. The lane must be the bearer's own. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `lane` | no | string, one of `operator`, `discord_voice`, `discord_presence`, `gameplay` | Defaults to the bearer's own lane. | | Status | Meaning | | --- | --- | | 200 | The lane's recall card (`text/plain`) | | 400 | Unknown lane | | 403 | Lane is not the bearer's own | ### `GET /v1/mcp` — Server-to-client notification stream for an open session Requires `mcp-session-id`; the bearer must resolve to the session's lane. **Bearer:** operator or captain or discord text or discord voice | Status | Meaning | | --- | --- | | 200 | SSE stream | | 400 | No session id | | 403 | The session belongs to another lane or conversation | | 404 | Unknown or expired session | ### `POST /v1/mcp` — A lane's tool bank over streamable-HTTP MCP Streamable-HTTP MCP over Clankie's authored tool registry. The bearer selects the lane and the lane selects the tools: the operator credential and the console captain token are `operator`, a Discord bridge bearer is the lane it serves, and a connection lists exactly that lane's authority plan (authored tools, the browser catalog, and the connected-service tools that lane may reach). An `initialize` POST opens a session and returns its id in `mcp-session-id`; every later request must carry that header and resolve to the same lane and conversation. `DELETE` ends the session. `clankie mcp` is the stdio bridge to this route (ADR 0152). **Bearer:** operator or captain or discord text or discord voice | Status | Meaning | | --- | --- | | 200 | JSON-RPC response for this request | | 400 | No session id on a non-initialize request | | 403 | The session belongs to another lane or conversation | | 404 | Unknown or expired session | ### `DELETE /v1/mcp` — End an MCP session **Bearer:** operator or captain or discord text or discord voice | Status | Meaning | | --- | --- | | 200 | Session closed | | 403 | The session belongs to another lane or conversation | | 404 | Unknown or expired session | ### `GET /v1/seat/events` — Long-poll the seat's outbox Wakes, herdr completion watches, and room escalations queued for a bound head (ADR 0152). Polling is what binds the seat: `clankie mcp` asks again the moment a poll returns and pushes each event into the session as a channel notification. Operator bearer only; a Discord bridge bearer is 403. `wait` is capped at 30000 ms. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `conversationId` | no | string | Existing operator global/workspace conversation; omitted selects the global head. | | query | `wait` | no | integer, 0–30000, default 0 | | | Status | Meaning | | --- | --- | | 200 | Events taken from the outbox, possibly none (`application/json`) | | 403 | Not the operator lane | ### `POST /v1/seat/events/{id}/reply` — Answer an escalation from the seat The reply lands in the escalating conversation as his own message and settles that run. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `conversationId` | no | string | Existing operator global/workspace conversation; omitted selects the global head. | | path | `id` | yes | string | | **Request body** (required JSON) ```json { "schemaVersion": 1, "text": "On it — the build is green." } ``` | Status | Meaning | | --- | --- | | 200 | Replied | | 404 | Nothing is waiting on that event | ### `GET /v1/fleet/seats/{paneId}/events` — Long-poll a fleet seat's mailbox A DM or room turn queued for a bound fleet seat (ADR 0161). Polling is what binds the mailbox: `clankie mcp --seat` asks again the moment a poll returns and pushes each event into the session as a channel notification. Operator bearer only; a Discord bridge bearer is 403. `wait` is capped at 30000 ms. `unknown_seat` (404) is the pane before herdr has classified the harness; the bridge retries. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | | query | `wait` | no | integer, 0–30000, default 0 | | | Status | Meaning | | --- | --- | | 200 | Events taken from the mailbox, possibly none (`application/json`) | | 403 | Not the operator lane | | 404 | No messageable agent in that pane | ### `GET /v1/fleet/seats/{paneId}/peers` — List native seats in the calling worker's fleet Requires the local process listener or registered remote relay's exact native pane/session proof. Operator and fleet bearers alone do not identify a peer sender. Returns sender and recipient seat IDs, panes, harnesses, titles and native binding hashes. The owner setting fleet.peerMessages=off refuses discovery. No workspace or account authority is granted by a peer message (ADR 0213 amendment). **Bearer:** none | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Current same-fleet native seat bindings | | 403 | Peer messaging off or native sender proof unavailable | ### `POST /v1/fleet/seats/{paneId}/peer-messages` — Send one same-fleet peer message through native seat delivery Uses the same native delivery path as message_seat, with server-authored sender attribution and agent-output framing. Requires native process admission, the sender binding, and the exact same-fleet recipient binding returned by peers. Rechecks the owner switch and native bindings before dispatch. Persists the original delivery UUID before sending. Uncertain attempts reconcile through GET only; neither a duplicate UUID nor a new follow-up replays the original. Records exchanges and receipts in the service audit and Clankie's agent-role transcript without an owner turn. **Bearer:** none | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | **Request body** (required JSON) ```json { "schemaVersion": 1, "seatId": "pc/recipient-terminal", "recipientBinding": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "text": "The shared interface is ready.", "delivery": { "id": "10000000-0000-4000-8000-000000000001", "binding": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } } ``` | Status | Meaning | | --- | --- | | 200 | Exact receipt with deliveryId, bindings, fingerprint, outcome and deliveryStage | | 400 | Invalid peer message | | 403 | Native peer sender proof unavailable | ### `GET /v1/fleet/seats/{paneId}/peer-messages/{id}` — Inspect the original peer delivery receipt without resending Requires the original sender's native process/session, original binding, delivery UUID and fingerprint. The fingerprint covers recipient seat, recipient binding and normalized text. Available while peer messaging is off. May match the original native transcript or exact channel/hook acknowledgment; absence of proof remains uncertain while the original recipient is bound. A lost recipient binding settles to recipient_gone with outcome unconfirmed: delivery remains unknown, never resends, and no longer blocks fresh messages. Older settled bodies are pruned after 100 messages; exact compact receipts and unresolved originals are retained. **Bearer:** none | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | | path | `id` | yes | string | | | query | `binding` | yes | string, matching `^[a-f0-9]{64}$` | | | query | `fingerprint` | yes | string, matching `^[a-f0-9]{64}$` | | | Status | Meaning | | --- | --- | | 200 | Original receipt, with no new send attempt | | 400 | Invalid original receipt reference | | 403 | Native peer sender proof unavailable | | 404 | No exact original receipt available; delivery remains uncertain | ### `POST /v1/fleet/seats/{paneId}/hook` — Report a hired seat's lifecycle hook The clankie-worker plugin's hooks, forwarded by `clankie seat-hook` from inside the seat's pane (VUH-1458). A Stop or StopFailure settles the seat's turn for its adapter and any completion watch, with the harness's own final text as `lastMessage`. The session must be the Claude session herdr reports in that pane. Lifecycle reports accept the operator or bound fleet lane. Only native process proof from the request transport can register or drain a next-turn receiver. UserPromptSubmit may return additionalContext and messageIds, with deliveryStage uncertain until the hook writes stdout and posts deliveredMessageIds on the same session. That acknowledgment confirms bridge delivery, never model consumption. Held mail expires within 24 hours; an uncertain take is never replayed. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | **Request body** (required JSON) ```json { "schemaVersion": 1, "event": "Stop", "sessionId": "10000000-0000-4000-8000-000000000001", "lastMessage": "Tests pass." } ``` | Status | Meaning | | --- | --- | | 200 | Recorded | | 400 | Not a seat hook | | 403 | Not the operator lane | | 404 | That pane holds no Claude seat with this session | ### `POST /v1/fleet/seats/{paneId}/tool-catalog` — Report the occupying native harness's accepted Clankie tools A linked Claude mod or Codex startup probe reports raw Clankie bridge names from the actual native session catalog. The service verifies the current pane, harness and session against Herdr, compares the bridge's served expectations and retains a matched/mismatch/unverified diagnostic. This report never grants tools or routes messages. Native probe failures carry error and remain unverified; process liveness is separate evidence. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `paneId` | yes | string | | **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Session-bound status | | 400 | Invalid native catalog report | | 403 | Fleet admission or native pane/session identity refused | ### `POST /v1/conversation-heads` — Designate or clear an explicit escalation head Operator only. Owner must be an existing runnable or canonical room conversation; head must be a writable global/workspace conversation. Rejects self/cycles. Grants no room authority. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated conversation projection | | 400 | Invalid strict request | | 401 | Operator authentication required | | 409 | Owner/head unavailable or cycle | ### `GET /v1/body-leases` — Conversation ownership of Clankie's body resources Operator or active paired device observe grant. Read-only public projection; no incarnation, actor, room text or request content. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Public body lease status (`application/json`) | | 403 | Observe authority required | ### `POST /v1/body-leases` — Explicitly acquire, renew, release, queue, ask or recover a body resource Computer ownership uses /v1/computer; this endpoint refuses computer mutations. Strict BodyLeaseRequest protocol union. Operator bound to a selected runnable conversation. Renew/release require the acquisition incarnation. Ordinary release is owner-only and confirms termination; explicit recover uses the current private host claim under operator authority. Queue/ask are bounded notifications and never run body effects. **Bearer:** operator **Request body** (required JSON) ```json { "action": "queue", "resource": "browser", "conversationId": "conversation-1", "request": "Notify me when the browser is free", "ttlMs": 300000 } ``` | Status | Meaning | | --- | --- | | 200 | Typed acquired, renewed, released, queued or asked result | | 409 | Typed busy with public holder and queue/ask choices, or rejected reason; no silent takeover | ### `POST /v1/computer/authority` — Recheck the owning conversation's computer authority Native observation hosts delegate their existing operator bearer and bound conversation to this service before each operation. Uses the same identity and grant checks as /v1/computer, even without a local adapter. A conversation ID is not a grant. Nothing is captured, driven or leased. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Matching conversationId and authorized true | | 400 | Invalid strict request | | 401 | Absent or revoked operator conversation authority | | 413 | Request exceeds 4 KiB | ### `POST /v1/computer` — Drive the conversation-leased computer body Strict ComputerRequestSchema (conversationId and command) from @clankie/interactive-environment. Operator authority bound to an existing runnable conversation, rechecked at each input. Actions: status, acquire, renew, release, revoke, recover, inventory, capture, frame, input. Capture returns fresh screenshot metadata and image-pixel coordinate mapping; frame returns bounded PNG media separately. Input is ordered and uses screenshotId plus a persistent requestId; identical retries reconcile, never replay. Failed and uncertain are distinct from confirmed. Expiry/restart/uncertainty retain the lease until host stop proof. Local macOS Peekaboo and an explicitly attached Windows native-harness host, read-only unless its owner explicitly sets allowInput true before acquisition; lease and status record that choice. Opted-in full Windows clients capture native accessibility strings and accept one foreground primitive per fresh observation, with an exact changed accessibility postcondition (input.expect.field/equals); scroll also needs an image-pixel at point. Native dispatch alone cannot confirm an effect. Host Win32 person activity is rechecked before input; shell/system targets and system-switching shortcuts refuse. Every native error retires the host. Input release requires James's W8 live stop proof. Observation-only clients remain read-only. No additional model loop starts; hosted display routes are not implemented. **Bearer:** operator **Request body** (required JSON) ```json { "conversationId": "global-default", "command": { "action": "status" } } ``` | Status | Meaning | | --- | --- | | 200 | Command result or typed busy/rejected receipt | | 400 | Invalid strict request | | 401 | Operator conversation authority required | | 409 | Stale reference | | 413 | Request exceeds 2 MiB | | 503 | Computer body unavailable on this host | ### `GET /v1/captain/readiness` — Read secret-free chat setup readiness Any active paired device or owner operator may read this. A live native operator bridge reports ready independently of the fallback model. Otherwise the shared captainReadiness rule reports no_model or no_credential, with model and provider ids when known. No credential material or authentication method leaves the body. Responses are no-store. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Ready boolean, optional model/providerId, and reason when unready | | 401 | Authentication required | | 503 | Readiness unavailable | ## Discord ### `GET /v1/discord/settings` — Read the server role, fleet and tracking setup with Advanced settings Operator-authorized non-secret settings, their revision and the shared setup definition. Normal setup connects a server with participant or admin, fleetEnabled and trackingLevel (off, project_updates, project_activity, all_issues). Raw IDs and machine grants stay under Advanced. The role-correct invitation and gateway checks are read-only. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | settings, revision and optional setup definition, role invite and permission checks | | 403 | Operator authority required | ### `POST /v1/discord/settings` — Save revision-fenced non-secret Discord settings Requires settings and expectedRevision. Connecting a server projects its ingress, presence and voice scope while preserving machine grants and lab opt-in. Stale writes are refused. Saving never posts to Discord. Admin actions refuse server deletion and ownership transfer at the body. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Saved settings, new revision and setup metadata | | 400 | Invalid or unknown settings | | 403 | Operator authority required or revoked | | 409 | Settings revision changed | ### `POST /v1/discord/setup/test-post` — Explicitly send one owner-requested setup message to a text room Settings-level operator authority is required; Observe or Steer cannot post. The current settings revision, connected account and Send Messages evidence are checked before dispatch. Reads, setup opening and picker edits never call this operation. Missing native receipts are unconfirmed and never retried. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | posted with messageId, unavailable with reason, or unconfirmed with post_receipt_unavailable | | 400 | Invalid request | | 403 | Operator authority required or revoked | | 409 | Settings or active account configuration changed | ### `POST /v1/discord/ingress` — Admit an encrypted Discord turn or scoped voice callback Remote connection API, not an operator bearer. P-256 ECDH and AES-GCM protect request and response; a 60-second Ed25519 fleet permit binds the tenant, installation, event digest and ephemeral response recipient. Durable delivery dedupe prevents repeated captain effects. Voice callbacks use the existing briefing, attributed captain handoff and three voice self-tools. See docs/discord-ingress.md. **Bearer:** none **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Sealed pending, reply, voice operation, silent or failed result | | 401 | Invalid, expired, unsealed or wrongly scoped event | | 409 | Delivery id already binds another event | | 413 | Envelope exceeds the byte limit | | 429 | Durable inbox is at capacity | | 503 | Durable admission unavailable | ### `GET /v1/discord/user-session/opt-in` — Current user-session opt-in **Bearer:** captain or operator | Status | Meaning | | --- | --- | | 200 | Opt-in or null | ### `POST /v1/discord/stream-watch` — Bridge report of shares and optional stills **Bearer:** captain | Status | Meaning | | --- | --- | | 204 | Report accepted | ### `GET /v1/discord/readiness` — Discord captain readiness Requires a Discord text or voice bridge bearer. **Bearer:** discord text or discord voice | Status | Meaning | | --- | --- | | 200 | Ready | | 503 | A required check is down | ### `POST /v1/discord/voice-briefing` — Compose the realtime voice briefing Voice-bridge bearer only. Request carries ids; persona is owner-authored. **Bearer:** discord voice **Request body** (required JSON) ```json { "schemaVersion": 1, "guildId": "123456789012345678", "channelId": "123456789012345679", "consentedUserIds": [ "123456789012345680" ] } ``` | Status | Meaning | | --- | --- | | 200 | Bounded instructions + briefing | ### `POST /v1/discord/presence-session-events` — Apply a Discord presence phase event **Bearer:** discord text or discord voice **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Accepted or idempotent replay | ### `GET /v1/discord/presence-sessions` — Full presence session records **Bearer:** captain | Status | Meaning | | --- | --- | | 200 | Session list | ### `GET /v1/discord/voice-history` — Completed voice stays **Bearer:** captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `limit` | no | integer, 1–32, default 5 | | | Status | Meaning | | --- | --- | | 200 | Recent stays | ### `POST /v1/discord/presence-actions` — Execute a policy-gated Discord action Needs a live-session claim in headers: `x-clankie-discord-presence-session`, `x-clankie-discord-presence-phase`, `x-clankie-discord-presence-revision`. **Bearer:** discord text or discord voice | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | header | `x-clankie-discord-presence-session` | yes | string | | | header | `x-clankie-discord-presence-phase` | yes | string | | | header | `x-clankie-discord-presence-revision` | yes | integer | | **Request body** (required JSON) ```json { "schemaVersion": 1, "idempotencyKey": "yaak-typing-1", "action": "discord.presence.typing_start", "identity": { "presenceSessionId": "replace-me", "correlationId": "yaak-1", "profileHash": "unversioned", "characterId": "clankie", "credentialRef": "discord_bot", "transportKind": "bot" }, "payload": { "kind": "typing_start", "channelId": "123456789012345679" } } ``` | Status | Meaning | | --- | --- | | 200 | Action result | | 409 | Stale live claim or action unavailable | ## Memory ### `GET /v1/memory` — Browse every memory note and Discord person fact Notes persist until forgotten. Catalog retention fields remain legacy compatibility metadata and no longer impose a count quota. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Complete operator-only memory catalog | ### `POST /v1/memory/discord-people/proposals` — Upsert a Discord person fact **Bearer:** discord text or discord voice **Request body** (required JSON) ```json { "schemaVersion": 1, "proposalId": "yaak-proposal-1", "fact": { "schemaVersion": 1, "factId": "yaak-fact-1", "subject": { "guildId": "123456789012345678", "userId": "123456789012345680" }, "kind": "person-fact", "body": "Prefers short replies.", "visibility": { "scope": "guild" }, "provenance": { "correlationId": "yaak-1", "sourceEventId": "yaak-src-1", "sourceSurface": "discord_text", "rawTranscript": false }, "confidence": 0.8, "createdAt": "2026-08-15T20:00:00.000Z", "updatedAt": "2026-08-15T20:00:00.000Z" } } ``` | Status | Meaning | | --- | --- | | 201 | Stored | ### `GET /v1/memory/discord-people/{guildId}/{userId}` — Recall Discord person facts **Bearer:** discord text or discord voice | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `guildId` | yes | string | | | path | `userId` | yes | string | | | query | `channelId` | no | string | | | query | `query` | no | string | | | Status | Meaning | | --- | --- | | 200 | Facts and optional recall card | ### `DELETE /v1/memory/discord-people/{guildId}/{userId}` — Delete every fact for a Discord person **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `guildId` | yes | string | | | path | `userId` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Deleted ids | ### `GET /v1/memory/discord-people/{guildId}/{userId}/export` — Operator export of a Discord person **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `guildId` | yes | string | | | path | `userId` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Export bundle | ### `PATCH /v1/memory/discord-people/{guildId}/{userId}/{factId}` — Edit one Discord person fact without changing its identity or provenance **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `guildId` | yes | string | | | path | `userId` | yes | string | | | path | `factId` | yes | string | | **Request body** (required JSON) ```json { "body": "Prefers concise replies.", "confidence": 0.9 } ``` | Status | Meaning | | --- | --- | | 200 | Updated fact | | 404 | Fact not found | ### `DELETE /v1/memory/discord-people/{guildId}/{userId}/{factId}` — Forget one Discord person fact **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `guildId` | yes | string | | | path | `userId` | yes | string | | | path | `factId` | yes | string | | | Status | Meaning | | --- | --- | | 204 | Forgotten | ### `GET /v1/memory/captain-episodes` — Recall memory notes for a lane Without `query`, the bounded recent card. With one, on-demand search over every persistent note, filtered by what that lane may see. Both branches answer with a rendered card and never with note records, so no caller reads another memory's provenance ids. **Bearer:** captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | query | `lane` | yes | string, one of `operator`, `discord_voice`, `discord_presence`, `gameplay` | | | query | `query` | no | string | Search terms; every term must appear in the note or the room it happened in. | | query | `limit` | no | integer, 1–… | Matches to return (default 8, capped at 32). | | Status | Meaning | | --- | --- | | 200 | Recall card — the recent one, or the search's when a query was given | ### `POST /v1/memory/captain-episodes` — Record a self-authored memory note Recording only ever adds. An id the store already holds is a conflict, not an update; a byte-identical retry returns the original. Editing an existing note goes through PATCH. A Discord bearer may only write the lane it serves (`discord_text` writes `discord_presence`, `discord_voice` writes its own). The trusted authenticator must also supply an exact admitted episodeSource. A generic bearer alone is refused. The host stamps sourceConversationId and provenance; request-supplied source identifiers confer no authority. Notes persist until forgotten. The optional retained field is legacy compatibility metadata and does not control lifetime or a count quota. **Bearer:** captain **Request body** (required JSON) ```json { "schemaVersion": 1, "episodeId": "yaak-ep-1", "lane": "operator", "targetId": "self", "summary": "Tried the HTTP API from Yaak.", "visibility": "operator_private", "provenance": { "characterId": "clankie", "sessionId": "captain", "selfAuthored": true, "rawTranscript": false }, "occurredAt": "2026-08-15T20:00:00.000Z" } ``` | Status | Meaning | | --- | --- | | 200 | Recorded id | | 403 | Missing exact host source binding, or lane/target does not match that source | | 409 | The note id already exists with different content. Nothing was changed. | ### `PATCH /v1/memory/captain-episodes/{lane}/{episodeId}` — Edit one memory note or its visibility Explicit operator management can edit notes across source conversations. The retained field remains accepted as legacy compatibility metadata; every note persists until forgotten regardless of its value. **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `lane` | yes | string, one of `operator`, `discord_voice`, `discord_presence`, `gameplay` | | | path | `episodeId` | yes | string | | **Request body** (required JSON) ```json { "summary": "Corrected note about what happened.", "visibility": "operator_private" } ``` | Status | Meaning | | --- | --- | | 200 | Updated note | | 404 | Note not found | | 409 | Legacy capacity refusal; persistent notes no longer use retention quotas | ### `DELETE /v1/memory/captain-episodes/{lane}/{episodeId}` — Forget one memory note **Bearer:** operator | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `lane` | yes | string, one of `operator`, `discord_voice`, `discord_presence`, `gameplay` | | | path | `episodeId` | yes | string | | | Status | Meaning | | --- | --- | | 204 | Forgotten | ## Embodiment ### `GET /v1/games/configuration` — Pokémon play defaults **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | Gameplay settings and restart instruction. An absent token override defaults to 250000 charged tokens. | ### `PUT /v1/games/configuration` — Replace gameplay configuration for subsequent sittings Owner operator or a device with Take Control. Restart Clankie to apply defaults. Usage caps include mind, commentary and interrupted decisions; the last call can cross a threshold. Missing usage reserves 16000 tokens and makes estimated cost unknown, which stops a dollar-capped session. **Bearer:** operator or device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Updated gameplay configuration and restart instruction | | 400 | Invalid configuration | | 401 | Authentication required | | 403 | Owner authority required | | 503 | Settings unavailable | ### `POST /v1/embodiment/intents` — Ask to start or stop play Select an existing writable captain conversation with conversationId. The operator identity is authenticated separately; originLane and requestedBy are host-stamped. A competing body lease returns a typed refusal. In-process captain tools carry their own admitted conversation identity. Budget accepts maxTokens (default 250000), maxCostUsd, maxTurns and maxDurationMs. Terminal receipts distinguish stopped, budget_exhausted, mind_unavailable and world_ended. **Bearer:** operator **Request body** (required JSON) *start* ```json { "kind": "start", "schemaVersion": 1, "intentId": "yaak-start-1", "conversationId": "replace-with-selected-conversation", "originLane": "operator", "requestedBy": "local-operator", "requestedAt": "2026-08-15T20:00:00.000Z", "environmentId": "pokemon-firered", "budget": { "maxTokens": 250000, "maxCostUsd": 1 } } ``` *stop* ```json { "kind": "stop", "schemaVersion": 1, "intentId": "yaak-stop-1", "conversationId": "replace-with-selected-conversation", "originLane": "operator", "requestedBy": "local-operator", "requestedAt": "2026-08-15T20:00:00.000Z", "sessionId": "replace-me" } ``` | Status | Meaning | | --- | --- | | 200 | Accepted, refused, or stop requested. A stop request does not prove that the executor has ended. | | 409 | Body lease refusal with bodyLease naming the held conversation or missing authority; queue or ask rather than taking over. | ### `GET /v1/embodiment/sessions/live` — Live play session Captain or operator. **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | `{ session }` or `{ session: null }` | ### `POST /v1/embodiment/sessions/live/stop` — Operator kill-switch (ordinary stop intent) **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Stop submitted | | 404 | Not playing | ### `POST /v1/embodiment/sessions/live/guide` — Suggest an objective or approach to the owning Pokémon mind Requires the selected conversation's live play ownership and current authority. Direction is bounded context; the play mind chooses its own objective and action. Does not start play. **Bearer:** operator **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Queued direction or not_playing refusal | | 400 | Invalid direction | | 401 | Operator authentication required | | 403 | Selected conversation authority required | | 409 | Play ownership or authority changed | | 503 | Play direction unavailable | ### `GET /v1/embodiment/sessions/live/activity` — Present-tense self-observation **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | not_playing, pending, or snapshot | ### `GET /v1/embodiment/sessions/live/still` — One still of the live play screen Read-only glance. PNG as base64. No controller. **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | not_playing, pending, or still | ### `GET /v1/embodiment/sessions/live/story` — Bounded story of this playthrough Journal projection — objective, maps, last notable moments. Not the raw log. **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | not_playing, pending, or card | ### `GET /v1/embodiment/sessions/{id}` — One embodiment session **Bearer:** captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Session | | 404 | Unknown | ## Browser ### `GET /v1/browser/harnesses` — Computer-use harnesses here and on linked Windows fleets Re-probes which installed harnesses (Codex computer use, Claude in Chrome) are signed in and configured for the owner's apps or Chrome (ADR 0199), including Codex on linked Windows fleets. Optional platform and machineId identify those reports; signed-out, disabled and unavailable probes remain explicit. Configuration does not prove app grants or input. Detection only; nothing is started. `detected` is false on a hosted body. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Harness list (`application/json`) | | 401 | Operator bearer required | ### `GET /v1/browser/tools` — Live browser tool catalog Browser Use Pi tools; native JavaScript is listed only for operators. **Bearer:** operator or captain | Status | Meaning | | --- | --- | | 200 | Catalog | | 503 | Browser host unavailable | ### `POST /v1/browser/call` — Call a browser tool Calls one Browser Use Pi tool under the selected operator conversation and existing machine authority. Direct captain requests without a host-admitted conversation binding are refused. **Bearer:** operator or captain | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | header | `x-clankie-conversation-id` | yes | string | Exact selected runnable operator conversation | **Request body** (required JSON) ```json { "schemaVersion": 1, "tool": "browser_use_snapshot", "arguments": {} } ``` | Status | Meaning | | --- | --- | | 200 | Result or typed refusal | ## Media ### `POST /v1/activity/shares` — Control owner-authorized Activity media shares Projects existing play or an exact hash-bound delivered PNG/GIF/MP4/WAV/MP3. Tenant and installation scope are service-assigned. No source path/URL or new capture permission is accepted. Hosted owner controls use the existing encrypted paired-device operator bridge; the private edge verifies the destination and returns a scoped launch/stop receipt. A confirmed invite is not evidence that an audience opened it. Local delegated shares retain their separate read grants; official local admission is a pending decision. Source switch advances generation and invalidates old media/grants. Stop, expiry and producer loss are terminal. Controls never retry; uncertainty carries operation and share/generation when known. Successful responses use Cache-Control no-store. See docs/cli.md#activity-shares. **Bearer:** operator **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | List returns sessions; image/switch returns session; grant returns grant and expiresAt; stop returns stopped true | | 400 | Invalid request or PNG media | | 401 | Current owner operator authentication required | | 404 | Exact artifact or active share unavailable | | 409 | Stale generation | | 429 | Local owner share request queue is at capacity | | 503 | Activity unavailable or possibly dispatched private control with typed uncertain outcome | ### `POST /v1/activity/viewer` — Read hosted Activity media with a live audience authorization Internal outbound-body route. Requires a fleet-signed media-only permit bound to the exact session and opaque live edge authorization. The body independently revalidates current audience authority and destination, including on quiet streams. Neither operator nor captain bearer grants this route. The gateway exposes read-only media to an admitted SDK viewer; it never exposes controller credentials or chooses a host from browser input. **Bearer:** none **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Bounded read-only session and PNG/PCM lifecycle envelopes; live authority required throughout (`application/x-ndjson`) | | 403 | Invalid | | 503 | Hosted Activity unavailable | ### `POST /v1/media/images` — Generate or edit an image Set personaReference true for self-depiction using only owner appearance references, never vibe images or frames. Mutually exclusive with sourceRef. OpenAI and Google support multiple references; the current Grok adapter supports one. **Bearer:** operator or captain **Request body** (required JSON) ```json { "schemaVersion": 1, "prompt": "a small robot sitting at a terminal", "aspectRatio": "1:1" } ``` | Status | Meaning | | --- | --- | | 200 | ok or refused | | 503 | Media plane unconfigured | ### `POST /v1/media/videos` — Start or resume a video render **Bearer:** operator or captain **Request body** (required JSON) ```json { "schemaVersion": 1, "prompt": "a small robot waving", "durationSeconds": 6 } ``` | Status | Meaning | | --- | --- | | 200 | ok, pending, or refused | ## Pairing ### `POST /v1/pairing/offer` — Mint a one-time pairing offer The offer secret appears once in the response. Do not log it. **Bearer:** operator | Status | Meaning | | --- | --- | | 200 | Offer wire (deep link + code); ordinary offers also include the existing single-use localCode for same-Mac direct pairing; review offers omit localCode | ### `POST /v1/pairing/redeem` — Redeem an offer into a pending device Unauthenticated. The offer secret or typed code is the capability. **Bearer:** none **Request body** (required JSON) ```json { "code": "123-456", "device": { "name": "Yaak", "platform": "macos" } } ``` | Status | Meaning | | --- | --- | | 200 | Pending device + completion token; a configured gateway also returns the host-scoped base URL | ### `POST /v1/pairing/complete` — Activate a pending device **Bearer:** none **Request body** (required JSON) ```json { "completionToken": "replace-me", "acceptedGrants": { "chat": true, "steer": true, "terminalObserve": true, "terminalControl": false } } ``` | Status | Meaning | | --- | --- | | 200 | Device token issued; relayUrl is the host-scoped gateway base when configured; optional directRoute carries explicitly configured controlPlaneUrl and relayUrl origins | ### `POST /v1/hosted/pair-offer` — Mint an authenticated encrypted offer for the hosted account page Managed bodies only. Plain gateway exchange carrying a single-use signed fleet ticket, not an account bearer. Version 2 binds the browser P-256 public-key hash and nonce into the ticket. The body signs the encrypted answer with its fleet-registered Ed25519 key. Replay refusal and the five-per-minute rate limit survive a service restart. See the hosted deployment README for the exact transcript and key derivation. **Bearer:** none **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Signed encrypted offer, with no plaintext link | | 401 | Invalid, expired, misbound, or consumed ticket | | 404 | Self-hosted body | | 429 | Too many offers; Retry-After is 60 seconds | | 503 | Pairing publication or replay persistence unavailable | ## Devices ### `GET /v1/composer/transcription/status` — Read local or managed availability and included allowance Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device | Status | Meaning | | --- | --- | | 200 | Availability and bounds (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `POST /v1/composer/transcription/begin` — Begin one bounded composer recording Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Device-scoped draft receipt; only complete carries text (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `POST /v1/composer/transcription/chunk` — Append one recording chunk at an exact byte offset Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Device-scoped draft receipt; only complete carries text (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `POST /v1/composer/transcription/commit` — Transcribe a completed recording once Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Device-scoped draft receipt; only complete carries text (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `POST /v1/composer/transcription/cancel` — Discard a recording and prevent late draft delivery Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Device-scoped draft receipt; only complete carries text (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `POST /v1/composer/transcription/receipt` — Reconcile the same recording without repeating provider dispatch Active ordinary paired device with chat access only; captain/operator bearers and support devices are refused. Gateway calls preserve the existing encrypted device envelope. No-store; never logs audio or text. **Bearer:** device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Device-scoped draft receipt; only complete carries text (`application/json`) | | 400 | Invalid request or PCM WAV audio | | 401 | Active paired device session required | | 403 | Forbidden | | 404 | Request does not belong to this device | | 409 | Recording id | | 413 | Request exceeds the bounded chunk envelope | | 429 | Capacity or rate limit reached | | 503 | Managed transcription unavailable | ### `GET /v1/devices/self/diagnostics-default` — Read the account diagnostics default Returns only diagnosticsDefault. A hosted body reads it through its signed fleet settings request; unavailable account authority returns 503. Self-hosted bodies use the existing true default. No-store. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Object with diagnosticsDefault boolean only | | 401 | Authentication required | | 503 | Account settings unavailable | ### `GET /v1/devices` — List paired devices Owner operators and active paired devices may read these secret-free rows. Optional lastSeenAt records successful authenticated device activity, at most once per minute per device. `push` carries the device's last delivery state — `{ registrationId, sequence, enabled }` — so the listing is truthful about a device that turned notifications off or whose registration the gateway invalidated. The APNs token and delivery key are never here (ADR 0159). **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Secret-free device rows | ### `POST /v1/devices/{id}/revoke` — Revoke a device Paired callers require Take Control. Local revocation is durable before the hosted security and wake-key updates. A 503 does not confirm success; retry the same device id. Fleet tombstones defeat restored old disks. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Revoked row | | 403 | Take Control required | | 404 | Unknown | | 503 | Hosted revocation could not be confirmed | ### `GET /v1/devices/self` — Device reads its own registration **Bearer:** device | Status | Meaning | | --- | --- | | 200 | Self row, including optional directRoute with explicitly configured controlPlaneUrl and relayUrl origins | ### `POST /v1/devices/wake-key` — Register a paired device wake public key on a managed body Managed bodies only; self-hosted installations answer 404. The gateway carries this route inside the encrypted device envelope. The live device session selects deviceId; callers cannot select a different device. Registration and device revocation are serialized. Revocation disables local access immediately, then removes the fleet key, retrying failures. **Bearer:** device **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Registered; returns deviceId | | 400 | Malformed public key request | | 401 | Device session is unknown, expired, or revoked | | 404 | Self-hosted body | | 503 | Fleet registration unavailable; retry later | ### `POST /v1/devices/self/push` — Device records or clears its push delivery reference Body is `{ registrationId, sequence, enabled }`. The host stores only the reference, its version and whether delivery is on — never the APNs token or the delivery key, which the app registers with the gateway directly (ADR 0159). Disabling records that state at its version rather than forgetting it, so ordering survives it. An older `sequence` is `409 stale_push_registration`. The same `sequence` is accepted only when it restates what was already recorded: a different `registrationId`, or the same one with `enabled` flipped, is `409 conflicting_push_registration`. That is also what refuses a re-enable at the version a gateway invalidation was recorded against. **Bearer:** device | Status | Meaning | | --- | --- | | 200 | Accepted reference, echoed with its enabled state | | 400 | Malformed body | | 401 | Device session is unknown, expired, or revoked | | 409 | Stale or conflicting registration version | ### `POST /v1/devices/self/session/refresh` — Renew a device session token **Bearer:** device | Status | Meaning | | --- | --- | | 200 | New token; relayUrl republishes the host-scoped gateway base when configured; optional directRoute carries explicitly configured controlPlaneUrl and relayUrl origins | ## Support ### `GET /v1/support/grants` — Read owner-issued support grants Exact active owner device with terminalControl or the local operator; captain and support-device authority are refused. Hosted inner requests require the hosted account operator device and cannot fall back to local operator authority. **Bearer:** operator or device | Status | Meaning | | --- | --- | | 200 | Grant history with body-only support references (`application/json`) | | 403 | Owner authority required | ### `POST /v1/support/grants` — Create a support window of at most 72 hours Owner authority only. Mandatory audit failure refuses creation on hosted bodies; 1024 concurrent unexpired original windows includes revoked tombstones until their original expiry. **Bearer:** operator or device **Request body** (required JSON) A JSON value. | Status | Meaning | | --- | --- | | 200 | Active grant (`application/json`) | | 400 | Invalid support grant | | 403 | Owner authority required | | 409 | Support window capacity reached | | 503 | Support audit or storage unavailable | ### `POST /v1/support/grants/{id}/revoke` — Revoke support access Owner authority only. Durable terminal state closes support reads before audit retries and remains closed after restart. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Current revoked or expired grant (`application/json`) | | 403 | Owner authority required | | 404 | Grant not found | ### `POST /v1/support/grants/{id}/pairing-offer` — Offer pairing for an active read-state support grant Owner authority only. Offers expire no later than five minutes or the grant window. All four ordinary device grants are false; the relay admits only the closed state/history read allowlist. Shell grants refuse pairing. Publishing rechecks owner authority and live grant before installing the offer. **Bearer:** operator or device | In | Name | Required | Type | Description | | --- | --- | --- | --- | --- | | path | `id` | yes | string | | | Status | Meaning | | --- | --- | | 200 | Read-state pairing offer wire | | 403 | Owner authority required | | 409 | support_pairing_requires_read_state | | 410 | Grant inactive or expired | | 503 | Public gateway unavailable | ### `POST /v1/hosted/support` — Apply an exact command authorized by a hosted account ticket Separate support crypto domain and durable one-use ticket fence. The signed ticket binds account, tenant, installation, exact command digest, browser key and nonce, with at most 120 seconds lifetime. No bearer fallback. Replies use authenticated Ed25519 plus ECDH/HKDF/AES-GCM; support references remain outside fleet metadata and central audit. **Bearer:** none **Request body** (required JSON) A JSON object. | Status | Meaning | | --- | --- | | 200 | Encrypted version-2 response with ephemeralPublicKey | | 401 | Invalid | | 404 | Hosted pairing unavailable on this body | | 429 | Support ticket rate limit reached | | 503 | Durable ticket admission or command unavailable | ## Workers ### `GET /v1/worker-mcp` — MCP session stream **Bearer:** worker | Status | Meaning | | --- | --- | | 200 | MCP stream | | 403 | Worker grant unavailable | ### `POST /v1/worker-mcp` — Initialize a worker MCP session or list/call its granted tools Standard Streamable HTTP MCP. Every request validates expiry, durable revocation and account binding. MCP sessions are bound to one grant. No operator tools or channel capabilities are exposed. **Bearer:** worker | Status | Meaning | | --- | --- | | 200 | MCP response | | 202 | MCP notification accepted | | 401 | Worker bearer required | | 403 | Grant unavailable or session belongs to another grant | | 404 | MCP session unavailable | ### `DELETE /v1/worker-mcp` — Close an authenticated worker MCP session **Bearer:** worker | Status | Meaning | | --- | --- | | 200 | Session closed | | 403 | Worker grant unavailable | --- <article class="article"> <p class="eyebrow">Public network surface</p> <h1>How your devices reach Clankie.</h1> <p class="article-lede"> This reference describes the gateway's allowed host routes. The gateway carries bounded exchanges to Clankie's authenticated host, whether that is your Mac or a managed machine. That host owns devices, grants, conversations, and terminal authority. Account and billing services at the same public origin have separate contracts; this is not a catalog of every hosted-service endpoint. </p> <h2>Connection shape</h2> <pre><code>iPhone or iPad ── HTTPS ── api.clankie.bot ── outbound WebSocket ── Clankie's host</code></pre> <p> The first pairing redemption uses the stable public origin. Successful redemption returns an opaque, host-scoped origin. Normal app calls use that origin and a device credential minted by Clankie's host. Users do not copy a host id, URL, or bearer token. </p> <h2>Published routes</h2> <p> This table is generated from the protocol package’s production allowlist. The docs build fails when a route changes without a matching access and purpose description. </p> </article> <div class="table-wrap"> <table> <thead> <tr> <th scope="col">Method</th> <th scope="col">Route</th> <th scope="col">Access</th> <th scope="col">Purpose</th> </tr> </thead> <tbody> <tr> <td><span class="method">GET</span></td> <td><code>/health</code></td> <td>Anonymous</td> <td>Deployment liveness only.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/gateway/v1/config</code></td> <td>Anonymous</td> <td>Publish the non-secret Cognito issuer, client id, and enrollment mode.</td> </tr> <tr> <td><span class="method">WS</span></td> <td><code>/gateway/v1/hosts/connect?hostId=…&installationId=…</code></td> <td>Machine account bearer</td> <td>Keep one authenticated outbound connection from a Clankie machine.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/gateway/v1/push/registrations</code></td> <td>Encrypted device proof verified by its machine, plus the app’s delivery key</td> <td>Register or move versioned APNs delivery when push is configured.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/gateway/v1/push/registrations/clear</code></td> <td>App delivery key; first allocation also requires an encrypted device proof</td> <td>Revoke delivery, including when the former machine is offline.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/activity/viewer</code></td> <td>Fleet-signed Activity media permit with current audience authorization</td> <td>Read the scoped hosted Activity stream while its live audience remains authorized.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/composer/transcription/status (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Read composer transcription availability and included recording allowance.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/composer/transcription/begin (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Begin a bounded, device-scoped composer recording.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/composer/transcription/chunk (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Append a bounded recording chunk at an exact byte offset.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/composer/transcription/commit (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Request one transcription for an editable draft; sending stays explicit.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/composer/transcription/cancel (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Discard that recording and prevent late draft delivery.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/composer/transcription/receipt (inside encrypted exchange)</code></td> <td>Active ordinary paired device bearer with chat access; encrypted exchange only</td> <td>Recover the same draft receipt without repeating provider dispatch.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/devices/self/diagnostics-default (inside encrypted exchange)</code></td> <td>Operator or paired device bearer</td> <td>Read the account's diagnostics default the device inherits; no other settings.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/captain/readiness (inside encrypted exchange)</code></td> <td>Operator or paired device bearer</td> <td>Say whether Clankie can answer and, if not, the secret-free setup reason.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/hosted/operator (inside encrypted exchange)</code></td> <td>Encrypted live device bearer with account-paired operator authority</td> <td>Run a bounded operator request on the hosted body; device revocation and inner route validation remain authoritative.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/discord/ingress</code></td> <td>Fleet-signed scoped permit and P-256 encrypted request and response</td> <td>A trusted Discord connection delivers a sealed addressed turn</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/model-keys (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Read the supported model catalog, Clankie model selection and stored-key status, never keys.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/set (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Store or replace a provider API key in the body credential broker.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/validate (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Check a stored provider key with a bounded provider request; return only a success or error code.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/select (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Choose Clankie’s model for its next turn using the shared CLI config.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/remove (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Remove a stored provider API key without exposing it.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/model-keys/subscriptions (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Name the providers signed in through an account (OAuth or subscription), without token details.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/model-keys/subscriptions/methods (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>List sign-in methods allowed by the body's provider policy.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/subscriptions/start (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Start a provider sign-in for this device and its chosen catalog model.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/subscriptions/status (inside encrypted exchange)</code></td> <td>Encrypted initiating device bearer with terminalControl (Take Control)</td> <td>Read this device's transient browser URL/code or sign-in outcome.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/subscriptions/cancel (inside encrypted exchange)</code></td> <td>Encrypted initiating device bearer with terminalControl (Take Control)</td> <td>Cancel a pending sign-in before its credential write is admitted.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/model-keys/options (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Name the providers that can serve a turn now and the running model's reasoning effort, without credentials.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/model-keys/effort (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Set or clear the running model's reasoning effort using the shared CLI config.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/accounts (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Read the body-owned GitHub, Linear and Google catalog with account, grants and recovery status, never tokens.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/github/start (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Start a GitHub device flow on the body; return the user code and verification URL.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/github/poll (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Poll a pending GitHub device flow; the body stores the token in its credential broker.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/linear/start (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Start a Linear OAuth PKCE flow; the verifier stays on the body.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/linear/complete (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Hand the Linear authorization code to the body, which exchanges it with its verifier.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/linear/app (inside encrypted exchange)</code></td> <td>Encrypted device bearer with terminal-control access</td> <td>Verify and connect a workspace-owned Linear app; client credentials stay on the host.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/disconnect (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Disable local account access and attempt provider revocation; Google disconnect disables all three Google connections and reports pending revocation if needed.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/google/start (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Start body-owned Google consent for Gmail, Calendar or selected-file Drive access, with state and PKCE.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/google/complete (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Exchange the one-time Google code and selected Drive file IDs on the body; return identity and grants without tokens.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/accounts/google/check (inside encrypted exchange)</code></td> <td>Encrypted active device bearer with terminalControl (Take Control)</td> <td>Refresh and verify the selected Google account's authorized access and return its recovery status.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/h/{hostId}/v1/gateway/challenge</code></td> <td>Host routing identity; no device bearer</td> <td>Obtain a one-use challenge for an encrypted device exchange.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/gateway/encrypted</code></td> <td>Authenticated device-to-host AES-GCM envelope</td> <td>Carry pairing, conversation, control, artifact and terminal traffic without revealing application bytes to the gateway.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/gateway/push-authorize</code></td> <td>Gateway-internal one-use encrypted device proof</td> <td>Authorize push delivery at the device’s Mac without exposing its bearer.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/pairing/redeem (inside encrypted exchange)</code></td> <td>One-time offer secret inside the authenticated pairing envelope</td> <td>Claim an active pairing offer and receive a completion token.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/pairing/complete (inside encrypted exchange)</code></td> <td>One-time completion token</td> <td>Accept a subset of the offered grants and activate the device.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/hosted/support (inside encrypted exchange)</code></td> <td>Signed single-use hosted account ticket bound to the exact support command</td> <td>Apply an owner support command and return an authenticated encrypted response.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/support/grants (inside encrypted exchange)</code></td> <td>Owner operator or active device bearer with terminal-control access</td> <td>Read the body's customer-issued support grants and lifecycle state.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/support/grants (inside encrypted exchange)</code></td> <td>Owner operator or active device bearer with terminal-control access</td> <td>Create a referenced read-state or shell support window of at most 72 hours.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/devices/self (inside encrypted exchange)</code></td> <td>Device bearer</td> <td>Read the paired device’s own registration and grants.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/devices (inside encrypted exchange)</code></td> <td>Operator or paired device bearer</td> <td>List the owner's paired devices for Settings → Devices.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/devices/:id/revoke (inside encrypted exchange)</code></td> <td>Operator, or a paired device holding terminal control</td> <td>Revoke one paired device, including the caller itself.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/devices/self/push (inside encrypted exchange)</code></td> <td>Device bearer</td> <td>Enable or disable this device’s versioned push reference on its machine.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/devices/self/session/refresh (inside encrypted exchange)</code></td> <td>Device bearer</td> <td>Renew the paired device’s short-lived session.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/hooks/linear</code></td> <td>Linear’s own HMAC signature over the request body</td> <td>Wake the operator thread when the owner comments on a Linear issue.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/devices/wake-key (inside encrypted exchange)</code></td> <td>Encrypted live device bearer; managed bodies only</td> <td>Register this device’s public key for waking its hosted body. Self-hosted bodies return 404.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/h/{hostId}/v1/hosted/pair-offer</code></td> <td>Single-use fleet ticket bound to the browser key; managed bodies only</td> <td>Return a signed, encrypted pairing offer to the account page’s browser.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/operator/v1/dispatch (inside encrypted exchange)</code></td> <td>Device bearer plus the operation’s grant</td> <td>Send a chat, fleet, steer, or terminal-control operation to Clankie’s host.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/operator/v1/tail (inside encrypted exchange)</code></td> <td>Device bearer with chat access</td> <td>Read the app conversation as a bounded long-poll stream.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/operator/v1/terminal-tail (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read terminal frames from the host’s supported Herdr integration.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/operator/v1/artifacts/download (inside encrypted exchange)</code></td> <td>Encrypted device bearer plus chat grant</td> <td>Download exact bytes of a delivered artifact scoped to its conversation.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/body-leases (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read the active body leases and their ownership on the paired host.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/discord/rooms (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read the Discord rooms and their routing state on the paired host.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/discord/settings (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read the host's Discord settings without credentials.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/discord/directory (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read the Discord guild and channel directory available to the host.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/discord/room-voice (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read the selected Discord room's current voice state.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/discord/voice-transcripts (inside encrypted exchange)</code></td> <td>Device bearer with terminal-observe access</td> <td>Read bounded voice transcripts for the selected Discord room.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/discord/room-guidance (inside encrypted exchange)</code></td> <td>Device bearer with steer access</td> <td>Update owner guidance for a Discord room through its paired host.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/discord/setup/test-post (inside encrypted exchange)</code></td> <td>Device bearer with terminal-control access</td> <td>Send the bounded setup test message through the paired host's Discord body.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/operator/fleet-settings (inside encrypted exchange)</code></td> <td>Encrypted live device bearer with terminalControl (Take Control)</td> <td>Read the owner's fleet size, models, work closure and machine setup responsibility.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/operator/fleet-settings (inside encrypted exchange)</code></td> <td>Encrypted live device bearer with terminalControl (Take Control)</td> <td>Update fleet settings against the current revision and owner authority.</td> </tr> <tr> <td><span class="method">GET</span></td> <td><code>/v1/operator/projects (inside encrypted exchange)</code></td> <td>Encrypted live device bearer with terminalControl (Take Control)</td> <td>Read project settings, including autonomy overrides when explicitly requested.</td> </tr> <tr> <td><span class="method">POST</span></td> <td><code>/v1/operator/projects/update (inside encrypted exchange)</code></td> <td>Encrypted live device bearer with terminalControl (Take Control)</td> <td>Update project settings and fleet responsibility overrides with current owner authority.</td> </tr> </tbody> </table> </div> <article class="article"> <h2>What the gateway does not do</h2> <ul> <li>It does not run Clankie, a model, a terminal, or a Herdr fleet.</li> <li>It does not mint device sessions or decide device grants.</li> <li>It does not expose arbitrary machine paths or arbitrary HTTP forwarding.</li> <li>It does not retain forwarded content bodies, message content, or terminal frames.</li> </ul> <h2>Optional push delivery</h2> <p> When push is configured, the gateway stores APNs tokens, routing identifiers, delivery-key hashes, and versioned revocations. The app’s key authorizes moving or clearing an existing registration, including when its former machine is offline. The machine holds only a registration reference. Wakes carry a fixed alert and host/conversation identifiers, never message text. Apple receives the token, timing, and those identifiers. Pairing and messaging work without push configuration. </p> <h2>Security boundary</h2> <p> Device application requests and responses use an authenticated encrypted envelope between the device and Clankie's host. TLS also protects both connections, but terminating TLS does not let the gateway decrypt device bearers, conversations or terminal bytes. The host enforces device grants on every request. Pairing uses a secure QR or full link whose fragment carries the encryption credential directly to the device; short codes work only on direct private connections. </p> <p> The gateway can observe routing identifiers, ciphertext sizes and timing, and can interrupt service. Push delivery retains the metadata described above. Cognito sign-in, signed Linear webhooks, managed web pairing and Discord ingress have separate authentication contracts; they are not ordinary device requests inside this envelope. Matching gateway, host and client versions are required; implementation alone does not establish deployment or release readiness. </p> <h2>For integrators</h2> <p> These routes document the product’s observable internet boundary; they are not a general third-party API. Clients pair through Clankie and must use credentials and grants issued by his host. The private local service has a larger contract — the <a href="https://docs.clankie.bot/api/">HTTP API</a> — which is served on the host, never wholesale at the public origin. </p> <p> See the <a href="https://github.com/Volpestyle/clankie">source repository</a> for the current protocol and the <a href="https://clankie.bot/privacy/">privacy notice</a> for data handling. </p> </article> --- # Architecture Clankie is a persistent agent with a personality, implemented as one service plus the clients and connections around it. The service owns his built-in pi runtime, conversations, goals, memory, tools, credentials, and authority. The React Native app in the private `clankie-app` repository reaches this service; the public gateway, accounts, and managed provisioning live in private `clankie-ops`. The service can run on an owner's machine or a hosted machine. The app and console are clients of that service; a worker runtime and a work tracker are independent connections. For a product overview, read [How he works](https://docs.clankie.bot/how-it-works/). For source setup and the subsystem map, use [Contributing](https://github.com/Volpestyle/clankie/blob/main/CONTRIBUTING.md) and the [library index](https://github.com/Volpestyle/clankie/blob/main/docs/README.md). This document owns the current system shape and cross-component request flows. Historical diagrams remain in the ADR archive. ```mermaid flowchart LR App["iPhone / iPad app"] <-->|"encrypted device exchanges"| Gateway["Public gateway"] App <-->|"optional direct device route"| Service Gateway <-->|"authenticated outbound connection"| Service["Clankie's service<br/>pi · conversations · goals · tools"] Console["Console / CLI"] --> Service Native["Optional native operator seat"] -->|"MCP + transcript bridge"| Service Discord["Configured Discord body"] --> Service Service --> State["Host-owned state<br/>memory · files · credential broker"] Service --> Models["Configured models and services"] Service --> Runtime["Execution connections<br/>built-in route: Herdr"] Runtime --> Workers["Native interactive worker agents"] Service <-->|"harness channels / session APIs"| Workers Service <--> Work["Repo tracker or task files"] Service --> World["Clankie's own PokeAgents seat"] World --> Viewer["Optional game watch surface"] Discord --> Vox["One native Vox child<br/>when media is enabled"] ``` Capabilities are configured per host. A managed Linux deployment does not implicitly provide desktop input, Discord media, or a game world. The [Linux guide](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md) owns that deployment's capability set; the [agent-host guide](https://github.com/Volpestyle/clankie/blob/main/packages/agent-hosts/README.md) owns native worker support. [ADR 0181](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0181-clankie-is-independent-of-his-connections.md) records the separation between Clankie and his connections. ## Customer support authority The body owns customer-issued support grants, durable revocation and mandatory audit. An owner operator or paired device with terminal control can create a referenced Read state or Shell window of at most 72 hours through [`clankie support`](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md). Read-state pairing carries no ordinary device grants: the relay admits only a closed state/history read allowlist and rechecks the live window before each disclosure, including streams. Shell windows refuse pairing; hosted shell enforcement belongs to private `clankie-ops`. Hosted account tickets bind the exact command, account, tenant, installation, browser key and nonce. The body durably fences replay before executing and seals the response. Neither account metadata nor captain authority creates a grant. Support audit uses keyed device references and a separate durable spool; disclosure fails closed without it. Independent sink acknowledgements govern spool pruning, regardless of diagnostic consent. Public/private artifacts and hosted rollout must be coordinated; source integration alone does not prove production enforcement. ## Approved commit integration The source-checkout service owns an approved-commit integration queue through `POST /v1/integrate` and `clankie integrate`. Each batch has independent Git clones and detached sibling worktrees, private gate environments and durable tested-HEAD records. Exact passed trees land core before app; partial landings preserve each confirmed SHA. Named deploy holds guard landing and runtime-update admission, with explicit audited operator overrides. [Integration](https://github.com/Volpestyle/clankie/blob/main/docs/integration.md) owns the contract, isolation boundary and recovery rules. ```mermaid flowchart LR Approvals["Ordered approved SHAs"] --> Queue["Service integration queue"] Queue --> Compose["Fresh origin + detached sibling worktrees"] Compose --> Gate["Private installs + full checks"] Gate --> Record["Durable exit code + tested HEAD"] Record --> Verify["Exact HEAD + clean tree + current origin"] Verify --> Hold["Deploy holds / audited owner override"] Hold --> Core["Fast-forward core"] Core --> App["Fast-forward app / retain partial result"] ``` ## Device and host authority The host issues pairing offers and device sessions and decides every grant. The public gateway carries bounded exchanges to an authenticated host over its outbound connection. A paired device follows the returned host-scoped route; its encrypted application payload stays between that device and the host. Self-hosted Macs can also advertise an explicitly configured direct device route. One pairing offer carries the available routes; direct pairing does not require an account, and device grants remain host-enforced ([ADR 0204](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0204-a-self-hosted-mac-pairs-the-app-directly.md)). Optional push delivery has a separate metadata store and authorization contract. The [network reference](https://docs.clankie.bot/network/) owns the public host-route table and transport boundary. On a self-managed Mac, account sign-in enrolls the host at that doorway. On a managed machine, signed bootstrap and pairing contracts supply the host identity. The service-side contracts are documented in [credentials](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md) and [Linux deployment](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md). Account, gateway deployment, and managed provisioning implementation belong in the private operations repository ([ADR 0183](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0183-the-harness-is-public-the-hosted-service-is-private.md)). The public service also defines optional host-selected [`runtime-provider` hooks](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/runtime-provider.ts), empty by default. Private managed-body composition supplies quotas, credit routes, heartbeat accounting and model-plan policy through those hooks. Generic service lifecycle, transport, signed pairing and device-security recovery stay public. The [Linux guide](https://github.com/Volpestyle/clankie/blob/main/infra/hosted/README.md#optional-runtime-provider) documents module selection across launcher restarts. ## How a message becomes a turn Each surface authenticates a request and selects a conversation. The service runs or steers the turn, retains its result and exposes replay or live tails. Discord transport, operator clients and native seats use the paths below; autonomous continuations re-enter the same conversation queue. ### Discord ingress The older [message-to-captain JPG](https://github.com/Volpestyle/clankie/blob/main/docs/diagrams/clankie-message-turn-sequence.jpg) is a historical snapshot; the present flow is described below. A Discord message reaches the active bridge. A text-only message in the live voice channel's attached chat enters that room's existing `VoiceFloor`; the realtime room thread may answer aloud, ask Clankie to act, or stay silent, and no separate text turn races it ([ADR 0124](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0124-one-self-has-many-local-threads.md)). Every other message posts to `POST /v1/captain/channel-turns`. The bridge asks for an immediate acknowledgment and polls `GET /v1/captain/channel-turns/{deliveryId}` until the turn settles, so a long model turn does not depend on one open HTTP request. A retry submits the same delivery ID; the service joins a surviving turn, and the bridge's inbox saves the final reply and permits only one progress post for that Discord message across restarts. Before dispatch, the service atomically records the exact ID, request fingerprint and authorized lane in `discord-turn-receipts.json` under its state directory. Completed results remain deduplicated. An unresolved receipt after restart, a rejected promise, or unreadable receipts return `uncertain`; neither an HTTP retry nor time passing starts a replacement turn. Only the original exact turn result settles that receipt. There is no blanket retry override or automatic reconciliation from another session's transcript. Pending means `stored`, an explicit native acknowledgment means `consumed` (not model-read), and a completed channel result reports `responded`; unknown failures stay `uncertain`. The self-hosted voice brain is selected through `/voice` or `clankie voice brain`. OpenAI/xAI realtime and the optional Claude text brain share that same floor and voice tools. Claude uses the existing per-speaker OpenAI transcription and ElevenLabs speech wrapper; it receives attributed text, and interruptions abort its request and retire the mouth's output. Native owner voice-setting changes persist public settings and require a body restart. Hosted provider selection is separate; [the manual Sonnet trial](https://github.com/Volpestyle/clankie/blob/main/docs/testing/2026-10-06-sonnet-voice/manual-trial.md) owns live latency and quality proof. The service normalizes it — untrusted body fenced and labelled, images resolved to bytes at the last hop, channel context attached — and prompts a pi session. Every room gets a continuing session (a pi JSONL tree that survives restarts): operator conversations, voice channels under `~/.clankie/captain/voice/`, and text channels under `~/.clankie/captain/rooms/` ([ADR 0118](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0118-a-text-room-is-a-durable-lane.md)). A message that arrives while that room's run is streaming is steered into it and reported `absorbed`, so a burst of messages gets one merged reply rather than one reply each ([ADR 0091](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0091-a-mid-turn-message-steers-the-turn.md)). The channel backlog still rides in with the request, and is used only when the lane does not already hold that conversation. A privileged turn drops to a one-shot, which writes its own tree under `~/.clankie/captain/turns/` so the tools it ran are readable afterwards ([ADR 0107](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0107-a-one-shot-turn-still-leaves-a-trail.md)). The reply carries the turn's last screenshot or generated image with it — and when that artifact cannot be resolved, the words still post and say the picture did not — while replying with the silence sentinel sends nothing: silence is a real answer. Healthy Pi turns have no total duration cap — looking something up properly is work, not a fault — but a Discord Pi turn with no executing tool and no event for five minutes is a dead stream, so the stall watchdog aborts its pi session and settles it as `captain_turn_stalled`. While someone waits on a slow requested turn, he can post one short `send_text_update` message to the channel ("hang on, pulling the bracket up") without ending it; work he elects to do on his own stays quiet. In channels where the owner enables `/clankie tools mode:on`, the host also edits one quiet tool-activity card with public-safe work categories, counts, elapsed time, and a terminal state; tool names, arguments, and results stay in the local Pi trail ([ADR 0134](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0134-discord-tool-work-is-a-status-card.md)). Discord shows him typing as soon as ingress accepts a live message asked of him (a DM, a mention, or one of his names), before calling Clankie, and it stays visible through thinking and tool work. Room chatter he is merely shown lights only when his reply stream can no longer be the silence sentinel, so a turn he ends in silence never shows the room a reply being written. Buffered, dropped, duplicate, and backlog catch-up messages do not start typing. ### Operator conversations and fleet views The TUI and relay speak the same operator-conversation contract (`/operator/v1/dispatch`): durable agent personas, their current fleet seats, one coherent cursor-long-polled fleet snapshot, revision-fenced sends, cursored replay, and long-polled tails. A tail carries two things: the durable events, and the message the captain is typing right now — a volatile draft held in memory, never in the event log, that the settled `message` event replaces in the block it streamed into ([ADR 0141](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0141-the-console-watches-him-type.md)). Herdr persona conversations are direct-send lanes with no Pi session. The persona owns its name, full appearance tuple, DM, and channel memberships; its current Herdr seat supplies live status, terminal routing, and its placement — the Herdr workspace and tab it sits in, read from `herdr api snapshot` beside the agent list — so a roster can be laid out the way the owner arranged the work. Their readable history folds the complete active user/assistant branch from Herdr's native Claude Code, Codex, Pi, or Grok session identity; raw terminal bytes stay on the terminal lane ([ADR 0135](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0135-a-herdr-seat-is-a-conversation.md)). The app and controlled managed-server Discord project those same host-owned records and logs. Discord faces are app-baked PNGs served under content-hashed HTTPS paths by the existing Activity origin ([ADR 0147](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0147-an-agent-persona-outlives-its-herdr-seat.md)). Herdr's native event subscription advances the volatile fleet cursor; persona, seat, channel, and stance changes advance the same cursor. Foreground apps therefore render one current seats/personas/channels moment without polling or persisting a second world projection ([ADR 0150](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0150-the-fleet-is-a-live-cursor.md)). ### Native operator seats Each fresh Claude, Codex or OpenCode launch creates a separate workspace chat at its launch directory. Multiple native seats keep separate transcripts, tools and outboxes, even in the same directory or account. `--resume` retains the last seat's binding; `--conversation ID` selects an existing chat, including `global-default` for the shared global head. Dry runs create no conversation. A seat can also select a canonical Discord room. Its live conversation channel receives worker reports, escalations, wakes, watches and room turns through the existing outbox. A shared admission fence keeps a service turn already started with its runner and pins taken or uncertain native deliveries; only definite pre-acceptance refusal permits fallback. New inputs use the service runner when the seat leaves. Room replies retain their original actor, route and mouth lease; the cached room MCP bank has social authority without generic operator body access. Worker ownership uses the persisted hire proof and changes when another conversation admits `message_seat`. Explicit ownership wins. Unadopted reports use the actual census parent/launcher's current native occupant and attached conversation or existing native channel. With no eligible parent or a removed adopted conversation, the default chat receives a tagged fallback reason; the roster and doctor name the parent pane lacking a bridge. Parent discovery grants no tools, and missing original room authority remains a refusal. Existing inbound receipts freeze accepted report IDs across adoption, handover and restart. Explicit watches retain their arming conversation. See [ADR 0218](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0218-native-seats-drive-their-attached-conversation.md). A five-minute service inactivity watchdog starts at reservation and includes cold preparation, before a Pi session exists. Host-observed preparation progress and Pi events renew it. The watchdog is suspended while one or more Pi tools execute; tools retain their own timeout and cancellation behavior. A full five-minute idle window resumes after the last tool ends. Before execution starts, or with no active tool and no preparation or streamed progress, inactivity still times out after five minutes. Healthy work has no total duration cap; queued runs do not consume the timeout while waiting. Question authority and hook checks before driver selection, and native attachment preparation before dispatch, are also bounded. On a stall, the host aborts and evicts the exact cached startup/session, prevents late completion from prompting or publishing, and releases admission so the attached seat can receive later queued inputs. Stored runs fail with `conversation_turn_stalled`; the service log records the conversation, run ID and stalled phase. The original acceptance and receipt remain, with no replay and potentially unknown earlier effects. Native delivery keeps its existing acknowledgment deadlines and ten-minute escalation reply wait; a timeout never turns accepted or uncertain native delivery into permission to replay it. `clankie codex` selects the [Codex plugin](https://github.com/Volpestyle/clankie/blob/main/integrations/codex-plugin/README.md). Its trusted native hooks add the shared identity, service context and memory card, and sync redacted transcript entries to the selected conversation. The real Codex TUI creates a thread on its owned app-server; the launcher reuses the same Codex seat driver as fleet hires and the existing outbox pump for wakes, watches and escalations. Hook trust is an owner step in `/hooks`. Until those hooks run, the launcher does not bind the outbox. Claude remains the default harness. The operator seat is a place any harness can sit ([ADR 0152](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0152-a-harness-takes-the-operator-seat.md)). `clankie claude` opens Claude Code, on the owner's own plan, as Clankie: the plugin at [`integrations/claude-plugin`](https://github.com/Volpestyle/clankie/blob/main/integrations/claude-plugin/README.md) forces his identity as the output style, injects the owner persona, reach, address, and service model card at session start (`clankie prompt`) and the newest memory card once per session and then only its new notes (`clankie memory-card --hook`), and names one stdio MCP server, `clankie mcp`, that bridges to the service's lane tool bank at `/v1/mcp` with the operator bearer read from the broker. The bank is the same authored registry the pi session is built from, wrapped once at runtime and scoped by the bearer's lane, so a Codex pane with the same entry is the same seat. A connected service lists only its `initialTools`; the rest of its catalog is reached through `mcp_tool_search` and `mcp_tool_call`, so a harness does not carry every tracker schema on each request. All lane and fleet wire catalogs are checked by `pnpm mcp:check` in the fast push/PR gate, using Claude Code's strict MCP SDK contract and Codex's input-schema conversion shape. Failures identify the tool and field. Connected catalogs validate each tool before admission; `mcp.host.tool_rejected` records the provider, tool and reason, leaving healthy tools available even when a provider returns one incompatible entry. Native catalog health is a separate session-bound observation: Claude's trusted plugin mod reads its accepted tools; managed Codex reads the original thread's native MCP status. The service compares that list to the bridge expectations and projects `toolCatalog` into the roster and `toolCatalogHealth` into doctor. A live process never substitutes for this evidence. Embedded hand-started Codex has no introspection endpoint and stays explicitly unverified with the managed hire action; its native introspection remains future work. Per-turn hook commands (`memory-card`, `seat-sync`, `seat-hook`) skip the launcher's import graph. A herdr pane named `clankie` is his head: the census binds it to his own persona rather than a fleet contact and projects its transcript into the conversation the app pins. While a seat is bound, self-wakes, herdr completion watches, and room escalations reach it as channel events pushed by `clankie mcp`; with no seat open they run the service conversation on pi. A native seat attached to a Discord room receives admitted turns with their original room authority, as described above; attachment does not grant operator tools. Every fleet seat has a mailbox of its own, and a Claude Code seat launched with the channel runs `clankie mcp --seat`, a channel-only bridge that polls it: a DM or room turn then lands as a channel event instead of keystrokes typed into the pane's pty. Local briefed Codex hires use a dedicated app-server: the native TUI creates the session, `turn/start` and `turn/steer` deliver messages, and `turn/completed` supplies completion. A native Codex TUI in Herdr connects to that same server and thread for viewing and owner takeover. The app-server runs outside Clankie's service process group so a service restart does not disconnect or stop the native worker. The adapter reports the thread ID explicitly, so the fleet census does not depend on shared-daemon hooks. Existing unmanaged Codex seats can use their native `codex queue` when available. Automated messages never fall back to typing in the terminal. Missing control, waiting consent, and uncertain delivery remain explicit outcomes. A `turn/steer` receipt reports guidance to the active turn, not an after-turn queue ([ADR 0207](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0207-work-records-and-native-agent-delivery.md)). ### Conversation selection and retention A TUI process opens the existing main Clankie conversation unless `--chat` selects another. `/new` creates a fresh conversation explicitly. A captain conversation and its Pi session are one lifetime: bounded retention removes their shared directory, while public event logs rotate with typed cursor recovery ([ADR 0111](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0111-a-console-process-starts-one-conversation.md)). `/btw` temporarily selects an ephemeral child made with Pi's native current-leaf fork, opening it on a clean screen at that boundary. A hidden boundary makes the inherited branch reference-only; Ctrl+X swaps between the child and its parent without discarding either, and Ctrl+C cancels and deletes the child, restores the parent transcript, and replays any parent events that arrived meanwhile ([ADR 0143](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0143-btw-is-an-ephemeral-pi-fork.md)). Discord text and voice captain rooms also appear in the operator conversation registry under `room` scopes. Their native Pi trees remain in `rooms/`, `voice/`, and `turns/`; the existing native-transcript projection folds context, messages, and tools into the same replay/tail API used by the TUI and CLI. One room groups its social, trusted, and one-shot session histories without merging their model contexts or authority. Source checkpoints prevent duplicate replay after restart. Ordinary operator sends and resets remain read-only for these records. An attached native seat can execute admitted room turns and return correlated replies through the existing Discord transport. See [ADR 0176](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0176-every-room-is-an-inspectable-conversation.md). Every admitted room handoff has a separate durable child conversation, visible under Clankie in the TUI dock and app with who asked, current work and result. A shared queue admits four voice and text requests globally, at most two per room, and holds at most 32 waiting jobs; excess requests receive a busy result. The canonical room retains authority and the reply destination. A service head runs separate Pi threads; a Claude head starts restricted native children; a Codex head starts native children only for the verified owner and uses Pi under the original room lane and grant for every other speaker. Actual native ancestry establishes child references, and taken or uncertain native requests are never replayed as Pi work. See [ADR 0229](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0229-room-handoffs-are-visible-parallel-threads.md). Conversations are files under `~/.clankie/captain/`. Each settled operator or Discord captain turn also appends one metrics line to `~/.clankie/captain/turn-settled.jsonl`: tool-name counts, first mutating tool, context-token occupancy, the model/provider/effort that actually executed the turn, and the provider-reported `totalTokens` summed over the turn with the number of reports that contributed. Execution identity is read off the live pi session as the turn executes, so a `/model` or `/effort` change under a live conversation lands on the next turn to execute rather than being reconstructed from a settings snapshot afterwards. Unknown is said out loud: a row from before the capture, or a provider that reported nothing, reads back as `null` — never zero, and context occupancy is never treated as usage or a charge. `GET /v1/captain/turn-metrics` and `clankie metrics` return the same bounded rows, newest first. The file sits beside `autonomy.json`, outside the conversation directory the retention pass deletes. It is not `~/.clankie/events.jsonl` — that log already uses `captain.turn.settled` for presence idle/waiting_user, and the captain does not write domain events. An absorbed steer ([ADR 0091](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0091-a-mid-turn-message-steers-the-turn.md)) shares the owning run's line. The full HTTP surface is listed in [`apps/clankie/openapi.yaml`](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/openapi.yaml); [`apps/clankie/scripts/setup-yaak.py`](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/scripts/setup-yaak.py) imports that canonical catalog into Yaak and adds a Keychain-backed `Local` environment for authenticated requests. ### Goals and autonomous continuation The service also keeps `autonomy.json`: one proposed or owner-approved goal and one replaceable self-wake per operator conversation, plus a global enable switch. An unreadable file fails closed and surfaces `state_unreadable` to operator clients instead of silently re-enabling autonomous work. An active goal in a Pi-owned conversation queues host-authored continuation turns through the same conversation chain as operator messages, so every tool call and reply stays in the existing Pi session and public event log. A human message that arrives while that run is streaming steers it by default; in-flight tool calls still finish. Explicit `delivery: "steer"` also joins a human-started Pi turn, while `delivery: "queue"` waits for a separate turn on the conversation FIFO ([ADR 0091](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0091-a-mid-turn-message-steers-the-turn.md)). A finite token budget (default 1,000,000) moves a goal to `budget_limited` before another provider request; failed turns retain recorded usage. Model calls persist inactive proposals, confirmed only by `/goal accept`. Native harness seats refuse service goals, and a queued goal pauses on discovering a native head. `/goal` owns activation, pause/resume, and clearing, while `/autonomy off` stops new continuations and wakes. A due wake queues one turn with Clankie's recorded reason and may be replaced by another. Neither path changes the conversation's tool set or authority ([ADR 0130](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0130-goals-and-self-wakes-share-the-operator-thread.md)). Each conversation also keeps an append-only goal decision journal under `~/.clankie/captain/goal-journal/` — one line per real choice made while working a goal, written through `note_goal_decision` and returned by `get_goal` so a continuation resumes from what was already decided ([ADR 0132](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0132-a-goal-keeps-a-decision-journal.md)). ### Independent evaluation The optional independent evaluator captures settled Pi turns and native Herdr reply projections under `~/.clankie/captain/evaluator/`. Its durable queue dispatches one Codex or Claude Code assessment at a time in a separate Herdr pane; structured reports retain outcome, efficiency, tool and harness judgments plus issue/MR links. Goal identity groups continuations, while conversation checkpoints leave task boundaries to the evaluator. Evaluator descendants are excluded from capture. `clankie evaluator` and `/evaluator` expose the operator API controls. Linear following remains independent. See [ADR 0178](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0178-the-evaluator-has-its-own-seat.md) for scheduling, restart recovery and evidence limits. Exact Discord speech is available through a bounded captain read that returns content only while owner-controlled transcript retention is enabled. The TUI and `clankie discord transcripts` use this shared transcript store. Operator input can invoke an exact loaded skill as `/name task` or `/skill:name task`. The service rewrites that verified invocation to Pi's native skill command and enables expansion for that prompt only. Discord input and ordinary operator prompts keep expansion disabled. Before each Pi run, a hidden host extension reads a bounded memory card into the system prompt. The host supplies the destination lane, filters operator-private notes out of ambient lanes, and refreshes recall without persisting duplicate cards in the conversation. Discord turns also receive the newest visible person facts for their authenticated guild/user identity. The selected notes persist until forgotten. The `memory` tool writes, searches, edits, and forgets them within the admitted conversation's authority. Notes and per-person fact files live under `~/.clankie/memory/`; the TUI's `/memory` command browses, edits, and forgets that same store through operator-only routes. [`docs/memory.md`](https://github.com/Volpestyle/clankie/blob/main/docs/memory.md) is the full picture — what each store holds, who may read it, and what bounds it. A second hidden extension appends the model card: the name, ref, and provider he is actually running on, his reasoning effort, and his context and output limits. It resolves the same selection Pi executes, on every run rather than once per session, because `/model` and `/effort` swap the model under a live conversation. Asked what he runs on, he answers from the prompt like he answers with his own address — no tool call, no guess, and silence if the selection cannot be resolved. ## Where things run - **Machine tools.** Coding tools (read/bash/edit/write) are pi built-ins. They attach to the operator console and to Discord turns authorized by the machine-control grants ([ADR 0095](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0095-discord-system-actors.md), [ADR 0105](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0105-voice-is-as-capable-as-the-room-it-is-in.md), [ADR 0133](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0133-a-machine-grant-belongs-to-a-discord-lane.md)). An individually granted actor gets a one-shot tool-bearing turn in shared rooms and a durable tool-bearing lane in an official-bot DM. Explicitly trusted guilds, optionally narrowed to channels, give every admitted member the same durable tool-bearing lane. Social and system histories have separate session keys, so revocation routes the next message away from the old tool bank. They land in the conversation's workspace — the directory a workspace-scoped operator conversation names, this repository for every other lane ([ADR 0104](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0104-clankie-works-where-you-launched-him.md)). Voice join/leave are the same argument-free tools on Discord and the operator console: a Discord turn follows the authenticated speaker, an operator turn follows the configured owner ([ADR 0062](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0062-voice-join-by-asking.md)). The canonical authored-tool registry is [`apps/clankie/src/captain/tools.ts`](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/tools.ts), connected-service additions live in [`captain/connect-tools.ts`](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/connect-tools.ts), and the HTTP surface is [`apps/clankie/openapi.yaml`](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/openapi.yaml). This document does not duplicate their changing census. - **Browser catalog.** The service owns a Browser Use Pi SDK session. Clankie drives its persistent JavaScript workspace directly through `execute()`; it does not run a second model or a hidden browser agent. Machine-authorized turns receive `browser_use_javascript`; social turns receive browser-only navigation, DOM evaluation, accessibility, input and screenshot tools. The host enforces the same boundary behind tool discovery and the HTTP API. Browser calls are sequential across rooms. Model-facing JSON previews retain up to 50 KiB or 2,000 serialized lines, followed by a truncation notice when needed. A large page string keeps a UTF-8-safe prefix instead of being dropped as an oversized line; full results remain in the Pi tool details. The SDK's JavaScript worker receives no Clankie credentials, but true filesystem/network isolation requires a VM or remote broker ([ADR 0082](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0082-clankie-holds-the-browser.md)). The persistent profile holds his own accounts, signed up for by hand: the `browser_use_open` tool's `headed` argument relaunches Chrome visible on the operator's screen, so he can hand over the window for a signup, a CAPTCHA, or a phone check rather than grinding at it ([ADR 0127](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0127-his-accounts-are-his.md)). Browsing defaults to headless. After 60 seconds without a browser call, the host saves any recording and closes the burst's tabs/windows, including a takeover window. Persistent logins remain; the next burst starts headless. The SDK launches Chrome lazily with that private profile and owns its shutdown. JavaScript bindings reset at idle close, mode changes or worker timeout; workspace files and persistent logins survive. Opt-in recordings sample the current tab every 750 ms through the SDK's public CDP primitives and encode WebM with FFmpeg. Hard work in the owner's own apps and Chrome goes to a hired computer-use harness where one is ready; the service detects them and the reach card lists them on machine-access lanes ([ADR 0199](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0199-hard-computer-work-goes-to-a-computer-use-harness.md)). - **Leading agents.** Native local hires use `hire_agent`, `message_seat`, and `herdr_watch` through [harness adapters](https://github.com/Volpestyle/clankie/blob/main/packages/agent-hosts/README.md#tool-flow-and-current-support). Claude, Codex, Pi, OpenCode, and Grok Build have local adapters; Prime Agent remains researched. The adapter guide owns platform, version, consent, and restart-recovery limits. Skills explain tool use while delivery code enforces the no-terminal-fallback boundary. Remote agents use the per-fleet link and native harness delivery. Herdr supplies the native terminals and process control. The service's selected runtime supplies every console's fleet view. Current binding and fallback behavior live in [the CLI reference](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md#herdr-statusopencreate--herdr-use-name). Native Herdr events wake fleet readers across workspaces ([ADR 0150](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0150-the-fleet-is-a-live-cursor.md)); the optional herdr-lead board is a view, not a second coordination authority. - **His body.** `runFreePlay` drives one seam, `GbaDriverIo` ([`packages/play`](https://github.com/Volpestyle/clankie/blob/main/packages/play/README.md)); its mind, voice, progress, learned transitions, and behavior loop hold no emulator and never learn what implements the seam. One body implements it: Clankie's separately credentialed seat in a PokeAgents world, reached through `WorldPlayerClient` on `@pokeagents/world-protocol/ipc` (`WORLD_ADDRESS` unix, tcp, or tls — defaulting to the world's own socket under `WORLD_STATE_DIR`) and entered with the `pokeagent_join_mmo` tool ([ADR 0103](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0103-a-hosted-world-is-another-body.md), [ADR 0145](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0145-the-world-is-the-only-body.md)). Clankie is the parent of that sitting; `@clankie/play` is the driver — the same split other harnesses get from an MCP Task, a subagent, or a CLI loop. That seam consumes verified FireRed adapter-v2 and Emerald adapter-v2 payloads, selected by the observation's `(gameId, adapterVersion)` pair; unknown pairs and game-specific extras the selected schema does not verify fail closed. A hosted world cannot be paused, changes without him acting, and can replace his body under him, so the loop offers no save, load, or restart action — the world persists its own cartridge. `pokeagentMmoEnabled` is the owner setting; with it off, or with no world reachable, the ask refuses out loud rather than falling back. Frames flow to the Discord activity surface. Every sitting carries a stable journey identity separate from its run id, the bounded story spans that journey, and the next sitting receives the last self-authored notes and objective while exact world state stays with the cartridge save ([ADR 0126](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0126-game-state-history-and-memory-have-separate-owners.md)). The journal records body provenance at each causal stage, and its `venue` still reads both values because journals written before ADR 0145 are on disk. Other harnesses reach the same world through PokeAgents' own front doors — `@pokeagents/world-mcp`, its CLI, or the `pokeagent-mmo` skill — each on its own credentialed seat, as the same parent-plus-driver sitting (MCP Task, host subagent, or CLI loop; PokeAgents ADR 0023), with no control over Clankie, Activity publication, play voice, or room input. PokeAgents owns player leases and action/session fencing. Clankie's play package retains typed body-action refusals beside its driver interface; the retired local environment lifecycle engine has no role in hosted play. - **Spider-Man (disabled bridge).** The integration is not currently available for live play; [setup](https://github.com/Volpestyle/clankie/blob/main/docs/rivals.md) records the re-enablement requirements. Its interface delegates tactical decisions and the guarded real-time pad loop to Rivals Agent. Clankie's `rivals` tool and operator API manage bounded sittings, objectives, fresh observations, and read-only sharing; the existing Go Live PNG publisher carries its video. The Pokémon seam remains unchanged in scope. See [ADR 0175](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0175-rivals-agent-is-a-gameplay-skill.md) and [setup](https://github.com/Volpestyle/clankie/blob/main/docs/rivals.md). - **Game extensions.** [ADR 0234](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0234-games-share-one-extension-contract.md) defines typed connector, skill, settings, Activity and lifecycle composition. `integrations/pokemon` implements it; core retains play leases, authority and recovery, persona/model selection, Discord/Activity destinations and evidence projections. Pokémon's existing API/CLI/TUI enter that extension through a compatibility composition point. Minecraft and Rivals adoption, and installed-extension discovery without core edits, remain follow-ups. - **PokeAgents boundary.** The sibling PokeAgents repository owns the `WORLD_OPERATIONS` catalog, capability schemas, native client transport, and the MCP projection derived from that catalog. MCP carries calls; the world contract and host enforce player identity, authority, and gameplay semantics. Clankie currently imports only the pinned `@pokeagents/world-protocol` package (including `/ipc`) and keeps host, emulator, persistence, and world-MCP packages out of product source. Hosted play composes `WorldPlayerClient`. Every catalog operation is classified body or mind; unclassified names stay off `pokeagent_world` until classified. The play loop owns BODY (`world.join`, `world.leave`, `play.observe`, `play.act`, `play.frame`, `play.watch`); the mind owns session, who, regions, travel, and challenges. Pokémon usage caps, failure backoff, bounded voice preemption and pre-action state rechecks live above this body seam in `packages/play`. The service sends each notable kind once to the original conversation under its existing grant, including terminal events after the initiating turn ends. `pokeagent_guide` offers context to that conversation's play mind; it never forces an action or replaces the mind's choice. See [play](https://github.com/Volpestyle/clankie/blob/main/packages/play/README.md). - **Auth.** Provider keys and OAuth tokens live in the credential broker (Keychain), written by the TUI `/auth` flow and read by pi through a credential-store bridge. Compatibility model/media provider keys may fall back to existing shell values or the gitignored root `.env.local` when the broker has no entry; Discord account and body credentials remain broker-only except documented operator/captain test overrides. Persona is owner-authored in `~/.config/clankie/settings.json`; the authenticated owner surfaces and CLI can update it. Untrusted messages and model output cannot override that identity. `/connect` stores Linear and mailbox credentials the same way; Discord remains a body configured by `/discord` ([credential guide](https://github.com/Volpestyle/clankie/blob/main/docs/credentials.md), [ADR 0093](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0093-owner-authored-service-connections.md)). The mailbox is his own address, not the owner's inbox: `email.fromAddress` carries the identity when the provider login differs, Clankie states that address from settings, and mail stays console-only because sign-in codes arrive there ([ADR 0127](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0127-his-accounts-are-his.md)). That address is public, so every message the mail tools return is labelled untrusted sender text the way a Discord body is ([ADR 0081](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0081-an-image-is-part-of-what-is-said.md)). A seat in a hosted world is a broker credential too — `pokeagent_mmo_world`, with the environment variant refused outright. Each media-enabled active Discord body owns one `clankvox` child through the Apache `@clankie/vox-client` boundary. A text-only official-bot process does not spawn Vox. Both media-enabled bodies use its primary role for voice, TTS, and music; the lab user body can concurrently watch screen shares and publish Go Live through separate roles ([Discord media guide](https://github.com/Volpestyle/clankie/blob/main/docs/discord-media.md), [ADR 0128](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0128-vox-is-the-sole-discord-media-owner.md)). `/discord` Active body picks which process is the mouth; the launcher starts only that one ([ADR 0048](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0048-discord-user-session-transport.md)). Who may ask him to drive this machine from Discord is configured under `discord.systemActorUserIds`, `systemActorGuildIds`, and `systemActorChannelIds` ([ADR 0133](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0133-a-machine-grant-belongs-to-a-discord-lane.md)). ## Shared bodies and present state Parallel conversations belong to one Clankie and arbitrate the shared `discord_mouth`, `voice`, `browser`, `computer`, and `play` resources through [body leases](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/body-leases.ts). Conversation identity and incarnation tokens fence stale operations; viewing a resource does not acquire it, and a lease never adds authority. Uncertain operations require explicit recovery rather than age-based takeover. The [router](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/body-lease-router.ts) preserves the original machine or social route when handing a request to the holder. See [ADR 0215](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0215-conversations-lease-one-body.md) and the [CLI reference](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md) for ownership and recovery operations. The Pokémon body remains a PokeAgents seat. Minecraft has a separate service-owned MCP motor under the same `play` resource: the existing service session decides its actions, while Mineflayer handles movement and physics. The offline body slice is implemented; online account authentication and live multiplayer/Discord acceptance remain deferred. Its current scope and setup live in [Minecraft](https://github.com/Volpestyle/clankie/blob/main/docs/minecraft.md) and [ADR 0219](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0219-minecraft-is-an-mcp-connected-body.md). The operator `presence` operation projects current thinking, voice, play, active seats, and pending owner work from their existing sources. It does not persist another mood state. The `desktop` tool publishes a bounded transient expression; publication does not prove a client displayed it. Quiet hours and expiry apply. The projection and expression ownership live in [presence.ts](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/presence.ts) and [desktop.ts](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/desktop.ts), following [ADR 0220](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0220-clankie-has-one-present-tense.md). The desktop pet consumer belongs to the private app. ## Native media plane Each media-enabled active bot or user-session body owns exactly one `clankvox` child. A text-only official-bot process owns none. Apache product code speaks through `@clankie/vox-client`; the AGPL executable owns DAVE, RTP/RTCP, codecs, capture, TTS/music pacing, screen-watch, and Go Live publishing. The TypeScript `DiscordVoiceSession` retains consent, attribution, floor, realtime-provider, and captain-handoff policy. Readiness is role-specific: versioned `process_ready` must exactly match the client IPC protocol and proves only that the child can serve IPC; `transport_state=ready` proves a role's Discord media transport; and positive `dave_state=ready` proves that role's negotiated DAVE session. Primary voice ready, connection, transport, DAVE, and error events are correlated by the caller's `connectionId`. The detailed current diagram and evidence rules live in [ADR 0128](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0128-vox-is-the-sole-discord-media-owner.md). ## Reading the source by domain Existing entry paths retain their public exports. These modules organize the implementation behind those entry points; callers continue to import the same paths. | Entry point | Domain modules | | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Protocol](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/index.ts) | [Operator conversations](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/operator-conversations.ts), [fleet messages](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/fleet-messages.ts), [terminal transport](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/operator-terminal.ts), [Discord presence](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/discord-presence.ts), [voice evidence](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/discord-voice-evidence.ts), [embodiment](https://github.com/Volpestyle/clankie/blob/main/packages/protocol/src/embodiment.ts), and the other named wire-contract modules beside the barrel. The package has no other workspace dependencies. | | [HTTP app](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app.ts) | [Runtime composition](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/runtime.ts), [seat delivery](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/seat-routes.ts), [Discord routing](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/discord-routes.ts), [voice briefing route and renderers](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/voice-briefing.ts), [memory](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/memory-routes.ts), [pairing](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/pairing-routes.ts), [operator conversations](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/conversation-routes.ts), and [signed Linear webhooks](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/app/linear-routes.ts). Route factories take explicit typed dependencies and preserve registration order. | | [Conversation store](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations.ts) | [Store and lifecycle](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/store.ts), [ordinary-chat Linear wakes](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/linear-wakes.ts), [worker reports](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/worker-reports.ts), [native seats](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/native-seats.ts), [transcripts](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/transcripts.ts), [channel projection](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/channel-projection.ts), and [questions](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/conversations/questions.ts). Extracted functions receive the typed store explicitly; class methods retain their public signatures. | | [Runtime orchestration](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain.ts) | [Discord turns](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-discord-turns.ts), [operator service and fleet roster](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-operator-service.ts), [conversation runner](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-conversation-runner.ts), [worker report recovery and delivery](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-worker-reports.ts), [goal budgets](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-goals.ts), [session helpers](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-session.ts), [prompts](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-prompts.ts), [models](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-model.ts), [drafts](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-draft.ts), and [operator formatting](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/captain/captain-operator-format.ts). Factories preserve live bindings through typed context accessors. | | [Voice session](https://github.com/Volpestyle/clankie/blob/main/packages/discord-presence-core/src/voice-session.ts) | Consent, attribution, instruction/briefing application, turn-taking, tools, and playback remain together here pending the voice fixes. The [voice floor](https://github.com/Volpestyle/clankie/blob/main/packages/discord-presence-core/src/voice-floor.ts) owns floor arbitration; Vox owns native media transport. | Fleet members reach verified connected accounts through the service's `clankie_tools` discovery and `clankie_call` invocation bridge. Fleet membership supplies that connection authority, controlled by `fleet.tools`; project roles, caps, hiring, tracker binding, and worker report ownership remain separate. [ADR 0217](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0217-fleet-membership-gets-connected-tools.md) records the connected tool boundary; [ADR 0207](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0207-work-records-and-native-agent-delivery.md) and [ADR 0218](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0218-native-seats-drive-their-attached-conversation.md) record native delivery and report routing. ## Current architecture constraints Clankie uses pi's `ModelRuntime` and `createAgentSession` for Clankie's models, sessions, tools, skills, and compaction. The agent runtime, HTTP surface, and play host share one service ([ADR 0101](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0101-pi-owns-the-captain-model-runtime.md)). The repo's tracker or task files hold work and results. The service exposes one Linear-shaped tracker tool surface to Clankie and workers, using the connected owner account or durable local storage when disconnected; repository conventions adapt GitHub and Markdown to that same surface. `clankie doctor` reports backend selection and reason ([ADR 0226](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0226-one-tracker-tool-surface.md)). Account Connections in the app and account page use body-owned GitHub device authorization and registered Linear S256 PKCE. Provider tokens stay in the body's broker; the portals exchange only sealed lifecycle requests and public connection metadata. Registered Linear API OAuth uses a separate `linear-api` credential and in-process tracker, preserving the existing MCP audience and grant fences ([ADR 0232](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0232-hosted-connections-use-the-body-broker.md)). Both Linear audiences share a service-owned [request budget](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/src/linear-request-budget.ts) per verified workspace and actor. Actual HTTP attempts are counted over a rolling hour, provider rate-limit headers tighten headroom, and background reads slow at 80%. Device Work refreshes and explicit CLI/fleet poll markers select background priority; owner/lead reads, writes and webhook context remain interactive. One logical read retains admission across provider pagination while every HTTP attempt obeys the hard cap. `clankie linear budget` and `/doctor` expose the observation; the 50% warning uses native alerts without starting a model turn. Herdr contains the native interactive workers; Clankie uses their supported channels or session APIs for delivery. Linked independent agents can write first with `message_clankie`. Untrusted input stays fenced, secrets stay in the credential broker, and every report describes observed outcomes rather than intentions. [`adr/`](https://github.com/Volpestyle/clankie/blob/main/docs/adr) records the active decisions for play mechanics, voice, presence, media, browsing, and operator control. ## Distribution The macOS Apple silicon release preserves these process boundaries inside one self-contained, versioned directory. A native `clankie` launcher starts the bundled TUI and Node runtime; the supervisor starts compiled service entrypoints instead of pnpm workspace scripts. Runtime state and credentials remain outside the immutable release. [ADR 0136](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0136-a-release-is-one-command-and-one-runtime.md) records the decision, and [`distribution.md`](https://github.com/Volpestyle/clankie/blob/main/docs/distribution.md) documents the artifact and installer. Product skills and `clankie doctor` travel with the release so he can describe and set up this machine without a git tree ([ADR 0142](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0142-the-install-tells-him-the-truth.md)). ## Canonical Homes The [documentation library](https://github.com/Volpestyle/clankie/blob/main/docs/README.md) maps each concern to its owning reference. Command syntax belongs in [CLI](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md), HTTP operations in [OpenAPI](https://github.com/Volpestyle/clankie/blob/main/apps/clankie/openapi.yaml), and subsystem implementation in the owning package. This architecture document links to those contracts rather than keeping a second catalog. ## Hosted Mac console The launcher resolves local/hosted mode before starting services. Hosted mode pairs a revocable operator device through account sign-in and carries requests inside the existing encrypted device envelope. No local body or operator bearer is started or exported. See the [ADR 0173 amendment](https://github.com/Volpestyle/clankie/blob/main/docs/adr/0173-the-gateway-cannot-read-device-traffic.md#amendment-the-mac-can-be-a-hosted-operator-device-2026-09-27-vuh-1110) for authority and the [CLI contract](https://github.com/Volpestyle/clankie/blob/main/docs/cli.md#local-and-hosted-connection-modes) for supported commands and recovery. Fleet ticket issuance stays private.