From 91412701e162b46a76673eacea976319e4f21a26 Mon Sep 17 00:00:00 2001 From: Clawd Date: Fri, 27 Feb 2026 21:38:25 -0800 Subject: Add research findings for Claude Agent SDK integration --- research.md | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 research.md diff --git a/research.md b/research.md new file mode 100644 index 0000000..0496f29 --- /dev/null +++ b/research.md @@ -0,0 +1,412 @@ +# 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` + +### 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. + +### 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. + +### 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 -- cgit v1.2.3