Skip to content

ahpd 0.15 ​

The good stuff ​

Agents get a terminal and ask the person, a failed turn can be resumed, and every client action is answered as VS Code expects.

Unreleased ​

Upgrading ​

  • The model calls have their own gate, modelApi. httpApi no longer serves /v1 or the root model paths. A host that calls models through ahpd runs ahpd config set modelApi true after the update, and restarts. See The model proxy.

Daemon ​

  • modelApi: { "port": N } serves the model API on a port of its own, apart from /api.
  • ahpd config set <key> <value> and ahpd config unset <key> write one setting of the configuration file. See Configure.
  • The descriptions of httpApi, modelApi and modelProxy are one short sentence each.

Plugins ​

  • An icon is at most 256 KB of image, up from 32 KB, as VS Code bounds an icon's image. An Activepieces piece shows its own logo again, WhatsApp and Resend included.
  • An agent record that is saved with new settings is asked again what it offers. A cofold agent given a baseUrl and an apiKey lists that endpoint's models with no restart.
  • A pi agent's env takes what a Claude agent's does: any variable by name, such as OPENROUTER_API_KEY, with text, { "fromEnv": "NAME" } or { "$secret": "host:NAME" } as its value. The form no longer lists pi's 38 key variables, and a key pi reads is still kept in the vault.

0.15.0-rc.1 - 2026-11-04 ​

Upgrading ​

  • A new session is now named ahp-session:/<uuid>, and its provider is on SessionSummary.provider. A client that read the provider from a session URI's scheme reads it from the summary instead. A session that already exists keeps its <provider>:/<id> name.
  • A terminal's terminal/data no longer carries the shell's OSC 133 and OSC 7 marks, as the protocol requires of a server. A client that read command boundaries or the directory from the marks reads terminal/commandExecuted, terminal/commandFinished and terminal/cwdChanged.
  • terminal/commandDetectionAvailable goes out with the shell's first OSC 133 mark rather than when the terminal opens, and supportsCommandDetection is false until then. A shell that prints no marks never announces it.
  • A tool call part is its kind and its toolCall, as the protocol declares one, from every agent. A client that found a call's part by an id beside them finds it by toolCall.toolCallId. A system notification part carries no id either.
  • An ACP session's commands are the children of one read-only commands directory customization, as the protocol holds a prompt, and no longer customizations of their own.
  • A pi ! command's call is named terminal and shown as Terminal, with the command as its intention, its line and its input, as every other backend's is. It was named shell, shown as the command, with the command as JSON for its input.
  • A client action the host accepts goes to every client of its channel, takes the next serverSeq and is kept for replay, as the protocol says. The host echoed some actions to their sender alone. A refusal still goes to its sender alone, and so does terminal input, so keystrokes such as a password typed at a prompt never reach another client.
  • ahpd.restartNeeded is a read-only value of the root config, set with root/configChanged. A client that read it from the root's _meta reads config.values instead.
  • A turn a wake or an async command's end starts carries origin: { kind: 'systemNotification' } and no vscode.chat.delegation entry, so a client draws it as a notice, not as the person's message.
  • A steering message a turn took in shows in that turn as a systemNotification part whose content is the message, with its id in _meta['ahpd.steer']. steerPart and steerOf in @ahpd/sdk build and read it.
  • Minting a team key needs proxy:call as well as user:write. Removing a team removes its keys. See docs/server/USERS.md.
  • The built-in guest role no longer holds mcp:call. plugin:apply and plugin:reload are part of plugin:write.
  • x-ahpd/invokeOperation checks the operation's own grant. A caller who holds only operation:invoke no longer runs every operation.
  • --plugin adds to the configured plugins instead of replacing them.
  • A session file this version cannot read is moved to <file>.unreadable-<time> at load and logged, so the next write does not replace it.
  • A release candidate is published under the npm next tag. npm i and ahpd update keep reading latest. See DEVELOPER.md.

