Rio
adr/0026-agent-first-slice-api-key.mdRaw Markdown · GitHub ↗

ADR-0026: The agent's first slice, on the official API key only

Context

With a working core and GUI in place, the agent subsystem (ADR-0020) could be started. The questions were its protocol, its provider contract, how much tool surface to expose first, and how the first real provider (Claude) authenticates. An early prototype signed in with a claude.ai subscription through OAuth, which only works if the request impersonates Anthropic's own Claude Code client.

Decision

Protocol. agent.send {text} is a streaming operation. It emits agent.delta (text chunks), agent.tool and agent.tool_result (tool activity), agent.propose (a write awaiting approval), agent.message (a completed turn) and agent.error (a classified failure). Conversation state lives in the core; the chat pane is a view. Events are structured data a terminal client could render.

Provider contract. A provider is called with the conversation, the tool specifications and a post callback, and posts delta, tool, done and error. The Claude provider is split into a shared inference module (request shaping, SSE parsing, tool-call mapping) and a thin authentication face, so the inference code does not depend on one credential scheme.

Tool surface. Reads (fs_list, fs_read, buffer_list, buffer_text) run automatically, confined to the project root and size-capped. Writes (propose_edit, propose_create) never run automatically: the loop emits agent.propose with a diff and suspends the turn's coroutine until agent.approve {turn, decision}. Approved edits to open buffers go through buffer.replace and are undoable. When approval arrives, the edit is located again in the current text, so typing during review cannot make it land in the wrong place; a vanished or ambiguous target is refused. A new message sent while a proposal is pending closes the dangling tool call with an "interrupted" result.

Authentication. Only the documented Anthropic Messages API with the user's API key (x-api-key), stored as a 0600 secret. The subscription OAuth path was removed and will not be revived.

Resilience. Endpoint, model, API version and request timeout are configuration data. Failures are classified (not configured, authentication rejected, rate limited, server error, network, unexpected response) and each names the next action. A provider failure is contained in agent.error.

Alternatives considered

The claude.ai subscription path. Making the API accept a subscription token required a forced "You are Claude Code" system prompt and an OAuth beta header. That is undocumented, against the provider's terms, and revocable without notice. rio does not ship code or architecture that depends on violating a service's terms of service. Any future alternative authentication must be a sanctioned mechanism.

Consequences

  • A useful agent shipped without the command-execution surface, which followed under its own guardrails (ADR-0083).
  • The approval gate is provider-agnostic; adding write tools needed no provider change.
  • Later changes: a system prompt (ADR-0034); Claude moved out of the tree into an installable provider (ADR-0069); the step cap was replaced by a Stop button (ADR-0104).