{"owner":"coleam00","repo":"Archon","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","AGENTS.md"],"skills":{"CLAUDE.md":"Agent rules: read @AGENTS.md\n","AGENTS.md":"## Project Overview\n\n**Remote Agentic Coding Platform**: Control AI coding assistants (Claude Code SDK, Codex SDK) remotely from Slack, Telegram, and GitHub. Built with **Bun + TypeScript + SQLite/PostgreSQL**, single-developer tool for AI-assisted development practitioners. Architecture prioritizes simplicity, flexibility, and user control.\n\n## Core Principles\n\n**Single-Developer Tool**\n\n- No multi-tenant complexity\n\n**Platform Agnostic**\n\n- Unified conversation interface across Slack/Telegram/GitHub/cli/web\n- Platform adapters implement `IPlatformAdapter`\n- Stream/batch AI responses in real-time to all platforms\n\n**Type Safety (CRITICAL)**\n\n- Strict TypeScript configuration enforced\n- All functions must have complete type annotations\n- No `any` types without explicit justification\n- Interfaces for all major abstractions\n\n**Zod Schema Conventions**\n\n- Schema naming: camelCase, descriptive suffix (e.g., `workflowRunSchema`, `errorSchema`)\n- Type derivation: always use `z.infer<typeof schema>` — never write parallel hand-crafted interfaces\n- Import `z` from `@hono/zod-openapi` (not from `zod` directly). Exception: `@archon/providers` imports `z` from `zod` directly in `claude/native-tools.ts` — it only builds the Zod shape the Claude SDK's `tool()` expects (never an OpenAPI schema), and being an SDK-deps-only leaf package it must not pull in Hono.\n- Record schemas: always pass an explicit key type — `z.record(z.string(), valueSchema)` — zod v4 dropped the single-arg `z.record(valueSchema)` form\n- All new/modified API routes must use `registerOpenApiRoute(createRoute({...}), handler)` — the local wrapper handles the TypedResponse bypass. Two narrow exceptions exist: (1) routes that serve raw non-JSON content (e.g. `/api/artifacts/:runId/*` returns `text/markdown`/`text/plain`) AND use wildcard path params that OpenAPI 3.0 can't represent, use `app.get(...)` with an explanatory comment; (2) multipart-or-JSON routes (e.g. `/api/conversations/:id/message`, `/api/workflows/:name/run`) register through `registerOpenApiRoute` but drop `request.body` from the route config so Zod doesn't validate multipart payloads against a JSON schema — the handler parses both content types manually.\n- Core row schemas live in `packages/core/src/schemas/` — one file per data shape (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run); `index.ts` re-exports all\n- Route schemas live in `packages/server/src/routes/schemas/` — one file per domain\n- Engine schemas live in `packages/workflows/src/schemas/` — one file per concern (dag-node, workflow, workflow-run, retry, loop, hooks); `index.ts` re-exports all\n- Engine schema naming: camelCase (e.g., `dagNodeSchema`, `workflowBaseSchema`, `nodeOutputSchema`)\n- `TRIGGER_RULES` and `WORKFLOW_HOOK_EVENTS` are derived from schema `.options` — never duplicate as a plain array (exception: `@archon/web` must define a local constant since `api.generated.d.ts` is type-only and cannot export runtime values)\n- `loader.ts` uses `dagNodeSchema.safeParse()` for node validation; graph-level checks (cycles, deps, `$nodeId.output` refs) remain as imperative code in `validateDagStructure()`\n\n**Git Workflow and Releases**\n\n- `main` is the release branch. Never commit directly to `main`.\n- `dev` is the working branch. All feature work branches off `dev` and merges back into `dev`.\n- All PRs must use the template at `.github/pull_request_template.md` — fill in every section. When opening a PR via `gh pr create`, copy the template into the body explicitly; GitHub only auto-applies it through the web UI.\n- Link the issue with `Closes #<number>` (or `Fixes` / `Resolves`) in the PR description so it auto-closes on merge.\n- To release, use the `/release` skill. It compares `dev` to `main`, generates changelog entries, bumps the version, and creates a PR to merge `dev` into `main`.\n- Releases follow Semantic Versioning: `/release` (patch), `/release minor`, `/release major`.\n- Changelog lives in `CHANGELOG.md` and follows Keep a Changelog format.\n- Version is the single `version` field in the root `package.json`.\n\n**Git as First-Class Citizen**\n\n- Let git handle what git does best (conflicts, uncommitted changes, branch management)\n- Surface git errors to users for actionable issues (conflicts, uncommitted changes)\n- Handle expected failure cases gracefully (missing directories during cleanup)\n- Trust git's natural guardrails (e.g., refuse to remove worktree with uncommitted changes)\n- Use `@archon/git` functions for git operations; use `execFileAsync` (not `exec`) when calling git directly\n- Worktrees enable parallel development per conversation without branch conflicts\n- Workspaces automatically sync with origin before worktree creation (ensures latest code)\n- **NEVER run `git clean -fd`** - it permanently deletes untracked files (use `git checkout .` instead)\n\n## Engineering Principles\n\nThese are implementation constraints, not slogans. Apply them by default.\n\n**KISS — Keep It Simple, Stupid**\n\n- Prefer straightforward control flow over clever meta-programming\n- Prefer explicit branches and typed interfaces over hidden dynamic behavior\n- Keep error paths obvious and localized\n\n**YAGNI — You Aren't Gonna Need It**\n\n- Do not add config keys, interface methods, feature flags, or workflow branches without a concrete accepted use case\n- Do not introduce speculative abstractions without at least one current caller\n- Keep unsupported paths explicit (error out) rather than adding partial fake support\n\n**DRY + Rule of Three**\n\n- Duplicate small, local logic when it preserves clarity\n- Extract shared utilities only after the same pattern appears at least three times and has stabilized\n- When extracting, preserve module boundaries and avoid hidden coupling\n\n**SRP + ISP — Single Responsibility + Interface Segregation**\n\n- Keep each module and package focused on one concern\n- Extend behavior by implementing existing narrow interfaces (`IPlatformAdapter`, `IAgentProvider`, `IDatabase`, `IWorkflowStore`) whenever possible\n- Avoid fat interfaces and \"god modules\" that mix policy, transport, and storage\n- Do not add unrelated methods to an existing interface — define a new one\n\n**Fail Fast + Explicit Errors** — Silent fallback in agent runtimes can create unsafe or costly behavior\n\n- Prefer throwing early with a clear error for unsupported or unsafe states — never silently swallow errors\n- Never silently broaden permissions or capabilities\n- Document fallback behavior with a comment when a fallback is intentional and safe; otherwise throw\n\n**No Autonomous Lifecycle Mutation Across Process Boundaries**\n\n- When a process cannot reliably distinguish \"actively running elsewhere\" from \"orphaned by a crash\" — typically because the work was started by a different process or input source (CLI, adapter, webhook, web UI, cron) — it must not autonomously mark that work as failed/cancelled/abandoned based on a timer or staleness guess.\n- Surface the ambiguous state to the user and provide a one-click action.\n- Heuristics for _recoverable_ operations (retry backoff, subprocess timeouts, hygiene cleanup of terminal-status data) remain appropriate; the rule is about destructive mutation of _non-terminal_ state owned by an unknowable other party.\n- Reference: #1216 and the CLI orphan-cleanup precedent at `packages/cli/src/cli.ts:256-258`.\n\n**Determinism + Reproducibility**\n\n- Prefer reproducible commands and locked dependency behavior in CI-sensitive paths\n- Keep tests deterministic — no flaky timing or network dependence without guardrails\n- Ensure local validation commands (`bun run validate`) map directly to CI expectations\n\n**Reversibility + Rollback-First Thinking**\n\n- Keep changes easy to revert: small scope, clear blast radius\n- For risky changes, define the rollback path before merging\n- Avoid mixed mega-patches that block safe rollback\n\n## Essential Commands\n\n### Development\n\n```bash\n# Start server + Web UI together (hot reload for both)\nbun run dev\n\n# Or start individually\nbun run dev:server  # Backend only (port 3090)\nbun run dev:web     # Frontend only (port 5173)\n```\n\nRegenerating frontend API types (requires server to be running at port 3090):\n\n```bash\nbun run dev:server  # must be running first\nbun --filter @archon/web generate:types\n```\n\nOptional: Use PostgreSQL instead of SQLite by setting `DATABASE_URL` in `.env`:\n\n```bash\ndocker-compose --profile with-db up -d postgres\n# Set DATABASE_URL=postgresql://postgres:postgres@localhost:5432/remote_coding_agent in .env\n```\n\n### Testing\n\n```bash\nbun run test                # Run all tests (per-package, isolated processes)\nbun test --watch            # Watch mode (single package)\nbun test packages/core/src/handlers/command-handler.test.ts  # Single file\n```\n\n**Test isolation (mock.module pollution):** Bun's `mock.module()` permanently replaces modules in the process-wide cache — `mock.restore()` does NOT undo it ([oven-sh/bun#7823](https://github.com/oven-sh/bun/issues/7823)). To prevent cross-file pollution, packages that have conflicting `mock.module()` calls split their tests into separate `bun test` invocations: `@archon/core` (20 batches), `@archon/workflows` (5), `@archon/adapters` (6), `@archon/isolation` (3). See each package's `package.json` for the exact splits.\n\n**Do NOT run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures. Always use `bun run test` (which uses `bun --filter '*' test` for per-package isolation).\n\n### Type Checking & Linting\n\n```bash\nbun run type-check\nbun run lint\nbun run lint:fix\nbun run format\nbun run format:check\n```\n\n### Pre-PR Validation\n\n**Always run before creating a pull request:**\n\n```bash\nbun run validate\n```\n\nThis runs `check:bundled`, `check:bundled-skill`, `check:bundled-schema`, type-check, lint, format check, and tests. All seven must pass for CI to succeed.\n\n### ESLint Guidelines\n\n**Zero-tolerance policy**: CI enforces `--max-warnings 0`. No warnings allowed.\n\n**When to use inline disable comments** (`// eslint-disable-next-line`):\n\n- **Almost never** - fix the issue instead\n- Only acceptable when:\n  1. External SDK types are incorrect (document which SDK and why)\n  2. Intentional type assertion after validation (must include comment explaining the validation)\n\n**Never acceptable:**\n\n- Disabling `no-explicit-any` without justification\n- Disabling rules to \"make CI pass\"\n- Bulk disabling at file level (`/* eslint-disable */`)\n\n### Database\n\n**Auto-Detection (SQLite is the default — zero setup):**\n\n- **Without `DATABASE_URL`**: Uses SQLite at `~/.archon/archon.db` (auto-initialized, recommended for most users)\n- **With `DATABASE_URL` set**: Uses PostgreSQL (schema auto-applied on startup; no manual `psql` needed). The Postgres adapter runs the idempotent `migrations/000_combined.sql` inside an advisory-lock transaction on first connection, so upgrades that add tables or columns converge automatically.\n\n### CLI (Command Line)\n\nRun workflows directly from the command line without needing the server. Workflow and isolation commands require running from within a git repository (subdirectories work - resolves to repo root).\n\n```bash\n# List available workflows (requires git repo)\nbun run cli workflow list\n\n# Machine-readable JSON output\nbun run cli workflow list --json\n\n# Run a workflow\nbun run cli workflow run assist \"What does the orchestrator do?\"\n\n# Run in a specific directory\nbun run cli workflow run plan --cwd /path/to/repo \"Add dark mode\"\n\n# Default: auto-creates worktree with generated branch name (isolation by default)\nbun run cli workflow run implement \"Add auth\"\n\n# Explicit branch name for the worktree\nbun run cli workflow run implement --branch feature-auth \"Add auth\"\n\n# Opt out of isolation (run in live checkout)\nbun run cli workflow run quick-fix --no-worktree \"Fix typo\"\n\n# Run in a detached background child (returns immediately; find it via `workflow runs`)\nbun run cli workflow run implement \"Add auth\" --detach\n\n# Show active runs (running + paused)\nbun run cli workflow status\n\n# List recent runs of ALL statuses, scoped to this project's codebase (cwd)\nbun run cli workflow runs\nbun run cli workflow runs --json                 # machine-readable { runs, total, counts }\nbun run cli workflow runs --status failed --limit 50\nbun run cli workflow runs --all                  # across all projects\n\n# Show detail for one run (any status); --verbose adds per-node summary\nbun run cli workflow get <run-id>\nbun run cli workflow get <run-id> --json\n\n# Resume a failed workflow (re-runs, skipping completed nodes)\nbun run cli workflow resume <run-id>\n\n# Discard a non-terminal run\nbun run cli workflow abandon <run-id>\n\n# Most read/write subcommands accept --json for machine-readable output:\n#   list, status, runs, get, approve, reject, abandon, resume.\n# For approve/reject/resume, --json records/validates the decision and returns a\n# clean JSON line WITHOUT the inline auto-resume (drive continuation separately).\n\n# Delete old workflow run records (default: 7 days)\nbun run cli workflow cleanup\nbun run cli workflow cleanup 30  # Custom days\n\n# Clear persisted per-node AI sessions for a workflow (persist_session memory)\n# Without --scope, wipes every scope and requires --yes; --node narrows to one node\nbun run cli workflow reset-sessions <workflow-name> [--scope <key>] [--node <id>] [--yes] [--json]\n\n# Emit a workflow event (used inside workflow loop prompts)\nbun run cli workflow event emit --run-id <uuid> --type <event-type> [--data <json>]\n\n# List active worktrees/environments\nbun run cli isolation list\n\n# Clean up stale environments (default: 7 days)\nbun run cli isolation cleanup\nbun run cli isolation cleanup 14  # Custom days\n\n# Clean up environments with branches merged into main (also deletes remote branches)\nbun run cli isolation cleanup --merged\n\n# Also remove environments with closed (abandoned) PRs\nbun run cli isolation cleanup --merged --include-closed\n\n# Validate workflow definitions and their referenced resources\nbun run cli validate workflows              # All workflows\nbun run cli validate workflows my-workflow  # Single workflow\nbun run cli validate workflows my-workflow --json  # Machine-readable output\n\n# Validate command files\nbun run cli validate commands               # All commands\nbun run cli validate commands my-command    # Single command\n\n# Complete branch lifecycle (remove worktree + local/remote branches)\nbun run cli complete <branch-name>\nbun run cli complete <branch-name> --force  # Skip uncommitted-changes check\n\n# Start the web UI server (compiled binary only, downloads web UI on first run)\nbun run cli serve\nbun run cli serve --port 4000\nbun run cli serve --download-only  # Download without starting\n\n# Install the bundled Archon skill into a project\nbun run cli skill install\nbun run cli skill install /path/to/project\n\n# Verify your Archon setup (Claude binary, gh auth, DB, adapters)\nbun run cli doctor\n\n# Connect your GitHub identity via device flow (multi-user installs only:\n# App mode + TOKEN_ENCRYPTION_KEY). Identity from ARCHON_USER_ID or $USER.\nbun run cli auth github\n\n# Inspect or rotate the anonymous telemetry install UUID\nbun run cli telemetry status\nbun run cli telemetry reset\n\n# Show version\nbun run cli version\n```\n\n## Architecture\n\n### Directory Structure\n\n**Monorepo Layout (Bun Workspaces):**\n\n```\npackages/\n├── cli/                      # @archon/cli - Command-line interface\n│   └── src/\n│       ├── adapters/         # CLI adapter (stdout output)\n│       ├── commands/         # CLI command implementations\n│       └── cli.ts            # CLI entry point\n├── providers/                # @archon/providers - AI agent providers (SDK deps live here)\n│   └── src/\n│       ├── types.ts          # Contract layer (IAgentProvider, SendQueryOptions, MessageChunk — ZERO SDK deps)\n│       ├── registry.ts       # Typed provider registry (ProviderRegistration records)\n│       ├── errors.ts         # UnknownProviderError\n│       ├── claude/           # ClaudeProvider + parseClaudeConfig + MCP/hooks/skills translation\n│       ├── codex/            # CodexProvider + parseCodexConfig + binary-resolver\n│       ├── community/pi/     # PiProvider (builtIn: false) — @earendil-works/pi-coding-agent, ~20 LLM backends\n│       ├── community/opencode/ # OpenCodeProvider (builtIn: false) — @archon/opencode SDK, local embedded runtime\n│       └── index.ts          # Package exports\n├── core/                     # @archon/core - Shared business logic\n│   └── src/\n│       ├── config/           # YAML config loading\n│       ├── db/               # Database connection, queries\n│       ├── handlers/         # Command handler (slash commands)\n│       ├── orchestrator/     # AI conversation management\n│       ├── services/         # Background services (cleanup)\n│       ├── schemas/          # Zod row schemas for core data shapes (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run)\n│       ├── state/            # Session state machine\n│       ├── types/            # TypeScript types and interfaces\n│       ├── utils/            # Shared utilities\n│       ├── workflows/        # Store adapter (createWorkflowStore) bridging core DB → IWorkflowStore\n│       └── index.ts          # Package exports\n├── workflows/                # @archon/workflows - Workflow engine (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── schemas/          # Zod schemas for engine types\n│       ├── loader.ts         # YAML parsing + validation (parseWorkflow)\n│       ├── workflow-discovery.ts # Workflow filesystem discovery (discoverWorkflows, discoverWorkflowsWithConfig)\n│       ├── executor-shared.ts # Shared executor infrastructure (error classification, variable substitution)\n│       ├── router.ts         # Prompt building + invocation parsing\n│       ├── executor.ts       # Workflow execution orchestrator (executeWorkflow)\n│       ├── dag-executor.ts   # DAG-specific execution logic\n│       ├── store.ts          # IWorkflowStore interface (database abstraction)\n│       ├── deps.ts           # WorkflowDeps injection types (IWorkflowPlatform, imports from @archon/providers/types)\n│       ├── event-emitter.ts  # Workflow observability events\n│       ├── logger.ts         # JSONL file logger\n│       ├── validator.ts      # Resource validation (command files, MCP configs, skill dirs)\n│       ├── defaults/         # Bundled default commands and workflows\n│       └── utils/            # Variable substitution, tool formatting, execution utilities\n├── git/                      # @archon/git - Git operations (no @archon/core dep)\n│   └── src/\n│       ├── branch.ts         # Branch operations (checkout, merge detection, etc.)\n│       ├── exec.ts           # execFileAsync and mkdirAsync wrappers\n│       ├── repo.ts           # Repository operations (clone, sync, remote URL)\n│       ├── types.ts          # Branded types (RepoPath, BranchName, etc.)\n│       ├── worktree.ts       # Worktree operations (create, remove, list)\n│       └── index.ts          # Package exports\n├── isolation/                # @archon/isolation - Worktree isolation (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── types.ts          # Isolation types and interfaces\n│       ├── errors.ts         # Error classifiers (classifyIsolationError, IsolationBlockedError)\n│       ├── factory.ts        # Provider factory (getIsolationProvider, configureIsolation)\n│       ├── resolver.ts       # IsolationResolver (request → environment resolution)\n│       ├── store.ts          # IIsolationStore interface\n│       ├── worktree-copy.ts  # File copy utilities for worktrees\n│       ├── providers/\n│       │   └── worktree.ts   # WorktreeProvider implementation\n│       └── index.ts          # Package exports\n├── paths/                    # @archon/paths - Path resolution and logger (zero @archon/* deps)\n│   └── src/\n│       ├── archon-paths.ts   # Archon directory path utilities\n│       ├── logger.ts         # Pino logger factory\n│       └── index.ts          # Package exports\n├── adapters/                 # @archon/adapters - Platform adapters (Slack, Telegram, GitHub, Discord)\n│   └── src/\n│       ├── chat/             # Chat platform adapters (Slack, Telegram)\n│       ├── forge/            # Forge adapters (GitHub)\n│       ├── community/        # Community adapters (Discord)\n│       ├── utils/            # Shared adapter utilities (message splitting)\n│       └── index.ts          # Package exports\n├── server/                   # @archon/server - HTTP server + Web adapter\n│   └── src/\n│       ├── adapters/         # Web platform adapter (SSE streaming)\n│       ├── routes/           # API routes (REST + SSE)\n│       └── index.ts          # Hono server entry point\n└── web/                      # @archon/web - React frontend (Web UI)\n    └── src/\n        ├── components/       # React components (chat, layout, projects, ui, workflows)\n        ├── hooks/            # Custom hooks (useSSE, etc.)\n        ├── lib/              # API client, types, utilities\n        ├── stores/           # Zustand stores (workflow-store)\n        ├── routes/           # Route pages (ChatPage, WorkflowsPage, WorkflowBuilderPage, etc.)\n        ├── experiments/      # Isolated in-repo spikes; lint-guarded against\n        │   │                 # importing production web modules. Drop-in or\n        │   │                 # delete cleanly. See experiments/README.md.\n        │   └── console/      # Run-centric console UI mounted at /console\n        └── App.tsx           # Router + layout\n```\n\n**Import Patterns:**\n\n**IMPORTANT**: Always use typed imports - never use generic `import *` for the main package.\n\n```typescript\n// ✅ CORRECT: Use `import type` for type-only imports\nimport type { IPlatformAdapter, Conversation, MergedConfig } from '@archon/core';\n\n// ✅ CORRECT: Use specific named imports for values\nimport { handleMessage, ConversationLockManager, pool } from '@archon/core';\n\n// ✅ CORRECT: Namespace imports for submodules with many exports\nimport * as conversationDb from '@archon/core/db/conversations';\nimport * as git from '@archon/git';\n\n// ✅ CORRECT: Import workflow engine types/functions from direct subpaths\nimport type { WorkflowDeps } from '@archon/workflows/deps';\nimport type { IWorkflowStore } from '@archon/workflows/store';\nimport type { WorkflowDefinition } from '@archon/workflows/schemas/workflow';\nimport { executeWorkflow } from '@archon/workflows/executor';\nimport { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery';\nimport { findWorkflow } from '@archon/workflows/router';\n\n// ❌ WRONG: Never use generic import for main package\nimport * as core from '@archon/core'; // Don't do this\n\n// ❌ WRONG: In @archon/web, never import from @archon/workflows (it's a server package)\nimport type { DagNode } from '@archon/workflows/schemas/dag-node'; // Don't do this from @archon/web\n// ✅ CORRECT: Use re-exports from api.ts (derived from generated OpenAPI spec)\nimport type { DagNode, WorkflowDefinition } from '@/lib/api';\n```\n\n### Database Schema\n\n**16 Tables (all prefixed with `remote_agent_`):**\n\n1. **`codebases`** - Repository metadata and commands (JSONB)\n2. **`conversations`** - Track platform conversations with titles and soft-delete support; nullable `user_id` records first creator\n3. **`sessions`** - Track AI SDK sessions with resume capability\n4. **`isolation_environments`** - Git worktree isolation tracking; nullable `created_by_user_id` preserves first creator\n5. **`workflow_runs`** - Workflow execution tracking and state; nullable `user_id` for per-run attribution\n6. **`workflow_events`** - Step-level workflow event log (step transitions, artifacts, errors)\n7. **`messages`** - Conversation message history with tool call metadata (JSONB); nullable `user_id` (NULL for assistant rows)\n8. **`codebase_env_vars`** - Per-project env vars injected into project-scoped execution surfaces (Claude, Codex, bash/script nodes, and direct chat when codebase-scoped), managed via Web UI or `env:` in config\n9. **`users`** - Archon-internal identity (one row per human/bot); created lazily on first sight by any adapter; `role` (`'admin'`(default)`/'member'`) is the identity seam for future per-resource scoping (visibility stays open today)\n10. **`user_identities`** - Per-platform mapping (Slack U-id, Telegram chat id, Discord snowflake, GitHub login, Better Auth web user id) → `users.id`; `UNIQUE(platform, platform_user_id)`\n11. **`workflow_node_sessions`** - Per-node provider session IDs persisted across workflow re-runs (opt-in via `persist_session`); keyed by `(workflow_name, node_id, scope_key, provider)`; `scope_key` is typically the conversation UUID\n12. **`user_github_tokens`** - Per-user GitHub device-flow tokens encrypted at rest (AES-256-GCM); one row per Archon user (`UNIQUE(user_id)`), cascades on user deletion; numeric `github_user_id` anchors the commit no-reply email\n    13–16. **`remote_agent_auth_user` / `remote_agent_auth_session` / `remote_agent_auth_account` / `remote_agent_auth_verification`** - Better Auth tables for opt-in web login (**PostgreSQL only**; always created on Postgres via the idempotent schema apply, but populated only when web auth is enabled — `DATABASE_URL` + `BETTER_AUTH_SECRET`). Owned and shaped by Better Auth (text ids, camelCase columns); Archon never queries them directly — a session maps to the canonical `users` row via `user_identities('web', <betterAuthUserId>)`\n\n**Key Patterns:**\n\n- Conversation ID format: Platform-specific (`thread_ts`, `chat_id`, `user/repo#123`)\n- One active session per conversation\n- Codebase commands stored in filesystem, paths in `codebases.commands` JSONB\n\n**Session Transitions:**\n\n- Sessions are immutable - transitions create new linked sessions\n- Each transition has explicit `TransitionTrigger` reason (first-message, plan-to-execute, reset-requested, etc.)\n- Audit trail: `parent_session_id` links to previous session, `transition_reason` records why\n- Only plan→execute creates new session immediately; other triggers deactivate current session\n\n### Architecture Layers\n\n**Package Split:**\n\n- **@archon/paths**: Path resolution utilities, Pino logger factory, web dist cache path (`getWebDistDir`), CWD env stripper (`stripCwdEnv`, `strip-cwd-env-boot`) (no @archon/\\* deps; `pino` and `dotenv` are allowed external deps)\n- **@archon/git**: Git operations - worktrees, branches, repos, exec wrappers (depends only on @archon/paths)\n- **@archon/providers**: AI agent providers (Claude, Codex, Pi community) — owns SDK deps, `IAgentProvider` interface, `sendQuery()` contract, and provider-specific option translation. `@archon/providers/types` is the contract subpath (zero SDK deps, zero runtime side effects) that `@archon/workflows` imports from. Providers receive raw `nodeConfig` + `assistantConfig` and translate to SDK-specific options internally. Core providers live under `claude/` and `codex/`; community providers live under `community/` (currently `community/pi/`, registered with `builtIn: false`).\n- **@archon/isolation**: Worktree isolation types, providers, resolver, error classifiers (depends only on @archon/git + @archon/paths)\n- **@archon/workflows**: Workflow engine - loader, router, executor, DAG, logger, bundled defaults (depends only on @archon/git + @archon/paths + @archon/providers/types + @hono/zod-openapi + zod; DB/AI/config injected via `WorkflowDeps`)\n- **@archon/cli**: Command-line interface for running workflows and starting the web UI server (depends on @archon/server + @archon/adapters for the serve command)\n- **@archon/core**: Business logic, database, orchestration (depends on @archon/providers for AI and @hono/zod-openapi for core Zod schemas; provides `createWorkflowStore()` adapter bridging core DB → `IWorkflowStore`)\n- **@archon/adapters**: Platform adapters for Slack, Telegram, GitHub, Discord (depends on @archon/core)\n- **@archon/server**: OpenAPIHono HTTP server (Zod + OpenAPI spec generation via `@hono/zod-openapi`), Web adapter (SSE), API routes, Web UI static serving (depends on @archon/adapters)\n- **@archon/web**: React frontend (Vite + Tailwind v4 + shadcn/ui + Zustand), SSE streaming to server. `WorkflowRunStatus`, `WorkflowDefinition`, and `DagNode` are all derived from `src/lib/api.generated.d.ts` (generated from the OpenAPI spec via `bun generate:types`; never import from `@archon/workflows`)\n\n**1. Platform Adapters**\n\n- Implement `IPlatformAdapter` interface\n- Handle platform-specific message formats\n- **Web** (`packages/server/src/adapters/web/`): Server-Sent Events (SSE) streaming, conversation ID = user-provided string\n- **Slack** (`packages/adapters/src/chat/slack/`): SDK with polling (not webhooks), conversation ID = `thread_ts`\n- **Telegram** (`packages/adapters/src/chat/telegram/`): Bot API with polling, conversation ID = `chat_id`\n- **GitHub** (`packages/adapters/src/forge/github/`): Webhooks + GitHub CLI, conversation ID = `owner/repo#number`\n- **Discord** (`packages/adapters/src/community/chat/discord/`): discord.js WebSocket, conversation ID = channel ID\n\n**Adapter Authorization Pattern:**\n\n- Auth checks happen INSIDE adapters (encapsulation, consistency)\n- Auth utilities co-located with each adapter (e.g., `packages/adapters/src/chat/slack/auth.ts`)\n- Parse whitelist from env var in constructor (e.g., `TELEGRAM_ALLOWED_USER_IDS`)\n- Check authorization in message handler (before calling `onMessage` callback)\n- Silent rejection for unauthorized users (no error response)\n- Log unauthorized attempts with masked user IDs for privacy\n- Adapters expose `onMessage(handler)` callback; errors handled by caller\n\n**2. Command Handler** (`packages/core/src/handlers/`)\n\n- Process slash commands (deterministic, no AI)\n- The orchestrator treats only these top-level commands as deterministic: `/help`, `/status`, `/reset`, `/workflow`, `/register-project`, `/update-project`, `/remove-project`, `/commands`, `/init`, `/worktree`\n- `/workflow` handles subcommands like `list`, `run`, `status`, `cancel`, `resume`, `abandon`, `approve`, `reject`, `reset-sessions`\n- Update database, perform operations, return responses\n\n**3. Orchestrator** (`packages/core/src/orchestrator/`)\n\n- Manage AI conversations\n- Load conversation + codebase context from database\n- Variable substitution: `$1`, `$2`, `$3`, `$ARGUMENTS`\n- Session management: Create new or resume existing\n- Stream AI responses to platform\n- System prompt gets a \"Managing Workflow Runs\" section (`buildRunManagementSection` in `prompt-builder.ts`) teaching the chat agent to drive run management (`archon workflow runs/get/status/run --detach/approve/reject/abandon`) directly via bash. It is appended **only for project-scoped chats on providers without the native `manage_run` tool** (Codex/OpenCode/Copilot) — gated in `orchestrator-agent.ts` on `!scopedCaps.nativeTools`. Claude and Pi instead receive the in-process `manage_run` native tool (the prompt section would be redundant for them). This is the CLI-bash delivery path for providers that have neither native tools nor `skills:` (direct chat doesn't consume the `skills:` option — it is workflow-node-only).\n\n**4. AI Agent Providers** (`packages/providers/src/`)\n\n- Implement `IAgentProvider` interface\n- **ClaudeProvider**: `@anthropic-ai/claude-agent-sdk`\n- **CodexProvider**: `@openai/codex-sdk`\n- **PiProvider** (community, `builtIn: false`): `@earendil-works/pi-coding-agent` — one harness for ~20 LLM backends via `<provider>/<model>` refs (e.g. `anthropic/claude-haiku-4-5`, `openrouter/qwen/qwen3-coder`); supports extensions, skills, tool restrictions, thinking level, best-effort structured output. See `packages/docs-web/src/content/docs/getting-started/ai-assistants.md` for setup, capability matrix, and extension config.\n- Streaming: `for await (const event of events) { await platform.send(event) }`\n\n### Configuration\n\n**Environment Variables:**\n\nsee .env.example\nsee .archon/config.yaml setup as needed\n\n**Assistant Defaults:**\n\nThe system supports configuring default models and options per assistant in `.archon/config.yaml`:\n\n```yaml\nassistants:\n  claude:\n    model: sonnet # or 'opus', 'haiku', 'claude-*', 'inherit'\n    settingSources: # Controls which CLAUDE.md, skills, commands, and agents the SDK loads\n      - project # Project-level <cwd>/.claude/ (included in default)\n      - user # User-level ~/.claude/ (included in default; omit both to restrict to project-only)\n    claudeBinaryPath:\n      /absolute/path/to/claude # Optional: Claude Code executable.\n      # Native binary (curl installer at\n      # ~/.local/bin/claude), npm cli.js, or\n      # the npm platform-package directory\n      # (e.g. @anthropic-ai/claude-code-win32-x64)\n      # which is auto-expanded to claude/claude.exe.\n      # Required in compiled binaries if\n      # CLAUDE_BIN_PATH env var is not set.\n  codex:\n    model: gpt-5.6-sol\n    modelReasoningEffort: medium # 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'\n    webSearchMode: live # 'disabled' | 'cached' | 'live'\n    additionalDirectories:\n      - /absolute/path/to/other/repo\n    codexBinaryPath: /usr/local/bin/codex # Optional: custom Codex CLI binary path\n\n\n# docs:\n#   path: docs  # Optional: default is docs/\n```\n\n**Configuration Priority:**\n\n1. Workflow-level options (in YAML `model`, `modelReasoningEffort`, etc.)\n2. Config file defaults (`.archon/config.yaml` `assistants.*`)\n3. SDK defaults\n\n**Model Validation:**\n\n- Workflows are validated at load time for provider _identity_ only — `provider:` (workflow-level and per-node) must be a registered provider id, otherwise the YAML is rejected with `Unknown provider '<id>'. Registered: claude, codex, pi`.\n- Model strings are NOT validated by Archon. Whatever the user writes in `model:` is forwarded verbatim to the resolved SDK. Vendor SDKs ship new models faster than Archon can update; the SDK and the upstream API are the source of truth for what names exist.\n- Provider is resolved via an explicit chain: `node.provider ?? workflow.provider ?? config.assistant`. Model never influences provider selection.\n\n### Running the App in Worktrees\n\nAgents working in worktrees can run the app for self-testing (make changes → run app → test via curl → fix). Ports are automatically allocated to avoid conflicts:\n\n```bash\n# Run in worktree (port auto-allocated based on path)\nbun dev &\n# [Hono] Worktree detected (/path/to/worktree)\n# [Hono] Auto-allocated port: 3637 (base: 3090, offset: +547)\n\n# Test via web API (production path)\n# 1) Create a conversation\ncurl -X POST http://localhost:3637/api/conversations \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'\n\n# 2) Send a message\ncurl -X POST http://localhost:3637/api/conversations/<conversationId>/message \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"/status\"}'\n\n# 3) Fetch messages (polling)\ncurl http://localhost:3637/api/conversations/<conversationId>/messages\n\n# Note: SSE streaming is available at /api/stream/<conversationId>\n```\n\n**Port Allocation:**\n\n- Worktrees: Automatic unique port (3190-4089 range, hash-based on path)\n- Main repo: Default 3090\n- Override: `PORT=4000 bun dev` (works in both contexts)\n- Same worktree always gets same port (deterministic)\n\n**Important:**\n\n- Use the web API routes for manual validation (avoid running multiple platform adapters)\n- Database is shared (same conversations/codebases available)\n- Kill the server when done: `pkill -f \"bun.*dev\"` or use the specific port\n\n### Archon Directory Structure\n\n**User-level (`~/.archon/`):**\n\n```\n~/.archon/\n├── workspaces/owner/repo/        # Project-centric layout\n│   ├── source/                   # Cloned repo or symlink → local path\n│   ├── worktrees/                # Git worktrees for this project\n│   ├── artifacts/                # Workflow artifacts (NEVER in git)\n│   │   ├── runs/{id}/            # Per-run artifacts ($ARTIFACTS_DIR)\n│   │   └── uploads/{convId}/     # Web UI file uploads (ephemeral)\n│   └── logs/                     # Workflow execution logs\n├── vendor/codex/                  # Codex native binary (binary builds, user-placed)\n├── web-dist/<version>/            # Cached web UI dist (archon serve, binary only)\n├── update-check.json              # Update check cache (binary builds, 24h TTL)\n├── archon.db                     # SQLite database (when DATABASE_URL not set)\n└── config.yaml                   # Global configuration (non-secrets)\n```\n\n**Repo-level (`.archon/` in any repository):**\n\n```\n.archon/\n├── commands/       # Custom commands\n├── workflows/      # Workflow definitions (YAML files)\n├── scripts/        # Named scripts for script: nodes (.ts/.js for bun, .py for uv)\n├── state/          # Cross-run workflow state (gitignored — never in git)\n└── config.yaml     # Repo-specific configuration\n```\n\n- `ARCHON_HOME` - Override the base directory (default: `~/.archon`)\n- Docker: Paths automatically set to `/.archon/`\n\n## Development Guidelines\n\n### UI and Visual Design\n\nAll UI changes — production web (`packages/web/`), experiments (`packages/web/src/experiments/`), the docs site, marketing surfaces, and any future visual surface — must align with the Archon brand foundation.\n\n- **Canonical brand guide:** https://archon.diy/brand/ (source: `packages/docs-web/src/content/docs/brand/index.md` + `packages/docs-web/public/brand/foundation.html`).\n- **Use brand tokens, not ad-hoc values.** Colors, gradients, surfaces, and typography must come from the established design tokens (`packages/web/src/index.css`) or the brand guide. Don't hard-code hex values that aren't in the system.\n- **Introducing a new visual token** (color, font, radius, spacing) means updating both the token source and the brand guide. Don't fork the palette per package.\n- **When in doubt, consult the brand guide first** before inventing new visual treatments. Open a discussion if the guide doesn't cover your case.\n\n### When Creating New Features\n\n**Quick reference:**\n\n- **Platform Adapters**: Implement `IPlatformAdapter`, handle auth, polling/webhooks\n- **AI Providers**: Implement `IAgentProvider`, session management, streaming\n- **Slash Commands**: Add to command-handler.ts, update database, no AI\n- **Database Operations**: Use `IDatabase` interface (supports PostgreSQL and SQLite via adapters)\n- **Plan insertion points**: Use stable text anchors (e.g., \"after the `it('throws on ...')` test block\"), never raw line numbers — line numbers drift on every preceding edit.\n\n### SDK Type Patterns\n\nWhen working with external SDKs (Claude Agent SDK, Codex SDK), prefer importing and using SDK types directly:\n\n```typescript\n// ✅ CORRECT - Import SDK types directly\nimport { query, type Options } from '@anthropic-ai/claude-agent-sdk';\n\nconst options: Options = {\n  cwd,\n  permissionMode: 'bypassPermissions',\n  // ...\n};\n\n// Use type assertions for SDK response structures\nconst message = msg as { message: { content: ContentBlock[] } };\n```\n\n```typescript\n// ❌ AVOID - Defining duplicate types\ninterface MyQueryOptions {  // Don't duplicate SDK types\n  cwd: string;\n  // ...\n}\nconst options: MyQueryOptions = { ... };\nquery({ prompt, options: options as any });  // Avoid 'as any'\n```\n\nThis ensures type compatibility with SDK updates and eliminates `as any` casts.\n\n### Testing\n\n**Unit Tests:**\n\n- Test pure functions (variable substitution, command parsing)\n- Mock external dependencies (database, AI SDKs, platform APIs)\n\n**Integration Tests:**\n\n- Test database operations with test database\n- Test end-to-end flows (mock platforms/AI but use real orchestrator)\n- Clean up test data after each test\n\n**Mock isolation rules (IMPORTANT):**\n\n- Bun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it\n- Do NOT add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()`, ensure its package.json test script runs it in a separate `bun test` invocation from any conflicting files\n\n**Manual Validation:** Use the web API (`curl`) or CLI commands directly for end-to-end testing of new features.\n\n### Logging\n\n**Structured logging with Pino** (`packages/paths/src/logger.ts`):\n\n```typescript\nimport { createLogger } from '@archon/paths';\n\nconst log = createLogger('orchestrator');\n\n// Event naming: {domain}.{action}_{state}\n// Standard states: _started, _completed, _failed, _validated, _rejected\nasync function createSession(conversationId: string, codebaseId: string) {\n  log.info({ conversationId, codebaseId }, 'session.create_started');\n\n  try {\n    const session = await doCreate();\n    log.info({ conversationId, codebaseId, sessionId: session.id }, 'session.create_completed');\n    return session;\n  } catch (e) {\n    const err = e as Error;\n    log.error(\n      { conversationId, error: err.message, errorType: err.constructor.name, err },\n      'session.create_failed'\n    );\n    throw err;\n  }\n}\n```\n\n**Event naming rules:**\n\n- Format: `{domain}.{action}_{state}` — e.g. `workflow.step_started`, `isolation.create_failed`\n- Avoid generic events like `processing` or `handling`\n- Always pair `_started` with `_completed` or `_failed`\n- Include context: IDs, durations, error details\n\n**Log Levels:** `fatal` > `error` > `warn` > `info` (default) > `debug` > `trace`\n\n**Verbosity:**\n\n- CLI: `archon --quiet` (errors only) — suppresses Pino logs and workflow progress output\n- CLI: `archon --verbose` (debug) — enables debug Pino logs and tool-level workflow progress events\n- Server: `LOG_LEVEL=debug bun run start`\n\n**Never log:** API keys or tokens (mask: `token.slice(0, 8) + '...'`), user message content, PII.\n\n### Command System\n\n**Variable Substitution:**\n\n- `$1`, `$2`, `$3` - Positional arguments\n- `$ARGUMENTS` - All arguments as single string\n- `$ARTIFACTS_DIR` - External artifacts directory for the current workflow run (pre-created by executor)\n- `$WORKFLOW_ID` - The workflow run ID\n- `$BASE_BRANCH` - Base branch; auto-detected from git when `worktree.baseBranch` is not set; fails only if referenced in a prompt and auto-detection also fails\n- `$DOCS_DIR` - Documentation directory path; configured via `docs.path` in `.archon/config.yaml`. Defaults to `docs/`. Never throws.\n- `$LOOP_USER_INPUT` - User feedback provided via `/workflow approve <id> <text>` at an interactive loop gate. Only populated on the first iteration of a resumed interactive loop; empty string on all other iterations.\n- `$REJECTION_REASON` - Reviewer feedback provided via `/workflow reject <id> <reason>` at an approval gate. Only populated in `on_reject` prompts; empty string elsewhere.\n- `$LOOP_PREV_OUTPUT` - Cleaned output of the previous loop iteration (loop nodes only). Empty string on the first iteration (no prior output exists). Useful for `fresh_context: true` loops that need to reference what the previous pass produced or why it failed without carrying full session history.\n\n**Command Types:**\n\n1. **Codebase Commands** (per-repo):\n   - Stored in `.archon/commands/` (plain text/markdown)\n   - Discovered from the repository `.archon/commands/` directory\n   - Surfaced via `GET /api/commands` for the workflow builder and invoked by workflow `command:` nodes\n\n2. **Workflows** (YAML-based):\n   - Stored in `.archon/workflows/` (searched recursively)\n   - Multi-step AI execution chains, discovered at runtime\n   - **`nodes:` (DAG format)**: Nodes with explicit `depends_on` edges; independent nodes in the same topological layer run concurrently. Node types: `command:` (named command file), `prompt:` (inline prompt), `bash:` (shell script, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured), `loop:` (iterative AI prompt until completion signal), `approval:` (human gate; pauses until user approves or rejects; `capture_response: true` stores the user's comment as `$<node-id>.output` for downstream nodes, default false), `script:` (inline TypeScript/Python or named script from `.archon/scripts/`, runs via `bun` or `uv`, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured, supports `deps:` for dependency installation and `timeout:` in ms, requires `runtime: bun` or `runtime: uv`) . Supports `when:` conditions, `trigger_rule` join semantics, `$nodeId.output` substitution, `output_format` for structured JSON output (SDK-enforced on Claude/Codex/OpenCode; best-effort prompt-augmentation + repair on Pi/Copilot — the parsed output is **validated against the declared schema for every provider**, best-effort providers (Pi/Copilot) re-ask up to 3× on a validation miss, and a node that declares `output_format` but returns no schema-valid output **fails** rather than degrading silently; `$nodeId.output.field` access is strict — a field not in the producer's schema, or a schemaless node whose output isn't JSON / lacks the key, fails the consuming node, while an author-declared-optional field resolves to `''`), `allowed_tools`/`denied_tools` for per-node tool restrictions (Claude only), `hooks` for per-node SDK hook callbacks (Claude only), `mcp` for per-node MCP server config files (Claude only, env vars expanded at execution time), and `skills` for per-node skill preloading via AgentDefinition wrapping (Claude only), `agents` for inline sub-agent definitions invokable via the Task tool (Claude only), and `effort`/`thinking`/`maxBudgetUsd`/`systemPrompt`/`fallbackModel`/`betas`/`sandbox` for Claude SDK advanced options (Claude only, also settable at workflow level), and `persist_session` for cross-run provider session continuity (node-level opt-in; workflow-level default via `persist_sessions: true`; requires a provider with the `sessionResume` capability)\n   - Workflow-level `requires: [github]` hard-blocks invocation (before any worktree/clone/AI cost) when the originating user hasn't connected their GitHub identity — enforced only when per-user GitHub is enabled (GitHub App + `TOKEN_ENCRYPTION_KEY`); a no-op for solo PAT installs\n   - Provider inherited from `.archon/config.yaml` unless explicitly set; per-node `provider` and `model` overrides supported\n   - Model and options can be set per workflow or inherited from config defaults\n   - `interactive: true` at the workflow level forces foreground execution on web (required for approval-gate workflows in the web UI)\n   - Model validation ensures provider/model compatibility at load time\n   - Commands: `/workflow list`, `/workflow reload`, `/workflow status`, `/workflow cancel`, `/workflow resume <id>` (re-runs failed workflow, skipping completed nodes), `/workflow abandon <id>`, `/workflow cleanup [days]` (CLI only — deletes old run records), `/workflow reset-sessions <name> [<node-id>]` (clears persisted `persist_session` memory; chat auto-scopes to the current conversation, CLI adds `--scope`/`--yes` for cross-scope control)\n   - Resilient loading: One broken YAML doesn't abort discovery; errors shown in `/workflow list`\n   - `resolveWorkflowName()` (in `router.ts`) resolves workflow names via a 4-tier fallback — exact, case-insensitive, suffix (`-name`), substring — with ambiguity detection; used by both the CLI and all chat platforms\n   - Router fallback: if no `/invoke-workflow` is produced, falls back to `archon-assist` (with \"Routing unclear\" notice); raw AI response returned only when `archon-assist` is unavailable\n   - Claude routing calls use `tools: []` to prevent tool use at the API level; Codex tool bypass is detected and triggers the same fallback\n\n**Defaults:**\n\n- Bundled in `.archon/commands/defaults/` and `.archon/workflows/defaults/`\n- Binary builds: Embedded at compile time (no filesystem access needed) via `packages/workflows/src/defaults/bundled-defaults.generated.ts`\n- Source builds: Loaded from filesystem at runtime\n- Merged with repo-specific commands/workflows (repo overrides defaults by name)\n- Opt-out: Set `defaults.loadDefaultCommands: false` or `defaults.loadDefaultWorkflows: false` in `.archon/config.yaml`\n- **After adding, removing, or editing a default file, run `bun run generate:bundled`** to refresh the embedded bundle. After editing `migrations/000_combined.sql`, run `bun run generate:bundled-schema` to keep the embedded schema in sync. `bun run validate` (and CI) run `check:bundled`, `check:bundled-skill`, and `check:bundled-schema` and will fail loudly if any generated file is stale.\n\n**Home-scoped (\"global\") workflows, commands, and scripts** (user-level, applies to every project):\n\n- Workflows: `~/.archon/workflows/` (or `$ARCHON_HOME/workflows/`)\n- Commands: `~/.archon/commands/` (or `$ARCHON_HOME/commands/`)\n- Scripts: `~/.archon/scripts/` (or `$ARCHON_HOME/scripts/`)\n- Source label: `source: 'global'` on workflows and commands (scripts don't have a source label)\n- Load priority: bundled < global < project (repo overrides global by filename or script name)\n- Subfolders: supported 1 level deep (e.g. `~/.archon/workflows/triage/foo.yaml`). Deeper nesting is ignored silently.\n- Discovery is automatic — `discoverWorkflowsWithConfig(cwd, loadConfig)` and `discoverScriptsForCwd(cwd)` both read home-scoped paths unconditionally; no caller option needed\n- **Migration from pre-0.x `~/.archon/.archon/workflows/`**: if Archon detects files at the old location it emits a one-time WARN with the exact `mv` command and does NOT load from there. Move with: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon`\n- See the docs site at `packages/docs-web/` for details\n\n### Error Handling\n\n**Database Errors:**\n\n```typescript\n// INSERT operations\ntry {\n  await db.query('INSERT INTO conversations ...', params);\n} catch (error) {\n  log.error({ err: error, params }, 'db_insert_failed');\n  throw new Error('Failed to create conversation');\n}\n\n// UPDATE operations - verify rowCount to catch missing records\ntry {\n  await db.updateConversation(conversationId, { codebase_id: codebaseId });\n} catch (error) {\n  // updateConversation throws if no rows matched (conversation not found)\n  log.error({ err: error, conversationId }, 'db_update_failed');\n  throw error; // Re-throw to surface the issue\n}\n```\n\n**Git Operation Errors (don't fail silently):**\n\n```typescript\n// When isolation environment creation fails:\ntry {\n  // ... isolation creation logic ...\n} catch (error) {\n  const err = error as Error;\n  const userMessage = classifyIsolationError(err);\n  log.error({ err, codebaseId, codebaseName }, 'isolation_creation_failed');\n  await platform.sendMessage(conversationId, userMessage);\n}\n```\n\nPattern: Use `classifyIsolationError()` (from `@archon/isolation`) to map git errors (permission denied, timeout, no space, not a git repo) to user-friendly messages. Always log the raw error for debugging and send a classified message to the user.\n\n### API Endpoints\n\n**Web UI REST API** (`packages/server/src/routes/api.ts`):\n\n**Workflow Management:**\n\n- `GET /api/workflows` - List available workflows; optional `?cwd=`; returns `{ workflows: [...], errors?: [...] }`\n- `POST /api/workflows/validate` - Validate a workflow definition in-memory (no save); body: `{ definition: object }`; returns `{ valid: boolean, errors?: string[] }`\n- `GET /api/workflows/:name` - Fetch a single workflow by name; optional `?cwd=` query param; returns `{ workflow, filename, source: 'project' | 'bundled' }`\n- `PUT /api/workflows/:name` - Save (create or update) a workflow YAML; body: `{ definition: object }`; validates before writing; requires `?cwd=` or registered codebase\n- `DELETE /api/workflows/:name` - Delete a user-defined workflow; bundled defaults cannot be deleted\n- `DELETE /api/workflows/:name/node-sessions` - Reset persisted per-node provider sessions; optional `?scope=` and `?node=` narrow the deletion; omitting `?scope=` is a cross-scope wipe and requires `?confirm=all-scopes`; returns `{ success, deleted }`\n\n**Workflow Run Lifecycle:**\n\n- `POST /api/workflows/runs/{runId}/resume` - Resume a failed run from where it left off (skips already-completed DAG nodes; AI session context is not restored).\n- `POST /api/workflows/runs/{runId}/abandon` - Abandon a non-terminal run (marks as cancelled)\n- `DELETE /api/workflows/runs/{runId}` - Delete a terminal workflow run and its events\n\n**Codebases:**\n\n- `GET /api/codebases` / `GET /api/codebases/:id` - List / fetch codebases\n- `POST /api/codebases` - Register a codebase (clone or local path)\n- `DELETE /api/codebases/:id` - Delete a codebase and clean up resources\n- `GET /api/codebases/:id/env` - List env var keys for a codebase (never returns values)\n- `PUT /api/codebases/:id/env` / `DELETE /api/codebases/:id/env/:key` - Upsert / delete a single codebase env var\n- `GET /api/codebases/:id/environments` - List tracked isolation environments for a codebase\n\n**Artifact Files:**\n\n- `GET /api/runs/:runId/artifacts` - List artifact files for a run; walks the on-disk artifact directory (dotfiles skipped) and returns `{ files: [{ path, size, modifiedAt }] }`; 400 on invalid run id or path-escape attempt, 404 if the run does not exist\n- `GET /api/artifacts/:runId/*` - Serve a workflow artifact file by run ID and relative path; returns `text/markdown` for `.md` files, `text/plain` otherwise; 400 on path traversal (`..`), 404 if run or file not found\n\n**Command Listing:**\n\n- `GET /api/commands` - List available command names (bundled + project-defined); optional `?cwd=`; returns `{ commands: [{ name, source: 'bundled' | 'project' }] }`\n\n**Providers:**\n\n- `GET /api/providers` - List registered AI providers; returns `{ providers: [{ id, displayName, capabilities, builtIn }] }`. `capabilities.nativeTools` is `true` for providers that accept in-process native tools (Claude, Pi) — Archon's `manage_run` tool is auto-injected into project-scoped chat for those providers only. `capabilities.structuredOutput` is a tiered union `'enforced' | 'best-effort' | false` (not a boolean): `'enforced'` = SDK/backend grammar-constrained (Claude/Codex/OpenCode), `'best-effort'` = prompt-augmentation + validate (Pi/Copilot), `false` = unsupported.\n\n**Web Auth (opt-in Better Auth; Postgres + `BETTER_AUTH_SECRET`):**\n\n- Better Auth mounts email/password login at `/api/auth/*` (sign-up/sign-in/sign-out/get-session). Mounted only when enabled; the catch-all explicitly falls through for Archon-owned `/api/auth/status` + `/api/auth/github*` paths so they aren't shadowed.\n- `GET /api/auth/status` - Web auth availability + signup posture (no auth required); returns `{ enabled: boolean, signup: 'allowlist' | 'open' | 'disabled' }`. Drives the Web UI login gate.\n- The per-request identity seam is `resolveAuthContext(c): { userId, role } | undefined` (in `routes/api.ts`): Better Auth session first, then the `X-Archon-User` header, then undefined. `resolveWebUserId` delegates to it; `requireWebUser` is the session-aware strict variant (401 missing / 503 backend). `role` rides the canonical user row (default `admin`).\n- **Server-side API gate** (`isApiGateEnabled`): when web auth is enabled, every `/api/*` request must resolve to an identity or gets **401** — except `/api/auth/*` (login surface) and `/api/health*` (healthcheck must stay reachable). `/webhooks/*` and `/internal/*` are outside `/api/*` and untouched. On by default; `ARCHON_WEB_AUTH_REQUIRED=false` keeps login-UI-only. This is what lets Better Auth replace the Caddy `forward_auth` sidecar as the real access boundary.\n- **Signup safety** (`getSignupMode`): with web auth on and no `ARCHON_AUTH_ALLOWED_EMAILS`, signup defaults to **disabled** (login only) + a boot WARN — never silently open. `ARCHON_AUTH_OPEN_SIGNUP=true` opts into open public signup.\n- `GET /api/workflows/runs?mine=true` and `GET /api/conversations?mine=true` - Non-enforcing \"my\" filter (narrows to `ctx.userId` only when an identity resolves; default lists everything). Not a security boundary.\n\n**GitHub Identity (per-user device flow; App mode + `TOKEN_ENCRYPTION_KEY`):**\n\n- `POST /api/auth/github/device/start` - Begin the device flow for the current web user (from `X-Archon-User`); returns `{ device_code, user_code, verification_uri, interval, expires_in }`; 401 if no web-auth header\n- `POST /api/auth/github/device/poll` - Single non-blocking poll; body `{ device_code }`; returns `{ status: 'pending' | 'connected' | 'expired' | 'denied' | 'error', githubLogin?, detail? }`\n- `GET /api/auth/github` - Connection status for the current web user; returns `{ connected, githubLogin }`\n- `DELETE /api/auth/github` - Disconnect the current web user's GitHub identity\n\n**System:**\n\n- `GET /api/health` - Health check with adapter/system status\n- `GET /api/update-check` - Check for available updates; returns `{ updateAvailable, currentVersion, latestVersion, releaseUrl }`; skips GitHub API call for non-binary builds\n\n**OpenAPI Spec:**\n\n- `GET /api/openapi.json` - Generated OpenAPI 3.0 spec for all Zod-validated routes\n\n**Webhooks:**\n\n- `POST /webhooks/github` - GitHub webhook events\n- Signature verification required (HMAC SHA-256)\n- Return 200 immediately, process async\n\n**Internal (App mode only; bind 127.0.0.1):**\n\n- `POST /internal/git-credential` - Git credential helper endpoint. Returns `{token}` for the installation matching the requested host/path. Used by the `git-credential-archon` script in worktree `.git/config` to refresh installation tokens for long-running workflow `git` operations. Hands out installation tokens — MUST NOT be exposed beyond loopback. Server **refuses to start** (not just WARN) if App mode is active and `hostname != 127.0.0.1/localhost`, unless `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1` is set as an opt-in escape hatch for deployments where the reverse proxy already drops `/internal/*`.\n\n**Security:**\n\n- Verify webhook signatures (GitHub: `X-Hub-Signature-256`)\n- Use `c.req.text()` for raw webhook body (signature verification)\n- Never log or expose tokens in responses\n- `/internal/*` paths hand out live credentials — the reverse proxy in production MUST drop them, or the server MUST bind to `127.0.0.1` only.\n\n**@Mention Detection:**\n\n- Parse `@archon` in issue/PR **comments only** (not descriptions)\n- Events: `issue_comment` only\n- Note: Descriptions often contain example commands or documentation - these are NOT command invocations (see #96)\n"},"files":{"CLAUDE.md":"Agent rules: read @AGENTS.md\n","AGENTS.md":"## Project Overview\n\n**Remote Agentic Coding Platform**: Control AI coding assistants (Claude Code SDK, Codex SDK) remotely from Slack, Telegram, and GitHub. Built with **Bun + TypeScript + SQLite/PostgreSQL**, single-developer tool for AI-assisted development practitioners. Architecture prioritizes simplicity, flexibility, and user control.\n\n## Core Principles\n\n**Single-Developer Tool**\n\n- No multi-tenant complexity\n\n**Platform Agnostic**\n\n- Unified conversation interface across Slack/Telegram/GitHub/cli/web\n- Platform adapters implement `IPlatformAdapter`\n- Stream/batch AI responses in real-time to all platforms\n\n**Type Safety (CRITICAL)**\n\n- Strict TypeScript configuration enforced\n- All functions must have complete type annotations\n- No `any` types without explicit justification\n- Interfaces for all major abstractions\n\n**Zod Schema Conventions**\n\n- Schema naming: camelCase, descriptive suffix (e.g., `workflowRunSchema`, `errorSchema`)\n- Type derivation: always use `z.infer<typeof schema>` — never write parallel hand-crafted interfaces\n- Import `z` from `@hono/zod-openapi` (not from `zod` directly). Exception: `@archon/providers` imports `z` from `zod` directly in `claude/native-tools.ts` — it only builds the Zod shape the Claude SDK's `tool()` expects (never an OpenAPI schema), and being an SDK-deps-only leaf package it must not pull in Hono.\n- Record schemas: always pass an explicit key type — `z.record(z.string(), valueSchema)` — zod v4 dropped the single-arg `z.record(valueSchema)` form\n- All new/modified API routes must use `registerOpenApiRoute(createRoute({...}), handler)` — the local wrapper handles the TypedResponse bypass. Two narrow exceptions exist: (1) routes that serve raw non-JSON content (e.g. `/api/artifacts/:runId/*` returns `text/markdown`/`text/plain`) AND use wildcard path params that OpenAPI 3.0 can't represent, use `app.get(...)` with an explanatory comment; (2) multipart-or-JSON routes (e.g. `/api/conversations/:id/message`, `/api/workflows/:name/run`) register through `registerOpenApiRoute` but drop `request.body` from the route config so Zod doesn't validate multipart payloads against a JSON schema — the handler parses both content types manually.\n- Core row schemas live in `packages/core/src/schemas/` — one file per data shape (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run); `index.ts` re-exports all\n- Route schemas live in `packages/server/src/routes/schemas/` — one file per domain\n- Engine schemas live in `packages/workflows/src/schemas/` — one file per concern (dag-node, workflow, workflow-run, retry, loop, hooks); `index.ts` re-exports all\n- Engine schema naming: camelCase (e.g., `dagNodeSchema`, `workflowBaseSchema`, `nodeOutputSchema`)\n- `TRIGGER_RULES` and `WORKFLOW_HOOK_EVENTS` are derived from schema `.options` — never duplicate as a plain array (exception: `@archon/web` must define a local constant since `api.generated.d.ts` is type-only and cannot export runtime values)\n- `loader.ts` uses `dagNodeSchema.safeParse()` for node validation; graph-level checks (cycles, deps, `$nodeId.output` refs) remain as imperative code in `validateDagStructure()`\n\n**Git Workflow and Releases**\n\n- `main` is the release branch. Never commit directly to `main`.\n- `dev` is the working branch. All feature work branches off `dev` and merges back into `dev`.\n- All PRs must use the template at `.github/pull_request_template.md` — fill in every section. When opening a PR via `gh pr create`, copy the template into the body explicitly; GitHub only auto-applies it through the web UI.\n- Link the issue with `Closes #<number>` (or `Fixes` / `Resolves`) in the PR description so it auto-closes on merge.\n- To release, use the `/release` skill. It compares `dev` to `main`, generates changelog entries, bumps the version, and creates a PR to merge `dev` into `main`.\n- Releases follow Semantic Versioning: `/release` (patch), `/release minor`, `/release major`.\n- Changelog lives in `CHANGELOG.md` and follows Keep a Changelog format.\n- Version is the single `version` field in the root `package.json`.\n\n**Git as First-Class Citizen**\n\n- Let git handle what git does best (conflicts, uncommitted changes, branch management)\n- Surface git errors to users for actionable issues (conflicts, uncommitted changes)\n- Handle expected failure cases gracefully (missing directories during cleanup)\n- Trust git's natural guardrails (e.g., refuse to remove worktree with uncommitted changes)\n- Use `@archon/git` functions for git operations; use `execFileAsync` (not `exec`) when calling git directly\n- Worktrees enable parallel development per conversation without branch conflicts\n- Workspaces automatically sync with origin before worktree creation (ensures latest code)\n- **NEVER run `git clean -fd`** - it permanently deletes untracked files (use `git checkout .` instead)\n\n## Engineering Principles\n\nThese are implementation constraints, not slogans. Apply them by default.\n\n**KISS — Keep It Simple, Stupid**\n\n- Prefer straightforward control flow over clever meta-programming\n- Prefer explicit branches and typed interfaces over hidden dynamic behavior\n- Keep error paths obvious and localized\n\n**YAGNI — You Aren't Gonna Need It**\n\n- Do not add config keys, interface methods, feature flags, or workflow branches without a concrete accepted use case\n- Do not introduce speculative abstractions without at least one current caller\n- Keep unsupported paths explicit (error out) rather than adding partial fake support\n\n**DRY + Rule of Three**\n\n- Duplicate small, local logic when it preserves clarity\n- Extract shared utilities only after the same pattern appears at least three times and has stabilized\n- When extracting, preserve module boundaries and avoid hidden coupling\n\n**SRP + ISP — Single Responsibility + Interface Segregation**\n\n- Keep each module and package focused on one concern\n- Extend behavior by implementing existing narrow interfaces (`IPlatformAdapter`, `IAgentProvider`, `IDatabase`, `IWorkflowStore`) whenever possible\n- Avoid fat interfaces and \"god modules\" that mix policy, transport, and storage\n- Do not add unrelated methods to an existing interface — define a new one\n\n**Fail Fast + Explicit Errors** — Silent fallback in agent runtimes can create unsafe or costly behavior\n\n- Prefer throwing early with a clear error for unsupported or unsafe states — never silently swallow errors\n- Never silently broaden permissions or capabilities\n- Document fallback behavior with a comment when a fallback is intentional and safe; otherwise throw\n\n**No Autonomous Lifecycle Mutation Across Process Boundaries**\n\n- When a process cannot reliably distinguish \"actively running elsewhere\" from \"orphaned by a crash\" — typically because the work was started by a different process or input source (CLI, adapter, webhook, web UI, cron) — it must not autonomously mark that work as failed/cancelled/abandoned based on a timer or staleness guess.\n- Surface the ambiguous state to the user and provide a one-click action.\n- Heuristics for _recoverable_ operations (retry backoff, subprocess timeouts, hygiene cleanup of terminal-status data) remain appropriate; the rule is about destructive mutation of _non-terminal_ state owned by an unknowable other party.\n- Reference: #1216 and the CLI orphan-cleanup precedent at `packages/cli/src/cli.ts:256-258`.\n\n**Determinism + Reproducibility**\n\n- Prefer reproducible commands and locked dependency behavior in CI-sensitive paths\n- Keep tests deterministic — no flaky timing or network dependence without guardrails\n- Ensure local validation commands (`bun run validate`) map directly to CI expectations\n\n**Reversibility + Rollback-First Thinking**\n\n- Keep changes easy to revert: small scope, clear blast radius\n- For risky changes, define the rollback path before merging\n- Avoid mixed mega-patches that block safe rollback\n\n## Essential Commands\n\n### Development\n\n```bash\n# Start server + Web UI together (hot reload for both)\nbun run dev\n\n# Or start individually\nbun run dev:server  # Backend only (port 3090)\nbun run dev:web     # Frontend only (port 5173)\n```\n\nRegenerating frontend API types (requires server to be running at port 3090):\n\n```bash\nbun run dev:server  # must be running first\nbun --filter @archon/web generate:types\n```\n\nOptional: Use PostgreSQL instead of SQLite by setting `DATABASE_URL` in `.env`:\n\n```bash\ndocker-compose --profile with-db up -d postgres\n# Set DATABASE_URL=postgresql://postgres:postgres@localhost:5432/remote_coding_agent in .env\n```\n\n### Testing\n\n```bash\nbun run test                # Run all tests (per-package, isolated processes)\nbun test --watch            # Watch mode (single package)\nbun test packages/core/src/handlers/command-handler.test.ts  # Single file\n```\n\n**Test isolation (mock.module pollution):** Bun's `mock.module()` permanently replaces modules in the process-wide cache — `mock.restore()` does NOT undo it ([oven-sh/bun#7823](https://github.com/oven-sh/bun/issues/7823)). To prevent cross-file pollution, packages that have conflicting `mock.module()` calls split their tests into separate `bun test` invocations: `@archon/core` (20 batches), `@archon/workflows` (5), `@archon/adapters` (6), `@archon/isolation` (3). See each package's `package.json` for the exact splits.\n\n**Do NOT run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures. Always use `bun run test` (which uses `bun --filter '*' test` for per-package isolation).\n\n### Type Checking & Linting\n\n```bash\nbun run type-check\nbun run lint\nbun run lint:fix\nbun run format\nbun run format:check\n```\n\n### Pre-PR Validation\n\n**Always run before creating a pull request:**\n\n```bash\nbun run validate\n```\n\nThis runs `check:bundled`, `check:bundled-skill`, `check:bundled-schema`, type-check, lint, format check, and tests. All seven must pass for CI to succeed.\n\n### ESLint Guidelines\n\n**Zero-tolerance policy**: CI enforces `--max-warnings 0`. No warnings allowed.\n\n**When to use inline disable comments** (`// eslint-disable-next-line`):\n\n- **Almost never** - fix the issue instead\n- Only acceptable when:\n  1. External SDK types are incorrect (document which SDK and why)\n  2. Intentional type assertion after validation (must include comment explaining the validation)\n\n**Never acceptable:**\n\n- Disabling `no-explicit-any` without justification\n- Disabling rules to \"make CI pass\"\n- Bulk disabling at file level (`/* eslint-disable */`)\n\n### Database\n\n**Auto-Detection (SQLite is the default — zero setup):**\n\n- **Without `DATABASE_URL`**: Uses SQLite at `~/.archon/archon.db` (auto-initialized, recommended for most users)\n- **With `DATABASE_URL` set**: Uses PostgreSQL (schema auto-applied on startup; no manual `psql` needed). The Postgres adapter runs the idempotent `migrations/000_combined.sql` inside an advisory-lock transaction on first connection, so upgrades that add tables or columns converge automatically.\n\n### CLI (Command Line)\n\nRun workflows directly from the command line without needing the server. Workflow and isolation commands require running from within a git repository (subdirectories work - resolves to repo root).\n\n```bash\n# List available workflows (requires git repo)\nbun run cli workflow list\n\n# Machine-readable JSON output\nbun run cli workflow list --json\n\n# Run a workflow\nbun run cli workflow run assist \"What does the orchestrator do?\"\n\n# Run in a specific directory\nbun run cli workflow run plan --cwd /path/to/repo \"Add dark mode\"\n\n# Default: auto-creates worktree with generated branch name (isolation by default)\nbun run cli workflow run implement \"Add auth\"\n\n# Explicit branch name for the worktree\nbun run cli workflow run implement --branch feature-auth \"Add auth\"\n\n# Opt out of isolation (run in live checkout)\nbun run cli workflow run quick-fix --no-worktree \"Fix typo\"\n\n# Run in a detached background child (returns immediately; find it via `workflow runs`)\nbun run cli workflow run implement \"Add auth\" --detach\n\n# Show active runs (running + paused)\nbun run cli workflow status\n\n# List recent runs of ALL statuses, scoped to this project's codebase (cwd)\nbun run cli workflow runs\nbun run cli workflow runs --json                 # machine-readable { runs, total, counts }\nbun run cli workflow runs --status failed --limit 50\nbun run cli workflow runs --all                  # across all projects\n\n# Show detail for one run (any status); --verbose adds per-node summary\nbun run cli workflow get <run-id>\nbun run cli workflow get <run-id> --json\n\n# Resume a failed workflow (re-runs, skipping completed nodes)\nbun run cli workflow resume <run-id>\n\n# Discard a non-terminal run\nbun run cli workflow abandon <run-id>\n\n# Most read/write subcommands accept --json for machine-readable output:\n#   list, status, runs, get, approve, reject, abandon, resume.\n# For approve/reject/resume, --json records/validates the decision and returns a\n# clean JSON line WITHOUT the inline auto-resume (drive continuation separately).\n\n# Delete old workflow run records (default: 7 days)\nbun run cli workflow cleanup\nbun run cli workflow cleanup 30  # Custom days\n\n# Clear persisted per-node AI sessions for a workflow (persist_session memory)\n# Without --scope, wipes every scope and requires --yes; --node narrows to one node\nbun run cli workflow reset-sessions <workflow-name> [--scope <key>] [--node <id>] [--yes] [--json]\n\n# Emit a workflow event (used inside workflow loop prompts)\nbun run cli workflow event emit --run-id <uuid> --type <event-type> [--data <json>]\n\n# List active worktrees/environments\nbun run cli isolation list\n\n# Clean up stale environments (default: 7 days)\nbun run cli isolation cleanup\nbun run cli isolation cleanup 14  # Custom days\n\n# Clean up environments with branches merged into main (also deletes remote branches)\nbun run cli isolation cleanup --merged\n\n# Also remove environments with closed (abandoned) PRs\nbun run cli isolation cleanup --merged --include-closed\n\n# Validate workflow definitions and their referenced resources\nbun run cli validate workflows              # All workflows\nbun run cli validate workflows my-workflow  # Single workflow\nbun run cli validate workflows my-workflow --json  # Machine-readable output\n\n# Validate command files\nbun run cli validate commands               # All commands\nbun run cli validate commands my-command    # Single command\n\n# Complete branch lifecycle (remove worktree + local/remote branches)\nbun run cli complete <branch-name>\nbun run cli complete <branch-name> --force  # Skip uncommitted-changes check\n\n# Start the web UI server (compiled binary only, downloads web UI on first run)\nbun run cli serve\nbun run cli serve --port 4000\nbun run cli serve --download-only  # Download without starting\n\n# Install the bundled Archon skill into a project\nbun run cli skill install\nbun run cli skill install /path/to/project\n\n# Verify your Archon setup (Claude binary, gh auth, DB, adapters)\nbun run cli doctor\n\n# Connect your GitHub identity via device flow (multi-user installs only:\n# App mode + TOKEN_ENCRYPTION_KEY). Identity from ARCHON_USER_ID or $USER.\nbun run cli auth github\n\n# Inspect or rotate the anonymous telemetry install UUID\nbun run cli telemetry status\nbun run cli telemetry reset\n\n# Show version\nbun run cli version\n```\n\n## Architecture\n\n### Directory Structure\n\n**Monorepo Layout (Bun Workspaces):**\n\n```\npackages/\n├── cli/                      # @archon/cli - Command-line interface\n│   └── src/\n│       ├── adapters/         # CLI adapter (stdout output)\n│       ├── commands/         # CLI command implementations\n│       └── cli.ts            # CLI entry point\n├── providers/                # @archon/providers - AI agent providers (SDK deps live here)\n│   └── src/\n│       ├── types.ts          # Contract layer (IAgentProvider, SendQueryOptions, MessageChunk — ZERO SDK deps)\n│       ├── registry.ts       # Typed provider registry (ProviderRegistration records)\n│       ├── errors.ts         # UnknownProviderError\n│       ├── claude/           # ClaudeProvider + parseClaudeConfig + MCP/hooks/skills translation\n│       ├── codex/            # CodexProvider + parseCodexConfig + binary-resolver\n│       ├── community/pi/     # PiProvider (builtIn: false) — @earendil-works/pi-coding-agent, ~20 LLM backends\n│       ├── community/opencode/ # OpenCodeProvider (builtIn: false) — @archon/opencode SDK, local embedded runtime\n│       └── index.ts          # Package exports\n├── core/                     # @archon/core - Shared business logic\n│   └── src/\n│       ├── config/           # YAML config loading\n│       ├── db/               # Database connection, queries\n│       ├── handlers/         # Command handler (slash commands)\n│       ├── orchestrator/     # AI conversation management\n│       ├── services/         # Background services (cleanup)\n│       ├── schemas/          # Zod row schemas for core data shapes (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run)\n│       ├── state/            # Session state machine\n│       ├── types/            # TypeScript types and interfaces\n│       ├── utils/            # Shared utilities\n│       ├── workflows/        # Store adapter (createWorkflowStore) bridging core DB → IWorkflowStore\n│       └── index.ts          # Package exports\n├── workflows/                # @archon/workflows - Workflow engine (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── schemas/          # Zod schemas for engine types\n│       ├── loader.ts         # YAML parsing + validation (parseWorkflow)\n│       ├── workflow-discovery.ts # Workflow filesystem discovery (discoverWorkflows, discoverWorkflowsWithConfig)\n│       ├── executor-shared.ts # Shared executor infrastructure (error classification, variable substitution)\n│       ├── router.ts         # Prompt building + invocation parsing\n│       ├── executor.ts       # Workflow execution orchestrator (executeWorkflow)\n│       ├── dag-executor.ts   # DAG-specific execution logic\n│       ├── store.ts          # IWorkflowStore interface (database abstraction)\n│       ├── deps.ts           # WorkflowDeps injection types (IWorkflowPlatform, imports from @archon/providers/types)\n│       ├── event-emitter.ts  # Workflow observability events\n│       ├── logger.ts         # JSONL file logger\n│       ├── validator.ts      # Resource validation (command files, MCP configs, skill dirs)\n│       ├── defaults/         # Bundled default commands and workflows\n│       └── utils/            # Variable substitution, tool formatting, execution utilities\n├── git/                      # @archon/git - Git operations (no @archon/core dep)\n│   └── src/\n│       ├── branch.ts         # Branch operations (checkout, merge detection, etc.)\n│       ├── exec.ts           # execFileAsync and mkdirAsync wrappers\n│       ├── repo.ts           # Repository operations (clone, sync, remote URL)\n│       ├── types.ts          # Branded types (RepoPath, BranchName, etc.)\n│       ├── worktree.ts       # Worktree operations (create, remove, list)\n│       └── index.ts          # Package exports\n├── isolation/                # @archon/isolation - Worktree isolation (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── types.ts          # Isolation types and interfaces\n│       ├── errors.ts         # Error classifiers (classifyIsolationError, IsolationBlockedError)\n│       ├── factory.ts        # Provider factory (getIsolationProvider, configureIsolation)\n│       ├── resolver.ts       # IsolationResolver (request → environment resolution)\n│       ├── store.ts          # IIsolationStore interface\n│       ├── worktree-copy.ts  # File copy utilities for worktrees\n│       ├── providers/\n│       │   └── worktree.ts   # WorktreeProvider implementation\n│       └── index.ts          # Package exports\n├── paths/                    # @archon/paths - Path resolution and logger (zero @archon/* deps)\n│   └── src/\n│       ├── archon-paths.ts   # Archon directory path utilities\n│       ├── logger.ts         # Pino logger factory\n│       └── index.ts          # Package exports\n├── adapters/                 # @archon/adapters - Platform adapters (Slack, Telegram, GitHub, Discord)\n│   └── src/\n│       ├── chat/             # Chat platform adapters (Slack, Telegram)\n│       ├── forge/            # Forge adapters (GitHub)\n│       ├── community/        # Community adapters (Discord)\n│       ├── utils/            # Shared adapter utilities (message splitting)\n│       └── index.ts          # Package exports\n├── server/                   # @archon/server - HTTP server + Web adapter\n│   └── src/\n│       ├── adapters/         # Web platform adapter (SSE streaming)\n│       ├── routes/           # API routes (REST + SSE)\n│       └── index.ts          # Hono server entry point\n└── web/                      # @archon/web - React frontend (Web UI)\n    └── src/\n        ├── components/       # React components (chat, layout, projects, ui, workflows)\n        ├── hooks/            # Custom hooks (useSSE, etc.)\n        ├── lib/              # API client, types, utilities\n        ├── stores/           # Zustand stores (workflow-store)\n        ├── routes/           # Route pages (ChatPage, WorkflowsPage, WorkflowBuilderPage, etc.)\n        ├── experiments/      # Isolated in-repo spikes; lint-guarded against\n        │   │                 # importing production web modules. Drop-in or\n        │   │                 # delete cleanly. See experiments/README.md.\n        │   └── console/      # Run-centric console UI mounted at /console\n        └── App.tsx           # Router + layout\n```\n\n**Import Patterns:**\n\n**IMPORTANT**: Always use typed imports - never use generic `import *` for the main package.\n\n```typescript\n// ✅ CORRECT: Use `import type` for type-only imports\nimport type { IPlatformAdapter, Conversation, MergedConfig } from '@archon/core';\n\n// ✅ CORRECT: Use specific named imports for values\nimport { handleMessage, ConversationLockManager, pool } from '@archon/core';\n\n// ✅ CORRECT: Namespace imports for submodules with many exports\nimport * as conversationDb from '@archon/core/db/conversations';\nimport * as git from '@archon/git';\n\n// ✅ CORRECT: Import workflow engine types/functions from direct subpaths\nimport type { WorkflowDeps } from '@archon/workflows/deps';\nimport type { IWorkflowStore } from '@archon/workflows/store';\nimport type { WorkflowDefinition } from '@archon/workflows/schemas/workflow';\nimport { executeWorkflow } from '@archon/workflows/executor';\nimport { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery';\nimport { findWorkflow } from '@archon/workflows/router';\n\n// ❌ WRONG: Never use generic import for main package\nimport * as core from '@archon/core'; // Don't do this\n\n// ❌ WRONG: In @archon/web, never import from @archon/workflows (it's a server package)\nimport type { DagNode } from '@archon/workflows/schemas/dag-node'; // Don't do this from @archon/web\n// ✅ CORRECT: Use re-exports from api.ts (derived from generated OpenAPI spec)\nimport type { DagNode, WorkflowDefinition } from '@/lib/api';\n```\n\n### Database Schema\n\n**16 Tables (all prefixed with `remote_agent_`):**\n\n1. **`codebases`** - Repository metadata and commands (JSONB)\n2. **`conversations`** - Track platform conversations with titles and soft-delete support; nullable `user_id` records first creator\n3. **`sessions`** - Track AI SDK sessions with resume capability\n4. **`isolation_environments`** - Git worktree isolation tracking; nullable `created_by_user_id` preserves first creator\n5. **`workflow_runs`** - Workflow execution tracking and state; nullable `user_id` for per-run attribution\n6. **`workflow_events`** - Step-level workflow event log (step transitions, artifacts, errors)\n7. **`messages`** - Conversation message history with tool call metadata (JSONB); nullable `user_id` (NULL for assistant rows)\n8. **`codebase_env_vars`** - Per-project env vars injected into project-scoped execution surfaces (Claude, Codex, bash/script nodes, and direct chat when codebase-scoped), managed via Web UI or `env:` in config\n9. **`users`** - Archon-internal identity (one row per human/bot); created lazily on first sight by any adapter; `role` (`'admin'`(default)`/'member'`) is the identity seam for future per-resource scoping (visibility stays open today)\n10. **`user_identities`** - Per-platform mapping (Slack U-id, Telegram chat id, Discord snowflake, GitHub login, Better Auth web user id) → `users.id`; `UNIQUE(platform, platform_user_id)`\n11. **`workflow_node_sessions`** - Per-node provider session IDs persisted across workflow re-runs (opt-in via `persist_session`); keyed by `(workflow_name, node_id, scope_key, provider)`; `scope_key` is typically the conversation UUID\n12. **`user_github_tokens`** - Per-user GitHub device-flow tokens encrypted at rest (AES-256-GCM); one row per Archon user (`UNIQUE(user_id)`), cascades on user deletion; numeric `github_user_id` anchors the commit no-reply email\n    13–16. **`remote_agent_auth_user` / `remote_agent_auth_session` / `remote_agent_auth_account` / `remote_agent_auth_verification`** - Better Auth tables for opt-in web login (**PostgreSQL only**; always created on Postgres via the idempotent schema apply, but populated only when web auth is enabled — `DATABASE_URL` + `BETTER_AUTH_SECRET`). Owned and shaped by Better Auth (text ids, camelCase columns); Archon never queries them directly — a session maps to the canonical `users` row via `user_identities('web', <betterAuthUserId>)`\n\n**Key Patterns:**\n\n- Conversation ID format: Platform-specific (`thread_ts`, `chat_id`, `user/repo#123`)\n- One active session per conversation\n- Codebase commands stored in filesystem, paths in `codebases.commands` JSONB\n\n**Session Transitions:**\n\n- Sessions are immutable - transitions create new linked sessions\n- Each transition has explicit `TransitionTrigger` reason (first-message, plan-to-execute, reset-requested, etc.)\n- Audit trail: `parent_session_id` links to previous session, `transition_reason` records why\n- Only plan→execute creates new session immediately; other triggers deactivate current session\n\n### Architecture Layers\n\n**Package Split:**\n\n- **@archon/paths**: Path resolution utilities, Pino logger factory, web dist cache path (`getWebDistDir`), CWD env stripper (`stripCwdEnv`, `strip-cwd-env-boot`) (no @archon/\\* deps; `pino` and `dotenv` are allowed external deps)\n- **@archon/git**: Git operations - worktrees, branches, repos, exec wrappers (depends only on @archon/paths)\n- **@archon/providers**: AI agent providers (Claude, Codex, Pi community) — owns SDK deps, `IAgentProvider` interface, `sendQuery()` contract, and provider-specific option translation. `@archon/providers/types` is the contract subpath (zero SDK deps, zero runtime side effects) that `@archon/workflows` imports from. Providers receive raw `nodeConfig` + `assistantConfig` and translate to SDK-specific options internally. Core providers live under `claude/` and `codex/`; community providers live under `community/` (currently `community/pi/`, registered with `builtIn: false`).\n- **@archon/isolation**: Worktree isolation types, providers, resolver, error classifiers (depends only on @archon/git + @archon/paths)\n- **@archon/workflows**: Workflow engine - loader, router, executor, DAG, logger, bundled defaults (depends only on @archon/git + @archon/paths + @archon/providers/types + @hono/zod-openapi + zod; DB/AI/config injected via `WorkflowDeps`)\n- **@archon/cli**: Command-line interface for running workflows and starting the web UI server (depends on @archon/server + @archon/adapters for the serve command)\n- **@archon/core**: Business logic, database, orchestration (depends on @archon/providers for AI and @hono/zod-openapi for core Zod schemas; provides `createWorkflowStore()` adapter bridging core DB → `IWorkflowStore`)\n- **@archon/adapters**: Platform adapters for Slack, Telegram, GitHub, Discord (depends on @archon/core)\n- **@archon/server**: OpenAPIHono HTTP server (Zod + OpenAPI spec generation via `@hono/zod-openapi`), Web adapter (SSE), API routes, Web UI static serving (depends on @archon/adapters)\n- **@archon/web**: React frontend (Vite + Tailwind v4 + shadcn/ui + Zustand), SSE streaming to server. `WorkflowRunStatus`, `WorkflowDefinition`, and `DagNode` are all derived from `src/lib/api.generated.d.ts` (generated from the OpenAPI spec via `bun generate:types`; never import from `@archon/workflows`)\n\n**1. Platform Adapters**\n\n- Implement `IPlatformAdapter` interface\n- Handle platform-specific message formats\n- **Web** (`packages/server/src/adapters/web/`): Server-Sent Events (SSE) streaming, conversation ID = user-provided string\n- **Slack** (`packages/adapters/src/chat/slack/`): SDK with polling (not webhooks), conversation ID = `thread_ts`\n- **Telegram** (`packages/adapters/src/chat/telegram/`): Bot API with polling, conversation ID = `chat_id`\n- **GitHub** (`packages/adapters/src/forge/github/`): Webhooks + GitHub CLI, conversation ID = `owner/repo#number`\n- **Discord** (`packages/adapters/src/community/chat/discord/`): discord.js WebSocket, conversation ID = channel ID\n\n**Adapter Authorization Pattern:**\n\n- Auth checks happen INSIDE adapters (encapsulation, consistency)\n- Auth utilities co-located with each adapter (e.g., `packages/adapters/src/chat/slack/auth.ts`)\n- Parse whitelist from env var in constructor (e.g., `TELEGRAM_ALLOWED_USER_IDS`)\n- Check authorization in message handler (before calling `onMessage` callback)\n- Silent rejection for unauthorized users (no error response)\n- Log unauthorized attempts with masked user IDs for privacy\n- Adapters expose `onMessage(handler)` callback; errors handled by caller\n\n**2. Command Handler** (`packages/core/src/handlers/`)\n\n- Process slash commands (deterministic, no AI)\n- The orchestrator treats only these top-level commands as deterministic: `/help`, `/status`, `/reset`, `/workflow`, `/register-project`, `/update-project`, `/remove-project`, `/commands`, `/init`, `/worktree`\n- `/workflow` handles subcommands like `list`, `run`, `status`, `cancel`, `resume`, `abandon`, `approve`, `reject`, `reset-sessions`\n- Update database, perform operations, return responses\n\n**3. Orchestrator** (`packages/core/src/orchestrator/`)\n\n- Manage AI conversations\n- Load conversation + codebase context from database\n- Variable substitution: `$1`, `$2`, `$3`, `$ARGUMENTS`\n- Session management: Create new or resume existing\n- Stream AI responses to platform\n- System prompt gets a \"Managing Workflow Runs\" section (`buildRunManagementSection` in `prompt-builder.ts`) teaching the chat agent to drive run management (`archon workflow runs/get/status/run --detach/approve/reject/abandon`) directly via bash. It is appended **only for project-scoped chats on providers without the native `manage_run` tool** (Codex/OpenCode/Copilot) — gated in `orchestrator-agent.ts` on `!scopedCaps.nativeTools`. Claude and Pi instead receive the in-process `manage_run` native tool (the prompt section would be redundant for them). This is the CLI-bash delivery path for providers that have neither native tools nor `skills:` (direct chat doesn't consume the `skills:` option — it is workflow-node-only).\n\n**4. AI Agent Providers** (`packages/providers/src/`)\n\n- Implement `IAgentProvider` interface\n- **ClaudeProvider**: `@anthropic-ai/claude-agent-sdk`\n- **CodexProvider**: `@openai/codex-sdk`\n- **PiProvider** (community, `builtIn: false`): `@earendil-works/pi-coding-agent` — one harness for ~20 LLM backends via `<provider>/<model>` refs (e.g. `anthropic/claude-haiku-4-5`, `openrouter/qwen/qwen3-coder`); supports extensions, skills, tool restrictions, thinking level, best-effort structured output. See `packages/docs-web/src/content/docs/getting-started/ai-assistants.md` for setup, capability matrix, and extension config.\n- Streaming: `for await (const event of events) { await platform.send(event) }`\n\n### Configuration\n\n**Environment Variables:**\n\nsee .env.example\nsee .archon/config.yaml setup as needed\n\n**Assistant Defaults:**\n\nThe system supports configuring default models and options per assistant in `.archon/config.yaml`:\n\n```yaml\nassistants:\n  claude:\n    model: sonnet # or 'opus', 'haiku', 'claude-*', 'inherit'\n    settingSources: # Controls which CLAUDE.md, skills, commands, and agents the SDK loads\n      - project # Project-level <cwd>/.claude/ (included in default)\n      - user # User-level ~/.claude/ (included in default; omit both to restrict to project-only)\n    claudeBinaryPath:\n      /absolute/path/to/claude # Optional: Claude Code executable.\n      # Native binary (curl installer at\n      # ~/.local/bin/claude), npm cli.js, or\n      # the npm platform-package directory\n      # (e.g. @anthropic-ai/claude-code-win32-x64)\n      # which is auto-expanded to claude/claude.exe.\n      # Required in compiled binaries if\n      # CLAUDE_BIN_PATH env var is not set.\n  codex:\n    model: gpt-5.6-sol\n    modelReasoningEffort: medium # 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'\n    webSearchMode: live # 'disabled' | 'cached' | 'live'\n    additionalDirectories:\n      - /absolute/path/to/other/repo\n    codexBinaryPath: /usr/local/bin/codex # Optional: custom Codex CLI binary path\n\n\n# docs:\n#   path: docs  # Optional: default is docs/\n```\n\n**Configuration Priority:**\n\n1. Workflow-level options (in YAML `model`, `modelReasoningEffort`, etc.)\n2. Config file defaults (`.archon/config.yaml` `assistants.*`)\n3. SDK defaults\n\n**Model Validation:**\n\n- Workflows are validated at load time for provider _identity_ only — `provider:` (workflow-level and per-node) must be a registered provider id, otherwise the YAML is rejected with `Unknown provider '<id>'. Registered: claude, codex, pi`.\n- Model strings are NOT validated by Archon. Whatever the user writes in `model:` is forwarded verbatim to the resolved SDK. Vendor SDKs ship new models faster than Archon can update; the SDK and the upstream API are the source of truth for what names exist.\n- Provider is resolved via an explicit chain: `node.provider ?? workflow.provider ?? config.assistant`. Model never influences provider selection.\n\n### Running the App in Worktrees\n\nAgents working in worktrees can run the app for self-testing (make changes → run app → test via curl → fix). Ports are automatically allocated to avoid conflicts:\n\n```bash\n# Run in worktree (port auto-allocated based on path)\nbun dev &\n# [Hono] Worktree detected (/path/to/worktree)\n# [Hono] Auto-allocated port: 3637 (base: 3090, offset: +547)\n\n# Test via web API (production path)\n# 1) Create a conversation\ncurl -X POST http://localhost:3637/api/conversations \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'\n\n# 2) Send a message\ncurl -X POST http://localhost:3637/api/conversations/<conversationId>/message \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"/status\"}'\n\n# 3) Fetch messages (polling)\ncurl http://localhost:3637/api/conversations/<conversationId>/messages\n\n# Note: SSE streaming is available at /api/stream/<conversationId>\n```\n\n**Port Allocation:**\n\n- Worktrees: Automatic unique port (3190-4089 range, hash-based on path)\n- Main repo: Default 3090\n- Override: `PORT=4000 bun dev` (works in both contexts)\n- Same worktree always gets same port (deterministic)\n\n**Important:**\n\n- Use the web API routes for manual validation (avoid running multiple platform adapters)\n- Database is shared (same conversations/codebases available)\n- Kill the server when done: `pkill -f \"bun.*dev\"` or use the specific port\n\n### Archon Directory Structure\n\n**User-level (`~/.archon/`):**\n\n```\n~/.archon/\n├── workspaces/owner/repo/        # Project-centric layout\n│   ├── source/                   # Cloned repo or symlink → local path\n│   ├── worktrees/                # Git worktrees for this project\n│   ├── artifacts/                # Workflow artifacts (NEVER in git)\n│   │   ├── runs/{id}/            # Per-run artifacts ($ARTIFACTS_DIR)\n│   │   └── uploads/{convId}/     # Web UI file uploads (ephemeral)\n│   └── logs/                     # Workflow execution logs\n├── vendor/codex/                  # Codex native binary (binary builds, user-placed)\n├── web-dist/<version>/            # Cached web UI dist (archon serve, binary only)\n├── update-check.json              # Update check cache (binary builds, 24h TTL)\n├── archon.db                     # SQLite database (when DATABASE_URL not set)\n└── config.yaml                   # Global configuration (non-secrets)\n```\n\n**Repo-level (`.archon/` in any repository):**\n\n```\n.archon/\n├── commands/       # Custom commands\n├── workflows/      # Workflow definitions (YAML files)\n├── scripts/        # Named scripts for script: nodes (.ts/.js for bun, .py for uv)\n├── state/          # Cross-run workflow state (gitignored — never in git)\n└── config.yaml     # Repo-specific configuration\n```\n\n- `ARCHON_HOME` - Override the base directory (default: `~/.archon`)\n- Docker: Paths automatically set to `/.archon/`\n\n## Development Guidelines\n\n### UI and Visual Design\n\nAll UI changes — production web (`packages/web/`), experiments (`packages/web/src/experiments/`), the docs site, marketing surfaces, and any future visual surface — must align with the Archon brand foundation.\n\n- **Canonical brand guide:** https://archon.diy/brand/ (source: `packages/docs-web/src/content/docs/brand/index.md` + `packages/docs-web/public/brand/foundation.html`).\n- **Use brand tokens, not ad-hoc values.** Colors, gradients, surfaces, and typography must come from the established design tokens (`packages/web/src/index.css`) or the brand guide. Don't hard-code hex values that aren't in the system.\n- **Introducing a new visual token** (color, font, radius, spacing) means updating both the token source and the brand guide. Don't fork the palette per package.\n- **When in doubt, consult the brand guide first** before inventing new visual treatments. Open a discussion if the guide doesn't cover your case.\n\n### When Creating New Features\n\n**Quick reference:**\n\n- **Platform Adapters**: Implement `IPlatformAdapter`, handle auth, polling/webhooks\n- **AI Providers**: Implement `IAgentProvider`, session management, streaming\n- **Slash Commands**: Add to command-handler.ts, update database, no AI\n- **Database Operations**: Use `IDatabase` interface (supports PostgreSQL and SQLite via adapters)\n- **Plan insertion points**: Use stable text anchors (e.g., \"after the `it('throws on ...')` test block\"), never raw line numbers — line numbers drift on every preceding edit.\n\n### SDK Type Patterns\n\nWhen working with external SDKs (Claude Agent SDK, Codex SDK), prefer importing and using SDK types directly:\n\n```typescript\n// ✅ CORRECT - Import SDK types directly\nimport { query, type Options } from '@anthropic-ai/claude-agent-sdk';\n\nconst options: Options = {\n  cwd,\n  permissionMode: 'bypassPermissions',\n  // ...\n};\n\n// Use type assertions for SDK response structures\nconst message = msg as { message: { content: ContentBlock[] } };\n```\n\n```typescript\n// ❌ AVOID - Defining duplicate types\ninterface MyQueryOptions {  // Don't duplicate SDK types\n  cwd: string;\n  // ...\n}\nconst options: MyQueryOptions = { ... };\nquery({ prompt, options: options as any });  // Avoid 'as any'\n```\n\nThis ensures type compatibility with SDK updates and eliminates `as any` casts.\n\n### Testing\n\n**Unit Tests:**\n\n- Test pure functions (variable substitution, command parsing)\n- Mock external dependencies (database, AI SDKs, platform APIs)\n\n**Integration Tests:**\n\n- Test database operations with test database\n- Test end-to-end flows (mock platforms/AI but use real orchestrator)\n- Clean up test data after each test\n\n**Mock isolation rules (IMPORTANT):**\n\n- Bun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it\n- Do NOT add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()`, ensure its package.json test script runs it in a separate `bun test` invocation from any conflicting files\n\n**Manual Validation:** Use the web API (`curl`) or CLI commands directly for end-to-end testing of new features.\n\n### Logging\n\n**Structured logging with Pino** (`packages/paths/src/logger.ts`):\n\n```typescript\nimport { createLogger } from '@archon/paths';\n\nconst log = createLogger('orchestrator');\n\n// Event naming: {domain}.{action}_{state}\n// Standard states: _started, _completed, _failed, _validated, _rejected\nasync function createSession(conversationId: string, codebaseId: string) {\n  log.info({ conversationId, codebaseId }, 'session.create_started');\n\n  try {\n    const session = await doCreate();\n    log.info({ conversationId, codebaseId, sessionId: session.id }, 'session.create_completed');\n    return session;\n  } catch (e) {\n    const err = e as Error;\n    log.error(\n      { conversationId, error: err.message, errorType: err.constructor.name, err },\n      'session.create_failed'\n    );\n    throw err;\n  }\n}\n```\n\n**Event naming rules:**\n\n- Format: `{domain}.{action}_{state}` — e.g. `workflow.step_started`, `isolation.create_failed`\n- Avoid generic events like `processing` or `handling`\n- Always pair `_started` with `_completed` or `_failed`\n- Include context: IDs, durations, error details\n\n**Log Levels:** `fatal` > `error` > `warn` > `info` (default) > `debug` > `trace`\n\n**Verbosity:**\n\n- CLI: `archon --quiet` (errors only) — suppresses Pino logs and workflow progress output\n- CLI: `archon --verbose` (debug) — enables debug Pino logs and tool-level workflow progress events\n- Server: `LOG_LEVEL=debug bun run start`\n\n**Never log:** API keys or tokens (mask: `token.slice(0, 8) + '...'`), user message content, PII.\n\n### Command System\n\n**Variable Substitution:**\n\n- `$1`, `$2`, `$3` - Positional arguments\n- `$ARGUMENTS` - All arguments as single string\n- `$ARTIFACTS_DIR` - External artifacts directory for the current workflow run (pre-created by executor)\n- `$WORKFLOW_ID` - The workflow run ID\n- `$BASE_BRANCH` - Base branch; auto-detected from git when `worktree.baseBranch` is not set; fails only if referenced in a prompt and auto-detection also fails\n- `$DOCS_DIR` - Documentation directory path; configured via `docs.path` in `.archon/config.yaml`. Defaults to `docs/`. Never throws.\n- `$LOOP_USER_INPUT` - User feedback provided via `/workflow approve <id> <text>` at an interactive loop gate. Only populated on the first iteration of a resumed interactive loop; empty string on all other iterations.\n- `$REJECTION_REASON` - Reviewer feedback provided via `/workflow reject <id> <reason>` at an approval gate. Only populated in `on_reject` prompts; empty string elsewhere.\n- `$LOOP_PREV_OUTPUT` - Cleaned output of the previous loop iteration (loop nodes only). Empty string on the first iteration (no prior output exists). Useful for `fresh_context: true` loops that need to reference what the previous pass produced or why it failed without carrying full session history.\n\n**Command Types:**\n\n1. **Codebase Commands** (per-repo):\n   - Stored in `.archon/commands/` (plain text/markdown)\n   - Discovered from the repository `.archon/commands/` directory\n   - Surfaced via `GET /api/commands` for the workflow builder and invoked by workflow `command:` nodes\n\n2. **Workflows** (YAML-based):\n   - Stored in `.archon/workflows/` (searched recursively)\n   - Multi-step AI execution chains, discovered at runtime\n   - **`nodes:` (DAG format)**: Nodes with explicit `depends_on` edges; independent nodes in the same topological layer run concurrently. Node types: `command:` (named command file), `prompt:` (inline prompt), `bash:` (shell script, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured), `loop:` (iterative AI prompt until completion signal), `approval:` (human gate; pauses until user approves or rejects; `capture_response: true` stores the user's comment as `$<node-id>.output` for downstream nodes, default false), `script:` (inline TypeScript/Python or named script from `.archon/scripts/`, runs via `bun` or `uv`, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured, supports `deps:` for dependency installation and `timeout:` in ms, requires `runtime: bun` or `runtime: uv`) . Supports `when:` conditions, `trigger_rule` join semantics, `$nodeId.output` substitution, `output_format` for structured JSON output (SDK-enforced on Claude/Codex/OpenCode; best-effort prompt-augmentation + repair on Pi/Copilot — the parsed output is **validated against the declared schema for every provider**, best-effort providers (Pi/Copilot) re-ask up to 3× on a validation miss, and a node that declares `output_format` but returns no schema-valid output **fails** rather than degrading silently; `$nodeId.output.field` access is strict — a field not in the producer's schema, or a schemaless node whose output isn't JSON / lacks the key, fails the consuming node, while an author-declared-optional field resolves to `''`), `allowed_tools`/`denied_tools` for per-node tool restrictions (Claude only), `hooks` for per-node SDK hook callbacks (Claude only), `mcp` for per-node MCP server config files (Claude only, env vars expanded at execution time), and `skills` for per-node skill preloading via AgentDefinition wrapping (Claude only), `agents` for inline sub-agent definitions invokable via the Task tool (Claude only), and `effort`/`thinking`/`maxBudgetUsd`/`systemPrompt`/`fallbackModel`/`betas`/`sandbox` for Claude SDK advanced options (Claude only, also settable at workflow level), and `persist_session` for cross-run provider session continuity (node-level opt-in; workflow-level default via `persist_sessions: true`; requires a provider with the `sessionResume` capability)\n   - Workflow-level `requires: [github]` hard-blocks invocation (before any worktree/clone/AI cost) when the originating user hasn't connected their GitHub identity — enforced only when per-user GitHub is enabled (GitHub App + `TOKEN_ENCRYPTION_KEY`); a no-op for solo PAT installs\n   - Provider inherited from `.archon/config.yaml` unless explicitly set; per-node `provider` and `model` overrides supported\n   - Model and options can be set per workflow or inherited from config defaults\n   - `interactive: true` at the workflow level forces foreground execution on web (required for approval-gate workflows in the web UI)\n   - Model validation ensures provider/model compatibility at load time\n   - Commands: `/workflow list`, `/workflow reload`, `/workflow status`, `/workflow cancel`, `/workflow resume <id>` (re-runs failed workflow, skipping completed nodes), `/workflow abandon <id>`, `/workflow cleanup [days]` (CLI only — deletes old run records), `/workflow reset-sessions <name> [<node-id>]` (clears persisted `persist_session` memory; chat auto-scopes to the current conversation, CLI adds `--scope`/`--yes` for cross-scope control)\n   - Resilient loading: One broken YAML doesn't abort discovery; errors shown in `/workflow list`\n   - `resolveWorkflowName()` (in `router.ts`) resolves workflow names via a 4-tier fallback — exact, case-insensitive, suffix (`-name`), substring — with ambiguity detection; used by both the CLI and all chat platforms\n   - Router fallback: if no `/invoke-workflow` is produced, falls back to `archon-assist` (with \"Routing unclear\" notice); raw AI response returned only when `archon-assist` is unavailable\n   - Claude routing calls use `tools: []` to prevent tool use at the API level; Codex tool bypass is detected and triggers the same fallback\n\n**Defaults:**\n\n- Bundled in `.archon/commands/defaults/` and `.archon/workflows/defaults/`\n- Binary builds: Embedded at compile time (no filesystem access needed) via `packages/workflows/src/defaults/bundled-defaults.generated.ts`\n- Source builds: Loaded from filesystem at runtime\n- Merged with repo-specific commands/workflows (repo overrides defaults by name)\n- Opt-out: Set `defaults.loadDefaultCommands: false` or `defaults.loadDefaultWorkflows: false` in `.archon/config.yaml`\n- **After adding, removing, or editing a default file, run `bun run generate:bundled`** to refresh the embedded bundle. After editing `migrations/000_combined.sql`, run `bun run generate:bundled-schema` to keep the embedded schema in sync. `bun run validate` (and CI) run `check:bundled`, `check:bundled-skill`, and `check:bundled-schema` and will fail loudly if any generated file is stale.\n\n**Home-scoped (\"global\") workflows, commands, and scripts** (user-level, applies to every project):\n\n- Workflows: `~/.archon/workflows/` (or `$ARCHON_HOME/workflows/`)\n- Commands: `~/.archon/commands/` (or `$ARCHON_HOME/commands/`)\n- Scripts: `~/.archon/scripts/` (or `$ARCHON_HOME/scripts/`)\n- Source label: `source: 'global'` on workflows and commands (scripts don't have a source label)\n- Load priority: bundled < global < project (repo overrides global by filename or script name)\n- Subfolders: supported 1 level deep (e.g. `~/.archon/workflows/triage/foo.yaml`). Deeper nesting is ignored silently.\n- Discovery is automatic — `discoverWorkflowsWithConfig(cwd, loadConfig)` and `discoverScriptsForCwd(cwd)` both read home-scoped paths unconditionally; no caller option needed\n- **Migration from pre-0.x `~/.archon/.archon/workflows/`**: if Archon detects files at the old location it emits a one-time WARN with the exact `mv` command and does NOT load from there. Move with: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon`\n- See the docs site at `packages/docs-web/` for details\n\n### Error Handling\n\n**Database Errors:**\n\n```typescript\n// INSERT operations\ntry {\n  await db.query('INSERT INTO conversations ...', params);\n} catch (error) {\n  log.error({ err: error, params }, 'db_insert_failed');\n  throw new Error('Failed to create conversation');\n}\n\n// UPDATE operations - verify rowCount to catch missing records\ntry {\n  await db.updateConversation(conversationId, { codebase_id: codebaseId });\n} catch (error) {\n  // updateConversation throws if no rows matched (conversation not found)\n  log.error({ err: error, conversationId }, 'db_update_failed');\n  throw error; // Re-throw to surface the issue\n}\n```\n\n**Git Operation Errors (don't fail silently):**\n\n```typescript\n// When isolation environment creation fails:\ntry {\n  // ... isolation creation logic ...\n} catch (error) {\n  const err = error as Error;\n  const userMessage = classifyIsolationError(err);\n  log.error({ err, codebaseId, codebaseName }, 'isolation_creation_failed');\n  await platform.sendMessage(conversationId, userMessage);\n}\n```\n\nPattern: Use `classifyIsolationError()` (from `@archon/isolation`) to map git errors (permission denied, timeout, no space, not a git repo) to user-friendly messages. Always log the raw error for debugging and send a classified message to the user.\n\n### API Endpoints\n\n**Web UI REST API** (`packages/server/src/routes/api.ts`):\n\n**Workflow Management:**\n\n- `GET /api/workflows` - List available workflows; optional `?cwd=`; returns `{ workflows: [...], errors?: [...] }`\n- `POST /api/workflows/validate` - Validate a workflow definition in-memory (no save); body: `{ definition: object }`; returns `{ valid: boolean, errors?: string[] }`\n- `GET /api/workflows/:name` - Fetch a single workflow by name; optional `?cwd=` query param; returns `{ workflow, filename, source: 'project' | 'bundled' }`\n- `PUT /api/workflows/:name` - Save (create or update) a workflow YAML; body: `{ definition: object }`; validates before writing; requires `?cwd=` or registered codebase\n- `DELETE /api/workflows/:name` - Delete a user-defined workflow; bundled defaults cannot be deleted\n- `DELETE /api/workflows/:name/node-sessions` - Reset persisted per-node provider sessions; optional `?scope=` and `?node=` narrow the deletion; omitting `?scope=` is a cross-scope wipe and requires `?confirm=all-scopes`; returns `{ success, deleted }`\n\n**Workflow Run Lifecycle:**\n\n- `POST /api/workflows/runs/{runId}/resume` - Resume a failed run from where it left off (skips already-completed DAG nodes; AI session context is not restored).\n- `POST /api/workflows/runs/{runId}/abandon` - Abandon a non-terminal run (marks as cancelled)\n- `DELETE /api/workflows/runs/{runId}` - Delete a terminal workflow run and its events\n\n**Codebases:**\n\n- `GET /api/codebases` / `GET /api/codebases/:id` - List / fetch codebases\n- `POST /api/codebases` - Register a codebase (clone or local path)\n- `DELETE /api/codebases/:id` - Delete a codebase and clean up resources\n- `GET /api/codebases/:id/env` - List env var keys for a codebase (never returns values)\n- `PUT /api/codebases/:id/env` / `DELETE /api/codebases/:id/env/:key` - Upsert / delete a single codebase env var\n- `GET /api/codebases/:id/environments` - List tracked isolation environments for a codebase\n\n**Artifact Files:**\n\n- `GET /api/runs/:runId/artifacts` - List artifact files for a run; walks the on-disk artifact directory (dotfiles skipped) and returns `{ files: [{ path, size, modifiedAt }] }`; 400 on invalid run id or path-escape attempt, 404 if the run does not exist\n- `GET /api/artifacts/:runId/*` - Serve a workflow artifact file by run ID and relative path; returns `text/markdown` for `.md` files, `text/plain` otherwise; 400 on path traversal (`..`), 404 if run or file not found\n\n**Command Listing:**\n\n- `GET /api/commands` - List available command names (bundled + project-defined); optional `?cwd=`; returns `{ commands: [{ name, source: 'bundled' | 'project' }] }`\n\n**Providers:**\n\n- `GET /api/providers` - List registered AI providers; returns `{ providers: [{ id, displayName, capabilities, builtIn }] }`. `capabilities.nativeTools` is `true` for providers that accept in-process native tools (Claude, Pi) — Archon's `manage_run` tool is auto-injected into project-scoped chat for those providers only. `capabilities.structuredOutput` is a tiered union `'enforced' | 'best-effort' | false` (not a boolean): `'enforced'` = SDK/backend grammar-constrained (Claude/Codex/OpenCode), `'best-effort'` = prompt-augmentation + validate (Pi/Copilot), `false` = unsupported.\n\n**Web Auth (opt-in Better Auth; Postgres + `BETTER_AUTH_SECRET`):**\n\n- Better Auth mounts email/password login at `/api/auth/*` (sign-up/sign-in/sign-out/get-session). Mounted only when enabled; the catch-all explicitly falls through for Archon-owned `/api/auth/status` + `/api/auth/github*` paths so they aren't shadowed.\n- `GET /api/auth/status` - Web auth availability + signup posture (no auth required); returns `{ enabled: boolean, signup: 'allowlist' | 'open' | 'disabled' }`. Drives the Web UI login gate.\n- The per-request identity seam is `resolveAuthContext(c): { userId, role } | undefined` (in `routes/api.ts`): Better Auth session first, then the `X-Archon-User` header, then undefined. `resolveWebUserId` delegates to it; `requireWebUser` is the session-aware strict variant (401 missing / 503 backend). `role` rides the canonical user row (default `admin`).\n- **Server-side API gate** (`isApiGateEnabled`): when web auth is enabled, every `/api/*` request must resolve to an identity or gets **401** — except `/api/auth/*` (login surface) and `/api/health*` (healthcheck must stay reachable). `/webhooks/*` and `/internal/*` are outside `/api/*` and untouched. On by default; `ARCHON_WEB_AUTH_REQUIRED=false` keeps login-UI-only. This is what lets Better Auth replace the Caddy `forward_auth` sidecar as the real access boundary.\n- **Signup safety** (`getSignupMode`): with web auth on and no `ARCHON_AUTH_ALLOWED_EMAILS`, signup defaults to **disabled** (login only) + a boot WARN — never silently open. `ARCHON_AUTH_OPEN_SIGNUP=true` opts into open public signup.\n- `GET /api/workflows/runs?mine=true` and `GET /api/conversations?mine=true` - Non-enforcing \"my\" filter (narrows to `ctx.userId` only when an identity resolves; default lists everything). Not a security boundary.\n\n**GitHub Identity (per-user device flow; App mode + `TOKEN_ENCRYPTION_KEY`):**\n\n- `POST /api/auth/github/device/start` - Begin the device flow for the current web user (from `X-Archon-User`); returns `{ device_code, user_code, verification_uri, interval, expires_in }`; 401 if no web-auth header\n- `POST /api/auth/github/device/poll` - Single non-blocking poll; body `{ device_code }`; returns `{ status: 'pending' | 'connected' | 'expired' | 'denied' | 'error', githubLogin?, detail? }`\n- `GET /api/auth/github` - Connection status for the current web user; returns `{ connected, githubLogin }`\n- `DELETE /api/auth/github` - Disconnect the current web user's GitHub identity\n\n**System:**\n\n- `GET /api/health` - Health check with adapter/system status\n- `GET /api/update-check` - Check for available updates; returns `{ updateAvailable, currentVersion, latestVersion, releaseUrl }`; skips GitHub API call for non-binary builds\n\n**OpenAPI Spec:**\n\n- `GET /api/openapi.json` - Generated OpenAPI 3.0 spec for all Zod-validated routes\n\n**Webhooks:**\n\n- `POST /webhooks/github` - GitHub webhook events\n- Signature verification required (HMAC SHA-256)\n- Return 200 immediately, process async\n\n**Internal (App mode only; bind 127.0.0.1):**\n\n- `POST /internal/git-credential` - Git credential helper endpoint. Returns `{token}` for the installation matching the requested host/path. Used by the `git-credential-archon` script in worktree `.git/config` to refresh installation tokens for long-running workflow `git` operations. Hands out installation tokens — MUST NOT be exposed beyond loopback. Server **refuses to start** (not just WARN) if App mode is active and `hostname != 127.0.0.1/localhost`, unless `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1` is set as an opt-in escape hatch for deployments where the reverse proxy already drops `/internal/*`.\n\n**Security:**\n\n- Verify webhook signatures (GitHub: `X-Hub-Signature-256`)\n- Use `c.req.text()` for raw webhook body (signature verification)\n- Never log or expose tokens in responses\n- `/internal/*` paths hand out live credentials — the reverse proxy in production MUST drop them, or the server MUST bind to `127.0.0.1` only.\n\n**@Mention Detection:**\n\n- Parse `@archon` in issue/PR **comments only** (not descriptions)\n- Events: `issue_comment` only\n- Note: Descriptions often contain example commands or documentation - these are NOT command invocations (see #96)\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"Agent rules: read @AGENTS.md\n","category":"root","tokens":8},{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"## Project Overview\n\n**Remote Agentic Coding Platform**: Control AI coding assistants (Claude Code SDK, Codex SDK) remotely from Slack, Telegram, and GitHub. Built with **Bun + TypeScript + SQLite/PostgreSQL**, single-developer tool for AI-assisted development practitioners. Architecture prioritizes simplicity, flexibility, and user control.\n\n## Core Principles\n\n**Single-Developer Tool**\n\n- No multi-tenant complexity\n\n**Platform Agnostic**\n\n- Unified conversation interface across Slack/Telegram/GitHub/cli/web\n- Platform adapters implement `IPlatformAdapter`\n- Stream/batch AI responses in real-time to all platforms\n\n**Type Safety (CRITICAL)**\n\n- Strict TypeScript configuration enforced\n- All functions must have complete type annotations\n- No `any` types without explicit justification\n- Interfaces for all major abstractions\n\n**Zod Schema Conventions**\n\n- Schema naming: camelCase, descriptive suffix (e.g., `workflowRunSchema`, `errorSchema`)\n- Type derivation: always use `z.infer<typeof schema>` — never write parallel hand-crafted interfaces\n- Import `z` from `@hono/zod-openapi` (not from `zod` directly). Exception: `@archon/providers` imports `z` from `zod` directly in `claude/native-tools.ts` — it only builds the Zod shape the Claude SDK's `tool()` expects (never an OpenAPI schema), and being an SDK-deps-only leaf package it must not pull in Hono.\n- Record schemas: always pass an explicit key type — `z.record(z.string(), valueSchema)` — zod v4 dropped the single-arg `z.record(valueSchema)` form\n- All new/modified API routes must use `registerOpenApiRoute(createRoute({...}), handler)` — the local wrapper handles the TypedResponse bypass. Two narrow exceptions exist: (1) routes that serve raw non-JSON content (e.g. `/api/artifacts/:runId/*` returns `text/markdown`/`text/plain`) AND use wildcard path params that OpenAPI 3.0 can't represent, use `app.get(...)` with an explanatory comment; (2) multipart-or-JSON routes (e.g. `/api/conversations/:id/message`, `/api/workflows/:name/run`) register through `registerOpenApiRoute` but drop `request.body` from the route config so Zod doesn't validate multipart payloads against a JSON schema — the handler parses both content types manually.\n- Core row schemas live in `packages/core/src/schemas/` — one file per data shape (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run); `index.ts` re-exports all\n- Route schemas live in `packages/server/src/routes/schemas/` — one file per domain\n- Engine schemas live in `packages/workflows/src/schemas/` — one file per concern (dag-node, workflow, workflow-run, retry, loop, hooks); `index.ts` re-exports all\n- Engine schema naming: camelCase (e.g., `dagNodeSchema`, `workflowBaseSchema`, `nodeOutputSchema`)\n- `TRIGGER_RULES` and `WORKFLOW_HOOK_EVENTS` are derived from schema `.options` — never duplicate as a plain array (exception: `@archon/web` must define a local constant since `api.generated.d.ts` is type-only and cannot export runtime values)\n- `loader.ts` uses `dagNodeSchema.safeParse()` for node validation; graph-level checks (cycles, deps, `$nodeId.output` refs) remain as imperative code in `validateDagStructure()`\n\n**Git Workflow and Releases**\n\n- `main` is the release branch. Never commit directly to `main`.\n- `dev` is the working branch. All feature work branches off `dev` and merges back into `dev`.\n- All PRs must use the template at `.github/pull_request_template.md` — fill in every section. When opening a PR via `gh pr create`, copy the template into the body explicitly; GitHub only auto-applies it through the web UI.\n- Link the issue with `Closes #<number>` (or `Fixes` / `Resolves`) in the PR description so it auto-closes on merge.\n- To release, use the `/release` skill. It compares `dev` to `main`, generates changelog entries, bumps the version, and creates a PR to merge `dev` into `main`.\n- Releases follow Semantic Versioning: `/release` (patch), `/release minor`, `/release major`.\n- Changelog lives in `CHANGELOG.md` and follows Keep a Changelog format.\n- Version is the single `version` field in the root `package.json`.\n\n**Git as First-Class Citizen**\n\n- Let git handle what git does best (conflicts, uncommitted changes, branch management)\n- Surface git errors to users for actionable issues (conflicts, uncommitted changes)\n- Handle expected failure cases gracefully (missing directories during cleanup)\n- Trust git's natural guardrails (e.g., refuse to remove worktree with uncommitted changes)\n- Use `@archon/git` functions for git operations; use `execFileAsync` (not `exec`) when calling git directly\n- Worktrees enable parallel development per conversation without branch conflicts\n- Workspaces automatically sync with origin before worktree creation (ensures latest code)\n- **NEVER run `git clean -fd`** - it permanently deletes untracked files (use `git checkout .` instead)\n\n## Engineering Principles\n\nThese are implementation constraints, not slogans. Apply them by default.\n\n**KISS — Keep It Simple, Stupid**\n\n- Prefer straightforward control flow over clever meta-programming\n- Prefer explicit branches and typed interfaces over hidden dynamic behavior\n- Keep error paths obvious and localized\n\n**YAGNI — You Aren't Gonna Need It**\n\n- Do not add config keys, interface methods, feature flags, or workflow branches without a concrete accepted use case\n- Do not introduce speculative abstractions without at least one current caller\n- Keep unsupported paths explicit (error out) rather than adding partial fake support\n\n**DRY + Rule of Three**\n\n- Duplicate small, local logic when it preserves clarity\n- Extract shared utilities only after the same pattern appears at least three times and has stabilized\n- When extracting, preserve module boundaries and avoid hidden coupling\n\n**SRP + ISP — Single Responsibility + Interface Segregation**\n\n- Keep each module and package focused on one concern\n- Extend behavior by implementing existing narrow interfaces (`IPlatformAdapter`, `IAgentProvider`, `IDatabase`, `IWorkflowStore`) whenever possible\n- Avoid fat interfaces and \"god modules\" that mix policy, transport, and storage\n- Do not add unrelated methods to an existing interface — define a new one\n\n**Fail Fast + Explicit Errors** — Silent fallback in agent runtimes can create unsafe or costly behavior\n\n- Prefer throwing early with a clear error for unsupported or unsafe states — never silently swallow errors\n- Never silently broaden permissions or capabilities\n- Document fallback behavior with a comment when a fallback is intentional and safe; otherwise throw\n\n**No Autonomous Lifecycle Mutation Across Process Boundaries**\n\n- When a process cannot reliably distinguish \"actively running elsewhere\" from \"orphaned by a crash\" — typically because the work was started by a different process or input source (CLI, adapter, webhook, web UI, cron) — it must not autonomously mark that work as failed/cancelled/abandoned based on a timer or staleness guess.\n- Surface the ambiguous state to the user and provide a one-click action.\n- Heuristics for _recoverable_ operations (retry backoff, subprocess timeouts, hygiene cleanup of terminal-status data) remain appropriate; the rule is about destructive mutation of _non-terminal_ state owned by an unknowable other party.\n- Reference: #1216 and the CLI orphan-cleanup precedent at `packages/cli/src/cli.ts:256-258`.\n\n**Determinism + Reproducibility**\n\n- Prefer reproducible commands and locked dependency behavior in CI-sensitive paths\n- Keep tests deterministic — no flaky timing or network dependence without guardrails\n- Ensure local validation commands (`bun run validate`) map directly to CI expectations\n\n**Reversibility + Rollback-First Thinking**\n\n- Keep changes easy to revert: small scope, clear blast radius\n- For risky changes, define the rollback path before merging\n- Avoid mixed mega-patches that block safe rollback\n\n## Essential Commands\n\n### Development\n\n```bash\n# Start server + Web UI together (hot reload for both)\nbun run dev\n\n# Or start individually\nbun run dev:server  # Backend only (port 3090)\nbun run dev:web     # Frontend only (port 5173)\n```\n\nRegenerating frontend API types (requires server to be running at port 3090):\n\n```bash\nbun run dev:server  # must be running first\nbun --filter @archon/web generate:types\n```\n\nOptional: Use PostgreSQL instead of SQLite by setting `DATABASE_URL` in `.env`:\n\n```bash\ndocker-compose --profile with-db up -d postgres\n# Set DATABASE_URL=postgresql://postgres:postgres@localhost:5432/remote_coding_agent in .env\n```\n\n### Testing\n\n```bash\nbun run test                # Run all tests (per-package, isolated processes)\nbun test --watch            # Watch mode (single package)\nbun test packages/core/src/handlers/command-handler.test.ts  # Single file\n```\n\n**Test isolation (mock.module pollution):** Bun's `mock.module()` permanently replaces modules in the process-wide cache — `mock.restore()` does NOT undo it ([oven-sh/bun#7823](https://github.com/oven-sh/bun/issues/7823)). To prevent cross-file pollution, packages that have conflicting `mock.module()` calls split their tests into separate `bun test` invocations: `@archon/core` (20 batches), `@archon/workflows` (5), `@archon/adapters` (6), `@archon/isolation` (3). See each package's `package.json` for the exact splits.\n\n**Do NOT run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures. Always use `bun run test` (which uses `bun --filter '*' test` for per-package isolation).\n\n### Type Checking & Linting\n\n```bash\nbun run type-check\nbun run lint\nbun run lint:fix\nbun run format\nbun run format:check\n```\n\n### Pre-PR Validation\n\n**Always run before creating a pull request:**\n\n```bash\nbun run validate\n```\n\nThis runs `check:bundled`, `check:bundled-skill`, `check:bundled-schema`, type-check, lint, format check, and tests. All seven must pass for CI to succeed.\n\n### ESLint Guidelines\n\n**Zero-tolerance policy**: CI enforces `--max-warnings 0`. No warnings allowed.\n\n**When to use inline disable comments** (`// eslint-disable-next-line`):\n\n- **Almost never** - fix the issue instead\n- Only acceptable when:\n  1. External SDK types are incorrect (document which SDK and why)\n  2. Intentional type assertion after validation (must include comment explaining the validation)\n\n**Never acceptable:**\n\n- Disabling `no-explicit-any` without justification\n- Disabling rules to \"make CI pass\"\n- Bulk disabling at file level (`/* eslint-disable */`)\n\n### Database\n\n**Auto-Detection (SQLite is the default — zero setup):**\n\n- **Without `DATABASE_URL`**: Uses SQLite at `~/.archon/archon.db` (auto-initialized, recommended for most users)\n- **With `DATABASE_URL` set**: Uses PostgreSQL (schema auto-applied on startup; no manual `psql` needed). The Postgres adapter runs the idempotent `migrations/000_combined.sql` inside an advisory-lock transaction on first connection, so upgrades that add tables or columns converge automatically.\n\n### CLI (Command Line)\n\nRun workflows directly from the command line without needing the server. Workflow and isolation commands require running from within a git repository (subdirectories work - resolves to repo root).\n\n```bash\n# List available workflows (requires git repo)\nbun run cli workflow list\n\n# Machine-readable JSON output\nbun run cli workflow list --json\n\n# Run a workflow\nbun run cli workflow run assist \"What does the orchestrator do?\"\n\n# Run in a specific directory\nbun run cli workflow run plan --cwd /path/to/repo \"Add dark mode\"\n\n# Default: auto-creates worktree with generated branch name (isolation by default)\nbun run cli workflow run implement \"Add auth\"\n\n# Explicit branch name for the worktree\nbun run cli workflow run implement --branch feature-auth \"Add auth\"\n\n# Opt out of isolation (run in live checkout)\nbun run cli workflow run quick-fix --no-worktree \"Fix typo\"\n\n# Run in a detached background child (returns immediately; find it via `workflow runs`)\nbun run cli workflow run implement \"Add auth\" --detach\n\n# Show active runs (running + paused)\nbun run cli workflow status\n\n# List recent runs of ALL statuses, scoped to this project's codebase (cwd)\nbun run cli workflow runs\nbun run cli workflow runs --json                 # machine-readable { runs, total, counts }\nbun run cli workflow runs --status failed --limit 50\nbun run cli workflow runs --all                  # across all projects\n\n# Show detail for one run (any status); --verbose adds per-node summary\nbun run cli workflow get <run-id>\nbun run cli workflow get <run-id> --json\n\n# Resume a failed workflow (re-runs, skipping completed nodes)\nbun run cli workflow resume <run-id>\n\n# Discard a non-terminal run\nbun run cli workflow abandon <run-id>\n\n# Most read/write subcommands accept --json for machine-readable output:\n#   list, status, runs, get, approve, reject, abandon, resume.\n# For approve/reject/resume, --json records/validates the decision and returns a\n# clean JSON line WITHOUT the inline auto-resume (drive continuation separately).\n\n# Delete old workflow run records (default: 7 days)\nbun run cli workflow cleanup\nbun run cli workflow cleanup 30  # Custom days\n\n# Clear persisted per-node AI sessions for a workflow (persist_session memory)\n# Without --scope, wipes every scope and requires --yes; --node narrows to one node\nbun run cli workflow reset-sessions <workflow-name> [--scope <key>] [--node <id>] [--yes] [--json]\n\n# Emit a workflow event (used inside workflow loop prompts)\nbun run cli workflow event emit --run-id <uuid> --type <event-type> [--data <json>]\n\n# List active worktrees/environments\nbun run cli isolation list\n\n# Clean up stale environments (default: 7 days)\nbun run cli isolation cleanup\nbun run cli isolation cleanup 14  # Custom days\n\n# Clean up environments with branches merged into main (also deletes remote branches)\nbun run cli isolation cleanup --merged\n\n# Also remove environments with closed (abandoned) PRs\nbun run cli isolation cleanup --merged --include-closed\n\n# Validate workflow definitions and their referenced resources\nbun run cli validate workflows              # All workflows\nbun run cli validate workflows my-workflow  # Single workflow\nbun run cli validate workflows my-workflow --json  # Machine-readable output\n\n# Validate command files\nbun run cli validate commands               # All commands\nbun run cli validate commands my-command    # Single command\n\n# Complete branch lifecycle (remove worktree + local/remote branches)\nbun run cli complete <branch-name>\nbun run cli complete <branch-name> --force  # Skip uncommitted-changes check\n\n# Start the web UI server (compiled binary only, downloads web UI on first run)\nbun run cli serve\nbun run cli serve --port 4000\nbun run cli serve --download-only  # Download without starting\n\n# Install the bundled Archon skill into a project\nbun run cli skill install\nbun run cli skill install /path/to/project\n\n# Verify your Archon setup (Claude binary, gh auth, DB, adapters)\nbun run cli doctor\n\n# Connect your GitHub identity via device flow (multi-user installs only:\n# App mode + TOKEN_ENCRYPTION_KEY). Identity from ARCHON_USER_ID or $USER.\nbun run cli auth github\n\n# Inspect or rotate the anonymous telemetry install UUID\nbun run cli telemetry status\nbun run cli telemetry reset\n\n# Show version\nbun run cli version\n```\n\n## Architecture\n\n### Directory Structure\n\n**Monorepo Layout (Bun Workspaces):**\n\n```\npackages/\n├── cli/                      # @archon/cli - Command-line interface\n│   └── src/\n│       ├── adapters/         # CLI adapter (stdout output)\n│       ├── commands/         # CLI command implementations\n│       └── cli.ts            # CLI entry point\n├── providers/                # @archon/providers - AI agent providers (SDK deps live here)\n│   └── src/\n│       ├── types.ts          # Contract layer (IAgentProvider, SendQueryOptions, MessageChunk — ZERO SDK deps)\n│       ├── registry.ts       # Typed provider registry (ProviderRegistration records)\n│       ├── errors.ts         # UnknownProviderError\n│       ├── claude/           # ClaudeProvider + parseClaudeConfig + MCP/hooks/skills translation\n│       ├── codex/            # CodexProvider + parseCodexConfig + binary-resolver\n│       ├── community/pi/     # PiProvider (builtIn: false) — @earendil-works/pi-coding-agent, ~20 LLM backends\n│       ├── community/opencode/ # OpenCodeProvider (builtIn: false) — @archon/opencode SDK, local embedded runtime\n│       └── index.ts          # Package exports\n├── core/                     # @archon/core - Shared business logic\n│   └── src/\n│       ├── config/           # YAML config loading\n│       ├── db/               # Database connection, queries\n│       ├── handlers/         # Command handler (slash commands)\n│       ├── orchestrator/     # AI conversation management\n│       ├── services/         # Background services (cleanup)\n│       ├── schemas/          # Zod row schemas for core data shapes (conversation, message, user, codebase, session, workflow-event, env-var, workflow-run)\n│       ├── state/            # Session state machine\n│       ├── types/            # TypeScript types and interfaces\n│       ├── utils/            # Shared utilities\n│       ├── workflows/        # Store adapter (createWorkflowStore) bridging core DB → IWorkflowStore\n│       └── index.ts          # Package exports\n├── workflows/                # @archon/workflows - Workflow engine (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── schemas/          # Zod schemas for engine types\n│       ├── loader.ts         # YAML parsing + validation (parseWorkflow)\n│       ├── workflow-discovery.ts # Workflow filesystem discovery (discoverWorkflows, discoverWorkflowsWithConfig)\n│       ├── executor-shared.ts # Shared executor infrastructure (error classification, variable substitution)\n│       ├── router.ts         # Prompt building + invocation parsing\n│       ├── executor.ts       # Workflow execution orchestrator (executeWorkflow)\n│       ├── dag-executor.ts   # DAG-specific execution logic\n│       ├── store.ts          # IWorkflowStore interface (database abstraction)\n│       ├── deps.ts           # WorkflowDeps injection types (IWorkflowPlatform, imports from @archon/providers/types)\n│       ├── event-emitter.ts  # Workflow observability events\n│       ├── logger.ts         # JSONL file logger\n│       ├── validator.ts      # Resource validation (command files, MCP configs, skill dirs)\n│       ├── defaults/         # Bundled default commands and workflows\n│       └── utils/            # Variable substitution, tool formatting, execution utilities\n├── git/                      # @archon/git - Git operations (no @archon/core dep)\n│   └── src/\n│       ├── branch.ts         # Branch operations (checkout, merge detection, etc.)\n│       ├── exec.ts           # execFileAsync and mkdirAsync wrappers\n│       ├── repo.ts           # Repository operations (clone, sync, remote URL)\n│       ├── types.ts          # Branded types (RepoPath, BranchName, etc.)\n│       ├── worktree.ts       # Worktree operations (create, remove, list)\n│       └── index.ts          # Package exports\n├── isolation/                # @archon/isolation - Worktree isolation (depends on @archon/git + @archon/paths)\n│   └── src/\n│       ├── types.ts          # Isolation types and interfaces\n│       ├── errors.ts         # Error classifiers (classifyIsolationError, IsolationBlockedError)\n│       ├── factory.ts        # Provider factory (getIsolationProvider, configureIsolation)\n│       ├── resolver.ts       # IsolationResolver (request → environment resolution)\n│       ├── store.ts          # IIsolationStore interface\n│       ├── worktree-copy.ts  # File copy utilities for worktrees\n│       ├── providers/\n│       │   └── worktree.ts   # WorktreeProvider implementation\n│       └── index.ts          # Package exports\n├── paths/                    # @archon/paths - Path resolution and logger (zero @archon/* deps)\n│   └── src/\n│       ├── archon-paths.ts   # Archon directory path utilities\n│       ├── logger.ts         # Pino logger factory\n│       └── index.ts          # Package exports\n├── adapters/                 # @archon/adapters - Platform adapters (Slack, Telegram, GitHub, Discord)\n│   └── src/\n│       ├── chat/             # Chat platform adapters (Slack, Telegram)\n│       ├── forge/            # Forge adapters (GitHub)\n│       ├── community/        # Community adapters (Discord)\n│       ├── utils/            # Shared adapter utilities (message splitting)\n│       └── index.ts          # Package exports\n├── server/                   # @archon/server - HTTP server + Web adapter\n│   └── src/\n│       ├── adapters/         # Web platform adapter (SSE streaming)\n│       ├── routes/           # API routes (REST + SSE)\n│       └── index.ts          # Hono server entry point\n└── web/                      # @archon/web - React frontend (Web UI)\n    └── src/\n        ├── components/       # React components (chat, layout, projects, ui, workflows)\n        ├── hooks/            # Custom hooks (useSSE, etc.)\n        ├── lib/              # API client, types, utilities\n        ├── stores/           # Zustand stores (workflow-store)\n        ├── routes/           # Route pages (ChatPage, WorkflowsPage, WorkflowBuilderPage, etc.)\n        ├── experiments/      # Isolated in-repo spikes; lint-guarded against\n        │   │                 # importing production web modules. Drop-in or\n        │   │                 # delete cleanly. See experiments/README.md.\n        │   └── console/      # Run-centric console UI mounted at /console\n        └── App.tsx           # Router + layout\n```\n\n**Import Patterns:**\n\n**IMPORTANT**: Always use typed imports - never use generic `import *` for the main package.\n\n```typescript\n// ✅ CORRECT: Use `import type` for type-only imports\nimport type { IPlatformAdapter, Conversation, MergedConfig } from '@archon/core';\n\n// ✅ CORRECT: Use specific named imports for values\nimport { handleMessage, ConversationLockManager, pool } from '@archon/core';\n\n// ✅ CORRECT: Namespace imports for submodules with many exports\nimport * as conversationDb from '@archon/core/db/conversations';\nimport * as git from '@archon/git';\n\n// ✅ CORRECT: Import workflow engine types/functions from direct subpaths\nimport type { WorkflowDeps } from '@archon/workflows/deps';\nimport type { IWorkflowStore } from '@archon/workflows/store';\nimport type { WorkflowDefinition } from '@archon/workflows/schemas/workflow';\nimport { executeWorkflow } from '@archon/workflows/executor';\nimport { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery';\nimport { findWorkflow } from '@archon/workflows/router';\n\n// ❌ WRONG: Never use generic import for main package\nimport * as core from '@archon/core'; // Don't do this\n\n// ❌ WRONG: In @archon/web, never import from @archon/workflows (it's a server package)\nimport type { DagNode } from '@archon/workflows/schemas/dag-node'; // Don't do this from @archon/web\n// ✅ CORRECT: Use re-exports from api.ts (derived from generated OpenAPI spec)\nimport type { DagNode, WorkflowDefinition } from '@/lib/api';\n```\n\n### Database Schema\n\n**16 Tables (all prefixed with `remote_agent_`):**\n\n1. **`codebases`** - Repository metadata and commands (JSONB)\n2. **`conversations`** - Track platform conversations with titles and soft-delete support; nullable `user_id` records first creator\n3. **`sessions`** - Track AI SDK sessions with resume capability\n4. **`isolation_environments`** - Git worktree isolation tracking; nullable `created_by_user_id` preserves first creator\n5. **`workflow_runs`** - Workflow execution tracking and state; nullable `user_id` for per-run attribution\n6. **`workflow_events`** - Step-level workflow event log (step transitions, artifacts, errors)\n7. **`messages`** - Conversation message history with tool call metadata (JSONB); nullable `user_id` (NULL for assistant rows)\n8. **`codebase_env_vars`** - Per-project env vars injected into project-scoped execution surfaces (Claude, Codex, bash/script nodes, and direct chat when codebase-scoped), managed via Web UI or `env:` in config\n9. **`users`** - Archon-internal identity (one row per human/bot); created lazily on first sight by any adapter; `role` (`'admin'`(default)`/'member'`) is the identity seam for future per-resource scoping (visibility stays open today)\n10. **`user_identities`** - Per-platform mapping (Slack U-id, Telegram chat id, Discord snowflake, GitHub login, Better Auth web user id) → `users.id`; `UNIQUE(platform, platform_user_id)`\n11. **`workflow_node_sessions`** - Per-node provider session IDs persisted across workflow re-runs (opt-in via `persist_session`); keyed by `(workflow_name, node_id, scope_key, provider)`; `scope_key` is typically the conversation UUID\n12. **`user_github_tokens`** - Per-user GitHub device-flow tokens encrypted at rest (AES-256-GCM); one row per Archon user (`UNIQUE(user_id)`), cascades on user deletion; numeric `github_user_id` anchors the commit no-reply email\n    13–16. **`remote_agent_auth_user` / `remote_agent_auth_session` / `remote_agent_auth_account` / `remote_agent_auth_verification`** - Better Auth tables for opt-in web login (**PostgreSQL only**; always created on Postgres via the idempotent schema apply, but populated only when web auth is enabled — `DATABASE_URL` + `BETTER_AUTH_SECRET`). Owned and shaped by Better Auth (text ids, camelCase columns); Archon never queries them directly — a session maps to the canonical `users` row via `user_identities('web', <betterAuthUserId>)`\n\n**Key Patterns:**\n\n- Conversation ID format: Platform-specific (`thread_ts`, `chat_id`, `user/repo#123`)\n- One active session per conversation\n- Codebase commands stored in filesystem, paths in `codebases.commands` JSONB\n\n**Session Transitions:**\n\n- Sessions are immutable - transitions create new linked sessions\n- Each transition has explicit `TransitionTrigger` reason (first-message, plan-to-execute, reset-requested, etc.)\n- Audit trail: `parent_session_id` links to previous session, `transition_reason` records why\n- Only plan→execute creates new session immediately; other triggers deactivate current session\n\n### Architecture Layers\n\n**Package Split:**\n\n- **@archon/paths**: Path resolution utilities, Pino logger factory, web dist cache path (`getWebDistDir`), CWD env stripper (`stripCwdEnv`, `strip-cwd-env-boot`) (no @archon/\\* deps; `pino` and `dotenv` are allowed external deps)\n- **@archon/git**: Git operations - worktrees, branches, repos, exec wrappers (depends only on @archon/paths)\n- **@archon/providers**: AI agent providers (Claude, Codex, Pi community) — owns SDK deps, `IAgentProvider` interface, `sendQuery()` contract, and provider-specific option translation. `@archon/providers/types` is the contract subpath (zero SDK deps, zero runtime side effects) that `@archon/workflows` imports from. Providers receive raw `nodeConfig` + `assistantConfig` and translate to SDK-specific options internally. Core providers live under `claude/` and `codex/`; community providers live under `community/` (currently `community/pi/`, registered with `builtIn: false`).\n- **@archon/isolation**: Worktree isolation types, providers, resolver, error classifiers (depends only on @archon/git + @archon/paths)\n- **@archon/workflows**: Workflow engine - loader, router, executor, DAG, logger, bundled defaults (depends only on @archon/git + @archon/paths + @archon/providers/types + @hono/zod-openapi + zod; DB/AI/config injected via `WorkflowDeps`)\n- **@archon/cli**: Command-line interface for running workflows and starting the web UI server (depends on @archon/server + @archon/adapters for the serve command)\n- **@archon/core**: Business logic, database, orchestration (depends on @archon/providers for AI and @hono/zod-openapi for core Zod schemas; provides `createWorkflowStore()` adapter bridging core DB → `IWorkflowStore`)\n- **@archon/adapters**: Platform adapters for Slack, Telegram, GitHub, Discord (depends on @archon/core)\n- **@archon/server**: OpenAPIHono HTTP server (Zod + OpenAPI spec generation via `@hono/zod-openapi`), Web adapter (SSE), API routes, Web UI static serving (depends on @archon/adapters)\n- **@archon/web**: React frontend (Vite + Tailwind v4 + shadcn/ui + Zustand), SSE streaming to server. `WorkflowRunStatus`, `WorkflowDefinition`, and `DagNode` are all derived from `src/lib/api.generated.d.ts` (generated from the OpenAPI spec via `bun generate:types`; never import from `@archon/workflows`)\n\n**1. Platform Adapters**\n\n- Implement `IPlatformAdapter` interface\n- Handle platform-specific message formats\n- **Web** (`packages/server/src/adapters/web/`): Server-Sent Events (SSE) streaming, conversation ID = user-provided string\n- **Slack** (`packages/adapters/src/chat/slack/`): SDK with polling (not webhooks), conversation ID = `thread_ts`\n- **Telegram** (`packages/adapters/src/chat/telegram/`): Bot API with polling, conversation ID = `chat_id`\n- **GitHub** (`packages/adapters/src/forge/github/`): Webhooks + GitHub CLI, conversation ID = `owner/repo#number`\n- **Discord** (`packages/adapters/src/community/chat/discord/`): discord.js WebSocket, conversation ID = channel ID\n\n**Adapter Authorization Pattern:**\n\n- Auth checks happen INSIDE adapters (encapsulation, consistency)\n- Auth utilities co-located with each adapter (e.g., `packages/adapters/src/chat/slack/auth.ts`)\n- Parse whitelist from env var in constructor (e.g., `TELEGRAM_ALLOWED_USER_IDS`)\n- Check authorization in message handler (before calling `onMessage` callback)\n- Silent rejection for unauthorized users (no error response)\n- Log unauthorized attempts with masked user IDs for privacy\n- Adapters expose `onMessage(handler)` callback; errors handled by caller\n\n**2. Command Handler** (`packages/core/src/handlers/`)\n\n- Process slash commands (deterministic, no AI)\n- The orchestrator treats only these top-level commands as deterministic: `/help`, `/status`, `/reset`, `/workflow`, `/register-project`, `/update-project`, `/remove-project`, `/commands`, `/init`, `/worktree`\n- `/workflow` handles subcommands like `list`, `run`, `status`, `cancel`, `resume`, `abandon`, `approve`, `reject`, `reset-sessions`\n- Update database, perform operations, return responses\n\n**3. Orchestrator** (`packages/core/src/orchestrator/`)\n\n- Manage AI conversations\n- Load conversation + codebase context from database\n- Variable substitution: `$1`, `$2`, `$3`, `$ARGUMENTS`\n- Session management: Create new or resume existing\n- Stream AI responses to platform\n- System prompt gets a \"Managing Workflow Runs\" section (`buildRunManagementSection` in `prompt-builder.ts`) teaching the chat agent to drive run management (`archon workflow runs/get/status/run --detach/approve/reject/abandon`) directly via bash. It is appended **only for project-scoped chats on providers without the native `manage_run` tool** (Codex/OpenCode/Copilot) — gated in `orchestrator-agent.ts` on `!scopedCaps.nativeTools`. Claude and Pi instead receive the in-process `manage_run` native tool (the prompt section would be redundant for them). This is the CLI-bash delivery path for providers that have neither native tools nor `skills:` (direct chat doesn't consume the `skills:` option — it is workflow-node-only).\n\n**4. AI Agent Providers** (`packages/providers/src/`)\n\n- Implement `IAgentProvider` interface\n- **ClaudeProvider**: `@anthropic-ai/claude-agent-sdk`\n- **CodexProvider**: `@openai/codex-sdk`\n- **PiProvider** (community, `builtIn: false`): `@earendil-works/pi-coding-agent` — one harness for ~20 LLM backends via `<provider>/<model>` refs (e.g. `anthropic/claude-haiku-4-5`, `openrouter/qwen/qwen3-coder`); supports extensions, skills, tool restrictions, thinking level, best-effort structured output. See `packages/docs-web/src/content/docs/getting-started/ai-assistants.md` for setup, capability matrix, and extension config.\n- Streaming: `for await (const event of events) { await platform.send(event) }`\n\n### Configuration\n\n**Environment Variables:**\n\nsee .env.example\nsee .archon/config.yaml setup as needed\n\n**Assistant Defaults:**\n\nThe system supports configuring default models and options per assistant in `.archon/config.yaml`:\n\n```yaml\nassistants:\n  claude:\n    model: sonnet # or 'opus', 'haiku', 'claude-*', 'inherit'\n    settingSources: # Controls which CLAUDE.md, skills, commands, and agents the SDK loads\n      - project # Project-level <cwd>/.claude/ (included in default)\n      - user # User-level ~/.claude/ (included in default; omit both to restrict to project-only)\n    claudeBinaryPath:\n      /absolute/path/to/claude # Optional: Claude Code executable.\n      # Native binary (curl installer at\n      # ~/.local/bin/claude), npm cli.js, or\n      # the npm platform-package directory\n      # (e.g. @anthropic-ai/claude-code-win32-x64)\n      # which is auto-expanded to claude/claude.exe.\n      # Required in compiled binaries if\n      # CLAUDE_BIN_PATH env var is not set.\n  codex:\n    model: gpt-5.6-sol\n    modelReasoningEffort: medium # 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'\n    webSearchMode: live # 'disabled' | 'cached' | 'live'\n    additionalDirectories:\n      - /absolute/path/to/other/repo\n    codexBinaryPath: /usr/local/bin/codex # Optional: custom Codex CLI binary path\n\n\n# docs:\n#   path: docs  # Optional: default is docs/\n```\n\n**Configuration Priority:**\n\n1. Workflow-level options (in YAML `model`, `modelReasoningEffort`, etc.)\n2. Config file defaults (`.archon/config.yaml` `assistants.*`)\n3. SDK defaults\n\n**Model Validation:**\n\n- Workflows are validated at load time for provider _identity_ only — `provider:` (workflow-level and per-node) must be a registered provider id, otherwise the YAML is rejected with `Unknown provider '<id>'. Registered: claude, codex, pi`.\n- Model strings are NOT validated by Archon. Whatever the user writes in `model:` is forwarded verbatim to the resolved SDK. Vendor SDKs ship new models faster than Archon can update; the SDK and the upstream API are the source of truth for what names exist.\n- Provider is resolved via an explicit chain: `node.provider ?? workflow.provider ?? config.assistant`. Model never influences provider selection.\n\n### Running the App in Worktrees\n\nAgents working in worktrees can run the app for self-testing (make changes → run app → test via curl → fix). Ports are automatically allocated to avoid conflicts:\n\n```bash\n# Run in worktree (port auto-allocated based on path)\nbun dev &\n# [Hono] Worktree detected (/path/to/worktree)\n# [Hono] Auto-allocated port: 3637 (base: 3090, offset: +547)\n\n# Test via web API (production path)\n# 1) Create a conversation\ncurl -X POST http://localhost:3637/api/conversations \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'\n\n# 2) Send a message\ncurl -X POST http://localhost:3637/api/conversations/<conversationId>/message \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"/status\"}'\n\n# 3) Fetch messages (polling)\ncurl http://localhost:3637/api/conversations/<conversationId>/messages\n\n# Note: SSE streaming is available at /api/stream/<conversationId>\n```\n\n**Port Allocation:**\n\n- Worktrees: Automatic unique port (3190-4089 range, hash-based on path)\n- Main repo: Default 3090\n- Override: `PORT=4000 bun dev` (works in both contexts)\n- Same worktree always gets same port (deterministic)\n\n**Important:**\n\n- Use the web API routes for manual validation (avoid running multiple platform adapters)\n- Database is shared (same conversations/codebases available)\n- Kill the server when done: `pkill -f \"bun.*dev\"` or use the specific port\n\n### Archon Directory Structure\n\n**User-level (`~/.archon/`):**\n\n```\n~/.archon/\n├── workspaces/owner/repo/        # Project-centric layout\n│   ├── source/                   # Cloned repo or symlink → local path\n│   ├── worktrees/                # Git worktrees for this project\n│   ├── artifacts/                # Workflow artifacts (NEVER in git)\n│   │   ├── runs/{id}/            # Per-run artifacts ($ARTIFACTS_DIR)\n│   │   └── uploads/{convId}/     # Web UI file uploads (ephemeral)\n│   └── logs/                     # Workflow execution logs\n├── vendor/codex/                  # Codex native binary (binary builds, user-placed)\n├── web-dist/<version>/            # Cached web UI dist (archon serve, binary only)\n├── update-check.json              # Update check cache (binary builds, 24h TTL)\n├── archon.db                     # SQLite database (when DATABASE_URL not set)\n└── config.yaml                   # Global configuration (non-secrets)\n```\n\n**Repo-level (`.archon/` in any repository):**\n\n```\n.archon/\n├── commands/       # Custom commands\n├── workflows/      # Workflow definitions (YAML files)\n├── scripts/        # Named scripts for script: nodes (.ts/.js for bun, .py for uv)\n├── state/          # Cross-run workflow state (gitignored — never in git)\n└── config.yaml     # Repo-specific configuration\n```\n\n- `ARCHON_HOME` - Override the base directory (default: `~/.archon`)\n- Docker: Paths automatically set to `/.archon/`\n\n## Development Guidelines\n\n### UI and Visual Design\n\nAll UI changes — production web (`packages/web/`), experiments (`packages/web/src/experiments/`), the docs site, marketing surfaces, and any future visual surface — must align with the Archon brand foundation.\n\n- **Canonical brand guide:** https://archon.diy/brand/ (source: `packages/docs-web/src/content/docs/brand/index.md` + `packages/docs-web/public/brand/foundation.html`).\n- **Use brand tokens, not ad-hoc values.** Colors, gradients, surfaces, and typography must come from the established design tokens (`packages/web/src/index.css`) or the brand guide. Don't hard-code hex values that aren't in the system.\n- **Introducing a new visual token** (color, font, radius, spacing) means updating both the token source and the brand guide. Don't fork the palette per package.\n- **When in doubt, consult the brand guide first** before inventing new visual treatments. Open a discussion if the guide doesn't cover your case.\n\n### When Creating New Features\n\n**Quick reference:**\n\n- **Platform Adapters**: Implement `IPlatformAdapter`, handle auth, polling/webhooks\n- **AI Providers**: Implement `IAgentProvider`, session management, streaming\n- **Slash Commands**: Add to command-handler.ts, update database, no AI\n- **Database Operations**: Use `IDatabase` interface (supports PostgreSQL and SQLite via adapters)\n- **Plan insertion points**: Use stable text anchors (e.g., \"after the `it('throws on ...')` test block\"), never raw line numbers — line numbers drift on every preceding edit.\n\n### SDK Type Patterns\n\nWhen working with external SDKs (Claude Agent SDK, Codex SDK), prefer importing and using SDK types directly:\n\n```typescript\n// ✅ CORRECT - Import SDK types directly\nimport { query, type Options } from '@anthropic-ai/claude-agent-sdk';\n\nconst options: Options = {\n  cwd,\n  permissionMode: 'bypassPermissions',\n  // ...\n};\n\n// Use type assertions for SDK response structures\nconst message = msg as { message: { content: ContentBlock[] } };\n```\n\n```typescript\n// ❌ AVOID - Defining duplicate types\ninterface MyQueryOptions {  // Don't duplicate SDK types\n  cwd: string;\n  // ...\n}\nconst options: MyQueryOptions = { ... };\nquery({ prompt, options: options as any });  // Avoid 'as any'\n```\n\nThis ensures type compatibility with SDK updates and eliminates `as any` casts.\n\n### Testing\n\n**Unit Tests:**\n\n- Test pure functions (variable substitution, command parsing)\n- Mock external dependencies (database, AI SDKs, platform APIs)\n\n**Integration Tests:**\n\n- Test database operations with test database\n- Test end-to-end flows (mock platforms/AI but use real orchestrator)\n- Clean up test data after each test\n\n**Mock isolation rules (IMPORTANT):**\n\n- Bun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it\n- Do NOT add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()`, ensure its package.json test script runs it in a separate `bun test` invocation from any conflicting files\n\n**Manual Validation:** Use the web API (`curl`) or CLI commands directly for end-to-end testing of new features.\n\n### Logging\n\n**Structured logging with Pino** (`packages/paths/src/logger.ts`):\n\n```typescript\nimport { createLogger } from '@archon/paths';\n\nconst log = createLogger('orchestrator');\n\n// Event naming: {domain}.{action}_{state}\n// Standard states: _started, _completed, _failed, _validated, _rejected\nasync function createSession(conversationId: string, codebaseId: string) {\n  log.info({ conversationId, codebaseId }, 'session.create_started');\n\n  try {\n    const session = await doCreate();\n    log.info({ conversationId, codebaseId, sessionId: session.id }, 'session.create_completed');\n    return session;\n  } catch (e) {\n    const err = e as Error;\n    log.error(\n      { conversationId, error: err.message, errorType: err.constructor.name, err },\n      'session.create_failed'\n    );\n    throw err;\n  }\n}\n```\n\n**Event naming rules:**\n\n- Format: `{domain}.{action}_{state}` — e.g. `workflow.step_started`, `isolation.create_failed`\n- Avoid generic events like `processing` or `handling`\n- Always pair `_started` with `_completed` or `_failed`\n- Include context: IDs, durations, error details\n\n**Log Levels:** `fatal` > `error` > `warn` > `info` (default) > `debug` > `trace`\n\n**Verbosity:**\n\n- CLI: `archon --quiet` (errors only) — suppresses Pino logs and workflow progress output\n- CLI: `archon --verbose` (debug) — enables debug Pino logs and tool-level workflow progress events\n- Server: `LOG_LEVEL=debug bun run start`\n\n**Never log:** API keys or tokens (mask: `token.slice(0, 8) + '...'`), user message content, PII.\n\n### Command System\n\n**Variable Substitution:**\n\n- `$1`, `$2`, `$3` - Positional arguments\n- `$ARGUMENTS` - All arguments as single string\n- `$ARTIFACTS_DIR` - External artifacts directory for the current workflow run (pre-created by executor)\n- `$WORKFLOW_ID` - The workflow run ID\n- `$BASE_BRANCH` - Base branch; auto-detected from git when `worktree.baseBranch` is not set; fails only if referenced in a prompt and auto-detection also fails\n- `$DOCS_DIR` - Documentation directory path; configured via `docs.path` in `.archon/config.yaml`. Defaults to `docs/`. Never throws.\n- `$LOOP_USER_INPUT` - User feedback provided via `/workflow approve <id> <text>` at an interactive loop gate. Only populated on the first iteration of a resumed interactive loop; empty string on all other iterations.\n- `$REJECTION_REASON` - Reviewer feedback provided via `/workflow reject <id> <reason>` at an approval gate. Only populated in `on_reject` prompts; empty string elsewhere.\n- `$LOOP_PREV_OUTPUT` - Cleaned output of the previous loop iteration (loop nodes only). Empty string on the first iteration (no prior output exists). Useful for `fresh_context: true` loops that need to reference what the previous pass produced or why it failed without carrying full session history.\n\n**Command Types:**\n\n1. **Codebase Commands** (per-repo):\n   - Stored in `.archon/commands/` (plain text/markdown)\n   - Discovered from the repository `.archon/commands/` directory\n   - Surfaced via `GET /api/commands` for the workflow builder and invoked by workflow `command:` nodes\n\n2. **Workflows** (YAML-based):\n   - Stored in `.archon/workflows/` (searched recursively)\n   - Multi-step AI execution chains, discovered at runtime\n   - **`nodes:` (DAG format)**: Nodes with explicit `depends_on` edges; independent nodes in the same topological layer run concurrently. Node types: `command:` (named command file), `prompt:` (inline prompt), `bash:` (shell script, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured), `loop:` (iterative AI prompt until completion signal), `approval:` (human gate; pauses until user approves or rejects; `capture_response: true` stores the user's comment as `$<node-id>.output` for downstream nodes, default false), `script:` (inline TypeScript/Python or named script from `.archon/scripts/`, runs via `bun` or `uv`, stdout captured as `$nodeId.output`, no AI, receives managed per-project env vars in its subprocess environment when configured, supports `deps:` for dependency installation and `timeout:` in ms, requires `runtime: bun` or `runtime: uv`) . Supports `when:` conditions, `trigger_rule` join semantics, `$nodeId.output` substitution, `output_format` for structured JSON output (SDK-enforced on Claude/Codex/OpenCode; best-effort prompt-augmentation + repair on Pi/Copilot — the parsed output is **validated against the declared schema for every provider**, best-effort providers (Pi/Copilot) re-ask up to 3× on a validation miss, and a node that declares `output_format` but returns no schema-valid output **fails** rather than degrading silently; `$nodeId.output.field` access is strict — a field not in the producer's schema, or a schemaless node whose output isn't JSON / lacks the key, fails the consuming node, while an author-declared-optional field resolves to `''`), `allowed_tools`/`denied_tools` for per-node tool restrictions (Claude only), `hooks` for per-node SDK hook callbacks (Claude only), `mcp` for per-node MCP server config files (Claude only, env vars expanded at execution time), and `skills` for per-node skill preloading via AgentDefinition wrapping (Claude only), `agents` for inline sub-agent definitions invokable via the Task tool (Claude only), and `effort`/`thinking`/`maxBudgetUsd`/`systemPrompt`/`fallbackModel`/`betas`/`sandbox` for Claude SDK advanced options (Claude only, also settable at workflow level), and `persist_session` for cross-run provider session continuity (node-level opt-in; workflow-level default via `persist_sessions: true`; requires a provider with the `sessionResume` capability)\n   - Workflow-level `requires: [github]` hard-blocks invocation (before any worktree/clone/AI cost) when the originating user hasn't connected their GitHub identity — enforced only when per-user GitHub is enabled (GitHub App + `TOKEN_ENCRYPTION_KEY`); a no-op for solo PAT installs\n   - Provider inherited from `.archon/config.yaml` unless explicitly set; per-node `provider` and `model` overrides supported\n   - Model and options can be set per workflow or inherited from config defaults\n   - `interactive: true` at the workflow level forces foreground execution on web (required for approval-gate workflows in the web UI)\n   - Model validation ensures provider/model compatibility at load time\n   - Commands: `/workflow list`, `/workflow reload`, `/workflow status`, `/workflow cancel`, `/workflow resume <id>` (re-runs failed workflow, skipping completed nodes), `/workflow abandon <id>`, `/workflow cleanup [days]` (CLI only — deletes old run records), `/workflow reset-sessions <name> [<node-id>]` (clears persisted `persist_session` memory; chat auto-scopes to the current conversation, CLI adds `--scope`/`--yes` for cross-scope control)\n   - Resilient loading: One broken YAML doesn't abort discovery; errors shown in `/workflow list`\n   - `resolveWorkflowName()` (in `router.ts`) resolves workflow names via a 4-tier fallback — exact, case-insensitive, suffix (`-name`), substring — with ambiguity detection; used by both the CLI and all chat platforms\n   - Router fallback: if no `/invoke-workflow` is produced, falls back to `archon-assist` (with \"Routing unclear\" notice); raw AI response returned only when `archon-assist` is unavailable\n   - Claude routing calls use `tools: []` to prevent tool use at the API level; Codex tool bypass is detected and triggers the same fallback\n\n**Defaults:**\n\n- Bundled in `.archon/commands/defaults/` and `.archon/workflows/defaults/`\n- Binary builds: Embedded at compile time (no filesystem access needed) via `packages/workflows/src/defaults/bundled-defaults.generated.ts`\n- Source builds: Loaded from filesystem at runtime\n- Merged with repo-specific commands/workflows (repo overrides defaults by name)\n- Opt-out: Set `defaults.loadDefaultCommands: false` or `defaults.loadDefaultWorkflows: false` in `.archon/config.yaml`\n- **After adding, removing, or editing a default file, run `bun run generate:bundled`** to refresh the embedded bundle. After editing `migrations/000_combined.sql`, run `bun run generate:bundled-schema` to keep the embedded schema in sync. `bun run validate` (and CI) run `check:bundled`, `check:bundled-skill`, and `check:bundled-schema` and will fail loudly if any generated file is stale.\n\n**Home-scoped (\"global\") workflows, commands, and scripts** (user-level, applies to every project):\n\n- Workflows: `~/.archon/workflows/` (or `$ARCHON_HOME/workflows/`)\n- Commands: `~/.archon/commands/` (or `$ARCHON_HOME/commands/`)\n- Scripts: `~/.archon/scripts/` (or `$ARCHON_HOME/scripts/`)\n- Source label: `source: 'global'` on workflows and commands (scripts don't have a source label)\n- Load priority: bundled < global < project (repo overrides global by filename or script name)\n- Subfolders: supported 1 level deep (e.g. `~/.archon/workflows/triage/foo.yaml`). Deeper nesting is ignored silently.\n- Discovery is automatic — `discoverWorkflowsWithConfig(cwd, loadConfig)` and `discoverScriptsForCwd(cwd)` both read home-scoped paths unconditionally; no caller option needed\n- **Migration from pre-0.x `~/.archon/.archon/workflows/`**: if Archon detects files at the old location it emits a one-time WARN with the exact `mv` command and does NOT load from there. Move with: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon`\n- See the docs site at `packages/docs-web/` for details\n\n### Error Handling\n\n**Database Errors:**\n\n```typescript\n// INSERT operations\ntry {\n  await db.query('INSERT INTO conversations ...', params);\n} catch (error) {\n  log.error({ err: error, params }, 'db_insert_failed');\n  throw new Error('Failed to create conversation');\n}\n\n// UPDATE operations - verify rowCount to catch missing records\ntry {\n  await db.updateConversation(conversationId, { codebase_id: codebaseId });\n} catch (error) {\n  // updateConversation throws if no rows matched (conversation not found)\n  log.error({ err: error, conversationId }, 'db_update_failed');\n  throw error; // Re-throw to surface the issue\n}\n```\n\n**Git Operation Errors (don't fail silently):**\n\n```typescript\n// When isolation environment creation fails:\ntry {\n  // ... isolation creation logic ...\n} catch (error) {\n  const err = error as Error;\n  const userMessage = classifyIsolationError(err);\n  log.error({ err, codebaseId, codebaseName }, 'isolation_creation_failed');\n  await platform.sendMessage(conversationId, userMessage);\n}\n```\n\nPattern: Use `classifyIsolationError()` (from `@archon/isolation`) to map git errors (permission denied, timeout, no space, not a git repo) to user-friendly messages. Always log the raw error for debugging and send a classified message to the user.\n\n### API Endpoints\n\n**Web UI REST API** (`packages/server/src/routes/api.ts`):\n\n**Workflow Management:**\n\n- `GET /api/workflows` - List available workflows; optional `?cwd=`; returns `{ workflows: [...], errors?: [...] }`\n- `POST /api/workflows/validate` - Validate a workflow definition in-memory (no save); body: `{ definition: object }`; returns `{ valid: boolean, errors?: string[] }`\n- `GET /api/workflows/:name` - Fetch a single workflow by name; optional `?cwd=` query param; returns `{ workflow, filename, source: 'project' | 'bundled' }`\n- `PUT /api/workflows/:name` - Save (create or update) a workflow YAML; body: `{ definition: object }`; validates before writing; requires `?cwd=` or registered codebase\n- `DELETE /api/workflows/:name` - Delete a user-defined workflow; bundled defaults cannot be deleted\n- `DELETE /api/workflows/:name/node-sessions` - Reset persisted per-node provider sessions; optional `?scope=` and `?node=` narrow the deletion; omitting `?scope=` is a cross-scope wipe and requires `?confirm=all-scopes`; returns `{ success, deleted }`\n\n**Workflow Run Lifecycle:**\n\n- `POST /api/workflows/runs/{runId}/resume` - Resume a failed run from where it left off (skips already-completed DAG nodes; AI session context is not restored).\n- `POST /api/workflows/runs/{runId}/abandon` - Abandon a non-terminal run (marks as cancelled)\n- `DELETE /api/workflows/runs/{runId}` - Delete a terminal workflow run and its events\n\n**Codebases:**\n\n- `GET /api/codebases` / `GET /api/codebases/:id` - List / fetch codebases\n- `POST /api/codebases` - Register a codebase (clone or local path)\n- `DELETE /api/codebases/:id` - Delete a codebase and clean up resources\n- `GET /api/codebases/:id/env` - List env var keys for a codebase (never returns values)\n- `PUT /api/codebases/:id/env` / `DELETE /api/codebases/:id/env/:key` - Upsert / delete a single codebase env var\n- `GET /api/codebases/:id/environments` - List tracked isolation environments for a codebase\n\n**Artifact Files:**\n\n- `GET /api/runs/:runId/artifacts` - List artifact files for a run; walks the on-disk artifact directory (dotfiles skipped) and returns `{ files: [{ path, size, modifiedAt }] }`; 400 on invalid run id or path-escape attempt, 404 if the run does not exist\n- `GET /api/artifacts/:runId/*` - Serve a workflow artifact file by run ID and relative path; returns `text/markdown` for `.md` files, `text/plain` otherwise; 400 on path traversal (`..`), 404 if run or file not found\n\n**Command Listing:**\n\n- `GET /api/commands` - List available command names (bundled + project-defined); optional `?cwd=`; returns `{ commands: [{ name, source: 'bundled' | 'project' }] }`\n\n**Providers:**\n\n- `GET /api/providers` - List registered AI providers; returns `{ providers: [{ id, displayName, capabilities, builtIn }] }`. `capabilities.nativeTools` is `true` for providers that accept in-process native tools (Claude, Pi) — Archon's `manage_run` tool is auto-injected into project-scoped chat for those providers only. `capabilities.structuredOutput` is a tiered union `'enforced' | 'best-effort' | false` (not a boolean): `'enforced'` = SDK/backend grammar-constrained (Claude/Codex/OpenCode), `'best-effort'` = prompt-augmentation + validate (Pi/Copilot), `false` = unsupported.\n\n**Web Auth (opt-in Better Auth; Postgres + `BETTER_AUTH_SECRET`):**\n\n- Better Auth mounts email/password login at `/api/auth/*` (sign-up/sign-in/sign-out/get-session). Mounted only when enabled; the catch-all explicitly falls through for Archon-owned `/api/auth/status` + `/api/auth/github*` paths so they aren't shadowed.\n- `GET /api/auth/status` - Web auth availability + signup posture (no auth required); returns `{ enabled: boolean, signup: 'allowlist' | 'open' | 'disabled' }`. Drives the Web UI login gate.\n- The per-request identity seam is `resolveAuthContext(c): { userId, role } | undefined` (in `routes/api.ts`): Better Auth session first, then the `X-Archon-User` header, then undefined. `resolveWebUserId` delegates to it; `requireWebUser` is the session-aware strict variant (401 missing / 503 backend). `role` rides the canonical user row (default `admin`).\n- **Server-side API gate** (`isApiGateEnabled`): when web auth is enabled, every `/api/*` request must resolve to an identity or gets **401** — except `/api/auth/*` (login surface) and `/api/health*` (healthcheck must stay reachable). `/webhooks/*` and `/internal/*` are outside `/api/*` and untouched. On by default; `ARCHON_WEB_AUTH_REQUIRED=false` keeps login-UI-only. This is what lets Better Auth replace the Caddy `forward_auth` sidecar as the real access boundary.\n- **Signup safety** (`getSignupMode`): with web auth on and no `ARCHON_AUTH_ALLOWED_EMAILS`, signup defaults to **disabled** (login only) + a boot WARN — never silently open. `ARCHON_AUTH_OPEN_SIGNUP=true` opts into open public signup.\n- `GET /api/workflows/runs?mine=true` and `GET /api/conversations?mine=true` - Non-enforcing \"my\" filter (narrows to `ctx.userId` only when an identity resolves; default lists everything). Not a security boundary.\n\n**GitHub Identity (per-user device flow; App mode + `TOKEN_ENCRYPTION_KEY`):**\n\n- `POST /api/auth/github/device/start` - Begin the device flow for the current web user (from `X-Archon-User`); returns `{ device_code, user_code, verification_uri, interval, expires_in }`; 401 if no web-auth header\n- `POST /api/auth/github/device/poll` - Single non-blocking poll; body `{ device_code }`; returns `{ status: 'pending' | 'connected' | 'expired' | 'denied' | 'error', githubLogin?, detail? }`\n- `GET /api/auth/github` - Connection status for the current web user; returns `{ connected, githubLogin }`\n- `DELETE /api/auth/github` - Disconnect the current web user's GitHub identity\n\n**System:**\n\n- `GET /api/health` - Health check with adapter/system status\n- `GET /api/update-check` - Check for available updates; returns `{ updateAvailable, currentVersion, latestVersion, releaseUrl }`; skips GitHub API call for non-binary builds\n\n**OpenAPI Spec:**\n\n- `GET /api/openapi.json` - Generated OpenAPI 3.0 spec for all Zod-validated routes\n\n**Webhooks:**\n\n- `POST /webhooks/github` - GitHub webhook events\n- Signature verification required (HMAC SHA-256)\n- Return 200 immediately, process async\n\n**Internal (App mode only; bind 127.0.0.1):**\n\n- `POST /internal/git-credential` - Git credential helper endpoint. Returns `{token}` for the installation matching the requested host/path. Used by the `git-credential-archon` script in worktree `.git/config` to refresh installation tokens for long-running workflow `git` operations. Hands out installation tokens — MUST NOT be exposed beyond loopback. Server **refuses to start** (not just WARN) if App mode is active and `hostname != 127.0.0.1/localhost`, unless `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1` is set as an opt-in escape hatch for deployments where the reverse proxy already drops `/internal/*`.\n\n**Security:**\n\n- Verify webhook signatures (GitHub: `X-Hub-Signature-256`)\n- Use `c.req.text()` for raw webhook body (signature verification)\n- Never log or expose tokens in responses\n- `/internal/*` paths hand out live credentials — the reverse proxy in production MUST drop them, or the server MUST bind to `127.0.0.1` only.\n\n**@Mention Detection:**\n\n- Parse `@archon` in issue/PR **comments only** (not descriptions)\n- Events: `issue_comment` only\n- Note: Descriptions often contain example commands or documentation - these are NOT command invocations (see #96)\n","category":"root","tokens":14656}]}