Agent tools ​

  • An agent gets VS Code's shell tools: bash or powershell, with read_, write_, list_ and _shutdown for the same shell. Each call shows its terminal, and a person can type into it. They are off by default, behind the root config key enableCustomTerminalTool, and never on Windows. On Claude the host's bash replaces the CLI's own, and on pi the host's bash and powershell do. cofold and ACP get them beside their own. Each tool checks the grant of the terminal method it matches, as the person who sent the turn.
  • The shell tool takes mode: "async". The command answers at once with its shell id and shows as a background row on the chat, with its terminal and a stop. When it ends, the row goes and a turn begins with the command, its exit code and the last 60 KB of its output, as a wake does.
  • A shell opened with no command, under a pseudoterminal and not in a machine, runs VS Code's shell integration, so each command is its own part with its exit code. nonInteractive and preventShellHistory are terminal options. Each terminal under a pseudoterminal keeps an @xterm/headless screen in the server, so the host knows when a program uses the alternate screen.
  • The ask_user host tool asks the person up to four questions, each with options, multiSelect and allowOther, and waits for the answer. A stopped or ended turn cancels a question still waiting. cofold uses it in place of its own. Claude keeps its own question tool.

Sessions and VS Code parity ​

  • A ! command's call names its terminal as soon as the terminal opens, with chat/toolCallContentChanged, so a client watches the output arrive, as VS Code's does. A backend hears the terminal from the second argument of Session.ran's run (RunCommand, CommandTerminal), and the handle of a terminal a backend opens says isPty. See docs/sdk/TERMINALS.md.
  • A terminal a tool call names opens for as long as its chat lasts. A ! command's terminal and a terminal a backend opens are named ahp-terminal://shell/<chat>/<key>, and what each printed is kept in the session's folder, a ! command's when the command ends and a backend's when its terminal exits. A subscribe that no live terminal answers gets the exited terminal back: after a client disposed it, after the backend released it, and after the daemon started again. What is kept goes with its turn on a truncation, with its chat and with its session, and moves with its chat. A backend keeps an output of its own with Start.terminalOutputs, and a transcript read is handed the outputs of the chat it reads. See docs/sdk/TERMINALS.md.
  • A ! command's turn survives a restart. The host keeps it in the session's folder as a local turn of its chat, anchored to the turn before it, and every read of a transcript puts it back there, so its terminal opens again too. A truncation or a fork at one goes to the backend at the turn before it, a truncation takes the ones it drops, and they go and move with their chat. cofold stores a turn's prompt under the turn's id and pi names its prompt's entry through onTurnRecorded, so their turns read back under the names the anchors use. Claude writes a turn's prompt under the turn's id, as VS Code's agent host does, or under a uuid it names through onTurnRecorded when the id is not a UUID, so a Claude turn read back after a restart also keeps its sender; the id was taken from an echo of the prompt that the CLI never sends. An ACP chat read back after a restart is its server's replay, which names its turns anew, so only a command before its first turn comes back. See docs/sdk/TERMINALS.md.
  • A chat read back after a restart shows each turn as it ran. The host records in the session's folder the transcript id each turn began under, its own id as it starts and what onTurnRecorded says, and a read puts a turn the record does not hold inside the turn before it, as Claude's CLI writes a steering message it runs after the turn's last answer as a prompt of its own. A turn written down twice, as a retried turn's prompt is, reads back as one turn. A chat from before the record, or on a store with no session folder, reads as its transcript gives it, and so does an ACP chat, whose server's replay names its turns anew. See docs/sdk/AGENT.md.
  • The Bot chat's bot setting is a picker of the bots this daemon has, labelled by each bot's name. A backend now answers for a key of its own schema with Agent.completions, so a key whose values the host knows arrives as a picker rather than a text box. See docs/sdk/AGENT.md.
  • A session's settings come in the order VS Code's agent host composes them: the computer and isolation first, then the agent's own settings and another plugin's, then the branch and the worktree keys. A running session reports the same order. Only the order moved: which value wins a name two of them declare is unchanged.
  • A turn that failed on a model call can be resumed with chat/turnResume, on Claude, cofold and pi. It ends with an error part marked resumable, and a resume runs it again from the end of the turn before. pi keeps the tools the turn already ran, and Claude and cofold run the turn again whole.
  • Every client action is answered as the protocol says: accepted, said back, or refused with a reason. This covers drafts, read and archived bits, working directories, terminals, reviews, queued and steering messages, stops with no turn running, and customization switches. A dispatch whose apply throws is refused with the error's message. A client's own action comes back to it with its origin, and nothing else carries that origin.
  • A queued message sent again as a steer, as VS Code's "Send Immediately" does, runs once in the running turn and not again as a turn of its own.
  • A message steered into a running turn stays visible in that turn, live and after a reload, in a Claude session and in a bot chat.
  • A confirmation or a question that waits leaves the session's activity as it was. The InputNeeded status says it, as in VS Code.
  • A token a client pushes for an MCP server's sign-in is spent only on the sessions that client's connection watches. A client can name one server in authenticate's target, and the token then goes to that server in that session alone. See docs/sdk/AUTHENTICATION.md.
  • A session an automation started keeps its origin after a restart: its summary names the automation, and its first message has origin automation.

