Files
curriculum-project-hub/docs/adr/0017-agent-session-is-provider-bound.md
T

64 lines
2.8 KiB
Markdown

# ADR 0017: Agent Runtime — Claude Code SDK via OpenRouter
## Status
Accepted.
## Context
The original ADR-0017 mandated a provider-agnostic agent layer: any
OpenAI-compatible model via OpenRouter, custom agent loop built on Vercel AI
SDK's `streamText`. The motivation was to avoid vendor lock-in.
In practice, the hand-rolled agent loop lacked capabilities that a production
agent needs: context compaction (long conversations), tool approval flows,
partial-message streaming, and a battle-tested multi-turn loop. Building these
ourselves meant re-implementing what the Claude Code SDK already provides.
The Feishu integration also proved costly to hand-roll — card schema quirks,
patch limitations, file support — each API quirk cost a round-trip.
## Decision
Adopt `@anthropic-ai/claude-agent-sdk` as the agent runtime, routed through
OpenRouter's **"Anthropic Skin"** (`https://openrouter.ai/api`).
OpenRouter exposes an Anthropic Messages API-compatible endpoint. Claude Code
SDK speaks its native protocol directly to OpenRouter — no proxy, no format
conversion. The SDK's `query()` owns the agent loop (tool dispatch, compaction,
streaming). Built-in tools (Read, Write, Bash, Glob, Grep) cover the entire
tool surface — no custom tools needed. `cph check` / `cph build` run via Bash.
The Hub `AgentSession` remains provider/model-bound, but it must persist the
provider runtime cursor needed to continue a conversation. For Claude Code SDK,
that cursor is the `result.session_id`; store it in `AgentSession.metadata` as
`claudeSessionId` and pass it back to the next `query()` call as
`options.resume`.
Environment variables:
```
ANTHROPIC_BASE_URL=https://openrouter.ai/api
ANTHROPIC_AUTH_TOKEN=<OpenRouter API key>
ANTHROPIC_API_KEY="" # must be explicitly empty
```
Model routing: `ANTHROPIC_DEFAULT_SONNET_MODEL` etc. accept OpenRouter model
IDs (e.g. `z-ai/glm-4.7`, `anthropic/claude-sonnet-4-20250514`). This means
**provider-agnosticism is preserved** — any OpenRouter model that supports tool
use can be used, not just Anthropic models.
## Consequences
- Provider-agnosticism preserved via OpenRouter — GLM, Claude, GPT, etc. all
work as long as the model supports tool use.
- The agent loop is Claude Code SDK's (compaction, tool approval, partial
message streaming) — not hand-rolled.
- Custom tools (read_file, write_file, cph_check, cph_build) are replaced by
the SDK's built-in Read/Write/Bash/Glob/Grep.
- The SSE event format matches Claude-to-IM's expected protocol natively.
- Per-run model selection works via role→model mapping; models are OpenRouter
IDs, not Anthropic-only aliases.
- OpenRouter recommends setting Anthropic as the top-priority provider for
Claude Code; non-Anthropic models may have reduced compatibility with some
SDK features (e.g. thinking blocks).