Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions docs/architecture/architecture-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ graph LR
| **模型适配器** | 归一化不同厂商的 Chat API 为统一的 `Generate()` + `EstimateInputTokens()` 接口;将厂商特定的流式响应格式转换为标准 `StreamEvent` | 厂商差异不泄漏到 Runtime;每个 Adapter 独立测试 | Provider |
| **工具执行器** | 暴露工具的 Schema 供模型选择;校验参数并执行工具调用;在每次执行前经过安全守卫的权限裁决 | 所有模型可调用的能力收敛于此角色;不在 Runtime 或客户端中绕过 | Tools (Manager) |
| **安全守卫** | 基于策略规则(Priority 排序)裁决每个操作的 allow/deny/ask 决策;校验工作区边界(路径穿越检测、Symlink 解析);管理会话级权限记忆 | 位于工具执行的关键路径上,不可跳过 | Security Engine |
| **上下文构建器** | 按会话状态 + 预算阈值动态组装 System Prompt 和消息列表;执行上下文压缩(MicroCompact / Full Compact / Trim) | 压缩时不丢失 System Prompt 和 Pin 标记的关键消息;组装顺序稳定 | Context |
| **上下文构建器** | 按会话状态 + 预算阈值动态组装 System Prompt 和消息列表;执行上下文压缩(Full Compact / Trim) | 压缩时不丢失 System Prompt;组装顺序稳定 | Context |
| **状态管理者** | 持久化会话消息历史(SQLite);管理 Checkpoint 快照的创建/恢复/修剪;执行过期会话的自动清理 | 同会话并发写串行化(sessionLock);消息追加原子化 | Session |
| **技能注入器** | 从文件系统扫描 SKILL.md;管理会话级 Skill 激活状态;按激活列表将 Skill Prompt 注入 System Prompt 的技能段落 | project 层覆盖 global 层(同名去重);单文件大小限制 1MB | Skills |
| **远程执行代理** | 在远程/本机独立进程中接收 Gateway 的工具执行请求;校验 Capability Token;在本地完成工具执行并返回结果 | 主动连接 Gateway(反向连接);不开放入站端口;受 WorkdirAllowlist 限制 | Runner |
Expand Down Expand Up @@ -718,7 +718,7 @@ D4(工具执行可控)要求 AI 的写操作可回滚。Checkpoint 在每次

### 8.6 Context(Prompt 构建与上下文压缩)

**存在理由:** "AI 看到了什么"是一个独立于"AI 怎么推理"的架构关注点。将 Context 从 Runtime 中分离出来,意味着 Compact 策略(MicroCompact / Full Compact / Trim)可以独立演进,不需要修改推理循环。
**存在理由:** "AI 看到了什么"是一个独立于"AI 怎么推理"的架构关注点。将 Context 从 Runtime 中分离出来,意味着 Compact 策略(Full Compact / Trim)可以独立演进,不需要修改推理循环。

**拥有的决策权:** System Prompt 的组装顺序(`corePrompt → capabilities → rules → taskState → planModeContext → todos → skillPrompt → repositoryContext → systemState` 的固定顺序);Compact 何时触发、采用什么级别(Micro vs Full vs Trim);哪些消息不能被压缩(Pin 标记)。

Expand Down Expand Up @@ -827,7 +827,7 @@ sequenceDiagram

**触发条件:** 每轮推理前 `prepareTurnBudgetSnapshot` 检测到 Token 消耗接近预算阈值(基于 Provider 的 `EstimateInputTokens` 估算 + 配置的 `compact_trigger_ratio`)。

**参与组件:** Runtime → Context Builder → MicroCompact → Compact Runner (Provider) → Session Store
**参与组件:** Runtime → Context Builder → Compact Runner (Provider) → Session Store

**流程:**

Expand All @@ -841,8 +841,6 @@ sequenceDiagram
CC-->>RT: needsCompact = true

RT->>CC: Compact(input)
CC->>CC: MicroCompact: 对可压缩 tool_result 摘要化
alt MicroCompact 不足
CC->>CC: Full Compact: CompactRunner.Generate() 生成结构化摘要
end
CC->>CC: Trim: 裁剪最旧消息(保留 System Prompt + Pin 标记)
Expand All @@ -855,8 +853,7 @@ sequenceDiagram

