forked from bai/curriculum-project-hub
docs(adr): ADR-0017 重写——从 provider-agnostic 改为 Claude Code SDK
放弃 provider-agnostic,用 @anthropic-ai/claude-agent-sdk。代价:不能走 OpenRouter(GLM 等),需 Anthropic API key。收益:compaction、工具审批、 partial message 流式、Claude-to-IM 原生兼容。迁移路径:如果将来需要 provider-agnostic,用 streamText + SSE 转换层(~100 行)替回 query()。
This commit is contained in:
@@ -1,50 +1,53 @@
|
|||||||
# ADR 0017: AgentSession Is Provider-Bound
|
# ADR 0017: Agent Runtime — Claude Code SDK
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted.
|
Accepted — supersedes the original ADR-0017 (provider-agnostic session).
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
ADR-0002 says a long-lived `AgentSession` can be reused by many runs, but never
|
The original ADR-0017 mandated a provider-agnostic agent layer: any
|
||||||
addressed the provider/model dimension. The agent layer is provider-agnostic
|
OpenAI-compatible model via OpenRouter, custom agent loop built on Vercel AI
|
||||||
(ADR-0001..0003 never required Claude specifically; the `@Claude` trigger name
|
SDK's `streamText`. The motivation was to avoid vendor lock-in.
|
||||||
is a product brand, not a provider commitment). In practice the Hub routes
|
|
||||||
model calls through an OpenAI-compatible client (e.g. OpenRouter), so a run may
|
|
||||||
target different models — and the motivation for multi-model is **role-based
|
|
||||||
routing**: drafting, reviewing, and triage suit different models.
|
|
||||||
|
|
||||||
That raises the question ADR-0002 left open: when a teacher switches model
|
In practice, the hand-rolled agent loop lacked capabilities that a production
|
||||||
mid-project, does the `AgentSession` carry live context across the switch, or
|
agent needs: context compaction (long conversations), tool approval flows,
|
||||||
is it bound to a single provider/model?
|
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. The
|
||||||
|
Claude-to-IM library (2.7K stars) has already solved these, but its
|
||||||
|
LLMProvider interface expects Claude Code SDK's SSE event format. Using it with
|
||||||
|
a generic provider would require a conversion layer.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
An `AgentSession` is **provider/model-bound**. A model or provider switch
|
Adopt `@anthropic-ai/claude-agent-sdk` as the agent runtime. The SDK's `query()`
|
||||||
starts a new `AgentSession`. The `AgentSession` does not carry live context
|
function owns the agent loop (tool dispatch, compaction, streaming). Built-in
|
||||||
across a switch.
|
tools (Read, Write, Bash, Glob, Grep) cover the entire tool surface — no custom
|
||||||
|
tools needed. `cph check` / `cph build` run via Bash.
|
||||||
|
|
||||||
Cross-session continuity is not the session's job — it is carried by ADR-0003's
|
This means:
|
||||||
project memory and anchors, which the Hub reads on demand when seeding a new
|
- The agent layer is **Anthropic-specific**, not provider-agnostic. Claude Code
|
||||||
run. This keeps B consistent with ADR-0003 ("the Hub avoids becoming a full
|
SDK speaks the Anthropic Messages API and cannot be pointed at OpenRouter
|
||||||
chat history store"): session continuity and history continuity are the same
|
(which is OpenAI-compatible).
|
||||||
mechanism, both via memory/anchors, never via a live cross-provider session.
|
- An Anthropic API key (or OAuth via Claude Pro/Max) is required.
|
||||||
|
- Model selection is within the Anthropic model family (Sonnet, Haiku, Opus),
|
||||||
Per-run model selection (the run carries its model) is the switching point.
|
not across vendors.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- Switching model mid-project = new session; the new run seeds from project
|
- We lose the ability to route to GLM or other non-Anthropic models via
|
||||||
memory/anchors (ADR-0003), not the prior session's live context.
|
OpenRouter. This is the accepted cost of getting a mature agent loop.
|
||||||
- The agent layer is provider-agnostic: model calls go through an
|
- The Vercel AI SDK dependency is dropped.
|
||||||
OpenAI-compatible client, and the tool-calling loop is delegated to the
|
- Custom tools (read_file, write_file, cph_check, cph_build) are replaced by
|
||||||
Vercel AI SDK (`generateText` + `tool`). The provider seam is the SDK's
|
the SDK's built-in Read/Write/Bash/Glob/Grep.
|
||||||
`LanguageModel` interface, not a hand-rolled loop; swapping provider is
|
- The SSE event format matches Claude-to-IM's expected protocol natively —
|
||||||
swapping the `createOpenAICompatible` factory, not rewriting the loop. No
|
adopting Claude-to-IM for Feishu integration is a direct fit, no conversion.
|
||||||
hard dependency on a single-vendor agent SDK.
|
- Per-run model selection still works (role→model mapping), but models are
|
||||||
- Role-based model routing (different run kinds default to different models) is
|
Anthropic aliases, not OpenRouter routing IDs.
|
||||||
a product/admin configuration concern, not a spec invariant.
|
- If provider-agnosticism becomes a hard requirement again, the migration path
|
||||||
- "AgentSession reused by many runs" (ADR-0002) holds *within* one
|
is: replace `query()` with a Vercel AI SDK `streamText` call + an SSE format
|
||||||
provider/model; it does not span a switch.
|
converter (~100 lines). The rest of the architecture (trigger, lock,
|
||||||
- The `@Claude` trigger name remains the product mention brand regardless of the
|
permission, Feishu client) is provider-agnostic already.
|
||||||
backing model.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user