- AgentConfig: 移除 10+ 个环境变量读取,仅保留 TOKEN_SOFT/HARD_LIMIT 两个
可调参数,context_char_limit 替换为统一的 token_soft_limit 阈值
- compact: find_safe_cut_point 重写为 HashSet O(n) 算法,
micro_compact 改为不可变风格,compress_context 签名升级为
token_soft_limit + max_messages 双参数,新增 COMPACTION_OUTPUT_RESERVE
- modes: ModeConfig.max_steps/tool_timeout_secs 去 Optional 化,
Deep Research 步数 16→100,Literature Reader 步数 6→25
- dashboard: 提取 AgentMarkdown/ThoughtCard/ToolCallCard/AnswerCard/
SubAgentContainer 等共享组件,ResearchAgentPanel 大幅瘦身,
交互卡片重构为 console-panel 紧凑风格
- security: 移除 HERMES_YOLO_MODE、AGENT_BLOCK_NETWORK 开关、
AGENT_CHECKPOINT_ENABLED 开关,关键安全机制强制启用
16 KiB
Agent 上下文管理系统
AstroResearch Agent 的上下文管理是一个多层防御架构,涵盖从系统提示词组装、运行时预算监控、多级压缩、错误恢复到跨会话持久化的完整生命周期。
架构概览
graph TB
subgraph 构建阶段["构建阶段"]
SP["SystemPrompt<br/>模块化组装"]
HL["+ 历史加载"]
TR["+ 任务恢复"]
SL["+ 技能列表"]
PM["+ 项目记忆"]
end
subgraph 运行时监控["运行时监控"]
TB["TokenBudget<br/>三级渐进Nudge"]
DR["DimReturns"]
TD["Todo提醒"]
BN["后台通知"]
LD["死循环检测"]
end
subgraph 压缩阶段["压缩阶段"]
L0["snip (L0)"]
L1["micro (L1)"]
L2["auto (L2)"]
L3["aggro_micro (L3)"]
L4["identity (L4)"]
L0 --> L1 --> L2 --> L3 --> L4
end
subgraph 恢复阶段["恢复阶段"]
ER["ErrorRecovery<br/>5级恢复阶梯"]
B4["429指数退避"]
CB["熔断器保护"]
FC["file_cache<br/>恢复注入"]
end
subgraph 持久化层["持久化层"]
AM["agent_msgs"]
AT["agent_tasks"]
MM["MEMORY.md"]
TJ["trajectory"]
AL["audit_log"]
end
构建阶段 --> 运行时监控 --> 压缩阶段 --> 恢复阶段 --> 持久化层
1. 上下文构建 (runtime/context.rs + runtime/session.rs)
1.1 构建流程
每个 turn 开始时 build_initial_context() 按以下顺序构建 LLM 消息列表:
- 从
agent_messages表加载历史消息(按agent_name='lead'隔离,排除子代理消息) - 若历史中无 System 消息,在最前面插入 System Prompt
- 追加当前用户问题
- 从
agent_tasks表恢复持久化任务状态,格式化为[当前任务状态]消息块
1.2 System Prompt 模块化组装 (runtime/system_prompt.rs)
静态与动态 section 分离,静态 section 在前以最大化 prompt cache 命中率:
| 顺序 | Section | 类型 | 内容 |
|---|---|---|---|
| 1 | identity |
静态 | "你是一位专业的天体物理学研究助手…" |
| 2 | tools |
动态 | 从 ToolRegistry 生成工具名称 + 一行描述(~20 tokens/tool) |
| 3 | skills |
动态 | 从 SkillRegistry 构建 <system-reminder> 技能列表(~20 tokens/skill) |
| 4 | memory |
动态 | 从 MemoryManager 加载最近 5 条项目记忆 |
| 5 | principles |
静态 | 9 条核心工作原则(主动搜索、引用来源、LaTeX 格式等) |
1.3 会话生命周期 (session.rs)
create_or_resume_session():新建会话生成 UUID,恢复会话验证存在性并计算 turn_indexload_history_for_agent():按session_id + agent_name加载,还原 role/content/tool_calls/tool_call_id/thought 字段- 消息隔离:
agent_name="lead"仅加载主代理历史,"*"加载全部(调试用)
2. 上下文运行时监控 (runtime/mod.rs ReAct 循环内)
每一步 LLM 调用前执行以下检查:
2.1 Token 预算管理 (runtime/token_budget.rs)
软限制: AGENT_TOKEN_SOFT_LIMIT (默认 32,000)
硬限制: AGENT_TOKEN_HARD_LIMIT (默认 40,000)
三级渐进式 Nudge(每步检查,按优先级仅注入一条):
| 级别 | 条件 | 图标 | 消息语义 |
|---|---|---|---|
near_soft |
total_spent ≥ soft_limit × 80% |
💡 | "Token 预算提示…请注意控制后续步骤的深度" |
over_soft |
total_spent ≥ soft_limit |
🟡 | "Token 预算警告…请尽快总结关键发现" |
over_hard |
total_spent ≥ hard_limit |
🔴 | "已耗尽…请立即总结并给出最终答案" |
Diminishing Returns 检测:
- 条件:3+ 次延续 + 连续 2 次检查的 token 增量 < 500
- 触发后:强制终止工具调用,注入"请直接给出最终答案"消息,调用
final_answer_without_tools()
2.2 TodoWrite Nag 提醒
连续 3 步未调用 todo_write → 注入提醒消息,防止模型陷入无计划循环。
2.3 后台任务通知注入 (background.rs)
慢速操作(download_paper, parse_paper)通过 bg_task_run 异步执行。
每轮 LLM 调用前,BgNotificationQueue::drain() 收集已完成结果并注入:
[后台任务完成] ✅ download_paper: 2024A&A... (task_abc123): 下载成功...
2.4 死循环检测 (DuplicateDetector)
- 同一
(tool_name, arguments)连续调用 ≥ 3 次 → 注入错误 tool_result 跳过 - 同时在 metrics 中记录
duplicate_detections
3. 上下文压缩系统 (compact.rs)
3.1 五层压缩策略
compress_with_fallback() 按顺序执行,每层后检查是否需要继续:
graph LR
L0["Layer 0: snip_compact<br/>零 API 调用<br/>消息超 MAX_MESSAGES 时截断"]
L1["Layer 1: micro_compact<br/>零 API 调用<br/>替换早期工具结果为占位符"]
L2["Layer 2: auto_compact<br/>LLM 摘要<br/>历史压缩为 <500 字中文"]
L3["Layer 3: aggressive_micro<br/>零 API 调用<br/>激进占位符 keep_recent=2"]
L4["Layer 4: identity_inject<br/>零 API 调用<br/>注入身份确认块"]
L0 --> L1 --> L2 --> L3 --> L4
| 层 | 触发条件 | 算法 | API 调用 | 关键参数 |
|---|---|---|---|---|
| snip (L0) | 消息数 > MAX_MESSAGES (默认 50) |
find_safe_cut_point 切中间段 → 插入占位消息 |
否 | HEAD_KEEP=3, tail_keep=47 |
| micro (L1) | L0 后仍超 token_soft_limit |
工具结果 → [Previous: used {tool_name}] |
否 | keep_recent=8 |
| auto (L2) | L1 后仍超限 | LLM 生成中文摘要替换历史 | 是 | 摘要 ≤500 字 |
| aggressive_micro (L3) | L2 后仍超限 | 同 L1 但仅保留最近 2 条工具结果 | 否 | keep_recent=2 |
| identity (L4) | 压缩后消息 ≤4 条 | 注入 [身份确认] 块 |
否 | 天体物理学研究助手身份 |
3.2 压缩触发机制 (在 ReAct 循环中)
flowchart TD
A["estimated_tokens > token_budget.soft_limit?"]
A --> B["检查熔断器 can_attempt()"]
B -->|"Closed / HalfOpen"| C["执行 snapshot_compress_restore()"]
B -->|"Open"| D["跳过,记录警告"]
C --> E{"messages.len() < before_len ?"}
E -->|"是"| F["record_success()"]
E -->|"否"| G["record_failure()"]
Token 估算策略:
- 优先使用 API 返回的精确
prompt_tokens - 辅以增量估算:新增消息数 × (content.len() + 4) 字符估算
- 首次无 API 数据时回退到
rough_estimate_tokens
3.3 手动压缩 (compress_context 工具触发)
- LLM 调用
compress_context工具 → 设置pending_manual_compress = true - 下一轮循环中执行压缩(若刚已自动压缩则跳过)
- 手动压缩不受熔断器限制,成功后重置熔断器
3.4 安全切割点 (find_safe_cut_point)
确保不破坏 assistant(tool_calls) ↔ tool_result 配对的算法:
- 计算候选切割点
messages.len() - desired_keep - 若切割点落在
tool消息上 → 向前追溯到对应assistant(tool_calls)一并保留 - 向前扫描孤立
assistant(tool_calls)(无 tool_result 配对)→ 切点前移
3.5 压缩熔断器 (runtime/circuit_breaker.rs)
防止无限自动压缩的三态熔断器,创建于 AgentRuntime::new(),跨 turn 共享:
stateDiagram-v2
[*] --> Closed: 初始状态
Closed --> Closed: record_success() → 重置计数
Closed --> Open: 连续失败 3 次
Open --> HalfOpen: 5 分钟后自动恢复
HalfOpen --> Closed: record_success()
HalfOpen --> Open: record_failure()
Closed --> Closed: reset() (手动压缩成功后)
| 参数 | 值 | 说明 |
|---|---|---|
MAX_CONSECUTIVE_FAILURES |
3 | 连续失败次数阈值 |
AUTO_RECOVERY_TIMEOUT_SECS |
300 | 熔断后自动尝试恢复的等待时间 |
| 压缩成功判定 | messages.len() < before_len |
消息数减少即视为成功 |
3.6 递归守卫
COMPACTING AtomicBool — 压缩内部触发的 LLM 调用可能再次触发压缩,递归守卫通过 compare_exchange 防止嵌套压缩死循环。
4. 文件缓存与压缩集成 (runtime/file_cache.rs)
压缩前后的文件状态保护循环 (snapshot_compress_restore):
压缩前
├─ FileStateCache.to_snapshot() → 保存所有已读文件快照(按时间戳降序)
└─ FileStateCache.clear() → 清空缓存
压缩后
├─ restore_from_snapshot(max=5) → 恢复最近 5 个文件到 LRU 缓存
└─ build_restore_context(max=5) → 生成 "[压缩后恢复: {path}]" 块注入消息
| 参数 | 值 | 说明 |
|---|---|---|
MAX_ENTRIES |
100 | 缓存条目上限 |
MAX_CACHE_SIZE_BYTES |
25 MB | 缓存内容总大小上限 |
POST_COMPACT_MAX_FILES_TO_RESTORE |
5 | 压缩后恢复的文件数 |
POST_COMPACT_MAX_CHARS_PER_FILE |
4,000 | 每文件恢复内容上限 |
FILE_UNCHANGED_STUB |
静态字符串 | 文件未变时返回的占位消息 |
LRU 淘汰策略:容量超限时自动驱逐最久未使用的条目。
5. 错误恢复系统 (runtime/error_recovery.rs)
5.1 错误分类 (classify_error)
| ErrorKind | 检测关键词 |
|---|---|
RateLimited |
429, rate limit, rate_limit, too many requests |
Overloaded |
529, overloaded, overload, service overloaded |
PromptTooLong |
prompt_too_long, context length, 413, context_window_exceeded, input length |
TokenExhausted |
max_tokens, token limit, token_exhausted, maximum context length |
Timeout |
timeout, timed out, deadline exceeded, 408, 504 |
ModelError |
默认归类,携带原始错误字符串 |
5.2 恢复阶梯
LLM 流式调用失败后的 5 级恢复:
| 步骤 | RecoveryStep | 操作 | 适用错误 |
|---|---|---|---|
| 1 | AggressiveCompact |
snip + micro(keep_recent=2) | PromptTooLong, TokenExhausted |
| 2 | ReactiveCompact |
LLM 摘要压缩 | 同上 |
| 3 | EscalateTokens |
提升 hard_limit → 64,000 | 同上 |
| 4 | MultiTurn |
注入分步恢复消息 | 同上 |
| 5 | Surface |
放弃恢复,暴露错误 | 不可恢复错误(ModelError 直接到此) |
每步有 has_attempted 守卫,防止无限循环。
5.3 429/529 退避重试(独立快速路径)
RateLimited/Overloaded 不走恢复阶梯,独立执行指数退避重试(最多 10 次):
- 延迟公式:
min(500 × 2^attempt, 32,000) + deterministic jitter - 支持
retry_afterheader 解析 - 529 连续 3 次过载 → 尝试切换到
FALLBACK_MODEL环境变量指定的备用模型 - 重试期间检查用户取消信号
6. 子代理上下文隔离 (subagent.rs)
父代理通过 delegate_research 委托子任务给子代理,子代理拥有独立的上下文环境:
| 组件 | 隔离方式 |
|---|---|
| 消息上下文 | 全新 [system_prompt, user_prompt],不含父代理中间工具调用 |
| 工具访问 | 共享 ToolRegistry(可配置受限 registry,如仅只读工具) |
| Hook 管道 | 完整的 PreToolUse/PostToolUse + PermissionChecker |
| 进度通知 | 通过 progress_tx 向父代理发送 Thought/ToolCall/ToolResult SSE 事件 |
| 最终返回 | 仅返回 [子代理活动记录] + [子代理结论] 文本摘要 |
| 消息持久化 | 以 agent_name=sub_xxx 写入父会话的 agent_messages 表 |
子代理也有独立的上下文压缩(同 compact::compress_context)、死循环检测(同 DuplicateDetector 逻辑)、强制终止(超 max_steps 时调用 force_final_answer)。
7. 跨会话持久化
7.1 数据库表
| 表 | 持久化内容 | 上下文用途 |
|---|---|---|
agent_sessions |
session_id, title, model, turn_count, metadata | 会话生命周期管理 |
agent_messages |
role, content, thought, tool_calls(JSON), tool_call_id, token_count, metadata, raw_json(完整 ChatMessage), agent_name | 历史恢复 + 审计追溯 |
agent_tasks |
task_id, content, status, blocked_by, owner | 跨 turn 任务状态恢复 |
agent_audit_log |
session_id, step, tool_name, status, elapsed_ms, output_preview | 完整审计追踪 |
7.2 文件系统记忆 (memory/)
- Agent 可通过
save_memory工具将重要信息写入~/.claude/projects/{project}/memory/ - 四类记忆:
user,feedback,project,reference - 写入时门控:内容质量检查 + Jaccard 相似度去重(70% 阈值)
- 每 turn 结束时 fire-and-forget 自动提取候选记忆
- 下次会话的 system prompt 中自动加载最近 5 条记忆
7.3 Trajectory 导出
每 turn 结束时,TrajectoryExporter::export() 将完整会话轨迹导出到文件系统,用于调试和审计。
8. Lifecycle Hooks 与上下文事件
| Hook | 触发时机 | 上下文影响 |
|---|---|---|
OnSessionStart |
会话创建/恢复 | 通知生命周期开始 |
UserPromptSubmit |
用户提交提示词后 | 记录/审计用户输入 (P2) |
PreToolUse |
工具执行前 | 可 MutateInput 注入上下文、Block 阻止 |
PostToolUse |
工具执行后 | 可 MutateOutput 修改结果、审计日志写入 |
PostToolUseFailure |
工具执行失败 | 记录错误信息,可 MutateOutput |
OnStepComplete |
每步结束 | 日志消息数/预算使用率 |
OnPreCompact |
压缩前 | 记录消息数/预估 tokens |
OnPostCompact |
压缩后 | 记录新消息数/压缩方法 |
OnSubagentStart |
子代理启动 | 通知子代理创建 |
OnSubagentStop |
子代理停止 | 记录结果摘要 |
PermissionRequest |
权限请求前 | 可 Override 权限决策 (P1) |
PermissionDenied |
权限被拒绝后 | 安全审计日志 (P1) |
OnSessionStop |
会话终止 | 清理取消状态、记录终止原因 |
9. SSE 流事件 — 前端上下文同步
Agent 内部上下文通过 SSE 事件实时同步到前端:
Session → Thought → (ToolCall ↔ ToolResult)* → TextDelta → Usage → Done
tool_call.id 贯穿全链路:LLM 生成 ID → 前端精确匹配 tool_call/tool_result 条目 → 审计日志关联。
10. 环境变量配置
| 变量 | 默认值 | 作用 |
|---|---|---|
AGENT_MAX_STEPS |
8 | ReAct 最大迭代步数 |
AGENT_TOOL_TIMEOUT_SECS |
120 | 工具执行超时(秒) |
AGENT_MAX_TOOL_OUTPUT_CHARS |
4000 | 工具输出截断长度(字符) |
AGENT_TOKEN_SOFT_LIMIT |
32000 | 各压缩层统一触发阈值(token) |
AGENT_TOKEN_SOFT_LIMIT |
32000 | Token 预算软限制(触发 nudging) |
AGENT_TOKEN_HARD_LIMIT |
40000 | Token 预算硬限制(触发强制动作) |
AGENT_MAX_MESSAGES |
50 | snip_compact 触发阈值(消息数) |
FALLBACK_MODEL |
— | 529 连续过载 3 次时的备用模型 |
11. 关键源文件索引
| 文件 | 职责 |
|---|---|
src/agent/compact.rs |
五层压缩 + 安全切割 + 递归守卫 + 身份注入 |
src/agent/runtime/mod.rs |
ReAct 循环 + 压缩触发 + 后台通知 + todo nag |
src/agent/runtime/context.rs |
初始上下文构建 + 任务状态恢复 |
src/agent/runtime/session.rs |
会话 CRUD + 历史消息加载 |
src/agent/runtime/system_prompt.rs |
模块化 System Prompt 组装 |
src/agent/runtime/token_budget.rs |
Token 预算 + 三级 Nudge + Dim Returns |
src/agent/runtime/circuit_breaker.rs |
压缩熔断器 |
src/agent/runtime/error_recovery.rs |
5 级恢复阶梯 + 错误分类 + 退避重试 |
src/agent/runtime/file_cache.rs |
文件状态缓存 + 压缩快照/恢复 |
src/agent/runtime/streaming.rs |
流式响应 + 取消竞速 |
src/agent/runtime/executor.rs |
工具验证 + 并行执行 + Hook 集成 |
src/agent/runtime/finalize.rs |
会话收尾 + 记忆提取 + 轨迹导出 |
src/agent/subagent.rs |
子代理上下文隔离 + 独立 ReAct 循环 |
src/agent/background.rs |
后台任务队列 + 通知注入 |
src/agent/hooks/ |
13 个生命周期事件 + 内置 3 Hook + AsyncAgentHook trait |
src/agent/skills.rs |
两层技能加载 + 热重载 + 条件激活 |
src/agent/tools/memory.rs |
save_memory 工具 + 写入门控 |