Daemon ​

  • On Bun, a terminal is a pseudoterminal with OSC 133 command detection: Bun's own, through the terminal option of Bun.spawn, from Bun 1.3.5 on Linux and macOS and from Bun 1.3.14 on Windows, where it is ConPTY. node-pty is never loaded on Bun, where it loads but every shell it starts dies at once. Node and Deno keep node-pty where it loads. See docs/sdk/TERMINALS.md.
  • ahpd doctor runs every check on this machine and prints one line for each, with the fix when something is wrong. --json gives the checks as an array, and the exit code is 1 when a check fails. See docs/server/RUN.md.
  • A client of the daemon's AHP restarts it with x-ahpd/restart, with the checks of ahpd restart. It takes config:write, and only the host's own connection may send it. The answer is { "restarting": true, "pid": <pid> }. See docs/server/RUN.md.
  • The host serves its tools that need no session at /mcp, to a person's MCP client such as VS Code or Claude Code, with their ahpd token and under their grants. The first tools list, create, message, read, rename and delete sessions. It needs mcp:call, which member holds. See docs/server/MCP.md.
  • x-ahpd-clients:// lists the open connections, with the address, the connect time and the traffic in each direction, and its drop operation closes one. Reading it needs client:read, and dropping client:drop.
  • ahpd start that gets SIGINT, SIGTERM or SIGHUP before the daemon is up stops the daemon it started.
  • When /usr/local is not writable, the install script also writes the PATH line into the shell's login file, so a desktop app finds ahpd from the next login. --uninstall removes it again.

Terminals ​

  • The output a shell printed before a mark goes out before the mark's action, so the output between terminal/commandExecuted and terminal/commandFinished is the command's. A mark that arrives in two reads is still read and taken out. See docs/sdk/TERMINALS.md.
  • On pipes, a character whose bytes arrive in two reads reaches the client whole rather than as two replacement characters: each pipe is decoded as one stream. See docs/sdk/TERMINALS.md.

Agents ​

  • Claude: a call that a stopped turn was in stays cancelled when the CLI answers it after the stop. A client that subscribes later reads what a client that watched built, no chat/toolCallComplete without a turnId is sent, and the stopped session does not say it is thinking again. An edit such a call made is recorded under its own turn. See docs/sdk/CHATS.md.
  • Claude: an answered AskUserQuestion reads back with its answers after a restart, in the session and in a subagent's chat. The answers are read from the transcript entry where the CLI writes them, which the SDK's reader leaves out. See packages/agent-claude/README.md.
  • Claude, ACP, cofold, pi and the bot send only what the protocol declares: no id on a tool call part or a notice, live or read back from a transcript. Each is driven through a host in its suite, every frame it sends and the chat a late client reads checked with the protocol's own schema. The wire suite reads the chat again after its turn, where it read only the session.
  • ACP and pi: a client that subscribes after a call ran reads the call a client that watched built, with its line, its confirmation and its result's content, as the protocol's reducer builds it.
  • Every agent on the root channel has a description, which AgentInfo requires: the backend's own, or its display name.
  • Claude, ACP, cofold and pi: a terminal a call names says isPty as the terminal's state does, so a client parses the VT sequences a pseudoterminal writes; it said isPty: false whatever ran the command. An ACP server's own terminal says it too.
  • cofold: a ! command's call is held as a client that watched it holds it, so a client that subscribes while the command runs reads the running call, and its ready says the command as its line.
  • pi: a turn names the entry pi wrote its prompt under however it ends, and when its session closes while it runs, as a host that stops closes it. A turn the host was running as it stopped read back without its sender.
  • pi: a ! command's call names the terminal it ran in, which a client opens to watch it and read it after.
  • Claude: a chat the host resumes by an id the SDK holds no conversation under, one nobody spoke to or one whose CLI could not sign in, starts a new conversation under that id, asked of the SDK's getSessionInfo as its first CLI starts, as VS Code's agent host asks. A transcript under that id in which the SDK finds no session is deleted first, through the SDK's deleteSession, since the CLI starts no conversation under an id it holds a transcript for. The CLI ended at startup with No conversation found when it was told to resume one. A conversation the SDK holds is resumed from any project folder, as its resume finds it. See packages/agent-claude/README.md.

