|
1 | | -# Architecture: fourmis-agents |
| 1 | +# Architecture: fourmis-agents-sdk |
2 | 2 |
|
3 | 3 | ## Overview |
4 | 4 |
|
5 | | -fourmis-agents is structured in 5 layers, from bottom (provider APIs) to top (public API). |
| 5 | +`fourmis-agents-sdk` is organized in layered components from provider SDKs up to the public `query()` API. |
6 | 6 |
|
7 | 7 | ``` |
8 | | -┌─────────────────────────────────────────────────┐ |
9 | | -│ Layer 5: Public API │ |
10 | | -│ query() → AsyncGenerator<AgentMessage> │ |
11 | | -│ createMcpServer(), mcpTool() │ |
12 | | -├─────────────────────────────────────────────────┤ |
13 | | -│ Layer 4: Agent Loop │ |
14 | | -│ prompt → LLM → tool calls → execute → repeat │ |
15 | | -│ Permission checks, hooks, subagent spawn │ |
16 | | -├─────────────────────────────────────────────────┤ |
17 | | -│ Layer 3: Tool System │ |
18 | | -│ Built-in tools (Bash, Read, Write, Edit, │ |
19 | | -│ Glob, Grep) + MCP tools + subagent tools │ |
20 | | -├─────────────────────────────────────────────────┤ |
21 | | -│ Layer 2: Provider Adapters │ |
22 | | -│ Anthropic, OpenAI, Gemini adapters │ |
23 | | -│ Normalize: messages, tool calls, streaming │ |
24 | | -├─────────────────────────────────────────────────┤ |
25 | | -│ Layer 1: Provider APIs │ |
26 | | -│ @anthropic-ai/sdk, openai, @google/genai │ |
27 | | -└─────────────────────────────────────────────────┘ |
| 8 | +┌──────────────────────────────────────────────────────────┐ |
| 9 | +│ Layer 5: Public API │ |
| 10 | +│ query(), Query controls, provider/tool/MCP exports │ |
| 11 | +├──────────────────────────────────────────────────────────┤ |
| 12 | +│ Layer 4: Agent Loop │ |
| 13 | +│ prompt -> provider -> tool calls -> execution -> repeat │ |
| 14 | +│ hooks, permissions, subagents, memory, budgets │ |
| 15 | +├──────────────────────────────────────────────────────────┤ |
| 16 | +│ Layer 3: Tool and Runtime Integration │ |
| 17 | +│ built-in tools, MCP tools/resources, Task tools │ |
| 18 | +├──────────────────────────────────────────────────────────┤ |
| 19 | +│ Layer 2: Provider Adapters │ |
| 20 | +│ Anthropic/OpenAI/Gemini normalization │ |
| 21 | +├──────────────────────────────────────────────────────────┤ |
| 22 | +│ Layer 1: Provider SDKs / HTTP APIs │ |
| 23 | +│ @anthropic-ai/sdk, openai, @google/genai, OAuth endpoints│ |
| 24 | +└──────────────────────────────────────────────────────────┘ |
28 | 25 | ``` |
29 | 26 |
|
30 | | -## Layer 1: Provider APIs |
| 27 | +## Layer 1: Provider SDKs / APIs |
31 | 28 |
|
32 | | -Official SDKs for each provider: |
| 29 | +- Anthropic: `@anthropic-ai/sdk` |
| 30 | +- OpenAI: `openai` |
| 31 | +- Gemini: `@google/genai` plus OAuth HTTP mode |
33 | 32 |
|
34 | | -```ts |
35 | | -import Anthropic from "@anthropic-ai/sdk"; // Anthropic direct API |
36 | | -import OpenAI from "openai"; // OpenAI |
37 | | -import { GoogleGenAI } from "@google/genai"; // Gemini (API key mode) |
38 | | -``` |
39 | | - |
40 | | -No subprocess spawning — we call APIs directly. |
| 33 | +Fourmis executes these directly (no mandatory subprocess model for host query execution). |
41 | 34 |
|
42 | 35 | ## Layer 2: Provider Adapters |
43 | 36 |
|
44 | | -Each adapter implements `ProviderAdapter`, normalizing a provider's API into a common streaming interface: |
45 | | - |
46 | | -```ts |
47 | | -interface ProviderAdapter { |
48 | | - name: string; |
49 | | - chat(request: ChatRequest): AsyncGenerator<ChatChunk>; |
50 | | - calculateCost(model: string, usage: TokenUsage): number; |
51 | | - getContextWindow(model: string): number; |
52 | | - supportsFeature(feature: ProviderFeature): boolean; |
53 | | -} |
54 | | -``` |
| 37 | +Adapters implement a common interface (`ProviderAdapter`) to normalize: |
55 | 38 |
|
56 | | -The adapters handle translation of tool call formats (Anthropic's `content[].type = "tool_use"` vs OpenAI's `tool_calls[].function` vs Gemini's `functionCall`/`functionResponse` parts) and tool result formats transparently. |
| 39 | +- message exchange |
| 40 | +- tool-call shape |
| 41 | +- streaming chunks |
| 42 | +- usage/cost accounting |
| 43 | +- feature capability checks |
57 | 44 |
|
58 | | -The Gemini adapter operates in two modes: |
59 | | -- **API key mode** — uses `@google/genai` SDK with `generateContentStream()` |
60 | | -- **OAuth mode** — direct HTTP to Google's Code Assist endpoint (`cloudcode-pa.googleapis.com`) with SSE streaming, using tokens from `gemini login` CLI |
| 45 | +Key files: |
| 46 | +- `src/providers/types.ts` |
| 47 | +- `src/providers/anthropic.ts` |
| 48 | +- `src/providers/openai.ts` |
| 49 | +- `src/providers/gemini.ts` |
| 50 | +- `src/providers/registry.ts` |
61 | 51 |
|
62 | | -New providers can be added via `registerProvider()`. |
| 52 | +Provider registry is lazy and extensible via `registerProvider()`. |
63 | 53 |
|
64 | | -## Layer 3: Tool System |
| 54 | +## Layer 3: Tool and Runtime Integration |
65 | 55 |
|
66 | 56 | ### Tool Registry |
67 | 57 |
|
68 | | -```ts |
69 | | -class ToolRegistry { |
70 | | - register(tool: ToolImplementation): void; |
71 | | - getDefinitions(): ToolDefinition[]; |
72 | | - execute(name: string, input: unknown, context: ToolContext): Promise<ToolResult>; |
73 | | - list(): string[]; |
74 | | -} |
75 | | -``` |
| 58 | +`ToolRegistry` provides registration, definition export, and execution dispatch. |
76 | 59 |
|
77 | | -6 built-in tools are available in 3 presets (`coding`, `readonly`, `minimal`). MCP tools and subagent tools (Task, TaskOutput, TaskStop) are registered dynamically at runtime. |
| 60 | +Key file: |
| 61 | +- `src/tools/registry.ts` |
78 | 62 |
|
79 | | -### Tool Context |
| 63 | +### Built-in Tools |
80 | 64 |
|
81 | | -```ts |
82 | | -type ToolContext = { |
83 | | - cwd: string; |
84 | | - signal: AbortSignal; |
85 | | - sessionId: string; |
86 | | - env?: Record<string, string>; |
87 | | -}; |
88 | | -``` |
| 65 | +Current built-ins: |
| 66 | + |
| 67 | +- `Bash`, `Read`, `Write`, `Edit`, `Glob`, `Grep` |
| 68 | +- `NotebookEdit`, `WebFetch`, `WebSearch` |
| 69 | +- `TodoWrite`, `Config`, `AskUserQuestion`, `ExitPlanMode` |
| 70 | + |
| 71 | +Preset resolution defaults to `claude_code` and can also accept explicit arrays. |
| 72 | + |
| 73 | +Key files: |
| 74 | +- `src/tools/index.ts` |
| 75 | +- `src/tools/presets.ts` |
| 76 | +- `src/tools/*.ts` |
| 77 | + |
| 78 | +### MCP Integration |
| 79 | + |
| 80 | +`McpClientManager` connects configured servers and projects tools as namespaced tool IDs (`mcp__<server>__<tool>`), plus resource tools: |
| 81 | + |
| 82 | +- `mcp__list_resources` |
| 83 | +- `mcp__read_resource` |
| 84 | + |
| 85 | +Key files: |
| 86 | +- `src/mcp/client.ts` |
| 87 | +- `src/mcp/server.ts` |
| 88 | +- `src/mcp/types.ts` |
| 89 | +- `src/tools/mcp-resources.ts` |
| 90 | + |
| 91 | +### Subagents |
| 92 | + |
| 93 | +Subagent definitions are injected into runtime and surfaced through `Task`, `TaskOutput`, `TaskStop` tools. |
| 94 | + |
| 95 | +Key files: |
| 96 | +- `src/agents/types.ts` |
| 97 | +- `src/agents/task-manager.ts` |
| 98 | +- `src/agents/tools.ts` |
89 | 99 |
|
90 | 100 | ## Layer 4: Agent Loop |
91 | 101 |
|
92 | | -The core execution engine (`agent-loop.ts`). An `AsyncGenerator<AgentMessage>` that: |
| 102 | +`agent-loop.ts` is the core execution engine and emits Claude-compatible envelopes. |
| 103 | + |
| 104 | +Main flow: |
93 | 105 |
|
94 | | -1. Sends messages to the LLM via the provider adapter |
95 | | -2. Streams text deltas as events |
96 | | -3. Collects tool calls from the response |
97 | | -4. Fires `PreToolUse` hooks (can deny or modify input) |
98 | | -5. Checks permissions via `PermissionManager` |
99 | | -6. Executes tools via the registry |
100 | | -7. Fires `PostToolUse` / `PostToolUseFailure` hooks |
101 | | -8. Feeds results back to the LLM |
102 | | -9. Repeats until no tool calls remain or limits are hit |
| 106 | +1. Initialize session/system state. |
| 107 | +2. Send conversation to selected provider adapter. |
| 108 | +3. Collect `assistant` text/tool-use blocks. |
| 109 | +4. Apply hooks (`PreToolUse` etc.) and permission decisions. |
| 110 | +5. Execute tool calls through registry. |
| 111 | +6. Append tool results as `user` content. |
| 112 | +7. Repeat until completion or a terminal condition. |
| 113 | +8. Emit terminal `result` (`success` or error subtype). |
103 | 114 |
|
104 | | -Limits: `maxTurns`, `maxBudgetUsd`, `AbortSignal`. |
| 115 | +Terminal controls/limits: |
| 116 | + |
| 117 | +- `maxTurns` |
| 118 | +- `maxBudgetUsd` |
| 119 | +- abort signal |
| 120 | +- structured output retry bounds |
| 121 | + |
| 122 | +Related files: |
| 123 | +- `src/agent-loop.ts` |
| 124 | +- `src/permissions.ts` |
| 125 | +- `src/hooks.ts` |
| 126 | +- `src/settings.ts` |
| 127 | +- `src/utils/session-store.ts` |
| 128 | +- `src/memory/*` |
| 129 | +- `src/skills/*` |
105 | 130 |
|
106 | 131 | ## Layer 5: Public API |
107 | 132 |
|
108 | 133 | ### `query()` |
109 | 134 |
|
110 | | -Single entry point — creates a provider, builds a tool registry, sets up permissions/hooks/MCP/subagents, and returns a `Query` (AsyncGenerator with `interrupt()` and `close()` control methods). |
| 135 | +`query()` builds runtime state and returns a `Query` (async iterable + controls). |
| 136 | + |
| 137 | +Controls include: |
| 138 | + |
| 139 | +- `interrupt()`, `close()` |
| 140 | +- `setPermissionMode()`, `setModel()`, `setMaxThinkingTokens()` |
| 141 | +- `supportedModels()`, `supportedCommands()`, `initializationResult()` |
| 142 | +- MCP runtime controls (`mcpServerStatus`, `toggleMcpServer`, `setMcpServers`, ...) |
| 143 | + |
| 144 | +Key files: |
| 145 | +- `src/api.ts` |
| 146 | +- `src/query.ts` |
| 147 | +- `src/index.ts` |
111 | 148 |
|
112 | | -### `createMcpServer()` / `mcpTool()` |
| 149 | +## Message Contract |
113 | 150 |
|
114 | | -Create in-process MCP servers with Zod-typed tool definitions. |
| 151 | +Fourmis emits Claude-style `SDKMessage` unions (`AgentMessage`) with core envelopes: |
115 | 152 |
|
116 | | -### `registerProvider()` / `getProvider()` |
| 153 | +- `system` (`init`, `status`, hook/task metadata) |
| 154 | +- `assistant` (text/tool_use blocks) |
| 155 | +- `user` (tool_result blocks) |
| 156 | +- `stream_event` |
| 157 | +- `tool_progress`, `tool_use_summary` |
| 158 | +- `result` (`success` or error subtype) |
117 | 159 |
|
118 | | -Manage the provider registry. Built-in providers (`anthropic`, `openai`) are lazy-created on first use. |
| 160 | +Key file: |
| 161 | +- `src/types.ts` |
119 | 162 |
|
120 | | -## File Structure |
| 163 | +## Compatibility Harness Architecture |
| 164 | + |
| 165 | +`tests/compat` provides strict side-by-side regression checks against `@anthropic-ai/claude-agent-sdk`. |
| 166 | + |
| 167 | +Components: |
| 168 | + |
| 169 | +- scenario definitions: `tests/compat/scenarios.ts` |
| 170 | +- pair runner + trace normalization: `tests/compat/harness.ts` |
| 171 | +- assertion engine (sdk + parity): `tests/compat/assertions.ts` |
| 172 | +- report/artifact writer: `tests/compat/report.ts` |
| 173 | +- orchestrator entrypoint: `tests/compat/run-compat.ts` |
| 174 | + |
| 175 | +Artifacts: |
| 176 | + |
| 177 | +- per-run `summary.json` |
| 178 | +- human-readable `report.md` |
| 179 | +- per-scenario normalized traces for both SDKs |
| 180 | + |
| 181 | +## Updated Repository Map |
121 | 182 |
|
122 | 183 | ``` |
123 | | -fourmis-agents/ |
| 184 | +fourmis-agent-sdk/ |
124 | 185 | ├── src/ |
125 | | -│ ├── index.ts # Public API exports |
126 | | -│ ├── api.ts # query() implementation |
127 | | -│ ├── query.ts # Query wrapper (AsyncGenerator + control methods) |
128 | | -│ ├── types.ts # All core types (messages, permissions, options) |
129 | | -│ ├── agent-loop.ts # Core agent execution loop |
130 | | -│ ├── permissions.ts # Permission manager (6 modes + rules + callback) |
131 | | -│ ├── hooks.ts # Hook system (12 event types) |
132 | | -│ ├── settings.ts # Settings file loader (.claude/settings*.json) |
133 | | -│ │ |
134 | | -│ ├── providers/ |
135 | | -│ │ ├── types.ts # ProviderAdapter interface, ChatRequest, ChatChunk |
136 | | -│ │ ├── registry.ts # Provider registry (register/get) |
137 | | -│ │ ├── anthropic.ts # Anthropic API adapter |
138 | | -│ │ ├── openai.ts # OpenAI API adapter (API key + Codex OAuth) |
139 | | -│ │ └── gemini.ts # Gemini API adapter (API key + CLI OAuth) |
140 | | -│ │ |
141 | | -│ ├── tools/ |
142 | | -│ │ ├── registry.ts # ToolRegistry class |
143 | | -│ │ ├── presets.ts # Tool presets (coding, readonly, minimal) |
144 | | -│ │ ├── bash.ts # Shell execution via Bun.spawn() |
145 | | -│ │ ├── read.ts # File reading with line numbers |
146 | | -│ │ ├── write.ts # File creation/overwriting |
147 | | -│ │ ├── edit.ts # String replacement editing |
148 | | -│ │ ├── glob.ts # File pattern matching |
149 | | -│ │ ├── grep.ts # Regex content search |
150 | | -│ │ └── mcp-resources.ts # MCP resource listing/reading tools |
151 | | -│ │ |
152 | | -│ ├── mcp/ |
153 | | -│ │ ├── index.ts # Re-exports |
154 | | -│ │ ├── client.ts # MCP client (connect to external servers) |
155 | | -│ │ ├── server.ts # In-process MCP server + tool() helper |
156 | | -│ │ └── types.ts # MCP config types (stdio, SSE, HTTP, SDK) |
157 | | -│ │ |
| 186 | +│ ├── api.ts |
| 187 | +│ ├── agent-loop.ts |
| 188 | +│ ├── hooks.ts |
| 189 | +│ ├── index.ts |
| 190 | +│ ├── permissions.ts |
| 191 | +│ ├── query.ts |
| 192 | +│ ├── settings.ts |
| 193 | +│ ├── types.ts |
158 | 194 | │ ├── agents/ |
159 | | -│ │ ├── index.ts # Re-exports |
160 | | -│ │ ├── types.ts # AgentDefinition, BackgroundTask |
161 | | -│ │ ├── task-manager.ts # Background task lifecycle |
162 | | -│ │ └── tools.ts # Task, TaskOutput, TaskStop tool implementations |
163 | | -│ │ |
164 | 195 | │ ├── auth/ |
165 | | -│ │ ├── login-openai.ts # OpenAI Codex login flow |
166 | | -│ │ ├── openai-oauth.ts # OpenAI OAuth token management |
167 | | -│ │ └── gemini-oauth.ts # Gemini CLI OAuth token management |
168 | | -│ │ |
169 | | -│ └── utils/ |
170 | | -│ ├── cost.ts # Per-model cost tables |
171 | | -│ └── system-prompt.ts # Default system prompt builder |
172 | | -│ |
173 | | -├── tests/ |
174 | | -│ ├── agent-loop.test.ts |
175 | | -│ ├── hooks.test.ts |
176 | | -│ ├── integration.test.ts |
| 196 | +│ ├── mcp/ |
| 197 | +│ ├── memory/ |
177 | 198 | │ ├── providers/ |
178 | | -│ │ ├── anthropic.test.ts |
179 | | -│ │ ├── openai.test.ts |
180 | | -│ │ └── openai-integration.test.ts |
| 199 | +│ ├── skills/ |
181 | 200 | │ ├── tools/ |
182 | | -│ │ ├── bash.test.ts |
183 | | -│ │ ├── edit.test.ts |
184 | | -│ │ ├── glob.test.ts |
185 | | -│ │ ├── grep.test.ts |
186 | | -│ │ ├── read.test.ts |
187 | | -│ │ └── write.test.ts |
188 | | -│ ├── mcp/ |
189 | | -│ │ ├── client.test.ts |
190 | | -│ │ ├── sdk-server.test.ts |
191 | | -│ │ └── integration.test.ts |
| 201 | +│ └── utils/ |
| 202 | +├── tests/ |
192 | 203 | │ ├── agents/ |
193 | | -│ │ ├── task-manager.test.ts |
194 | | -│ │ ├── tools.test.ts |
195 | | -│ │ └── integration.test.ts |
196 | 204 | │ ├── auth/ |
197 | | -│ │ ├── openai-oauth.test.ts |
198 | | -│ │ └── openai-codex-integration.test.ts |
199 | | -│ └── compare/ # SDK comparison scenarios |
200 | | -│ └── scenarios/ |
201 | | -│ |
202 | | -├── package.json |
203 | | -├── tsconfig.json |
| 205 | +│ ├── compat/ |
| 206 | +│ ├── mcp/ |
| 207 | +│ ├── providers/ |
| 208 | +│ ├── tools/ |
| 209 | +│ ├── agent-loop.test.ts |
| 210 | +│ ├── hooks.test.ts |
| 211 | +│ └── integration.test.ts |
204 | 212 | ├── README.md |
205 | 213 | └── ARCHITECTURE.md |
206 | 214 | ``` |
0 commit comments