# Research: Claude Flow Architecture ## Existing Codebase Analysis ### Template Structure The starting point is a minimal Electron + Vite + React + better-sqlite3 template: ``` minimal-electron-vite-react-better-sqlite/ ├── src/main/ │ ├── index.ts # Electron main process │ └── preload.ts # IPC bridge (empty) ├── renderer/ │ ├── index.html # Entry HTML │ └── src/ │ └── main.tsx # React entry ("hi") ├── package.json ├── tsconfig.json └── vite.config.ts ``` ### Key Patterns in Template **Main Process (`src/main/index.ts`):** - Uses `app.isPackaged` for dev/prod detection - Database stored in `app.getPath('userData')` — correct for Electron apps - Uses WAL mode for SQLite (`journal_mode = WAL`) - Window created with `contextIsolation: true`, `nodeIntegration: false` — secure defaults - Preload script path: `path.join(__dirname, 'preload.js')` **Vite Config:** - Root is `renderer/` directory - Base is `./` for file:// loads in production - Dev server on port 5173 with `strictPort: true` - Output to `renderer/dist` **Build/Dev Scripts:** - `npm run dev` — concurrent Vite + TypeScript watch + Electron - Uses `wait-on tcp:5173` to wait for Vite before launching Electron - `@electron/rebuild` handles native module rebuilding ### What's Missing (We Need to Build) 1. IPC handlers for renderer → main communication 2. Proper database layer with migrations 3. React components and state management 4. Claude integration 5. Workflow state machine --- ## Claude Agent SDK Research ### Package Information The SDK has been renamed from "Claude Code SDK" to "Claude Agent SDK". **NPM Package:** `@anthropic-ai/claude-agent-sdk` **Installation:** ```bash npm install @anthropic-ai/claude-agent-sdk ``` **Authentication:** - Set `ANTHROPIC_API_KEY` environment variable - Also supports Bedrock, Vertex AI, Azure via environment flags ### Core API: `query()` The primary function returns an async generator that streams messages: ```typescript import { query } from "@anthropic-ai/claude-agent-sdk"; for await (const message of query({ prompt: "Find and fix the bug in auth.py", options: { allowedTools: ["Read", "Edit", "Bash"], permissionMode: "acceptEdits", cwd: "/path/to/project" } })) { console.log(message); } ``` ### Built-in Tools | Tool | Purpose | |------|---------| | **Read** | Read files (text, images, PDFs, notebooks) | | **Write** | Create/overwrite files | | **Edit** | Precise string replacements | | **Bash** | Run terminal commands | | **Glob** | Find files by pattern | | **Grep** | Search file contents with regex | | **WebSearch** | Search the web | | **WebFetch** | Fetch/parse web pages | | **Task** | Spawn subagents | ### Permission Modes | Mode | Behavior | |------|----------| | `default` | Standard permissions, may prompt | | `acceptEdits` | Auto-approve file edits | | `bypassPermissions` | Allow everything (dangerous) | | `plan` | Planning only, no execution | **Critical for our workflow:** - **Research/Plan/Annotate phases**: Use `plan` mode or restrict `allowedTools` to read-only - **Implement phase**: Use `acceptEdits` or `bypassPermissions` // REVIEW: make a user toggle ### System Prompts Can be customized via options: ```typescript options: { systemPrompt: "You are in RESEARCH mode. Read files deeply, write findings to research.md. DO NOT modify any source files.", // OR use preset with append: systemPrompt: { type: "preset", preset: "claude_code", append: "Additional instructions here..." } } ``` ### Session Management Sessions can be resumed using session IDs: ```typescript // First query captures session ID let sessionId: string; for await (const message of query({ prompt: "Read auth module" })) { if (message.type === "system" && message.subtype === "init") { sessionId = message.session_id; } } // Resume later with full context for await (const message of query({ prompt: "Now find all places that call it", options: { resume: sessionId } })) { // Claude remembers the previous conversation } ``` **Key insight:** Sessions persist to disk by default. We can store the session ID in SQLite and resume later. ### Hooks Hooks intercept agent behavior at key points: ```typescript import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk"; const blockWrites: HookCallback = async (input, toolUseID, { signal }) => { const preInput = input as PreToolUseHookInput; if (["Write", "Edit"].includes(preInput.tool_name)) { return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "In planning mode - no code changes allowed" } }; } return {}; }; for await (const message of query({ prompt: "...", options: { hooks: { PreToolUse: [{ matcher: "Write|Edit", hooks: [blockWrites] }] } } })) { ... } ``` **Available hook events:** - `PreToolUse` — Before tool executes (can block/modify) - `PostToolUse` — After tool executes (can log/transform) - `Stop` — Agent execution ending - `SessionStart` / `SessionEnd` — Session lifecycle - `SubagentStart` / `SubagentStop` — Subagent lifecycle ### Message Types The query yields different message types: ```typescript type SDKMessage = | SDKAssistantMessage // Claude's response (includes tool_use) | SDKUserMessage // User input | SDKResultMessage // Final result with usage stats | SDKSystemMessage // Init message with session_id, tools, etc. | SDKPartialMessage // Streaming chunks (if enabled) | SDKStatusMessage // Status updates | ... ``` **SDKResultMessage** contains: - `result` — Final text output - `total_cost_usd` — API cost - `usage` — Token counts - `duration_ms` — Total time - `num_turns` — Conversation turns ### Query Object Methods The query object has methods for control: ```typescript const q = query({ prompt: "...", options: { ... } }); // Change settings mid-session await q.setPermissionMode("acceptEdits"); await q.setModel("opus"); // Get session info const init = await q.initializationResult(); const commands = await q.supportedCommands(); const models = await q.supportedModels(); // Interrupt/cancel await q.interrupt(); q.close(); ``` --- ## Architecture Decisions ### Phase Enforcement Strategy We have two complementary approaches: **1. Permission Mode + Allowed Tools:** ```typescript const phaseConfig = { research: { permissionMode: "plan", allowedTools: ["Read", "Glob", "Grep", "WebSearch", "WebFetch"], systemPrompt: "You are in RESEARCH mode..." }, plan: { permissionMode: "plan", allowedTools: ["Read", "Glob", "Grep", "Write"], // Write only for plan.md systemPrompt: "You are in PLANNING mode..." }, annotate: { permissionMode: "plan", allowedTools: ["Read", "Write"], // Only update plan.md systemPrompt: "You are in ANNOTATION mode..." }, implement: { permissionMode: "acceptEdits", allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], systemPrompt: "You are in IMPLEMENTATION mode..." } }; ``` **2. Hooks for Fine-Grained Control:** ```typescript const enforcePhaseHook: HookCallback = async (input, toolUseID, { signal }) => { const { tool_name, tool_input } = input as PreToolUseHookInput; const phase = getCurrentPhase(); // From app state if (phase !== "implement" && ["Write", "Edit"].includes(tool_name)) { const filePath = (tool_input as any).file_path; // Allow only plan.md/research.md in non-implement phases if (!filePath.endsWith("plan.md") && !filePath.endsWith("research.md")) { return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: `Cannot modify ${filePath} in ${phase} phase` } }; } } return {}; }; ``` ### Session Persistence **Option A: Use SDK's built-in session persistence** - Sessions saved to disk automatically - Store session ID in SQLite - Resume with `options: { resume: sessionId }` - Pro: Simpler, full context preserved - Con: Less control over what's stored **Option B: Store messages in SQLite ourselves** - Store each message in our database - Reconstruct context when resuming - Pro: Full control, searchable history - Con: More complex, may lose SDK internal state **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. // REVIEW: option a ### Artifact Management The blog workflow uses `research.md` and `plan.md` as persistent artifacts. **Options:** 1. **Store in SQLite** — Searchable, version history, but not editable externally 2. **Store as files in project** — Visible in git, editable in any editor 3. **Both** — Files as source of truth, sync to SQLite for search **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. // REVIEW: recommendation is great ### IPC Architecture Electron requires IPC for renderer ↔ main communication: ```typescript // preload.ts import { contextBridge, ipcRenderer } from "electron"; contextBridge.exposeInMainWorld("api", { // Projects listProjects: () => ipcRenderer.invoke("projects:list"), createProject: (data) => ipcRenderer.invoke("projects:create", data), // Sessions listSessions: (projectId) => ipcRenderer.invoke("sessions:list", projectId), createSession: (data) => ipcRenderer.invoke("sessions:create", data), // Claude sendMessage: (sessionId, message) => ipcRenderer.invoke("claude:send", sessionId, message), onMessage: (callback) => ipcRenderer.on("claude:message", callback), setPhase: (sessionId, phase) => ipcRenderer.invoke("claude:setPhase", sessionId, phase), }); ``` --- ## Open Questions Resolved ### 1. Claude Code SDK vs CLI **Answer:** Use the SDK (`@anthropic-ai/claude-agent-sdk`). It provides: - Programmatic control via TypeScript - Hooks for intercepting behavior - Session management - Streaming messages ### 2. Artifact Storage **Answer:** Store as files in project directory (`.claude-flow/`) for: - Editability in any editor (VSCode, etc.) - Git visibility - Matches the blog workflow ### 3. Session Context / Compaction **Answer:** Use SDK's built-in session persistence: - Store session ID in SQLite - Resume with `options: { resume: sessionId }` - SDK handles context compaction automatically ### 4. Multi-file Editing **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. --- ## Dependencies to Add ```json { "dependencies": { "@anthropic-ai/claude-agent-sdk": "latest", "better-sqlite3": "12.2.0", "react": "^19.1.1", "react-dom": "^19.1.1", "uuid": "^11.0.0" }, "devDependencies": { "@types/uuid": "^10.0.0" // ... existing devDeps } } ``` --- ## Summary The Claude Agent SDK is well-suited for building Claude Flow: 1. **Session management** — Built-in persistence, resume capability 2. **Permission modes** — `plan` mode prevents execution, `acceptEdits` for implementation 3. **Hooks** — Fine-grained control over what Claude can do 4. **Streaming** — Real-time message display in UI 5. **System prompts** — Customizable per phase The main work is: - Building the Electron app shell (IPC, windows) - SQLite layer for projects/sessions - React UI for chat, artifacts, phase controls - Wiring everything together with the SDK