| 级别 | 触发条件 | 操作 | 对上下文的影响 |
|------|----------|------|---------------|
| **MicroCompact** | 单次 Tool Call 结果过大,导致本轮预算紧张 | 对单个 tool_result 内容摘要化(保留关键输出,丢弃冗长中间日志) | 仅影响当前工具结果,不改变历史 |
| **Full Compact** | MicroCompact 后仍超预算,或累计历史消息过多 | 将历史消息中可压缩的部分通过 LLM 生成结构化摘要,替换原始消息 | 历史消息被摘要替代,System Prompt + 最近 N 轮保留 |
| **Full Compact** | 累计历史消息过多 | 将历史消息中可压缩的部分通过 LLM 生成结构化摘要,替换原始消息 | 历史消息被摘要替代,System Prompt + 最近 N 轮保留 |

**关键不变量:**
- System Prompt(corePrompt + capabilities + rules + skillPrompt)永不参与压缩
Expand Down Expand Up @@ -1065,7 +1062,7 @@ sequenceDiagram

opt 若 Token 预算接近阈值
RT->>CTX: Compact(session)
CTX->>CTX: MicroCompact(tool_results) → FullCompact(history)
CTX->>CTX: FullCompact(history)
CTX->>SS: ReplaceTranscript()
end

Expand Down Expand Up @@ -1773,7 +1770,6 @@ SessionID(会话级) + RunID(单次运行级)
|------|------|
| **ReAct Loop** | Reasoning + Acting 循环:模型推理 → 解析工具调用 → 执行工具 → 回灌结果 → 继续推理,直到产出最终文本回复 |
| **Compact** | 上下文压缩:当对话历史累积到接近 Token 预算上限时,自动将历史消息摘要化或裁剪,以释放上下文空间 |
| **MicroCompact** | 轻量级压缩:仅对单个 tool_result 内容做摘要化,不改变消息列表结构。是 Compact 的第一阶段 |
| **StreamRelay** | 流式中继:Gateway 内部将 Runtime 的异步事件按 SessionID/RunID 广播到所有订阅客户端连接的 pub/sub 机制 |
| **Checkpoint** | 代码版本快照:AI 执行写操作前自动创建的文件状态快照,支持恢复和 Diff 查看 |
| **Human-in-the-loop** | 人机协作模式:AI 在执行可能危险的操作(如写文件、执行 Bash)前暂停,等待人类审批 |
Expand Down
5 changes: 2 additions & 3 deletions docs/architecture/architecture-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ graph TD
|------|----------|----------|
| **LLM 输出不稳定** | 同样的 Prompt,不同模型(甚至同一模型的不同请求)可能产出完全不同的工具调用策略和代码质量 | Provider 归一化 + Compact 保持上下文一致性 |
| **工具执行具有副作用** | 模型决定执行 `rm -rf` 或修改关键配置文件,后果不可撤销 | Security Engine 四层防御 + Checkpoint 自动快照 |
| **上下文窗口有限** | 模型的 context window 有硬上限(4K–200K tokens),长对话和大代码库必然超限 | Context 模块两级 Compact 策略(MicroCompact / Full Compact) |
| **上下文窗口有限** | 模型的 context window 有硬上限(4K–200K tokens),长对话和大代码库必然超限 | Context 模块 Compact 策略(Full Compact / Trim) |
| **多轮任务需要状态管理** | 一次任务可能跨越数十轮推理,中间包含工具调用、审批暂停、错误重试,状态必须一致 | Session 持久化 + Runtime 集中管理会话状态 |
| **多端接入的一致性** | TUI、Web、Desktop、飞书、CI 脚本需要用统一协议接入,且行为一致 | Gateway 作为唯一 RPC 边界 + JSON-RPC 2.0 标准协议 |

Expand Down Expand Up @@ -169,7 +169,7 @@ flowchart TD

**第一步:构建上下文。** Runtime 把当前会话状态(消息历史、Todo 列表、激活的 Skills、已批准的 Plan)交给 Context 模块。Context 按固定顺序组装 System Prompt——核心行为准则、工具能力列表、项目规则、当前任务状态、Plan 上下文——然后返回给 Runtime。

**Runtime 为什么不自己拼 Prompt。** Prompt 的组装逻辑是一个独立的关注点。上下文压缩(Compact)的策略——什么时候触发、用 MicroCompact 还是 Full Compact、哪些消息不能裁剪——需要在 Context 模块内独立演进。如果 Runtime 内嵌了 Prompt 拼接,修改压缩策略就需要改推理循环,两者耦合。
**Runtime 为什么不自己拼 Prompt。** Prompt 的组装逻辑是一个独立的关注点。上下文压缩(Compact)的策略——什么时候触发、哪些消息不能裁剪——需要在 Context 模块内独立演进。如果 Runtime 内嵌了 Prompt 拼接,修改压缩策略就需要改推理循环,两者耦合。

