Files
curriculum-project-hub/docs/adr/0017-agent-session-is-provider-bound.md
T
hongjr03 f8c3e16a52 feat(hub): 事件去重(A-4) + 审计写入路径(A-8)
A-4 FeishuEventReceipt:
- 新增 FeishuEventReceipt 表(eventId @unique),lark ws 至少一次投递的幂等兜底
- MessageReceiveEvent 加 header.event_id 字段
- trigger 入口查重:已存在的 event_id → 跳过(不建第二次 run)
- resetDb 加入 FeishuEventReceipt

A-8 审计写入:
- AuditEntry 加 projectId(权限拒绝/slash 命令等无 run 的审计点可定位)
- 新建 src/audit.ts:writeAudit(prisma, entry) best-effort 写入(吞错,不阻断 trigger)
- trigger 五个审计点:trigger.denied / trigger.role_denied / run.created / run.lock_race / run.finished / run.failed
- 新增 audit unit 测试(3) + trigger 集成测试(去重 + 审计,2)

59 tests 全过。
2026-07-07 21:34:15 +08:00

2.3 KiB

ADR 0017: AgentSession Is Provider-Bound

Status

Accepted.

Context

ADR-0002 says a long-lived AgentSession can be reused by many runs, but never addressed the provider/model dimension. The agent layer is provider-agnostic (ADR-0001..0003 never required Claude specifically; the @Claude trigger name 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 mid-project, does the AgentSession carry live context across the switch, or is it bound to a single provider/model?

Decision

An AgentSession is provider/model-bound. A model or provider switch starts a new AgentSession. The AgentSession does not carry live context across a switch.

Cross-session continuity is not the session's job — it is carried by ADR-0003's project memory and anchors, which the Hub reads on demand when seeding a new run. This keeps B consistent with ADR-0003 ("the Hub avoids becoming a full chat history store"): session continuity and history continuity are the same mechanism, both via memory/anchors, never via a live cross-provider session.

Per-run model selection (the run carries its model) is the switching point.

Consequences

  • Switching model mid-project = new session; the new run seeds from project memory/anchors (ADR-0003), not the prior session's live context.
  • The agent layer is provider-agnostic: model calls go through an OpenAI-compatible client, and the tool-calling loop is delegated to the Vercel AI SDK (generateText + tool). The provider seam is the SDK's LanguageModel interface, not a hand-rolled loop; swapping provider is swapping the createOpenAICompatible factory, not rewriting the loop. No hard dependency on a single-vendor agent SDK.
  • Role-based model routing (different run kinds default to different models) is a product/admin configuration concern, not a spec invariant.
  • "AgentSession reused by many runs" (ADR-0002) holds within one provider/model; it does not span a switch.
  • The @Claude trigger name remains the product mention brand regardless of the backing model.