Claude ​

  • A turn that starts on a new Claude CLI, for an agent the message picked, an edited record or another login, sends its prompt to that CLI, as VS Code's agent host keeps its prompt queue across a rebind. The SDK reads a new CLI's input only once its initialize request is out, which a session offering a client's tools writes late, and the prompt went to the reader of the CLI it replaced, whose closed query dropped it: the turn sat at "Thinking" for good.
  • A Claude turn that a client starts after announcing its tools is offered those tools, as VS Code's agent host rebuilds its query with them before it sends a turn. The turn waits until the CLI has taken the change, and changes reach the CLI one at a time in the order they came. The turn went out beside the change and ran without the client's tools, and two changes close together could leave the first set in place.
  • A steering message the CLI answers after the turn's last answer is answered in that turn, as VS Code's agent host keeps it. The turn stays open until the CLI has answered every prompt it was handed in it, and a queued message waits for that. The answer opened a turn of its own with an empty message, and a queued message that started in between took the answer for its own. See docs/sdk/CHATS.md.
  • A steering message the CLI still holds when its turn is stopped is answered in a turn of its own, which opens as that turn ends and carries the message, so a turn sent meanwhile waits for it. A stop of that turn stops the message with it, and one the CLI holds when its turn fails fails with it. What the stopped answer said after the stop opened a turn with an empty message, which its interrupted result then ended as a failure, and a turn begun after the stop took the steering message's answer.
  • A Claude turn begun right after a stop runs to its own end. The stopped turn's result ended it as a failure.
  • A Claude transcript read back after a restart shows the turns a client saw live. A steering message the CLI took in at a tool call reads back in that turn, and so does one it ran after the turn's last answer, from the host's record of the turns a chat began; a stop reads back as the end of a cancelled turn. Each read back as a turn of its own, and a stop as a turn of the CLI's stop text. See docs/sdk/CHATS.md.
  • A Bash result too large to send shows the whole output Claude saved in a terminal on the call, as VS Code does. The call keeps Claude's notice as its text, and the terminal has exited, with 0 for a command that worked. The host keeps the output for as long as the chat lasts, and a call read back from the transcript names the terminal again.
  • An agent runs on several Claude logins, each a connector of the claude-login adapter this plugin registers: a sign-in on claude.ai, or a token claude setup-token printed. A record names them in logins, in the order a turn tries them, and a turn runs on the first ready one. A request that fails with no connection, a 429 or a 5xx before the turn showed anything goes to the next login, as the model proxy hands a call to its next entry. A refused credential ends the turn naming the login, a failure that names a retry time keeps its login out until then, and each failure is logged and checked on the login's row. See packages/agent-claude/README.md.
  • A Claude session is offered the MCP servers the host offers every session. A stdio server whose folder is not the session's is left out, and the log says so.
  • A skill, command, agent or rule names the file it came from, so a client opens the real SKILL.md. The CLAUDE.md files and .claude/rules/**/*.md are listed as rules. One written after a session opened shows at once.
  • A failed turn ends with the CLI's own words. A login refused for its limit says so on its connector's row, with when it is back.
  • A turn stopped while a call waits tells Claude "The turn was stopped" for that call, not that the person refused it.
  • A background task's row stays after its turn ends, and a stop removes only that task's row. When a task ends, the turn the CLI opens shows its summary as a notice, live and after a restart.
  • A symlink loop in .claude/rules no longer stops the daemon: the walk visits each folder once.