**第二步:调用模型。** Runtime 把组装好的 Prompt 交给 Provider。Provider 是模型厂商的抽象层——它唯一的职责就是把不同厂商的 API 归一化为两个操作:估算 Token 数、发起流式推理。

Expand Down Expand Up @@ -345,7 +345,6 @@ flowchart LR

**上下文裁剪(Compact)。** 当消息历史的 Token 数接近模型窗口上限时,Context 模块自动触发压缩:

- **MicroCompact**:移除较早的工具调用细节,保留摘要。优先裁剪输出最长的 Tool Result。
- **Full Compact**:调用 LLM 对整段历史生成摘要,替换原始消息列表。旧消息删除和新摘要插入在同一个 SQLite 事务中完成,保证原子性。

数据回流发生在两处:**工具结果回灌**(Tool Result 写入 Session Messages,供下一轮推理使用)和 **Compact 结果回写**(压缩后的摘要替换原始历史)。
Expand Down
6 changes: 0 additions & 6 deletions docs/context-compact.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ context:
compact:
manual_strategy: keep_recent
manual_keep_recent_messages: 10
micro_compact_retained_tool_spans: 6
read_time_max_message_spans: 24
max_summary_chars: 1200
micro_compact_disabled: false
budget:
prompt_budget: 0
reserve_tokens: 13000
Expand All @@ -38,12 +36,8 @@ context:
在 `keep_recent` 模式下保留的最近消息数,并按 tool call / tool result 的原子块整体保留。
- `read_time_max_message_spans`
控制 `context.Builder` 读时 trim 可保留的 message span 上限。
- `micro_compact_retained_tool_spans`
控制 read-time micro compact 默认保留原始内容的最近可压缩工具块数量。
- `max_summary_chars`
控制 compact summary 的最大字符数。
- `micro_compact_disabled`
控制是否关闭默认启用的 read-time micro compact。

### `context.budget`

Expand Down
4 changes: 0 additions & 4 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ context:
compact:
manual_strategy: keep_recent
manual_keep_recent_messages: 10
micro_compact_retained_tool_spans: 6
read_time_max_message_spans: 24
max_summary_chars: 1200
micro_compact_disabled: false
budget:
prompt_budget: 0
reserve_tokens: 13000
Expand All @@ -89,10 +87,8 @@ context:
|------|------|
| `context.compact.manual_strategy` | `/compact` 手动压缩策略,支持 `keep_recent` / `full_replace` |
| `context.compact.manual_keep_recent_messages` | `keep_recent` 下保留的最近消息数 |
| `context.compact.micro_compact_retained_tool_spans` | read-time micro compact 默认保留原始内容的最近工具块数量 |
| `context.compact.read_time_max_message_spans` | context 构建时保留的 message span 上限 |
| `context.compact.max_summary_chars` | compact summary 最大字符数 |
| `context.compact.micro_compact_disabled` | 是否关闭默认启用的 micro compact |

### `context.budget`

Expand Down
1 change: 0 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
| **传输层全 HTTP 化** | 当前 `transport/` 中残留的 Unix socket / Named pipe 逻辑增加了双平台代码路径和维护负担。统一到 HTTP JSON-RPC 后:第三方客户端接入更简单(只需要发 HTTP POST,不需要理解 Unix socket 地址规则)、Windows 和 Linux/macOS 的客户端连接逻辑完全一致 | 高——正在进行 |
| **Gateway 大文件拆分** | `bootstrap.go` 超 1600 行,包含帧路由、认证、session CRUD、RPC 处理、流绑定等所有 Gateway 逻辑。5 人团队每人负责不同模块,但 Gateway 的改动集中在同一大文件中 → 持续的合并冲突。按功能域拆分为 `auth_handler.go`、`session_handler.go`、`stream_handler.go` 后,各自改自己的文件 | 高——直接影响并行开发效率 |
| **Runner 工具并行执行** | Runner 当前串行处理 Gateway 下发的工具请求。在"手机飞书下指令 → 工位 Runner 执行"场景中,模型经常一次产出多个独立的 tool call(如同时读 3 个文件),串行执行导致不必要的延迟。改为并行执行可显著改善远程场景的响应体验 | 中——核心差异化场景的性能瓶颈 |
| **Compact 配置收敛** | MicroCompact 的 `MicroCompactConfig`、Full Compact 的 `CompactConfig`、预算阈值在 `RuntimeConfig` 中分散定义。调整上下文压缩策略时需要理解三个不同的配置入口,容易产生不一致的配置。收敛为单一 `CompactPolicy` 结构体 | 中——降低调优门槛 |

### 17.2 中期(巩固和放大现有差异化优势)

