Arcana 系统架构文档¶
本文档对应当前默认实现:V2 引擎
ConversationAgent。 V1 (Agent+AdaptivePolicy+Reducer) 已降级为兼容路径,详见末节"遗留兼容"。
1. 项目简介¶
Arcana 是 LLM Agent 的操作系统 (OS),不是流水线 (Pipeline)。
A pipeline dictates: do step 1, then step 2, then step 3. An operating system provides: here are your capabilities, here are your boundaries, go solve the problem. — CONSTITUTION.md, Chapter I
Arcana 是一个 Contracts-First 的 Agent 平台:所有数据流都先在 contracts/ 里以 Pydantic Schema 定义,实现层永远不发明 ad-hoc dict。这使得未来向 Go/Rust 迁移可以保持上层逻辑不变。
核心设计原则(与 CONSTITUTION.md 九条 Principles 对齐):
- Default to Direct, Escalate to Agent — 简单请求走单次 LLM 调用,复杂请求才进入 Agent 循环
- Context as Working Set, Not Archive — 上下文是当前推理步骤的工作集,不是仓库
- Tools as Capabilities, Not Interfaces — 工具不是 API 包装,而是 LLM 可推理的能力
- Allow Strategy Leaps — LLM 不欠我们逐步思考过程,可以一步跳到答案
- Structured, Actionable Feedback — 错误反馈必须可推理,不是裸异常字符串
- Runtime as OS, Not Form Engine — Runtime 提供服务,不规定流程
- Judge by Outcomes, Not by Process — 衡量 Agent 看是否达成目标,不是是否走完模板
- Agent Autonomy in Collaboration — 多 Agent 协作时框架只提供通信基建,不强加层级
- Cognitive Primitives as Services — Runtime 也为 LLM 自身的推理状态提供服务(recall / pin / unpin),但绝不代为调用
v3.6 补充了 2025-2026 的外部趋势判断:MCP / connectors / A2A 这类协议是能力传输层,不是信任边界;guardrails 是边界,不是隐藏工作流;evals 是发布证据,不是运行时监督者。Arcana 可以接入这些协议,但必须先把它们规整进自己的 contracts、trace、side-effect、approval、provenance 体系。
四条禁令 (CONSTITUTION.md Chapter II):
- No Premature Structuring · No Controllability Theater · No Context Hoarding · No Mechanical Retry
2. 模块进度概览¶
| 模块 | 状态 | 说明 |
|---|---|---|
| Contracts | 完成 | Pydantic 数据契约:turn / routing / context / diagnosis / llm / tool / state / streaming / cognitive 等 |
| Trace | 完成 | JSONL 审计日志:Writer / Reader / Replay;每次 LLM 调用自动落盘 |
| Gateway | 完成 | ModelGatewayRegistry + OpenAICompatibleProvider,多 provider fallback、prompt caching、batch_generate |
| Runtime | 完成 | ConversationAgent (V2,默认引擎):LLM Turn → TurnFacts → TurnAssessment → State;并行工具、思考信号、ask_user 拦截、checkpoint |
| Routing | 完成 | IntentClassifier 规则 + LLM 双层分流;DIRECT_ANSWER 走单次调用,其余进入 ConversationAgent |
| Context | 完成 | WorkingSetBuilder 工作集装配 + 4 级保真度压缩 (L0-L3) + LLM 摘要兜底 |
| ToolGateway | 完成 | 鉴权 / 校验 / 幂等 / 重试 / 审计;call_many 按 side-effect 分派:read 并发、write 顺序 |
| Protocol Bridges | 规划 | MCP / A2A / connectors 等外部协议适配层;只负责能力发现、身份、授权、版本、传输错误和 provenance,不负责规划或调度 |
| Cognitive Primitives | 部分 | 已实现:recall / pin / unpin(拦截式工具,不走 ToolGateway);branch / anchor / hint 仍在 roadmap |
| Diagnosis | 完成 | ErrorDiagnosis 结构化错误简报,工具失败转为下一轮 user 消息注入给 LLM |
| Multi-Agent | 完成 | AgentPool + Channel + SharedContext;用户控制编排,框架只提供通信基建 |
| Streaming | 完成 | StreamEvent / StreamEventType;astream() / chat.stream() |
| Guardrails / Evals | 部分 | eval 模块已存在;协议安全、tool-call guardrails、prompt-injected tool output、remote capability approval 仍需补齐 |
| Memory / RAG | 部分 | Runtime 级 RunMemoryStore;长期记忆 / RAG 检索仍在演进 |
| Storage | 待实现 | 持久化后端(LevelDB / Redis / Postgres) |
3. 默认执行路径¶
flowchart LR
User([用户 Goal]) --> Run["Runtime.run / chat / chain / run_batch"]
Run --> Router["Intent Router<br/>(routing.classifier)"]
Router -->|DIRECT_ANSWER| Direct["Direct Answer<br/>1 次 LLM 调用"]
Router -->|其他意图| Agent["ConversationAgent<br/>(runtime.conversation)"]
Direct --> Result([RunResult])
Agent --> Result
入口是 Runtime,它持有长寿命资源(providers、tools、budget、trace、memory)。每次 run() 内部:
- 意图分类(可选):
IntentClassifier用规则 + LLM fallback 决定是否走快路径 - 快路径 (
DIRECT_ANSWER):调用DirectExecutor.direct_answer,单次 LLM 调用直接返回 - Agent 路径:交给
ConversationAgent.astream()跑 turn 循环
No Premature Structuring:Arcana 不在 Agent 进入循环前规划步骤;规划是 LLM 自己的事。
4. Turn Loop(V2 核心)¶
ConversationAgent 以一条很薄的抽象取代了 V1 的 Policy → StepExecutor → Reducer 链:
LLMResponse→TurnFacts→TurnAssessment→State
flowchart TD
Start([astream goal]) --> Init["初始化 AgentState + Messages"]
Init --> LoopHead{turn < max_turns?}
LoopHead -->|否| Exhausted["状态 → FAILED<br/>(max_turns 耗尽)"]
LoopHead -->|是| BudgetCheck["1. Budget 检查<br/>BudgetTracker.check_budget"]
BudgetCheck --> CtxBuild["2. WorkingSetBuilder<br/>abuild_conversation_context<br/>(L0-L3 保真度压缩)"]
CtxBuild --> LLMCall["3. LLM 调用 (流式)<br/>gateway.stream / generate"]
LLMCall --> Parse["4. _parse_turn(response)<br/>→ TurnFacts<br/>(纯映射,零判断)"]
Parse --> Assess["5. _assess_turn(facts, state)<br/>→ TurnAssessment<br/>(完成 / 失败 / 信心度)"]
Assess --> Trace["6. _trace_turn<br/>(facts + assessment 分别落盘)"]
Trace --> Branch{有 tool_calls?}
Branch -->|是| Tools["7. _execute_tools<br/>ask_user / cognitive 拦截<br/>其余走 Channel / ToolGateway"]
Tools --> Diagnose["失败工具 → ErrorDiagnosis<br/>注入下一轮 user 消息"]
Diagnose --> NextTurn
Branch -->|否, completed| Complete["状态 → COMPLETED<br/>working_memory.answer"]
Branch -->|否, failed| Failed["状态 → FAILED"]
Branch -->|否, 中间文本| Continue["追加 assistant 消息"]
Continue --> NextTurn
NextTurn["增 step / 计 token / checkpoint?"] --> LoopHead
Complete --> Done([RUN_COMPLETE])
Failed --> Done
Exhausted --> Done
4.1 TurnFacts vs TurnAssessment —— 必须可见地分离¶
contracts/turn.py 强制此分离:
| 由谁产出 | 内容 | |
|---|---|---|
TurnFacts |
LLM (provider response) | assistant_text · tool_calls · usage · finish_reason · thinking |
TurnAssessment |
Runtime | completed · failed · answer · completion_reason · confidence |
The LLM does not judge its own output; the runtime does not generate content.
_parse_turn() 只做 provider → framework 的纯映射,禁止填写任何 completed / failed。_assess_turn() 才做解读:
- 有
tool_calls→ 未完成(LLM 还想做事) - 既无文本也无工具 →
failed = empty_response finish_reason == "stop"且有文本 →completed,置信度 0.85- 若 thinking 含"need to verify / 需要验证"等 →
completed = False(Thinking as Signal) - 若 thinking 含"not sure / 不确定" →
confidence *= 0.6
关键约束:Runtime 听 thinking 信号是为了改善自身判断,不是反过来约束 LLM 的策略。"听" ≠ "强迫思考"。
5. Runtime Services¶
Runtime as OS —— Runtime 提供服务,不规定流程。每个服务都是 LLM 可调用的能力,而不是它必须经过的步骤。
5.1 Budget Enforcement¶
gateway.budget.BudgetTracker 管 max_cost_usd / max_tokens / max_iterations / max_time_ms。每个 turn 开头 check_budget() 一次,每次 LLM 用量 add_usage()。超额抛 BudgetExceededError。
5.2 Trace¶
TraceWriter 写 JSONL:TURN(含 facts + assessment)、CONTEXT_DECISION、TOOL_CALL、COGNITIVE_PRIMITIVE、可选 PROMPT_SNAPSHOT。所有摘要走 utils/hashing.py 的 SHA-256 截断 16 字符。
5.3 Tool Dispatch¶
工具调用走 ExecutionChannel(默认 LocalChannel 包装 ToolGateway),便于未来切到子进程 / 容器 / 远程后端。
派发模型(按宪章 Principle 3 + side-effect 区分):
- Read 工具:
asyncio.gather并发执行 - Write 工具:顺序执行,避免冲突
- 由
ToolGateway.call_many()根据ToolSpec.side_effect自动分流
ask_user 与认知原语(recall / pin / unpin)不走 ToolGateway —— ConversationAgent._execute_tools 直接拦截分发。
5.3.1 Protocol Bridges — 外部协议只进 contracts¶
外部协议接入应落在 ToolGateway / ExecutionChannel / Channel 之前的适配层,暂称 ProtocolBridge。它的职责是把 MCP / connectors / A2A / browser-control / computer-use 这类外部能力规整成 Arcana 内部已经存在的契约:
| 外部概念 | Arcana 内部形态 |
|---|---|
| MCP / connector tool | ToolSpec + ToolProvider,必须带 origin / authority / side_effect / approval / provenance |
| 远程工具调用失败 | ToolError(category=TRANSPORT | PERMISSION | VALIDATION | UNEXPECTED, ...) |
| A2A peer / remote agent | Channel 可寻址 endpoint,或者未来的 ExecutionChannel remote backend |
| 协议版本 / agent card / capability discovery | trace 事件 + capability hash;不直接进入 prompt,除非当前 turn 需要 |
| 人工确认 / connector approval | requires_confirmation 或显式 approval 结果,拒绝时返回结构化反馈 |
这层不做:
- 不根据 A2A graph 或 MCP catalog 自动规划下一步
- 不把远程 agent 变成框架内置 supervisor
- 不把远程工具目录整包塞进上下文
- 不因协议标准化而跳过权限、side-effect、approval、trace
v3.6 规则:协议是能力传输层,不是信任边界。远程能力进入 LLM 视野前,必须先变成可审计、可授权、可解释失败的 Arcana capability。
5.4 Context — Working Set + Fidelity Compression¶
context.builder.WorkingSetBuilder.abuild_conversation_context() 每个 turn 重建一次工作集,按 token budget 在 4 级保真度间裁剪:
| 级别 | 处理 |
|---|---|
| L0 | 原文保留 |
| L1 | 截断到约 300 字符 |
| L2 | 单行摘要 |
| L3 | 仅保留角色标签;继续超额时整条丢弃 |
低相关度消息先被降级;预算仍紧张时回退到 LLM 摘要 → 直接丢弃。pinned 内容(来自 pin 原语)以 pinned=True 的 ContextBlock 注入 Working 层,不可压缩。
5.5 Diagnostics¶
runtime.diagnosis.diagnoser.diagnose_tool_error 把 ToolError 转成 ErrorDiagnosis:包含失败类别、可能原因、可执行的下一步建议。结果作为 user 消息追加到对话,让 LLM 自己决定恢复策略 —— 这是宪章 Principle 5 (Structured, Actionable Feedback) + 第 4 禁令 (No Mechanical Retry) 的实现。
5.6 ask_user — 能力,不是流程¶
runtime.ask_user.ASK_USER_SPEC 注册为内置工具,schema 出现在工具列表里。当 LLM 调用 ask_user 时:
- 在
_execute_tools里被拦截,不进 ToolGateway - 通过
AskUserHandler调用用户提供的input_handler - 若没有 input_handler:LLM 收到 fallback 消息,继续按自身判断推进
Inviolable Rule:用户从不被强制中途交互。
ask_user是 LLM 可开的门,不是它必须走的走廊。LLM 也绝不阻塞等待用户回答 —— 没回应就当作信号,自己想办法。
5.7 Cognitive Primitives — 仅 recall / pin / unpin¶
v0.7.0 引入,由 RuntimeConfig.cognitive_primitives 显式启用(默认空,零行为变化)。以拦截式工具(与 ask_user 同机制)暴露给 LLM:
recall(turn, include?)— 取出某历史 turn 的原始内容,绕开 working-set 压缩pin(content, label?, until_turn?)— 把关键内容钉住,未来 working-set 不压缩;幂等(同内容返回同pin_id);超出pin_budget_fraction * total_window即拒绝unpin(pin_id)— 释放 pin
Roadmap(当前未实现,宪章 Principle 9 中已提及但
runtime/cognitive.py尚未实现): -branch— 沙箱化推理分支,可提交或丢弃 -anchor— 标记暂定假设,未来可被信号化失效 -hint— 提示下一次 working-set 构建偏好这三个是未来工作;引用它们不应假设当前可调用。
Inviolable Rule:框架绝不代 LLM 调用认知原语,绝不在 prompt 里暗示使用,绝不评估 LLM 是否"用得对"。提供服务 ≠ 规定使用。
5.8 Guardrails / Evals — 边界与证据¶
Arcana 的安全和质量控制分两层:
- Guardrails:运行时边界。适合放在输入、输出、tool-call、remote protocol、computer-use、write side-effect 等边界上。它们可以阻止、要求确认、降权、脱敏或返回结构化错误,但不能偷偷替 LLM 选择策略。
- Evals:发布证据。适合覆盖 prompt injection、工具误用、remote capability discovery、approval denial、协议 transport failure、长任务预算耗尽、多 agent failure propagation 等风险。eval 失败应阻止发布或暴露风险,不应变成运行时隐藏 supervisor。
当前 eval/ 已提供基础评估框架;下一步应该补齐面向协议接入和 agentic safety 的 focused suites,而不是引入强制 planner。
6. 公开 API¶
入口都在 arcana.Runtime 上:
| 方法 | 用途 |
|---|---|
runtime.run(goal, ...) |
单次任务:路由 → ConversationAgent / Direct → RunResult |
runtime.stream(goal) |
流式版本,逐事件吐 StreamEvent |
runtime.chat() |
多轮会话;返回 ChatSession,send / stream 内部委托给 ConversationAgent,因此自动获得 ask_user / 懒加载工具 / 保真度压缩 / 思考评估 |
runtime.chain(steps, input?) |
顺序流水线,ChainStep 组成;嵌套 list[ChainStep] 表示并行分支;上一步输出自动作为下一步 <context> |
runtime.run_batch(tasks, concurrency=5) |
并发跑多个独立任务,asyncio.Semaphore 控量;返回 BatchResult(含每条 RunResult) |
runtime.collaborate(...) |
多 Agent 协作池;返回 AgentPool(async context manager) |
runtime.session() |
手动控制底层 Session(高级用法) |
runtime.budget_scope(...) |
局部 budget 子作用域 |
runtime.on / off |
Runtime 事件钩子(pub/sub) |
run() 关键参数:engine(默认 "conversation")、provider / model、tools、response_format(Pydantic 类,结构化输出)、images(多模态)、input_handler、system、context、on_parse_error、budget。
7. Contracts 层 —— 数据法¶
所有跨模块数据都先在 src/arcana/contracts/ 定义为 Pydantic 模型。这是 Arcana 的"数据法":实现可以替换,契约不能脱钩。
| 文件 | 关键模型 |
|---|---|
turn.py |
TurnFacts · TurnAssessment · TurnOutcome |
routing.py |
RoutingDecision · IntentCategory · IntentType |
context.py |
ContextBlock · ContextLayer · TokenBudget · ContextDecision · MessageDecision · ContextReport · ContextStrategy · WorkingSet · StepContext |
diagnosis.py |
ErrorDiagnosis · ErrorCategory |
llm.py |
LLMRequest · LLMResponse · ModelConfig · Message · ContentBlock · ToolCallRequest · TokenUsage |
tool.py |
ToolSpec · ToolCall · ToolResult · ToolError · SideEffect · ASK_USER_TOOL_NAME |
cognitive.py |
RecallRequest/Result · PinRequest/Result/State/Entry · UnpinRequest/Result |
state.py |
AgentState · ExecutionStatus |
runtime.py |
RuntimeConfig 等运行期配置 |
streaming.py |
StreamEvent · StreamEventType |
channel.py |
ExecutionChannel 协议(Brain / Hands 通信分离) |
Canonical Hashing:utils/hashing.py.canonical_hash(obj) 对排序后的 JSON 算 SHA-256,截 16 字符,用于 trace 摘要和幂等键。
Trace = Audit Log:每次 LLM 调用、工具调用、context 决策、cognitive 调用都被记录。可用 TraceReader 离线回放(replay_prompt)—— 当 RuntimeConfig.trace_include_prompt_snapshots=True 或 dev_mode=True 时,会额外落盘 PROMPT_SNAPSHOT。
8. Provider 抽象¶
gateway/registry.py.ModelGatewayRegistry 是单一接入点:
OpenAICompatibleProvider—— 通用适配器,把 OpenAI 格式 API 复用给 DeepSeek / Kimi / GLM / MiniMax / Ollama 等,只需改base_url- 原生适配器 —— Anthropic、Gemini 各自原生 SDK,把响应规整到统一的
LLMResponse(带anthropic/gemini子字段保留 provider 特性) - Fallback Chain —— 多 provider 注册时按 dict 插入顺序自动建链;首选失败且
error.retryable=True时自动切到下一个 - Prompt Caching(透明优化):
- Anthropic:自动给 system prompt + tool schemas 打
cache_control标签 - OpenAI:自动追踪
cached_tokens - 应用层零感知,零改动
batch_generate(requests, concurrency=...)—— 单 provider 与 Registry 都暴露;用asyncio.Semaphore限流
ProviderProfile 描述 provider 的能力(是否支持 tools、json_schema、思考、流式等),不支持的请求会自动降级(例如 MiniMax 收到 tools 请求会降级为文本提示)。
9. 多 Agent 协作¶
runtime.collaborate(...) 返回 AgentPool(multi_agent/agent_pool.py):
- 共享通信基建:
Channel(消息总线)+SharedContext(共享存储)+ 共享BudgetTracker - 每个 Agent 是独立
ChatSession:自己的 system prompt、provider/model 选择、可选独立 cognitive_primitives 配置 - 用户控制编排:何时谁说话、读谁的消息、何时结束 —— 全部由用户代码决定
Principle 8 (Agent Autonomy):框架绝不强加 planner/executor 之类的层级。如果出现这种模式,那是因为 Agent 的 prompt 自己定义了角色,而不是框架强迫的拓扑。
9.1 A2A / 跨框架互操作边界¶
如果未来接入 A2A 或类似协议,Arcana 只承诺下面这些 OS 级能力:
- 发现远程 agent / capability,并记录 identity、origin、version、authority
- 把远程 agent 暴露为可寻址 peer,而不是内置拓扑节点
- 把远程消息、任务状态、transport failure 规整成
Channel/ trace / structured feedback - 保留用户代码对 turn order、supervision policy、retry / cancel / escalate 的控制权
换句话说,A2A 可以成为 AgentPool 的远程 transport 或 peer discovery 机制;不能成为 Arcana 的默认 orchestrator。
详见 docs/guide/multi-agent.md(如有)以及 examples/ 中的协作样例。
10. 遗留兼容(V1)¶
V1 的执行模型 —— Agent + AdaptivePolicy (含 ReAct / Plan-Execute 历史变体) + Reducer —— 仍保留在 runtime/agent.py,可通过 engine="adaptive" 选择:
这不是推荐路径。V1 的多策略 Policy / StepExecutor / Reducer 链与宪章第 1 禁令(No Premature Structuring)和第 4、6 条 Principles 存在张力,仅为了向后兼容而保留,不再接受新功能:
- 新增功能(多模态、并行工具、保真度压缩、cognitive 原语、collaborate)只在 V2 路径上实现
- V1 旧学习文档已归档至
docs/legacy/ - 长期路线图:当 V1 用户全部迁移到 V2 后移除(移除策略见
CONSTITUTION.mdAmendment 2 / removals policy)
11. 关键路径速查¶
| 概念 | 路径 |
|---|---|
| V2 Agent 引擎 | src/arcana/runtime/conversation.py |
| Runtime + Session + Chat + Chain + Batch | src/arcana/runtime_core.py |
| Turn 契约 | src/arcana/contracts/turn.py |
| Intent 路由 | src/arcana/routing/classifier.py · routing/executor.py |
| Working Set + 保真度压缩 | src/arcana/context/builder.py |
| Tool 执行 | src/arcana/tool_gateway/gateway.py · tool_gateway/local_channel.py |
| ask_user | src/arcana/runtime/ask_user.py |
| 认知原语 (recall/pin/unpin) | src/arcana/runtime/cognitive.py · contracts/cognitive.py |
| 错误诊断 | src/arcana/runtime/diagnosis/diagnoser.py |
| Provider Gateway | src/arcana/gateway/registry.py · gateway/openai_compat.py |
| Trace | src/arcana/trace/writer.py · trace/reader.py |
| 多 Agent | src/arcana/multi_agent/agent_pool.py · multi_agent/channel.py |
| Evals | src/arcana/eval/ · tests/test_eval.py |
| 协议接入 roadmap | specs/constitution-amendment-5-agent-protocols-and-safety.md |
| V1 (legacy) | src/arcana/runtime/agent.py · runtime/policies/ |
12. 扩展阅读¶
constitution.md— 九条 Principles + 四条禁令 + 责任划分(这是法律,不是建议)guide/quickstart.md·guide/api.md— 快速开始、配置、Provider、API 参考specs/— 各模块设计 spec(含 V2 conversation-loop 详设、cognitive primitives amendment 等)legacy/— V1 学习材料(仅供历史参考)- 仓库根
CLAUDE.md— 开发者速查(不在 mkdocs 渲染范围内)
文档版本:3.1(V2 + v3.6 protocol/safety update)
对应代码:runtime/conversation.py · runtime_core.py
最近大幅变动:v0.7.0 引入认知原语 (recall/pin/unpin);v0.8.x 加固 Channel / MessageBus 边界;team() deprecated → collaborate();v1.0.0 物理移除 runtime.team(),2026-05-03 进一步移除 TeamOrchestrator / RoleConfig / MessageBus(Amendment 3 后续清理);2026-05-27 宪法 v3.6 明确 MCP / A2A / connectors / guardrails / evals 的框架边界