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

2.8 KiB

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).