Expand Down
2 changes: 1 addition & 1 deletion docs/runtime-provider-event-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ runtime 不再消费旧的 builder 压缩建议,而是使用冻结快照上的
- 组装 `system prompt`
- 读取 `AGENTS.md`
- 注入 `Task State` / `Todo State` / `Skills` / `Memo`
- 执行 read-time trim 和 micro compact
- 执行 read-time trim
- 输出最终 `SystemPrompt` 与消息列表

`context.Builder` 不再负责:
Expand Down
1 change: 0 additions & 1 deletion docs/tech-debt.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
|--------|------|------|-------------|
| **底层传输层 IPC 残留** | `internal/gateway/transport/` — Unix domain socket / Named pipe | 客户端连接路径复杂(需判断平台选 socket 类型),迁移到全 HTTP 后可消除 | 短期——已在迁移计划中 |
| **`runtime/run.go` 单文件过长** | ReAct 主循环逻辑集中在 `run.go` (~400 行) 和 `runtime.go` (~540 行) | 新成员理解核心循环需要较长时间;修改风险集中在少数大文件中 | 中期——可按阶段拆分(pre-processing / loop body / termination) |
| **Compact 策略配置分散** | MicroCompact 配置在 `MicroCompactConfig`,Full Compact 在 `CompactConfig`,部分阈值在 `RuntimeConfig` | 调整上下文管理策略需要理解三处配置 | 中期——收敛为统一的 `CompactPolicy` 结构体 |
| **Gateway Bootstrap 单文件** | `bootstrap.go` 超过 1600 行,包含帧路由、认证、session CRUD、RPC 处理 | 单体文件难以定位和维护 | 中期——拆分为 `session_handler.go`、`rpc_handler.go`、`auth_handler.go` |
| **Acceptance 测试耗时长** | `runtime/acceptance/` 的端到端测试依赖真实模型 API | CI 成本高、不稳定(网络波动导致 flaky) | 长期——增加录制/回放(VCR)模式,CI 中默认使用录制的 fixture |

Expand Down
11 changes: 2 additions & 9 deletions internal/app/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,7 @@ func BuildGatewayServerDeps(ctx context.Context, opts BootstrapOptions) (Runtime
log.Printf("session cleanup warning: %v", err)
}

// 注册内置工具的内容摘要器,使 micro-compact 在清理旧工具结果时保留关键上下文。
tools.RegisterBuiltinSummarizers(toolRegistry)