Machines ​

  • A box in ssh.machines says what it can run: read computer://ssh.<name>/server for its OS, CPUs, memory, disk, Docker, KVM and libvirt, with the lines that fix what is missing. Nothing is installed on the box.
  • A profile with server makes its machines on that box's Docker. The session's code arrives by bundle or by clone, and the work comes back to the session's branch. No credential of this host is mounted.
  • A profile with runtime: "libvirt" or runtime: "proxmox" makes its machines as VMs, on the libvirt of its server or on a Proxmox node. A VM can be suspended, and a suspended VM is not metered. See docs/plugins/COMPUTERS.md.
  • A cofold or pi agent record runs in a machine. The machine gets the record by name, and an ssh box or a joined node gets it without its secrets.

Plugins and connectors ​

  • The new session form and the plugins screen come in the language a client sends as locale in initialize or reconnect. The host and each first-party plugin ship pt and es, and a string with no translation stays in English. A plugin ships its own text with ahpd.i18n. See docs/plugins/PLUGINS.md.
  • An engine's schema may mark a field x-ahpd-connector with an adapter, as an automation's may. The agents channel then refuses a put that names a connector the host does not serve, one of another adapter, or one that is neither the record owner's nor the host's. See docs/plugins/PLUGINS.md.
  • A tunnel kind's field marked x-ahpd-connector is checked too. A tunnel create or update is refused when the field names no host connector of that adapter, or a person's connector. See docs/plugins/PLUGINS.md.
  • A client draws a field marked x-ahpd-connector as a list. The host sends its connectors of the adapter in enum, with their titles in enumLabels, on the tunnel forms, the engine rows and the plugin trigger definitions. An engine or trigger field is also marked enumDynamic, and the new x-ahpd/completions command answers the reader's own connectors. See docs/plugins/PLUGINS.md.
  • probe, the check one call with a credential answers, is exported from @ahpd/sdk for an adapter's check, and takes a JSON body for a check that posts.
  • A Dev Tunnel enabled at boot waits for its connector's token refresh before it calls the service. It started with the vault's token, which has expired when the daemon was down longer than eight hours, and failed with GitHub API returned 401 (Unauthorized) while its connector's check said it works.
  • The host keeps a list of tunnels at x-ahpd-tunnels://, and a plugin adds a kind of tunnel. Dev Tunnels is the first kind, run in the daemon. A connector of the devtunnel adapter signs in with GitHub by a device code. The system tunnel that VS Code and ahpc find stays at the same address.
  • An adapter may declare a device sign-in: the host answers a code and the provider's page, and waits for the person. A connector whose check the provider refuses is unauthorized until a check passes or it signs in again.
  • A plugin shows its state on its page with showView, from a small set of components with literal values. The Dev Tunnel plugin shows its address, port, relay state and client count.
  • A plugin that fails after it loads says so on its row.
  • A plugin registers the grants it requires with host.registerGrants, so a role editor lists them. A required grant nothing lists is one line in the plugin's problems.
  • A connector row shows <set> for every writeOnly value, at any depth.
  • examples/canvas is a plugin that serves a canvas from its own HTTP server on a loopback port.

Access and usage ​

  • Nobody gives a grant they do not hold: writing or removing a role or a person is bounded by the caller's own grants, over AHP as on the command line. A person with role:put could make themselves admin through role://.
  • A minute limit with pool: each counts each person, as a minute limit with pool: all counts everybody.
  • x-ahpd/me on a plugin's own connection answers the plugin's grants and its plugin:<name> pool.

Push ​

  • @ahpd/push sends to a browser through its Web Push service, as it sends to a phone through Expo. A browser registers under push://devices and reads the host's key from push://vapid. See docs/plugins/push.md.

Bots ​

  • A message steered into a bot chat keeps its sender on the turn it starts: origin and _meta come from the steered message rather than falling back to the user. Session.steer takes them as an optional from after attachments, and a backend that takes no sender ignores it.
  • A message to a bot goes to its back session as a queued message, and a message steered into the open main turn goes to the back turn as a steer.
  • A back session can post more than once in one turn, and a turn typed in the back session opens a turn in the main chat. A question or a call the back session stops asking about leaves the main chat at once.
  • AGENTS.md is the bot's one copy of its persona.
  • The Bot chat's picker lists only the bots its reader may read, and a chat with another person's bot is refused.