diff options
| author | Clawd <ai@clawd.bot> | 2026-02-27 21:38:25 -0800 |
|---|---|---|
| committer | Clawd <ai@clawd.bot> | 2026-02-27 21:38:25 -0800 |
| commit | 91412701e162b46a76673eacea976319e4f21a26 (patch) | |
| tree | 4f5ea37e9198b73843454a9f8916fd176749d35a | |
| parent | e179e4e17d8d8bf6fa8c01a7b8a2bf60380008a5 (diff) | |
Add research findings for Claude Agent SDK integration
| -rw-r--r-- | research.md | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/research.md b/research.md new file mode 100644 index 0000000..0496f29 --- /dev/null +++ b/research.md | |||
| @@ -0,0 +1,412 @@ | |||
| 1 | # Research: Claude Flow Architecture | ||
| 2 | |||
| 3 | ## Existing Codebase Analysis | ||
| 4 | |||
| 5 | ### Template Structure | ||
| 6 | |||
| 7 | The starting point is a minimal Electron + Vite + React + better-sqlite3 template: | ||
| 8 | |||
| 9 | ``` | ||
| 10 | minimal-electron-vite-react-better-sqlite/ | ||
| 11 | ├── src/main/ | ||
| 12 | │ ├── index.ts # Electron main process | ||
| 13 | │ └── preload.ts # IPC bridge (empty) | ||
| 14 | ├── renderer/ | ||
| 15 | │ ├── index.html # Entry HTML | ||
| 16 | │ └── src/ | ||
| 17 | │ └── main.tsx # React entry ("hi") | ||
| 18 | ├── package.json | ||
| 19 | ├── tsconfig.json | ||
| 20 | └── vite.config.ts | ||
| 21 | ``` | ||
| 22 | |||
| 23 | ### Key Patterns in Template | ||
| 24 | |||
| 25 | **Main Process (`src/main/index.ts`):** | ||
| 26 | - Uses `app.isPackaged` for dev/prod detection | ||
| 27 | - Database stored in `app.getPath('userData')` — correct for Electron apps | ||
| 28 | - Uses WAL mode for SQLite (`journal_mode = WAL`) | ||
| 29 | - Window created with `contextIsolation: true`, `nodeIntegration: false` — secure defaults | ||
| 30 | - Preload script path: `path.join(__dirname, 'preload.js')` | ||
| 31 | |||
| 32 | **Vite Config:** | ||
| 33 | - Root is `renderer/` directory | ||
| 34 | - Base is `./` for file:// loads in production | ||
| 35 | - Dev server on port 5173 with `strictPort: true` | ||
| 36 | - Output to `renderer/dist` | ||
| 37 | |||
| 38 | **Build/Dev Scripts:** | ||
| 39 | - `npm run dev` — concurrent Vite + TypeScript watch + Electron | ||
| 40 | - Uses `wait-on tcp:5173` to wait for Vite before launching Electron | ||
| 41 | - `@electron/rebuild` handles native module rebuilding | ||
| 42 | |||
| 43 | ### What's Missing (We Need to Build) | ||
| 44 | |||
| 45 | 1. IPC handlers for renderer → main communication | ||
| 46 | 2. Proper database layer with migrations | ||
| 47 | 3. React components and state management | ||
| 48 | 4. Claude integration | ||
| 49 | 5. Workflow state machine | ||
| 50 | |||
| 51 | --- | ||
| 52 | |||
| 53 | ## Claude Agent SDK Research | ||
| 54 | |||
| 55 | ### Package Information | ||
| 56 | |||
| 57 | The SDK has been renamed from "Claude Code SDK" to "Claude Agent SDK". | ||
| 58 | |||
| 59 | **NPM Package:** `@anthropic-ai/claude-agent-sdk` | ||
| 60 | |||
| 61 | **Installation:** | ||
| 62 | ```bash | ||
| 63 | npm install @anthropic-ai/claude-agent-sdk | ||
| 64 | ``` | ||
| 65 | |||
| 66 | **Authentication:** | ||
| 67 | - Set `ANTHROPIC_API_KEY` environment variable | ||
| 68 | - Also supports Bedrock, Vertex AI, Azure via environment flags | ||
| 69 | |||
| 70 | ### Core API: `query()` | ||
| 71 | |||
| 72 | The primary function returns an async generator that streams messages: | ||
| 73 | |||
| 74 | ```typescript | ||
| 75 | import { query } from "@anthropic-ai/claude-agent-sdk"; | ||
| 76 | |||
| 77 | for await (const message of query({ | ||
| 78 | prompt: "Find and fix the bug in auth.py", | ||
| 79 | options: { | ||
| 80 | allowedTools: ["Read", "Edit", "Bash"], | ||
| 81 | permissionMode: "acceptEdits", | ||
| 82 | cwd: "/path/to/project" | ||
| 83 | } | ||
| 84 | })) { | ||
| 85 | console.log(message); | ||
| 86 | } | ||
| 87 | ``` | ||
| 88 | |||
| 89 | ### Built-in Tools | ||
| 90 | |||
| 91 | | Tool | Purpose | | ||
| 92 | |------|---------| | ||
| 93 | | **Read** | Read files (text, images, PDFs, notebooks) | | ||
| 94 | | **Write** | Create/overwrite files | | ||
| 95 | | **Edit** | Precise string replacements | | ||
| 96 | | **Bash** | Run terminal commands | | ||
| 97 | | **Glob** | Find files by pattern | | ||
| 98 | | **Grep** | Search file contents with regex | | ||
| 99 | | **WebSearch** | Search the web | | ||
| 100 | | **WebFetch** | Fetch/parse web pages | | ||
| 101 | | **Task** | Spawn subagents | | ||
| 102 | |||
| 103 | ### Permission Modes | ||
| 104 | |||
| 105 | | Mode | Behavior | | ||
| 106 | |------|----------| | ||
| 107 | | `default` | Standard permissions, may prompt | | ||
| 108 | | `acceptEdits` | Auto-approve file edits | | ||
| 109 | | `bypassPermissions` | Allow everything (dangerous) | | ||
| 110 | | `plan` | Planning only, no execution | | ||
| 111 | |||
| 112 | **Critical for our workflow:** | ||
| 113 | - **Research/Plan/Annotate phases**: Use `plan` mode or restrict `allowedTools` to read-only | ||
| 114 | - **Implement phase**: Use `acceptEdits` or `bypassPermissions` | ||
| 115 | |||
| 116 | ### System Prompts | ||
| 117 | |||
| 118 | Can be customized via options: | ||
| 119 | |||
| 120 | ```typescript | ||
| 121 | options: { | ||
| 122 | systemPrompt: "You are in RESEARCH mode. Read files deeply, write findings to research.md. DO NOT modify any source files.", | ||
| 123 | // OR use preset with append: | ||
| 124 | systemPrompt: { | ||
| 125 | type: "preset", | ||
| 126 | preset: "claude_code", | ||
| 127 | append: "Additional instructions here..." | ||
| 128 | } | ||
| 129 | } | ||
| 130 | ``` | ||
| 131 | |||
| 132 | ### Session Management | ||
| 133 | |||
| 134 | Sessions can be resumed using session IDs: | ||
| 135 | |||
| 136 | ```typescript | ||
| 137 | // First query captures session ID | ||
| 138 | let sessionId: string; | ||
| 139 | for await (const message of query({ prompt: "Read auth module" })) { | ||
| 140 | if (message.type === "system" && message.subtype === "init") { | ||
| 141 | sessionId = message.session_id; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Resume later with full context | ||
| 146 | for await (const message of query({ | ||
| 147 | prompt: "Now find all places that call it", | ||
| 148 | options: { resume: sessionId } | ||
| 149 | })) { | ||
| 150 | // Claude remembers the previous conversation | ||
| 151 | } | ||
| 152 | ``` | ||
| 153 | |||
| 154 | **Key insight:** Sessions persist to disk by default. We can store the session ID in SQLite and resume later. | ||
| 155 | |||
| 156 | ### Hooks | ||
| 157 | |||
| 158 | Hooks intercept agent behavior at key points: | ||
| 159 | |||
| 160 | ```typescript | ||
| 161 | import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk"; | ||
| 162 | |||
| 163 | const blockWrites: HookCallback = async (input, toolUseID, { signal }) => { | ||
| 164 | const preInput = input as PreToolUseHookInput; | ||
| 165 | if (["Write", "Edit"].includes(preInput.tool_name)) { | ||
| 166 | return { | ||
| 167 | hookSpecificOutput: { | ||
| 168 | hookEventName: "PreToolUse", | ||
| 169 | permissionDecision: "deny", | ||
| 170 | permissionDecisionReason: "In planning mode - no code changes allowed" | ||
| 171 | } | ||
| 172 | }; | ||
| 173 | } | ||
| 174 | return {}; | ||
| 175 | }; | ||
| 176 | |||
| 177 | for await (const message of query({ | ||
| 178 | prompt: "...", | ||
| 179 | options: { | ||
| 180 | hooks: { | ||
| 181 | PreToolUse: [{ matcher: "Write|Edit", hooks: [blockWrites] }] | ||
| 182 | } | ||
| 183 | } | ||
| 184 | })) { ... } | ||
| 185 | ``` | ||
| 186 | |||
| 187 | **Available hook events:** | ||
| 188 | - `PreToolUse` — Before tool executes (can block/modify) | ||
| 189 | - `PostToolUse` — After tool executes (can log/transform) | ||
| 190 | - `Stop` — Agent execution ending | ||
| 191 | - `SessionStart` / `SessionEnd` — Session lifecycle | ||
| 192 | - `SubagentStart` / `SubagentStop` — Subagent lifecycle | ||
| 193 | |||
| 194 | ### Message Types | ||
| 195 | |||
| 196 | The query yields different message types: | ||
| 197 | |||
| 198 | ```typescript | ||
| 199 | type SDKMessage = | ||
| 200 | | SDKAssistantMessage // Claude's response (includes tool_use) | ||
| 201 | | SDKUserMessage // User input | ||
| 202 | | SDKResultMessage // Final result with usage stats | ||
| 203 | | SDKSystemMessage // Init message with session_id, tools, etc. | ||
| 204 | | SDKPartialMessage // Streaming chunks (if enabled) | ||
| 205 | | SDKStatusMessage // Status updates | ||
| 206 | | ... | ||
| 207 | ``` | ||
| 208 | |||
| 209 | **SDKResultMessage** contains: | ||
| 210 | - `result` — Final text output | ||
| 211 | - `total_cost_usd` — API cost | ||
| 212 | - `usage` — Token counts | ||
| 213 | - `duration_ms` — Total time | ||
| 214 | - `num_turns` — Conversation turns | ||
| 215 | |||
| 216 | ### Query Object Methods | ||
| 217 | |||
| 218 | The query object has methods for control: | ||
| 219 | |||
| 220 | ```typescript | ||
| 221 | const q = query({ prompt: "...", options: { ... } }); | ||
| 222 | |||
| 223 | // Change settings mid-session | ||
| 224 | await q.setPermissionMode("acceptEdits"); | ||
| 225 | await q.setModel("opus"); | ||
| 226 | |||
| 227 | // Get session info | ||
| 228 | const init = await q.initializationResult(); | ||
| 229 | const commands = await q.supportedCommands(); | ||
| 230 | const models = await q.supportedModels(); | ||
| 231 | |||
| 232 | // Interrupt/cancel | ||
| 233 | await q.interrupt(); | ||
| 234 | q.close(); | ||
| 235 | ``` | ||
| 236 | |||
| 237 | --- | ||
| 238 | |||
| 239 | ## Architecture Decisions | ||
| 240 | |||
| 241 | ### Phase Enforcement Strategy | ||
| 242 | |||
| 243 | We have two complementary approaches: | ||
| 244 | |||
| 245 | **1. Permission Mode + Allowed Tools:** | ||
| 246 | ```typescript | ||
| 247 | const phaseConfig = { | ||
| 248 | research: { | ||
| 249 | permissionMode: "plan", | ||
| 250 | allowedTools: ["Read", "Glob", "Grep", "WebSearch", "WebFetch"], | ||
| 251 | systemPrompt: "You are in RESEARCH mode..." | ||
| 252 | }, | ||
| 253 | plan: { | ||
| 254 | permissionMode: "plan", | ||
| 255 | allowedTools: ["Read", "Glob", "Grep", "Write"], // Write only for plan.md | ||
| 256 | systemPrompt: "You are in PLANNING mode..." | ||
| 257 | }, | ||
| 258 | annotate: { | ||
| 259 | permissionMode: "plan", | ||
| 260 | allowedTools: ["Read", "Write"], // Only update plan.md | ||
| 261 | systemPrompt: "You are in ANNOTATION mode..." | ||
| 262 | }, | ||
| 263 | implement: { | ||
| 264 | permissionMode: "acceptEdits", | ||
| 265 | allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], | ||
| 266 | systemPrompt: "You are in IMPLEMENTATION mode..." | ||
| 267 | } | ||
| 268 | }; | ||
| 269 | ``` | ||
| 270 | |||
| 271 | **2. Hooks for Fine-Grained Control:** | ||
| 272 | ```typescript | ||
| 273 | const enforcePhaseHook: HookCallback = async (input, toolUseID, { signal }) => { | ||
| 274 | const { tool_name, tool_input } = input as PreToolUseHookInput; | ||
| 275 | const phase = getCurrentPhase(); // From app state | ||
| 276 | |||
| 277 | if (phase !== "implement" && ["Write", "Edit"].includes(tool_name)) { | ||
| 278 | const filePath = (tool_input as any).file_path; | ||
| 279 | // Allow only plan.md/research.md in non-implement phases | ||
| 280 | if (!filePath.endsWith("plan.md") && !filePath.endsWith("research.md")) { | ||
| 281 | return { | ||
| 282 | hookSpecificOutput: { | ||
| 283 | hookEventName: "PreToolUse", | ||
| 284 | permissionDecision: "deny", | ||
| 285 | permissionDecisionReason: `Cannot modify ${filePath} in ${phase} phase` | ||
| 286 | } | ||
| 287 | }; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | return {}; | ||
| 291 | }; | ||
| 292 | ``` | ||
| 293 | |||
| 294 | ### Session Persistence | ||
| 295 | |||
| 296 | **Option A: Use SDK's built-in session persistence** | ||
| 297 | - Sessions saved to disk automatically | ||
| 298 | - Store session ID in SQLite | ||
| 299 | - Resume with `options: { resume: sessionId }` | ||
| 300 | - Pro: Simpler, full context preserved | ||
| 301 | - Con: Less control over what's stored | ||
| 302 | |||
| 303 | **Option B: Store messages in SQLite ourselves** | ||
| 304 | - Store each message in our database | ||
| 305 | - Reconstruct context when resuming | ||
| 306 | - Pro: Full control, searchable history | ||
| 307 | - Con: More complex, may lose SDK internal state | ||
| 308 | |||
| 309 | **Recommendation:** Use **Option A** (SDK persistence) with session ID in SQLite. We can still store messages for display/search, but rely on SDK for actual context. | ||
| 310 | |||
| 311 | ### Artifact Management | ||
| 312 | |||
| 313 | The blog workflow uses `research.md` and `plan.md` as persistent artifacts. | ||
| 314 | |||
| 315 | **Options:** | ||
| 316 | 1. **Store in SQLite** — Searchable, version history, but not editable externally | ||
| 317 | 2. **Store as files in project** — Visible in git, editable in any editor | ||
| 318 | 3. **Both** — Files as source of truth, sync to SQLite for search | ||
| 319 | |||
| 320 | **Recommendation:** Store as **files in the project directory** (e.g., `.claude-flow/research.md`, `.claude-flow/plan.md`). This matches the blog workflow where the human edits the plan.md directly. | ||
| 321 | |||
| 322 | ### IPC Architecture | ||
| 323 | |||
| 324 | Electron requires IPC for renderer ↔ main communication: | ||
| 325 | |||
| 326 | ```typescript | ||
| 327 | // preload.ts | ||
| 328 | import { contextBridge, ipcRenderer } from "electron"; | ||
| 329 | |||
| 330 | contextBridge.exposeInMainWorld("api", { | ||
| 331 | // Projects | ||
| 332 | listProjects: () => ipcRenderer.invoke("projects:list"), | ||
| 333 | createProject: (data) => ipcRenderer.invoke("projects:create", data), | ||
| 334 | |||
| 335 | // Sessions | ||
| 336 | listSessions: (projectId) => ipcRenderer.invoke("sessions:list", projectId), | ||
| 337 | createSession: (data) => ipcRenderer.invoke("sessions:create", data), | ||
| 338 | |||
| 339 | // Claude | ||
| 340 | sendMessage: (sessionId, message) => ipcRenderer.invoke("claude:send", sessionId, message), | ||
| 341 | onMessage: (callback) => ipcRenderer.on("claude:message", callback), | ||
| 342 | setPhase: (sessionId, phase) => ipcRenderer.invoke("claude:setPhase", sessionId, phase), | ||
| 343 | }); | ||
| 344 | ``` | ||
| 345 | |||
| 346 | --- | ||
| 347 | |||
| 348 | ## Open Questions Resolved | ||
| 349 | |||
| 350 | ### 1. Claude Code SDK vs CLI | ||
| 351 | |||
| 352 | **Answer:** Use the SDK (`@anthropic-ai/claude-agent-sdk`). It provides: | ||
| 353 | - Programmatic control via TypeScript | ||
| 354 | - Hooks for intercepting behavior | ||
| 355 | - Session management | ||
| 356 | - Streaming messages | ||
| 357 | |||
| 358 | ### 2. Artifact Storage | ||
| 359 | |||
| 360 | **Answer:** Store as files in project directory (`.claude-flow/`) for: | ||
| 361 | - Editability in any editor (VSCode, etc.) | ||
| 362 | - Git visibility | ||
| 363 | - Matches the blog workflow | ||
| 364 | |||
| 365 | ### 3. Session Context / Compaction | ||
| 366 | |||
| 367 | **Answer:** Use SDK's built-in session persistence: | ||
| 368 | - Store session ID in SQLite | ||
| 369 | - Resume with `options: { resume: sessionId }` | ||
| 370 | - SDK handles context compaction automatically | ||
| 371 | |||
| 372 | ### 4. Multi-file Editing | ||
| 373 | |||
| 374 | **Answer:** The SDK handles this natively via the Edit/Write tools. The plan.md should list all files to be modified, and Claude executes them in order during implementation. | ||
| 375 | |||
| 376 | --- | ||
| 377 | |||
| 378 | ## Dependencies to Add | ||
| 379 | |||
| 380 | ```json | ||
| 381 | { | ||
| 382 | "dependencies": { | ||
| 383 | "@anthropic-ai/claude-agent-sdk": "latest", | ||
| 384 | "better-sqlite3": "12.2.0", | ||
| 385 | "react": "^19.1.1", | ||
| 386 | "react-dom": "^19.1.1", | ||
| 387 | "uuid": "^11.0.0" | ||
| 388 | }, | ||
| 389 | "devDependencies": { | ||
| 390 | "@types/uuid": "^10.0.0" | ||
| 391 | // ... existing devDeps | ||
| 392 | } | ||
| 393 | } | ||
| 394 | ``` | ||
| 395 | |||
| 396 | --- | ||
| 397 | |||
| 398 | ## Summary | ||
| 399 | |||
| 400 | The Claude Agent SDK is well-suited for building Claude Flow: | ||
| 401 | |||
| 402 | 1. **Session management** — Built-in persistence, resume capability | ||
| 403 | 2. **Permission modes** — `plan` mode prevents execution, `acceptEdits` for implementation | ||
| 404 | 3. **Hooks** — Fine-grained control over what Claude can do | ||
| 405 | 4. **Streaming** — Real-time message display in UI | ||
| 406 | 5. **System prompts** — Customizable per phase | ||
| 407 | |||
| 408 | The main work is: | ||
| 409 | - Building the Electron app shell (IPC, windows) | ||
| 410 | - SQLite layer for projects/sessions | ||
| 411 | - React UI for chat, artifacts, phase controls | ||
| 412 | - Wiring everything together with the SDK | ||