microCompactCfg := agentcontext.MicroCompactConfig{
Policies: toolRegistry,
Summarizers: toolRegistry,
}
var contextBuilder agentcontext.Builder = agentcontext.NewConfiguredBuilder(microCompactCfg)
var contextBuilder agentcontext.Builder = agentcontext.NewConfiguredBuilder()
var memoSvc *memo.Service
if cfg.Memo.Enabled {
memoStore := memo.NewFileStore(sharedDeps.ConfigManager.BaseDir(), cfg.Workdir)
Expand All @@ -195,7 +188,7 @@ func BuildGatewayServerDeps(ctx context.Context, opts BootstrapOptions) (Runtime
if invalidator, ok := memoSource.(interface{ InvalidateCache() }); ok {
sourceInvl = invalidator.InvalidateCache
}
contextBuilder = agentcontext.NewConfiguredBuilder(microCompactCfg, memoSource)
contextBuilder = agentcontext.NewConfiguredBuilder(memoSource)
memoSvc = memo.NewService(memoStore, cfg.Memo, sourceInvl)
toolRegistry.Register(memotool.NewRememberTool(memoSvc))
toolRegistry.Register(memotool.NewRecallTool(memoSvc))
Expand Down
3 changes: 0 additions & 3 deletions internal/app/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2184,9 +2184,6 @@ func (s *stubRemoteRuntimeForBootstrap) Close() error {
func (s stubToolForBootstrap) Name() string { return s.name }
func (s stubToolForBootstrap) Description() string { return "stub" }
func (s stubToolForBootstrap) Schema() map[string]any { return map[string]any{"type": "object"} }
func (s stubToolForBootstrap) MicroCompactPolicy() tools.MicroCompactPolicy {
return tools.MicroCompactPolicyCompact
}
func (s stubToolForBootstrap) Execute(ctx context.Context, call tools.ToolCallInput) (tools.ToolResult, error) {
return tools.ToolResult{Name: s.name, Content: s.content}, nil
}
Expand Down
10 changes: 0 additions & 10 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1129,15 +1129,11 @@ func TestCompactConfigDefaultsAndRoundTrip(t *testing.T) {
compactCfg.ReadTimeMaxMessageSpans,
)
}
if compactCfg.MicroCompactDisabled {
t.Fatalf("expected micro compact to be enabled by default")
}

cfg.Context.Compact.ManualStrategy = CompactManualStrategyFullReplace
cfg.Context.Compact.ManualKeepRecentMessages = 2
cfg.Context.Compact.MaxSummaryChars = 900
cfg.Context.Compact.ReadTimeMaxMessageSpans = 30
cfg.Context.Compact.MicroCompactDisabled = true
if err := loader.Save(context.Background(), cfg); err != nil {
t.Fatalf("Save() error = %v", err)
}
Expand All @@ -1152,9 +1148,6 @@ func TestCompactConfigDefaultsAndRoundTrip(t *testing.T) {
if strings.Contains(text, "manual_keep_recent_spans:") {
t.Fatalf("expected persisted config to drop legacy manual_keep_recent_spans key, got:\n%s", text)
}
if !strings.Contains(text, "micro_compact_disabled: true") {
t.Fatalf("expected persisted config to include micro_compact_disabled, got:\n%s", text)
}
if !strings.Contains(text, "read_time_max_message_spans: 30") {
t.Fatalf("expected persisted config to include read_time_max_message_spans, got:\n%s", text)
}
Expand All @@ -1175,9 +1168,6 @@ func TestCompactConfigDefaultsAndRoundTrip(t *testing.T) {
if reloaded.Context.Compact.ReadTimeMaxMessageSpans != 30 {
t.Fatalf("expected read_time_max_message_spans=30, got %d", reloaded.Context.Compact.ReadTimeMaxMessageSpans)
}
if !reloaded.Context.Compact.MicroCompactDisabled {
t.Fatalf("expected micro_compact_disabled to persist")
}
}

func TestCompactConfigValidateFailures(t *testing.T) {
Expand Down
17 changes: 5 additions & 12 deletions internal/config/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const (
DefaultBudgetReserveTokens = 13000
DefaultBudgetFallbackPromptBudget = 100000
DefaultBudgetMaxReactiveCompacts = 3
DefaultMicroCompactRetainedToolSpans = 6
DefaultCompactReadTimeMaxMessageSpans = 24
DefaultAskMaxInputTokens = 8000
DefaultAskRetainTurns = 5
Expand All @@ -30,13 +29,11 @@ type ContextConfig struct {
}

type CompactConfig struct {
ManualStrategy string `yaml:"manual_strategy,omitempty"`
ManualKeepRecentMessages int `yaml:"manual_keep_recent_messages,omitempty"`
MaxSummaryChars int `yaml:"max_summary_chars,omitempty"`
MicroCompactDisabled bool `yaml:"micro_compact_disabled,omitempty"`
MicroCompactRetainedToolSpans int `yaml:"micro_compact_retained_tool_spans,omitempty"`
ReadTimeMaxMessageSpans int `yaml:"read_time_max_message_spans,omitempty"`
MaxArchivedPromptChars int `yaml:"max_archived_prompt_chars,omitempty"`
ManualStrategy string `yaml:"manual_strategy,omitempty"`
ManualKeepRecentMessages int `yaml:"manual_keep_recent_messages,omitempty"`
MaxSummaryChars int `yaml:"max_summary_chars,omitempty"`
ReadTimeMaxMessageSpans int `yaml:"read_time_max_message_spans,omitempty"`
MaxArchivedPromptChars int `yaml:"max_archived_prompt_chars,omitempty"`
}

// BudgetConfig 定义上下文预算控制面的配置。
Expand Down Expand Up @@ -79,7 +76,6 @@ func defaultCompactConfig() CompactConfig {
ManualStrategy: CompactManualStrategyKeepRecent,
ManualKeepRecentMessages: DefaultCompactManualKeepRecentMessages,
MaxSummaryChars: DefaultCompactMaxSummaryChars,
MicroCompactRetainedToolSpans: DefaultMicroCompactRetainedToolSpans,
ReadTimeMaxMessageSpans: DefaultCompactReadTimeMaxMessageSpans,
}
}
Expand Down Expand Up @@ -143,9 +139,6 @@ func (c *CompactConfig) ApplyDefaults(defaults CompactConfig) {
if c.MaxSummaryChars <= 0 {
c.MaxSummaryChars = defaults.MaxSummaryChars
}
if c.MicroCompactRetainedToolSpans <= 0 {
c.MicroCompactRetainedToolSpans = defaults.MicroCompactRetainedToolSpans
}
if c.ReadTimeMaxMessageSpans <= 0 {
c.ReadTimeMaxMessageSpans = defaults.ReadTimeMaxMessageSpans
}
Expand Down
Loading
Loading