From 1756841b8bfe17ba6749c9db263e01ba86627df4 Mon Sep 17 00:00:00 2001 From: jonnyparris <6400000+jonnyparris@users.noreply.github.com> Date: Mon, 25 May 2026 13:12:27 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20trim=20AGENTS.md=20for=20token=20effici?= =?UTF-8?q?ency=20(249=20=E2=86=92=20112=20lines)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every line of AGENTS.md loads in every session. Move reference material to dedicated docs that only load when relevant. - docs/system-prompt.md — system prompt design + tool surface (was inline, 64 lines) - docs/skills.md — skill loader + MCP CRUD (was inline, 25 lines) - docs/file-map.md — full src/ map (was inline, 35 lines) - docs/linting.md — two-layer rationale (was inline, 10 lines) - docs/deploying.md — probe details + experimental-flag history (was inline) AGENTS.md keeps: focus, commands, rules, where-to-look table, git discipline (condensed), top-level file map (8 entry points), architecture diagram, invariants. No behavior change — pure reorganisation. --- AGENTS.md | 249 ++++++++++-------------------------------- docs/deploying.md | 15 +++ docs/file-map.md | 57 ++++++++++ docs/linting.md | 10 ++ docs/skills.md | 25 +++++ docs/system-prompt.md | 63 +++++++++++ 6 files changed, 226 insertions(+), 193 deletions(-) create mode 100644 docs/deploying.md create mode 100644 docs/file-map.md create mode 100644 docs/linting.md create mode 100644 docs/skills.md create mode 100644 docs/system-prompt.md diff --git a/AGENTS.md b/AGENTS.md index 5367243..ed49031 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,127 +4,84 @@ Autonomous coding agent on Cloudflare Workers. Self-hostable, multi-tenant, sand ## Focus -- Multi-tenant architecture: per-user state via UserControl DO, global state via SharedIndex DO. -- CodingAgent built on `@cloudflare/think` for message persistence, chat loop, and durable fibers. -- Keep the implementation publishable and self-hostable. -- Prioritize correctness and test coverage over feature expansion. +- Multi-tenant: per-user state in UserControl DO, global state in SharedIndex DO. +- CodingAgent built on `@cloudflare/think` (chat loop, message persistence, durable fibers). +- Keep publishable and self-hostable. No container- or VM-specific assumptions. +- Correctness and test coverage over feature expansion. ## Commands - `npm install` - `npm run typecheck` -- `npm run lint` +- `npm run lint` (CI uses Biome; same signal locally) - `npm test` - `npm run dev` -- `npm run deploy` +- `npm run deploy:safe` — build + deploy + post-deploy smoke probe. Use plain `npm run deploy` only when you mean to skip the probe. ## Rules - Run `npm test` and `npm run typecheck` regularly while changing behavior. +- All Think imports route through `src/think-adapter.ts` — never import `@cloudflare/think` directly elsewhere. - Prefer small, typed modules over large all-in-one files. - Keep Durable Object state persistent and observable. -- Preserve Cloudflare Access in production; local test bypass is allowed only in test/dev config. -- Do not introduce container- or VM-specific assumptions. -- All Think imports route through `src/think-adapter.ts` — never import `@cloudflare/think` directly elsewhere. - -## Linting +- Preserve Cloudflare Access in production; local test bypass is only for test/dev config. -Two layers: +## Where to Look -1. **In-isolate** — the `typecheck` tool with `extraStrict: true`. Catches unused locals/parameters, missing returns, and switch fall-through via TypeScript's own diagnostics. No extra bundle cost, sub-second feedback inside a session. -2. **External (CI)** — Biome runs in `.github/workflows/dodo-verify.yml` for every PR and dispatched verify run. Catches the wider set: unused imports, suspicious patterns, double-equals, etc. Configured in `biome.jsonc` (correctness + suspicious rules only — formatting and style are intentionally off). +| Task | Location | +|------|----------| +| Source file map | `docs/file-map.md` | +| System prompt design + tool surface | `docs/system-prompt.md` | +| Skills loader + MCP CRUD | `docs/skills.md` | +| Linting (two-layer, why) | `docs/linting.md` | +| Deploy details + post-deploy probe | `docs/deploying.md` | +| Facets / scheduled sessions / notifications | `docs/facets.md`, `docs/scheduled-sessions.md`, `docs/notifications.md` | +| Testing patterns | `docs/testing.md` | +| Watchdog | `docs/session-watchdog.md` | -Why two layers: Biome's wasm bundle is ~8 MB gzipped, which would push Dodo past the Workers 10 MB compressed script limit if loaded in-isolate. The in-isolate check is the agent's fast feedback loop; the CI check is the thorough gate before merge. +## Git Discipline -Run `npm run lint` locally before pushing if you want the same signal CI will give you. `npm run lint:fix` applies safe auto-fixes. +**Never commit directly to `main`.** All changes go through feature branches. Two workflows — pick by runtime. -## Deploying +### Inside a Dodo session (sandboxed clone in a DO) -**Deploys are manual.** Run `npm run deploy:safe` locally after merging to `main` — it builds, deploys, then runs `scripts/post-deploy-smoke.sh` against the deployed Worker (asserting `/health`, `/version.json` matches HEAD, codemode `execute` round-trips through `globalOutbound`, and a real `git clone` lands in the workspace). Use plain `npm run deploy` only when you intentionally want to skip the probe (e.g. you're about to run it manually with extra args). +The session's clone *is* the workspace. **No worktrees** — they have no meaning here. -Workers Builds CI is disabled because the `"experimental"` compat flag (required by `@cloudflare/think` and the Agents SDK `subAgent()` facet API) blocks non-local deploys by design — see [issue #46](https://github.com/jonnyparris/dodo/issues/46). When Think graduates out of experimental, this can be re-enabled. +Git is split: +- **Top-level tools:** `git_status`, `git_add`, `git_commit`, `git_diff`. +- **Inside codemode (`git.`):** `git_clone_known`, `git_clone`, `git_push`, `git_push_checked`, `git_pull`, `git_branch`, `git_checkout`, `git_log`, `git_verify_remote_branch`, `pr_create`. -## Git Discipline — Pick the Workflow That Matches Your Runtime +Workflow: -**Never commit directly to `main`.** All changes go through feature branches. The exact mechanics differ depending on where you're running. +1. After cloning, branch off `main` inside codemode: `await git.git_branch({ name: "feat/x" })` → `await git.git_checkout({ branch: "feat/x" })`. +2. Edit. Use top-level `git_status` / `git_add` / `git_commit`. +3. Push with `git.git_push_checked` (explicit branch ref, never `main`). +4. **Open the draft PR with `git.pr_create` before replying to the user.** Auto-detects GitHub vs GitLab, fills title/body from the latest commit, returns the URL. Quote that URL. If `pr_create` fails (e.g. missing per-user token), fall back to a compare URL and say what's missing — don't leave the user with a pushed branch and no PR. +5. Branch naming: `fix/sse-serialization`, `feat/per-user-auth`, `docs/update-readme`, `chore/`. -### If you're a Dodo session (sandboxed clone in a Durable Object) +### Local via OpenCode CLI in `~/dev/dodo` -This is the case when the agent reading this file is running inside Dodo itself — the workspace is an ephemeral clone in a session DO, you have git tools (not a shell), and there is no `~/dev/dodo` parent checkout to share with. +Concurrent agents share `~/dev/dodo`. Use worktrees: -**Do not use `git worktree`.** It has no meaning here — there's nothing to share with, no concurrent local agents to collide with, and no `..` parent dir. The session's clone *is* the workspace. +1. `git worktree add ../dodo- -b ` +2. Work in the worktree, not the main checkout. +3. Commit, push, open PR. Don't merge your own PRs. +4. Clean up: `git worktree remove ../dodo- && git branch -d `. -Git is split across two surfaces: -- **Top-level tools (call directly):** `git_status`, `git_add`, `git_commit`, `git_diff`. The hot path. -- **Inside codemode (call as `git.`):** `git_clone_known`, `git_clone`, `git_push`, `git_push_checked`, `git_pull`, `git_branch`, `git_checkout`, `git_log`, `git_verify_remote_branch`, `pr_create`. Wrap them in a `codemode({ code: ... })` JS block. +Without worktrees, concurrent agents create divergent histories on `main` and ugly merge commits. Worktrees in the Dodo-session case waste turns solving a problem that doesn't exist. -Workflow: +## File Map (top level) -1. After cloning, create a branch directly inside codemode: `await git.git_branch({ name: "feat/x" })` then `await git.git_checkout({ branch: "feat/x" })`. Branch off `main`. -2. Make changes. Use top-level `git_status` / `git_add` / `git_commit` for the inner loop. -3. Push with `git.git_push_checked` from inside codemode — pass an explicit branch ref, never push to `main`. -4. **Open the draft PR with `git.pr_create` before replying to the user.** It auto-detects GitHub vs GitLab, auto-fills title and body from your latest commit, and returns the PR/MR URL. Quote that URL in your reply. If `git.pr_create` fails (e.g. missing per-user token), fall back to a compare URL and tell the user what's missing — but don't just leave them with a pushed branch and no PR. -5. Branch naming: `fix/sse-serialization`, `feat/per-user-auth`, `docs/update-readme`, `chore/`. +See `docs/file-map.md` for the full list. Entry points: -### If you're running locally via OpenCode CLI in `~/dev/dodo` - -This is the case when the agent reading this file is running on a developer laptop, has shell access, and shares `~/dev/dodo` with potentially-concurrent agents. Use the worktree workflow: - -1. **Create a worktree branch** before making any changes: - ```bash - git worktree add ../dodo- -b - ``` -2. **Work in the worktree directory**, not in the main checkout. -3. **Commit and push** the branch, then open a PR on GitHub. -4. **Do not merge your own PRs** — let the repo owner review and merge. -5. **Clean up** the worktree after the PR is merged: - ```bash - git worktree remove ../dodo- - git branch -d - ``` - -### Why the split - -Without worktrees in the local case, concurrent agents create divergent histories on `main` that require merge commits and conflict resolution. This has already caused confusing commit graphs and near-loss of work. - -In the Dodo-session case, worktrees solve a problem that doesn't exist — there's no shared checkout. Trying to use them wastes turns and confuses the agent (it tried, the tool didn't exist, then it improvised badly). - -## File Map - -- `src/index.ts` — Worker router (Hono), all HTTP routes, auth middleware, session fork, admin routes -- `src/coding-agent.ts` — per-session agent DO (extends Think, chat via Think.chat(), fibers, workspace, git, cron, prompts, snapshots, SSE). **Contains the system prompt.** -- `src/think-adapter.ts` — Think integration boundary: re-exports, types (DodoConfig, MessageMetadata, SnapshotV2), adapter functions -- `src/agentic.ts` — LLM provider construction (buildProvider), tool composition (buildToolsForThink), git tools -- `src/user-control.ts` — per-user DO (config, sessions, memory, tasks, skills, key envelope, encrypted secrets, fork snapshots) -- `src/skill-registry.ts` — Claude/OpenCode-compatible SKILL.md loader (parser, manifest renderer, workspace scanner, R2 asset helpers) -- `src/builtin-skills.ts` — built-in SKILL.md content shipped with Dodo -- `src/shared-index.ts` — global singleton DO (user allowlist, host allowlist, models cache, session shares/permissions) -- `src/executor.ts` — DynamicWorkerExecutor wrapper for sandboxed code execution (direct API route) -- `src/git.ts` — git helpers via @cloudflare/shell, multi-host token injection (GitHub + GitLab) -- `src/mcp.ts` — MCP server exposing all Dodo capabilities as tools -- `src/crypto.ts` — hybrid envelope encryption (PBKDF2 + HKDF + AES-GCM) for per-user secrets -- `src/auth.ts` — Cloudflare Access JWT verification, user allowlist check, admin guard -- `src/notify.ts` — push notifications via ntfy.sh (per-user topic from encrypted secrets) -- `src/outbound.ts` — AllowlistOutbound WorkerEntrypoint for gated sandbox fetch -- `src/presence.ts` — WebSocket presence tracking -- `src/sql-helpers.ts` — lightweight SQLite query helpers -- `src/health-check.ts` — health endpoint handler -- `src/logger.ts` — structured logging helpers -- `src/mcp-catalog.ts` — curated catalog of recommended MCP servers -- `src/mcp-codemode.ts` — code-mode MCP endpoint (2 tools, minimal context) -- `src/mcp-gatekeeper.ts` — MCP server auth and rate limiting -- `src/notify.ts` — push notifications via ntfy.sh -- `src/onboarding.ts` — guided passkey and secrets setup -- `src/rate-limit.ts` — per-user rate limiting -- `src/repos.ts` — known repository registry for orchestration -- `src/rpc-api.ts` — JSON-RPC API surface -- `src/rpc-transport.ts` — JSON-RPC transport layer -- `src/share.ts` — session sharing (tokens, permissions, cookies) -- `src/types.ts` — shared TypeScript types -- `test/dodo.test.ts` — integration tests via vitest-pool-workers -- `public/index.html` — three-panel web UI (mobile-responsive) -- `public/docs.html` — architecture documentation page -- `public/howto.html` — task-oriented how-to guides +- `src/index.ts` — Worker router, HTTP routes, auth, session fork +- `src/coding-agent.ts` — per-session DO. **Contains the system prompt.** +- `src/think-adapter.ts` — Think integration boundary +- `src/agentic.ts` — provider + tool composition +- `src/user-control.ts` — per-user DO +- `src/shared-index.ts` — global singleton DO +- `src/mcp.ts` — MCP server exposing Dodo capabilities +- `src/executor.ts` — DynamicWorkerExecutor for sandboxed code ## Architecture @@ -146,104 +103,10 @@ Worker (Hono router + CF Access auth) ## Key Invariants -1. One Think session per Dodo DO — never more -2. All Think imports through `src/think-adapter.ts` -3. No `cf_agent_chat_*` WebSocket messages trigger Think chat -4. Fiber methods use `stashFiber()` checkpoints — never assume resume-mid-execution -5. Snapshot import handles both v1 and v2 -6. Git auth flows through Dodo's `resolveRemoteToken()` -7. SSE/WS event protocol unchanged from client perspective - ---- - -## System Prompt Design - -The system prompt lives in `src/coding-agent.ts` as `const SYSTEM_PROMPT`. It's a static string — not dynamically assembled from config or user preferences. The `CodingAgent` class returns it via `getSystemPrompt()`. - -### Design principles - -**Teach the tools, not the model.** The prompt documents what tools exist and when to use each one. It doesn't teach the model how to code — that's what the model already knows. The prompt bridges the gap between the model's general capabilities and Dodo's specific tool surface. - -**Match reality.** Every tool mentioned in the prompt must actually exist. Every constraint mentioned (30s timeout, 10-step limit, sandboxed network) must be accurate. The prompt is a contract with the model. - -**Concise over exhaustive.** A Sonnet-class model doesn't need 1500 lines of instruction. Cover identity, tool surface, key behaviors, safety rules, and limits. Trust the model for everything else. - -**No sycophancy instructions.** The model should be direct and technically accurate. No "Great question!" or "I'd be happy to help." Focus on the work. - -### Sections - -| Section | Purpose | -|---------|---------| -| Identity | "You are Dodo" — establishes the agent's name and platform | -| Tone and style | Concise, markdown, no emojis, prefer edits over new files | -| Doing tasks | Todo discipline: explicit lists of task shapes that always need todos (cloned-repo work, multi-file edits, review/audit/investigate tasks) vs. shapes that can skip them; post-compaction `todo_list` re-grounding hint; when to delegate with `task` | -| Workspace tools | Documents read_file, write_file, search_files, replace_in_file | -| Code execution | Documents codemode: sandboxed JS, fetch with auto-auth, 30s timeout | -| Git | Documents all git tools, auto-auth for GitHub/GitLab | -| Git safety | Stage specific files, clear commit messages, no force-push | -| Working with errors | State what failed, fix it, move on | -| Limits | 10-step cap, ephemeral workspace, no shell | - -### Changing the prompt - -When modifying the system prompt: - -1. Keep it factual. If you add a section about a tool, verify the tool exists in `buildToolsForThink()`. -2. Test with real prompts. The prompt shapes every interaction — small wording changes can have outsized effects on behavior. -3. Don't duplicate tool descriptions. The AI SDK sends tool schemas automatically. The prompt should explain *when* and *why* to use tools, not re-document their parameters. -4. The max steps limit (10) is set in `getMaxSteps()` on the `CodingAgent` class. If you change it, update the prompt too. - -### Tool surface - -These tools are available to the agent at runtime (built in `src/agentic.ts`): - -**Workspace tools** (from `@cloudflare/think/tools/workspace`): -- `read_file` — read file contents -- `write_file` — create or overwrite a file -- `search_files` — glob + content search -- `replace_in_file` — find-and-replace within a file - -**Git tools** (built in `buildGitTools()`): -- **Top-level (hot path):** `git_status`, `git_add`, `git_commit`, `git_diff` -- **Inside codemode only (call as `git.`):** `git_clone_known`, `git_clone`, `git_push`, `git_push_checked`, `git_pull`, `git_branch`, `git_checkout`, `git_log`, `git_verify_remote_branch`, `pr_create` - -The lower-frequency git tools are reachable via codemode's `git` provider namespace rather than as individual top-level tools. This saves roughly 1k tokens of tool-schema budget per turn without losing any capability — see `buildTools()` in `src/agentic.ts` for the split. - -**Code execution** (from `@cloudflare/think/tools/execute`): -- `codemode` — sandboxed JS execution with workspace filesystem and git access, gated outbound fetch - -**Typecheck** (from `src/typecheck.ts`): -- `typecheck` — runs `tsc --noEmit` against the workspace inside the CodingAgent DO. Bundles `typescript` and every `lib.*.d.ts` into the Worker so the check happens without a subprocess. Honours user `tsconfig.json`; refuses oversized projects (> 50 .ts/.tsx files or > 5 MB) with a structured `skipped` payload. Pass `extraStrict: true` to layer `noUnusedLocals` + `noUnusedParameters` + `noImplicitReturns` + `noFallthroughCasesInSwitch` on top of the user's tsconfig — a cheap stand-in for a real linter at zero extra bundle cost. Lib map (`src/typecheck-libs.generated.ts`) is checked in and regenerated by `scripts/generate-typecheck-libs.mjs` during `npm run build`. Manual validation: `npm run test:typecheck-smoke`. - -**Skill loader** (`src/skill-registry.ts`): -- `skill` — load a SKILL.md body on demand. The system prompt's `` block lists name + description per skill; this tool returns the full body when the model picks one. Two-stage progressive disclosure mirrors Claude Code / OpenCode. - -The hot-path tools (workspace primitives, the four top-level git tools, `codemode`, the subagent tools, and `skill`) are top-level. The rest of the git surface is exposed only through codemode's provider namespaces. - ---- - -## Skills - -Dodo supports SKILL.md files compatible with both Claude Code and OpenCode. Three sources merged into one deduplicated list (precedence: personal > workspace > builtin): - -1. **Personal** — per-user, stored in UserControl DO SQLite. Created via the `skill_write` MCP tool or `POST /api/skills`. Bundled assets live in R2 under `skills/{userId}/{skillName}/...`. -2. **Workspace** — scanned from the cloned repo's `.dodo/skills/`, `.claude/skills/`, `.agents/skills/`, `.opencode/skill/`, `.opencode/skills/` directories. Read-only — promote to personal to edit. -3. **Builtin** — shipped with Dodo via `src/builtin-skills.ts`. - -### Loading model - -Two-stage progressive disclosure (matches Claude Code / OpenCode): - -- **Session start:** `getSystemPrompt()` injects `` with name + description per enabled skill (~150 tokens each, capped at 4 KB total). -- **On demand:** the `skill` tool returns the full SKILL.md body and a sampled list of bundled file paths. Bundled files are NOT auto-loaded — the model uses `read` to fetch. - -### MCP tools - -- `skill_list` — list personal skills -- `skill_read` — get full body of a personal skill -- `skill_write` — create/update a personal skill -- `skill_enable` — toggle enabled flag -- `skill_delete` — remove a personal skill -- `skill_import_url` — fetch a SKILL.md from a URL and store it as personal - -Workspace and built-in skills are visible from inside the chat (via the `skill` tool) but cannot be modified through the MCP CRUD surface. To edit a workspace skill, copy its body into a personal skill via `skill_write`. +1. One Think session per Dodo DO — never more. +2. All Think imports through `src/think-adapter.ts`. +3. No `cf_agent_chat_*` WebSocket messages trigger Think chat. +4. Fiber methods use `stashFiber()` checkpoints — never assume resume-mid-execution. +5. Snapshot import handles both v1 and v2. +6. Git auth flows through Dodo's `resolveRemoteToken()`. +7. SSE/WS event protocol unchanged from the client's perspective. diff --git a/docs/deploying.md b/docs/deploying.md new file mode 100644 index 0000000..779b789 --- /dev/null +++ b/docs/deploying.md @@ -0,0 +1,15 @@ +# Deploying + +**Deploys are manual.** Run `npm run deploy:safe` locally after merging to `main` — it builds, deploys, then runs `scripts/post-deploy-smoke.sh` against the deployed Worker. The probe asserts: + +- `/health` and `/version.json` (deployed commit equals HEAD) +- codemode `execute` round-trips through `globalOutbound` +- a real `git clone` lands in the workspace + +Use plain `npm run deploy` only when you intentionally want to skip the probe (e.g. you're about to run it manually with extra args). + +## Why CI deploys are disabled + +Workers Builds CI is off because the `"experimental"` compat flag (required by `@cloudflare/think` and the Agents SDK `subAgent()` facet API) blocks non-local deploys by design — see [issue #46](https://github.com/jonnyparris/dodo/issues/46). When Think graduates out of experimental, this can be re-enabled. + +PR validation runs via `.github/workflows/dodo-verify.yml` (typecheck + test) instead of a deploy. diff --git a/docs/file-map.md b/docs/file-map.md new file mode 100644 index 0000000..2372a18 --- /dev/null +++ b/docs/file-map.md @@ -0,0 +1,57 @@ +# File Map + +Source layout. `src/index.ts` is the entry point; everything else hangs off the DOs it routes to. + +## Entry & routing + +- `src/index.ts` — Worker router (Hono), all HTTP routes, auth middleware, session fork, admin routes +- `src/auth.ts` — Cloudflare Access JWT verification, user allowlist check, admin guard +- `src/rpc-api.ts` / `src/rpc-transport.ts` — JSON-RPC API surface and transport +- `src/share.ts` — session sharing (tokens, permissions, cookies) +- `src/onboarding.ts` — guided passkey and secrets setup + +## Agent core + +- `src/coding-agent.ts` — per-session agent DO (extends Think, chat via Think.chat(), fibers, workspace, git, cron, prompts, snapshots, SSE). **Contains the system prompt.** +- `src/think-adapter.ts` — Think integration boundary: re-exports, types (DodoConfig, MessageMetadata, SnapshotV2), adapter functions +- `src/agentic.ts` — LLM provider construction (buildProvider), tool composition (buildToolsForThink), git tools +- `src/executor.ts` — DynamicWorkerExecutor wrapper for sandboxed code execution (direct API route) +- `src/typecheck.ts` — in-isolate `tsc --noEmit` tool +- `src/presence.ts` — WebSocket presence tracking + +## Per-user / global state + +- `src/user-control.ts` — per-user DO (config, sessions, memory, tasks, skills, key envelope, encrypted secrets, fork snapshots) +- `src/shared-index.ts` — global singleton DO (user allowlist, host allowlist, models cache, session shares/permissions) +- `src/crypto.ts` — hybrid envelope encryption (PBKDF2 + HKDF + AES-GCM) for per-user secrets +- `src/rate-limit.ts` — per-user rate limiting + +## Skills + +- `src/skill-registry.ts` — Claude/OpenCode-compatible SKILL.md loader (parser, manifest renderer, workspace scanner, R2 asset helpers) +- `src/builtin-skills.ts` — built-in SKILL.md content shipped with Dodo + +## Git, MCP, outbound + +- `src/git.ts` — git helpers via @cloudflare/shell, multi-host token injection (GitHub + GitLab) +- `src/repos.ts` — known repository registry for orchestration +- `src/mcp.ts` — MCP server exposing all Dodo capabilities as tools +- `src/mcp-codemode.ts` — code-mode MCP endpoint (2 tools, minimal context) +- `src/mcp-gatekeeper.ts` — MCP server auth and rate limiting +- `src/mcp-catalog.ts` — curated catalog of recommended MCP servers +- `src/outbound.ts` — AllowlistOutbound WorkerEntrypoint for gated sandbox fetch +- `src/notify.ts` — push notifications via ntfy.sh (per-user topic from encrypted secrets) + +## Plumbing + +- `src/sql-helpers.ts` — lightweight SQLite query helpers +- `src/health-check.ts` — health endpoint handler +- `src/logger.ts` — structured logging helpers +- `src/types.ts` — shared TypeScript types + +## Tests & UI + +- `test/dodo.test.ts` — integration tests via vitest-pool-workers +- `public/index.html` — three-panel web UI (mobile-responsive) +- `public/docs.html` — architecture documentation page +- `public/howto.html` — task-oriented how-to guides diff --git a/docs/linting.md b/docs/linting.md new file mode 100644 index 0000000..9e05bc3 --- /dev/null +++ b/docs/linting.md @@ -0,0 +1,10 @@ +# Linting + +Two layers: + +1. **In-isolate** — the `typecheck` tool with `extraStrict: true`. Catches unused locals/parameters, missing returns, and switch fall-through via TypeScript's own diagnostics. No extra bundle cost, sub-second feedback inside a session. +2. **External (CI)** — Biome runs in `.github/workflows/dodo-verify.yml` for every PR and dispatched verify run. Catches the wider set: unused imports, suspicious patterns, double-equals, etc. Configured in `biome.jsonc` (correctness + suspicious rules only — formatting and style are intentionally off). + +**Why two layers:** Biome's wasm bundle is ~8 MB gzipped, which would push Dodo past the Workers 10 MB compressed script limit if loaded in-isolate. The in-isolate check is the agent's fast feedback loop; the CI check is the thorough gate before merge. + +Run `npm run lint` locally before pushing if you want the same signal CI will give you. `npm run lint:fix` applies safe auto-fixes. diff --git a/docs/skills.md b/docs/skills.md new file mode 100644 index 0000000..0fb9e57 --- /dev/null +++ b/docs/skills.md @@ -0,0 +1,25 @@ +# Skills + +Dodo supports SKILL.md files compatible with both Claude Code and OpenCode. Three sources merged into one deduplicated list (precedence: personal > workspace > builtin): + +1. **Personal** — per-user, stored in UserControl DO SQLite. Created via the `skill_write` MCP tool or `POST /api/skills`. Bundled assets live in R2 under `skills/{userId}/{skillName}/...`. +2. **Workspace** — scanned from the cloned repo's `.dodo/skills/`, `.claude/skills/`, `.agents/skills/`, `.opencode/skill/`, `.opencode/skills/` directories. Read-only — promote to personal to edit. +3. **Builtin** — shipped with Dodo via `src/builtin-skills.ts`. + +## Loading model + +Two-stage progressive disclosure (matches Claude Code / OpenCode): + +- **Session start:** `getSystemPrompt()` injects `` with name + description per enabled skill (~150 tokens each, capped at 4 KB total). +- **On demand:** the `skill` tool returns the full SKILL.md body and a sampled list of bundled file paths. Bundled files are NOT auto-loaded — the model uses `read` to fetch. + +## MCP tools + +- `skill_list` — list personal skills +- `skill_read` — get full body of a personal skill +- `skill_write` — create/update a personal skill +- `skill_enable` — toggle enabled flag +- `skill_delete` — remove a personal skill +- `skill_import_url` — fetch a SKILL.md from a URL and store it as personal + +Workspace and built-in skills are visible from inside the chat (via the `skill` tool) but cannot be modified through the MCP CRUD surface. To edit a workspace skill, copy its body into a personal skill via `skill_write`. diff --git a/docs/system-prompt.md b/docs/system-prompt.md new file mode 100644 index 0000000..2df71d9 --- /dev/null +++ b/docs/system-prompt.md @@ -0,0 +1,63 @@ +# System Prompt Design + +The system prompt lives in `src/coding-agent.ts` as `const SYSTEM_PROMPT`. It's a static string — not dynamically assembled from config or user preferences. The `CodingAgent` class returns it via `getSystemPrompt()`. + +## Design principles + +**Teach the tools, not the model.** The prompt documents what tools exist and when to use each one. It doesn't teach the model how to code — that's what the model already knows. The prompt bridges the gap between the model's general capabilities and Dodo's specific tool surface. + +**Match reality.** Every tool mentioned in the prompt must actually exist. Every constraint mentioned (30s timeout, 10-step limit, sandboxed network) must be accurate. The prompt is a contract with the model. + +**Concise over exhaustive.** A Sonnet-class model doesn't need 1500 lines of instruction. Cover identity, tool surface, key behaviors, safety rules, and limits. Trust the model for everything else. + +**No sycophancy instructions.** The model should be direct and technically accurate. No "Great question!" or "I'd be happy to help." Focus on the work. + +## Sections + +| Section | Purpose | +|---------|---------| +| Identity | "You are Dodo" — establishes the agent's name and platform | +| Tone and style | Concise, markdown, no emojis, prefer edits over new files | +| Doing tasks | Todo discipline: explicit lists of task shapes that always need todos (cloned-repo work, multi-file edits, review/audit/investigate tasks) vs. shapes that can skip them; post-compaction `todo_list` re-grounding hint; when to delegate with `task` | +| Workspace tools | Documents read_file, write_file, search_files, replace_in_file | +| Code execution | Documents codemode: sandboxed JS, fetch with auto-auth, 30s timeout | +| Git | Documents all git tools, auto-auth for GitHub/GitLab | +| Git safety | Stage specific files, clear commit messages, no force-push | +| Working with errors | State what failed, fix it, move on | +| Limits | 10-step cap, ephemeral workspace, no shell | + +## Changing the prompt + +When modifying the system prompt: + +1. Keep it factual. If you add a section about a tool, verify the tool exists in `buildToolsForThink()`. +2. Test with real prompts. The prompt shapes every interaction — small wording changes can have outsized effects on behavior. +3. Don't duplicate tool descriptions. The AI SDK sends tool schemas automatically. The prompt should explain *when* and *why* to use tools, not re-document their parameters. +4. The max steps limit (10) is set in `getMaxSteps()` on the `CodingAgent` class. If you change it, update the prompt too. + +## Tool surface + +These tools are available to the agent at runtime (built in `src/agentic.ts`): + +**Workspace tools** (from `@cloudflare/think/tools/workspace`): +- `read_file` — read file contents +- `write_file` — create or overwrite a file +- `search_files` — glob + content search +- `replace_in_file` — find-and-replace within a file + +**Git tools** (built in `buildGitTools()`): +- **Top-level (hot path):** `git_status`, `git_add`, `git_commit`, `git_diff` +- **Inside codemode only (call as `git.`):** `git_clone_known`, `git_clone`, `git_push`, `git_push_checked`, `git_pull`, `git_branch`, `git_checkout`, `git_log`, `git_verify_remote_branch`, `pr_create` + +The lower-frequency git tools are reachable via codemode's `git` provider namespace rather than as individual top-level tools. This saves roughly 1k tokens of tool-schema budget per turn without losing any capability — see `buildTools()` in `src/agentic.ts` for the split. + +**Code execution** (from `@cloudflare/think/tools/execute`): +- `codemode` — sandboxed JS execution with workspace filesystem and git access, gated outbound fetch + +**Typecheck** (from `src/typecheck.ts`): +- `typecheck` — runs `tsc --noEmit` against the workspace inside the CodingAgent DO. Bundles `typescript` and every `lib.*.d.ts` into the Worker so the check happens without a subprocess. Honours user `tsconfig.json`; refuses oversized projects (> 50 .ts/.tsx files or > 5 MB) with a structured `skipped` payload. Pass `extraStrict: true` to layer `noUnusedLocals` + `noUnusedParameters` + `noImplicitReturns` + `noFallthroughCasesInSwitch` on top of the user's tsconfig — a cheap stand-in for a real linter at zero extra bundle cost. Lib map (`src/typecheck-libs.generated.ts`) is checked in and regenerated by `scripts/generate-typecheck-libs.mjs` during `npm run build`. Manual validation: `npm run test:typecheck-smoke`. + +**Skill loader** (`src/skill-registry.ts`): +- `skill` — load a SKILL.md body on demand. The system prompt's `` block lists name + description per skill; this tool returns the full body when the model picks one. Two-stage progressive disclosure mirrors Claude Code / OpenCode. + +The hot-path tools (workspace primitives, the four top-level git tools, `codemode`, the subagent tools, and `skill`) are top-level. The rest of the git surface is exposed only through codemode's provider namespaces.