diff --git a/docs/architecture/agent-refactor/agent-rename-plan.md b/docs/architecture/agent-refactor/agent-rename-plan.md new file mode 100644 index 000000000..f4ab408fe --- /dev/null +++ b/docs/architecture/agent-refactor/agent-rename-plan.md @@ -0,0 +1,100 @@ +# Agent File Rename Plan + +## Goal + +Unify `pkg/agent/` package file naming to resolve the `loop_*` prefix naming confusion and unclear responsibility boundaries. + +## Change Overview + +### File Renames (12 files) + +| Original | New | Description | +|----------|-----|-------------| +| `loop.go` | `agent.go` | AgentLoop main body + lifecycle methods | +| `loop_message.go` | `agent_message.go` | Message handling and routing | +| `loop_outbound.go` | `agent_outbound.go` | Response publishing | +| `loop_event.go` | `agent_event.go` | Event system | +| `loop_command.go` | `agent_command.go` | Command processing | +| `loop_steering.go` | `agent_steering.go` | Steering message handling | +| `loop_transcribe.go` | `agent_transcribe.go` | Audio transcription | +| `loop_media.go` | `agent_media.go` | Media processing | +| `loop_mcp.go` | `agent_mcp.go` | MCP initialization | +| `loop_utils.go` | `agent_utils.go` | Utility functions | +| `loop_inject.go` | `agent_inject.go` | Dependency injection | +| `loop_turn.go` | `turn_coord.go` | Turn coordinator | + +### File Merges (2 → 1) + +| Original | New | Description | +|----------|-----|-------------| +| `turn.go` + `turn_exec.go` | `turn_state.go` | Turn-related type definitions | + +## Final File Structure + +``` +pkg/agent/ +├── agent.go # AgentLoop + Run/Stop/Close lifecycle +├── agent_message.go # Message processing +├── agent_outbound.go # Response publishing +├── agent_event.go # Event system +├── agent_command.go # Command processing +├── agent_steering.go # Steering +├── agent_transcribe.go # Transcription +├── agent_media.go # Media processing +├── agent_mcp.go # MCP +├── agent_utils.go # Utility functions +├── agent_inject.go # Dependency injection +├── turn_coord.go # runTurn + coordinator +├── turn_state.go # turnState + turnExecution + Control + ToolControl + LLMPhase +├── pipeline.go # Pipeline struct + NewPipeline +├── pipeline_setup.go +├── pipeline_llm.go +├── pipeline_execute.go +└── pipeline_finalize.go +``` + +## Naming Convention + +| Prefix | Content | Example | +|--------|---------|---------| +| `agent_*` | AgentLoop method files | `agent_message.go`, `agent_event.go` | +| `turn_*` | Turn lifecycle related | `turn_coord.go`, `turn_state.go` | +| `pipeline_*` | Pipeline methods | `pipeline_setup.go`, `pipeline_llm.go` | +| `context_*` | Context management | `context_manager.go`, `context_legacy.go` | +| `hook_*` | Hook system | `hook_process.go`, `hook_mount.go` | + +## Architecture Layers + +``` +┌─────────────────────────────────────────────────────────┐ +│ AgentLoop (agent.go) │ +│ - Message loop Run/Stop/Close │ +│ - Dependency injection (agent_inject.go) │ +│ - Message routing (agent_message.go) │ +│ - Response publishing (agent_outbound.go) │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Turn Coordinator (turn_coord.go) │ +│ - runTurn(): main coordinator │ +│ - abortTurn(): abort │ +│ - askSideQuestion(): side question │ +│ - selectCandidates(): model selection │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Pipeline (pipeline_*.go) │ +│ - SetupTurn(): initialization │ +│ - CallLLM(): LLM call │ +│ - ExecuteTools(): tool execution │ +│ - Finalize(): finalization │ +└─────────────────────────────────────────────────────────┘ +``` + +## Verification Results + +- ✅ `go build ./pkg/agent/...` - Pass +- ✅ `go vet ./pkg/agent/...` - No warnings +- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - Pass diff --git a/docs/agent-rename-plan.md b/docs/architecture/agent-refactor/agent-rename-plan.zh.md similarity index 88% rename from docs/agent-rename-plan.md rename to docs/architecture/agent-refactor/agent-rename-plan.zh.md index f924bcbfb..938817e10 100644 --- a/docs/agent-rename-plan.md +++ b/docs/architecture/agent-refactor/agent-rename-plan.zh.md @@ -1,4 +1,4 @@ -# Agent 重构文档 +# Agent 文件重命名计划 ## 目标 @@ -38,7 +38,7 @@ pkg/agent/ ├── agent_outbound.go # 响应发布 ├── agent_event.go # 事件系统 ├── agent_command.go # 命令处理 -├── agent_steering.go # Steering +├── agent_steering.go # Steering ├── agent_transcribe.go # 转录 ├── agent_media.go # 媒体处理 ├── agent_mcp.go # MCP @@ -69,7 +69,7 @@ pkg/agent/ ┌─────────────────────────────────────────────────────────┐ │ AgentLoop (agent.go) │ │ - 消息循环 Run/Stop/Close │ -│ - 依赖注入 (agent_inject.go) │ +│ - 依赖注入 (agent_inject.go) │ │ - 消息路由 (agent_message.go) │ │ - 响应发布 (agent_outbound.go) │ └─────────────────────────────────────────────────────────┘ @@ -77,18 +77,18 @@ pkg/agent/ ▼ ┌─────────────────────────────────────────────────────────┐ │ Turn Coordinator (turn_coord.go) │ -│ - runTurn(): 主协调器 │ -│ - abortTurn(): 中止 │ -│ - askSideQuestion(): 侧问 │ -│ - selectCandidates(): 模型选择 │ +│ - runTurn(): 主协调器 │ +│ - abortTurn(): 中止 │ +│ - askSideQuestion(): 侧问 │ +│ - selectCandidates(): 模型选择 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ -│ Pipeline (pipeline_*.go) │ -│ - SetupTurn(): 初始化 │ -│ - CallLLM(): LLM 调用 │ -│ - ExecuteTools(): 工具执行 │ +│ Pipeline (pipeline_*.go) │ +│ - SetupTurn(): 初始化 │ +│ - CallLLM(): LLM 调用 │ +│ - ExecuteTools(): 工具执行 │ │ - Finalize(): 终结 │ └─────────────────────────────────────────────────────────┘ ``` @@ -97,4 +97,4 @@ pkg/agent/ - ✅ `go build ./pkg/agent/...` - 通过 - ✅ `go vet ./pkg/agent/...` - 无警告 -- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - 通过 \ No newline at end of file +- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - 通过 diff --git a/docs/architecture/agent-refactor/loop-split.md b/docs/architecture/agent-refactor/loop-split.md index 0c759e63d..5395baeeb 100644 --- a/docs/architecture/agent-refactor/loop-split.md +++ b/docs/architecture/agent-refactor/loop-split.md @@ -1,5 +1,7 @@ # AgentLoop File Split +> **Note:** This document describes the file split that was completed in a previous phase. The `loop_*` naming has since been renamed to `agent_*` and `turn_*`. See [agent-rename-plan.md](./agent-rename-plan.md) for the current file structure. + ## Overview The `pkg/agent/loop.go` file (originally 4384 lines) has been split into 12 focused source files. This is a pure refactoring with no behavioral changes. @@ -11,76 +13,65 @@ The `pkg/agent/loop.go` file (originally 4384 lines) has been split into 12 focu - Maintain all existing functionality and tests - Keep imports minimal per file -## File Map +## Original File Map (Renamed in Phase 2) -| File | Lines | Responsibility | -|------|-------|----------------| -| `loop.go` | ~650 | Core `AgentLoop` struct, `Run`, `Stop`, `Close`, `ReloadProviderAndConfig`, `runAgentLoop` | -| `loop_turn.go` | ~1880 | Turn execution: `runTurn`, `abortTurn`, `selectCandidates`, `askSideQuestion`, `isolatedSideQuestionProvider`, side question model config | -| `loop_utils.go` | ~480 | Standalone utility functions: formatters, cloners, helpers (no receiver) | -| `loop_init.go` | ~355 | `NewAgentLoop` constructor and `registerSharedTools` | -| `loop_message.go` | ~300 | Message handling: `processMessage`, `processSystemMessage`, routing helpers, `ProcessDirect`, `ProcessHeartbeat` | -| `loop_command.go` | ~265 | Command processing: `handleCommand`, `applyExplicitSkillCommand`, pending skills management | -| `loop_mcp.go` | ~235 | MCP runtime: `ensureMCPInitialized`, server discovery, deferred server handling | -| `loop_event.go` | ~205 | Event system helpers: `emitEvent`, `logEvent`, `hookAbortError`, `newTurnEventScope`, `MountHook`, `SubscribeEvents` | -| `loop_media.go` | ~198 | Media resolution: `resolveMediaRefs`, artifact building, MIME detection | -| `loop_outbound.go` | ~165 | Response publishing: `PublishResponseIfNeeded`, `publishPicoReasoning`, `handleReasoning` | -| `loop_transcribe.go` | ~110 | Audio transcription: `transcribeAudioInMessage`, `sendTranscriptionFeedback` | -| `loop_steering.go` | ~97 | Steering queue: `runTurnWithSteering`, `processMessageSync`, `resolveSteeringTarget` | -| `loop_inject.go` | ~104 | Setter injection: `SetChannelManager`, `SetMediaStore`, `SetTranscriber`, `GetRegistry`, `GetConfig`, `RecordLastChannel` | +| Old File | New File | Responsibility | +|----------|----------|----------------| +| `loop.go` | `agent.go` | Core `AgentLoop` struct, `Run`, `Stop`, `Close` | +| `loop_turn.go` | `turn_coord.go` + `pipeline_*.go` | Turn execution: coordinator + Pipeline methods | +| `loop_utils.go` | `agent_utils.go` | Standalone utility functions | +| `loop_init.go` | `agent_init.go` | `NewAgentLoop` constructor and tool registration | +| `loop_message.go` | `agent_message.go` | Message handling and routing | +| `loop_command.go` | `agent_command.go` | Command processing | +| `loop_mcp.go` | `agent_mcp.go` | MCP runtime | +| `loop_event.go` | `agent_event.go` | Event system helpers | +| `loop_media.go` | `agent_media.go` | Media resolution | +| `loop_outbound.go` | `agent_outbound.go` | Response publishing | +| `loop_transcribe.go` | `agent_transcribe.go` | Audio transcription | +| `loop_steering.go` | `agent_steering.go` | Steering queue | +| `loop_inject.go` | `agent_inject.go` | Setter injection | + +## Current File Structure + +See [agent-rename-plan.md](./agent-rename-plan.md) for the complete current file structure. + +## Phase 2: Rename and Pipeline Restructuring + +Phase 2 completed the following: + +1. **File renaming**: All `loop_*` files renamed to `agent_*` or `turn_*` +2. **Turn state merging**: `turn.go` + `turn_exec.go` → `turn_state.go` +3. **Pipeline extraction**: Split large `runTurn` into Pipeline methods + +### Pipeline Architecture + +The Pipeline methods provide structured turn execution: + +| Method | File | Responsibility | +|--------|------|----------------| +| `SetupTurn()` | `pipeline_setup.go` | History assembly, message building, candidate selection | +| `CallLLM()` | `pipeline_llm.go` | PreLLM hooks, fallback, retry, AfterLLM hooks | +| `ExecuteTools()` | `pipeline_execute.go` | Tool execution with hooks | +| `Finalize()` | `pipeline_finalize.go` | Session persistence, compression | ## Core Principles Applied ### 1. Same Package, Independent Files -All files belong to the `agent` package and compile together. This preserves the original visibility rules — no interface abstraction was introduced in this phase. +All files belong to the `agent` package and compile together. This preserves the original visibility rules. ### 2. No Logic Changes -All functions were moved verbatim (except updating import statements). The extraction script used the original `loop.go.backup` as source of truth to ensure no drift. +All functions were moved verbatim. The extraction preserved behavioral equivalence. -### 3. Shared Types Remain in loop.go -The `AgentLoop` struct, `processOptions`, `continuationTarget`, and all hook/event types stay in `loop.go` since they are referenced across files. - -### 4. Turn State Is Central -`loop_turn.go` is the largest file because the turn lifecycle (`runTurn`) is inherently large. It contains the core LLM interaction loop, tool execution, subturn spawning, and steering injection. - -## What's Left in loop.go - -```go -// Core struct -type AgentLoop struct { ... } - -// Main lifecycle -func (al *AgentLoop) Run(ctx context.Context) error -func (al *AgentLoop) Stop() -func (al *AgentLoop) Close() -func (al *AgentLoop) ReloadProviderAndConfig(ctx, provider, cfg) - -// Turn orchestration (calls into loop_turn.go) -func (al *AgentLoop) runAgentLoop(ctx, agent, opts) (string, error) -``` - -## Extraction Method - -The split was done programmatically using Node.js to: -1. Identify function boundaries using brace counting -2. Extract each function to its target file -3. Add necessary imports to each file -4. Remove the extracted function from loop.go -5. Run `go fmt` and `go vet` to verify +### 3. Shared Types in turn_state.go +The `turnState`, `turnExecution`, `Control`, `ToolControl`, and `LLMPhase` types are centralized in `turn_state.go`. ## Testing -All existing tests pass. The 5 failing tests (`TestGlobalSkillFileContentChange` and 4 Seahorse tests) are pre-existing failures unrelated to this refactor (database file locking issues on Windows). +All existing tests pass. The 5 failing tests (`TestGlobalSkillFileContentChange` and 4 Seahorse tests) are pre-existing failures unrelated to this refactor. Build status: `go build ./pkg/agent/...` passes with no errors. -## Phase 2: Dependency Inversion (Planned) - -A future phase will introduce interface types to decouple `AgentLoop` from its dependencies, enabling: -- Easier testing with mock dependencies -- Alternative runtime configurations -- Cleaner boundaries for MCP and other extensions - ## See Also +- [agent-rename-plan.md](./agent-rename-plan.md) — Current file naming convention - [context.md](context.md) — context management and session handling diff --git a/docs/architecture/agent-refactor/pipeline-restructuring-plan.md b/docs/architecture/agent-refactor/pipeline-restructuring-plan.md new file mode 100644 index 000000000..b77987af1 --- /dev/null +++ b/docs/architecture/agent-refactor/pipeline-restructuring-plan.md @@ -0,0 +1,68 @@ +# Pipeline Restructuring Plan + +## Goal + +Split `agent/pipeline.go` (~1400 lines) into multiple logical files, organizing code by responsibility. + +## Final File Structure + +``` +pkg/agent/ +├── pipeline.go # Pipeline struct + NewPipeline (~39 lines) +├── pipeline_setup.go # SetupTurn method (~115 lines) +├── pipeline_llm.go # CallLLM method (~519 lines) +├── pipeline_execute.go # ExecuteTools method (~693 lines) +└── pipeline_finalize.go # Finalize method (~78 lines) +``` + +## Actual Line Counts + +| File | Lines | +|------|-------| +| `pipeline.go` | 39 | +| `pipeline_setup.go` | 115 | +| `pipeline_llm.go` | 519 | +| `pipeline_execute.go` | 693 | +| `pipeline_finalize.go` | 78 | +| **Total** | **1444** | + +## Responsibility Matrix + +| File | Method | Responsibility | +|------|--------|----------------| +| `pipeline.go` | `Pipeline` struct, `NewPipeline()` | Pipeline dependency container | +| `pipeline_setup.go` | `SetupTurn()` | Turn initialization: history assembly, message building, candidate selection | +| `pipeline_llm.go` | `CallLLM()` | LLM call: PreLLM hooks, fallback, retry, AfterLLM hooks | +| `pipeline_execute.go` | `ExecuteTools()` | Tool execution: BeforeTool/ApproveTool/AfterTool hooks, media sending, steering handling | +| `pipeline_finalize.go` | `Finalize()` | Turn finalization: session save, compression, status setting | + +## Relationship Between Pipeline and Turn Coordinator + +``` +AgentLoop (agent.go) + │ + ├── runAgentLoop() ──────────────────┐ + │ │ + │ ┌───────────────────────────────▼───────────────────────────────┐ + │ │ Turn Coordinator (turn_coord.go) │ + │ │ │ + │ │ runTurn() { │ + │ │ exec = pipeline.SetupTurn() │ + │ │ loop { │ + │ │ ctrl = pipeline.CallLLM() ──► Pipeline (pipeline_*.go) │ + │ │ if ctrl == ToolLoop { │ + │ │ toolCtrl = pipeline.ExecuteTools() │ + │ │ } │ + │ │ } │ + │ │ return pipeline.Finalize() │ + │ │ } │ + │ └─────────────────────────────────────────────────────────────┘ + │ + └── Publish response (agent_outbound.go) +``` + +## Verification Results + +- ✅ `go build ./pkg/agent/...` - Pass +- ✅ `go vet ./pkg/agent/...` - No warnings +- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - Pass diff --git a/docs/pipeline-restructuring-plan.md b/docs/architecture/agent-refactor/pipeline-restructuring-plan.zh.md similarity index 96% rename from docs/pipeline-restructuring-plan.md rename to docs/architecture/agent-refactor/pipeline-restructuring-plan.zh.md index 923de6dfc..2de1396ad 100644 --- a/docs/pipeline-restructuring-plan.md +++ b/docs/architecture/agent-refactor/pipeline-restructuring-plan.zh.md @@ -44,19 +44,19 @@ AgentLoop (agent.go) ├── runAgentLoop() ──────────────────┐ │ │ │ ┌───────────────────────────────▼───────────────────────────────┐ - │ │ Turn Coordinator (turn_coord.go) │ + │ │ Turn Coordinator (turn_coord.go) │ │ │ │ │ │ runTurn() { │ │ │ exec = pipeline.SetupTurn() │ - │ │ loop { │ + │ │ loop { │ │ │ ctrl = pipeline.CallLLM() ──► Pipeline (pipeline_*.go) │ - │ │ if ctrl == ToolLoop { │ - │ │ toolCtrl = pipeline.ExecuteTools() │ - │ │ } │ + │ │ if ctrl == ToolLoop { │ + │ │ toolCtrl = pipeline.ExecuteTools() │ + │ │ } │ │ │ } │ │ │ return pipeline.Finalize() │ - │ │ } │ - │ └───────────────────────────────────────────────────────────┘ + │ │ } │ + │ └─────────────────────────────────────────────────────────────┘ │ └── 发布响应 (agent_outbound.go) ``` @@ -65,4 +65,4 @@ AgentLoop (agent.go) - ✅ `go build ./pkg/agent/...` - 通过 - ✅ `go vet ./pkg/agent/...` - 无警告 -- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - 通过 \ No newline at end of file +- ✅ `go test ./pkg/agent/... -skip "TestSeahorse|TestGlobalSkillFileContentChange"` - 通过 diff --git a/docs/architecture/routing-system.md b/docs/architecture/routing-system.md index 3b4663ee8..ad6c3abfc 100644 --- a/docs/architecture/routing-system.md +++ b/docs/architecture/routing-system.md @@ -19,7 +19,7 @@ It does not describe the launcher's HTTP `ServeMux` routes or the frontend's Tan | Agent dispatch | `pkg/routing/route.go`, `pkg/routing/agent_id.go` | Choose the target agent for the inbound message. | | Session policy selection | `pkg/routing/route.go` | Decide which dimensions should define session isolation for that routed turn. | | Model routing | `pkg/routing/router.go`, `pkg/routing/features.go`, `pkg/routing/classifier.go` | Choose between the primary model and a configured light model based on message complexity. | -| Runtime integration | `pkg/agent/registry.go`, `pkg/agent/loop_message.go`, `pkg/agent/loop_turn.go` | Apply the route result, allocate session scope, and select model candidates before provider execution. | +| Runtime integration | `pkg/agent/registry.go`, `pkg/agent/agent_message.go`, `pkg/agent/turn_coord.go` | Apply the route result, allocate session scope, and select model candidates before provider execution. | ## End-To-End Flow @@ -242,8 +242,8 @@ That makes the following behavior intentional: Agent dispatch and model routing happen in different places: - `pkg/agent/registry.go` owns `RouteResolver` -- `pkg/agent/loop_message.go` resolves the route and allocates session scope -- `pkg/agent/loop_turn.go:selectCandidates` calls `agent.Router.SelectModel(...)` +- `pkg/agent/agent_message.go` resolves the route and allocates session scope +- `pkg/agent/turn_coord.go:selectCandidates` calls `agent.Router.SelectModel(...)` When the light model is selected, the agent loop swaps to `agent.LightCandidates`. When it is not selected, execution stays on the agent's primary provider candidate set. @@ -252,7 +252,7 @@ When it is not selected, execution stays on the agent's primary provider candida One nuance sits just outside `pkg/routing` but matters for the full routing story. -After a route is allocated, `pkg/agent/loop_utils.go:resolveScopeKey` preserves an explicit incoming session key when the caller already supplied: +After a route is allocated, `pkg/agent/agent_utils.go:resolveScopeKey` preserves an explicit incoming session key when the caller already supplied: - an opaque canonical key - a legacy `agent:...` key @@ -278,5 +278,5 @@ They are separate from the runtime routing system described here. - `pkg/routing/agent_id.go` - `pkg/session/allocator.go` - `pkg/agent/registry.go` -- `pkg/agent/loop_message.go` -- `pkg/agent/loop_turn.go` +- `pkg/agent/agent_message.go` +- `pkg/agent/turn_coord.go` diff --git a/docs/architecture/session-system.md b/docs/architecture/session-system.md index 7f896d367..b87f9c38e 100644 --- a/docs/architecture/session-system.md +++ b/docs/architecture/session-system.md @@ -29,7 +29,7 @@ The session system has four jobs: | Session adapter | `pkg/session/jsonl_backend.go` | Adapts `pkg/memory.Store` to `SessionStore`, including alias and scope metadata support. | | Durable storage | `pkg/memory/jsonl.go` | Append-only JSONL storage plus `.meta.json` sidecar metadata. | | Scope and key building | `pkg/session/scope.go`, `pkg/session/key.go`, `pkg/session/allocator.go` | Builds structured scopes, opaque canonical keys, and legacy aliases from routing results. | -| Runtime integration | `pkg/agent/instance.go`, `pkg/agent/loop.go`, `pkg/agent/loop_message.go` | Initializes the store, allocates session scope, and persists metadata before turns run. | +| Runtime integration | `pkg/agent/instance.go`, `pkg/agent/agent.go`, `pkg/agent/agent_message.go` | Initializes the store, allocates session scope, and persists metadata before turns run. | ## Session Data Model @@ -90,7 +90,7 @@ The agent loop also preserves explicit incoming session keys when the caller alr - opaque canonical key - legacy `agent:...` key -That behavior lives in `pkg/agent/loop_utils.go:resolveScopeKey`. +That behavior lives in `pkg/agent/agent_utils.go:resolveScopeKey`. ## Allocation Flow @@ -108,7 +108,7 @@ InboundMessage More concretely: -1. `pkg/agent/loop_message.go` resolves the agent route from normalized inbound context. +1. `pkg/agent/agent_message.go` resolves the agent route from normalized inbound context. 2. `session.AllocateRouteSession` converts the route's `SessionPolicy` plus inbound context into a structured `SessionScope`. 3. The allocator builds: - `SessionKey`: canonical routed session key @@ -251,5 +251,5 @@ The session system is consumed by more than the agent loop: - `pkg/session/allocator.go` - `pkg/memory/jsonl.go` - `pkg/agent/instance.go` -- `pkg/agent/loop.go` -- `pkg/agent/loop_message.go` +- `pkg/agent/agent.go` +- `pkg/agent/agent_message.go`