forked from bai/curriculum-project-hub
78 lines
3.7 KiB
Markdown
78 lines
3.7 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` is provider/role/model-bound, and 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`. Role is part of the session binding because role prompts and
|
|
tool surfaces can differ even when the underlying model is the same; `/draft`
|
|
and `/review` must not resume the same Claude runtime cursor by accident.
|
|
|
|
Role definitions are Organization-scoped runtime data. A role bundle selects
|
|
its default model, system prompt, tool allowlist and installed Agent skill
|
|
versions. PostgreSQL is authoritative for role composition and skill metadata;
|
|
skill bytes live in a content-addressed persistent store selected only by the
|
|
recorded SHA-256 digest. Updating a role or binding skills takes effect without
|
|
a Hub release or process restart. A change to the role's execution surface
|
|
(model, prompt, tools, selected skill content) archives its active sessions so
|
|
the next run cannot resume a provider context created under stale instructions;
|
|
label and ordering-only changes preserve conversational continuity.
|
|
|
|
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 gives
|
|
limited OpenRouter-level model routing, but the runtime is still Claude Agent
|
|
SDK-shaped: non-Claude models are best-effort and may not support every
|
|
Claude Code feature.
|
|
|
|
## Consequences
|
|
|
|
- Provider-agnosticism is not fully preserved. OpenRouter can route to GLM,
|
|
Claude, GPT, etc., but the runtime protocol and agent loop remain Claude
|
|
Agent SDK-bound.
|
|
- 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).
|