This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
OpenHelm is a local-first macOS desktop application that turns high-level goals into scheduled, self-correcting Claude Code jobs. Users type a goal ("Improve test coverage"), the system generates a plan of Claude Code tasks, and those tasks run autonomously in the background on a schedule.
The project is currently in the planning/bootstrapping phase. The docs/ directory contains the PRD (prd.md), v1 implementation plan (plan_1_v1.md), and competitor research (competitors.md).
| Layer | Choice |
|---|---|
| Desktop framework | Tauri 2 (Rust shell + native WebView) |
| Frontend | React 18 + TypeScript |
| Styling | Tailwind CSS 4 + shadcn/ui |
| Local database | SQLite via better-sqlite3 |
| ORM | Drizzle ORM |
| Background agent | Node.js sidecar process |
| IPC (UI ↔ agent) | Tauri stdin/stdout sidecar (JSON-RPC) |
| AI planning/summarisation | Claude Code CLI (--print mode) |
| State management | Zustand |
Prerequisites: Node 20+, Rust toolchain, Xcode Command Line Tools.
# Root — start Tauri dev server (frontend + Rust shell + agent sidecar)
npm run tauri dev
# Frontend only (Vite)
npm run dev
# Build the agent sidecar
cd agent && npm run build
# Run Drizzle migrations
cd agent && npx drizzle-kit migrate
# Type-check everything
npm run typecheck
# Lint
npm run lintThese commands are defined in the implementation plan but the project is not yet scaffolded. Update this section once the Tauri scaffold exists.
openhelm/
├── src-tauri/ # Tauri Rust shell (keep minimal — thin wrappers only)
│ ├── src/
│ │ ├── main.rs # Entry point, sidecar launch, window setup
│ │ └── lib.rs # Tauri commands (thin wrappers only)
│ └── Cargo.toml
│
├── src/ # React frontend
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── layout/ # Sidebar, shell, navigation
│ │ ├── data-tables/ # Data table CRUD UI (list, detail, grid, cells)
│ │ ├── goals/
│ │ ├── jobs/
│ │ ├── runs/
│ │ ├── onboarding/
│ │ └── shared/
│ ├── hooks/ # Custom React hooks
│ ├── stores/ # Zustand stores
│ ├── lib/ # API client (agent-client.ts), type utils
│ └── App.tsx
│
├── agent/ # Node.js background agent (sidecar)
│ ├── src/
│ │ ├── index.ts # Entry point, IPC server init
│ │ ├── data-tables/ # Embedding, retrieval, prompt injection for data tables
│ │ ├── scheduler/ # Job queue and scheduling engine
│ │ ├── executor/ # Claude Code process management
│ │ ├── planner/ # Goal → job plan generation (via CLI)
│ │ ├── db/ # Drizzle schema, migrations, queries
│ │ ├── claude-code/ # ClaudeCodeRunner abstraction layer
│ │ └── ipc/ # JSON-RPC server (stdin/stdout)
│ └── package.json
│
├── shared/ # Types shared between frontend and agent
│ └── types.ts # IpcRequest, IpcResponse, IpcEvent
│
└── docs/ # Planning documents
All LLM operations route through the Claude Code CLI. Users only need a Claude Code subscription — no separate API key required.
-
Job execution — spawns Claude Code via
agent/src/claude-code/runner.tswith--print --output-format stream-jsonfor real-time streaming. -
Planning, assessment, and summarisation — spawns Claude Code via
agent/src/claude-code/print.tswith--print --output-format text --tools ""for single-turn text completions. Uses--model sonnetfor planning and--model claude-haiku-4-5-20251001for classification/summarisation.
All CLI invocations must go through the agent/src/claude-code/ directory — never call Claude Code from elsewhere.
Project id, name, description, directory_path, git_url (nullable, cloud only),
created_at, updated_at
Goal id, project_id, description, status(active|paused|archived), created_at
Job id, goal_id (nullable), project_id, name, description, prompt,
schedule_type(once|interval|cron), schedule_config(JSON),
is_enabled, next_fire_at, created_at, updated_at
Run id, job_id, status(queued|running|succeeded|failed|permanent_failure|cancelled),
trigger_source(scheduled|manual|corrective),
started_at, finished_at, exit_code, summary, created_at
RunLog id, run_id, sequence, stream(stdout|stderr), text, timestamp
Settings key, value, updated_at
DataTable id, project_id, name, description, columns(JSON), embedding(JSON),
row_count, created_by(user|ai), created_at, updated_at
DataTableRow id, table_id, data(JSON), sort_order, created_at, updated_at
DataTableChange id, table_id, row_id, action(insert|update|delete|schema_change),
actor(user|ai|system), run_id, diff(JSON), created_at
Database lives at ~/.openhelm/openhelm.db. WAL mode enabled; foreign keys enforced.
Cloud-mode note: git_url is optional and unused by default. Cloud sandboxes boot empty; the agent uses authenticated Connections (CLIs, MCPs) to fetch whatever the prompt requires. The executor only runs git clone when git_url is set to a valid URL (https://, git@, or ssh://). A generic sandbox-context preamble (see worker/src/sandbox-preamble.ts) is prepended to every prompt listing available connections.
MCP transports: stdio, HTTP, and SSE are all first-class.
- stdio MCPs need a pre-installed binary (cloud mode requires them to be in
WORKER_BAKED_MCP_SERVERS). - HTTP / SSE MCPs take a URL + arbitrary
headersmap onMcpConfig. Header values containing${SECRET}or${OAUTH_TOKEN}are substituted at launch viaresolveHeaders()(shared/src/secret-resolver.ts) against macOS Keychain (local) or Supabase Vault (cloud). Local mode emits discriminated-union{type: "http" | "sse", url, headers}entries into Claude Code CLI's--mcp-configJSON. Cloud mode bridges each remote MCP throughnpx mcp-remoteas an additional--with-extensionarg in the Goose launch command. Discovery + RFC 7591 Dynamic Client Registration live inshared/src/mcp-oauth-discovery.tsand are exposed via theconnections.setupMcpOauthIPC method.
The agent communicates via stdin/stdout using newline-delimited JSON:
- Requests (UI → agent):
{ id: string, method: string, params?: unknown } - Responses (agent → UI):
{ id: string, result?: unknown, error?: { code, message } } - Events (agent → UI, unprompted):
{ event: string, data: unknown }— e.g.run.log,run.statusChanged
Frontend uses src/lib/agent-client.ts for all agent communication. Events are re-dispatched as CustomEvent on window under the name agent:<event>.
The agent owns three subsystems that must be fault-isolated:
- Scheduler — 1-minute tick; enqueues runs when
next_fire_at <= now - Executor — worker pool (default concurrency: 1); spawns Claude Code, streams logs to DB, updates run status
- Watchdog — per-run timeout (default 30 min); SIGTERMs hung processes, marks run as failed
A UI crash must never interrupt a running job.
agent/src/claude-code/is the only entry point to the Claude Code CLI (runner.tsfor jobs,print.tsfor LLM calls). Pin a minimum supported CLI version; warn users if their installed version is below it.- No plan runs without user approval. The plan review step is mandatory before any job fires for the first time.
- Jobs can exist without a goal (
goal_idis nullable). Manual job creation is a first-class feature. - File size limit: keep code files ≤ 225 lines; split into submodules when a file grows beyond this.
- Log to stderr from the agent (stdout is reserved for IPC).
- The hosted tier (v3+) is a separate business layer; the OSS version runs fully locally with no external services in v1.
- Chat permission modes are
plan_first(default) orbypass. Plan-first runs the LLM read-only and queues every mutation as an XML<tool_call>block — OpenHelm CRUD, MCP server methods (browser navigate, Notion create-page, etc.), and the cloud sandbox spawn itself all become approval cards before they execute. The MCP read/write taxonomy lives inagent/src/mcp-servers/taxonomies/*.json(per-server) andagent/src/connections/taxonomy.ts(per Connection type) — unknown tools default to write. Approved MCP writes dispatch viaagent/src/mcp-client/locally orworker.executeApprovedActions(one sandbox per batch) in cloud mode. Seedocs/plans/plan_20_plan_first.md.