summaryrefslogtreecommitdiff
path: root/docs/plans
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-22 08:53:53 +0000
committerClaude <noreply@anthropic.com>2026-05-22 08:53:53 +0000
commitc24fdaa9b6bce49f5c0a1c589fd064d46b62b88d (patch)
tree6098bf380d418cbdcd9e68b8bd2f7cee0633d3fa /docs/plans
parent68399a598924775a3ec22a39c2336ae497fb07f3 (diff)
docs: add OSS replacement redesign plan
Captures the locked architectural decisions from the chatbot-driven redesign discussion: SQLite as source of truth with external trackers as input feeds, events table replacing ad-hoc task columns, per-runner structured agent channel, three-runner roster with classifier, scoped auth, multi-dimensional budget, per-runner sovereign resume. https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
Diffstat (limited to 'docs/plans')
-rw-r--r--docs/plans/claudomator-oss-replacement.md199
1 files changed, 199 insertions, 0 deletions
diff --git a/docs/plans/claudomator-oss-replacement.md b/docs/plans/claudomator-oss-replacement.md
new file mode 100644
index 0000000..f0fd3dc
--- /dev/null
+++ b/docs/plans/claudomator-oss-replacement.md
@@ -0,0 +1,199 @@
+# Claudomator OSS Replacement — Architectural Redesign
+
+## Context
+
+Claudomator today is a useful but overgrown harness: ~10 task states, a stories layer, an elaborate-via-REST endpoint, file-based agent escape hatches, a manual task-creation web form, and a stub GeminiRunner that silently returns canned output. The primary mode of interaction we actually want is now: **a chatbot drives task creation and orchestration on the user's behalf; the human only intervenes when an agent asks a question; the web UI is for observability, not for primary input.**
+
+This plan locks the architectural shape of that redesign. Implementation phasing is sketched at the end but the focus here is decision capture so subsequent work can proceed without re-litigating.
+
+**Mission:**
+- Reduce custom code we maintain.
+- Support chatbot-as-primary-driver (us, or any chatbot with MCP).
+- Push-notify the user when human input is needed.
+- Web UI = observability layer (task tree, live logs, event stream, accept/reject).
+- Multi-runner (Claude cloud, Gemini cloud, OSS local) with budget-aware routing.
+
+**Non-goals (deferred):**
+- Cross-model resume — each runner is sovereign over its own continuity.
+- Non-code-task UX optimization — shape must remain *possible* (project_dir is optional) but no code or docs accommodate it yet.
+- Autonomous chatbot responding to agent questions without human — the subscription model must not foreclose it, but it's not built.
+
+---
+
+## Locked Decisions
+
+### 1. SQLite is source of truth; external trackers are input feeds
+
+Tasks live in our `tasks` table. GitHub/GitLab/whatever-tracker integrations create and update tasks through the existing webhook surface — they are not the data model. Each task carries an `external_refs` slice (`[{tracker, url}]`) for opportunistic back-syncing of state, but nothing load-bearing depends on the external tracker being available.
+
+Rejected: GitHub Issues as source of truth (Option B). Too much lock-in, non-code tasks fit poorly, and we'd still need our own DB for execution data.
+
+### 2. Events table replaces ad-hoc task-state columns
+
+A single `events` table replaces `question_json`, `interactions_json`, `summary`, `elaboration_input` on `tasks`. Schema:
+
+```
+events: id, task_id, seq, ts, kind, actor, payload_json
+```
+
+- `seq` is monotonic per task.
+- `actor ∈ {agent, user, chatbot, system, webhook}`.
+- `kind ∈ {state_change, agent_message, clarification_request, clarification_answer, human_message, summary, commit_made, subtask_spawned, cost_report, execution_started, execution_ended}`.
+- Granularity: **milestones, not raw stream chunks.** Raw Claude stream-json stays in `stdout.log` on disk, linked from the `execution_started` event.
+- Replay: read events in `seq` order, fold state transitions, reconstruct UI. **For humans, not for re-prompting the agent.** Cross-model agent replay is explicitly out of scope.
+
+### 3. Per-runner structured agent channel (no more file conventions)
+
+Today the agent uses `CLAUDOMATOR_QUESTION_FILE`, `CLAUDOMATOR_SUMMARY_FILE`, and `POST /api/tasks` for subtasks — orchestrated by a ~1KB prompt preamble. We replace this with a `Runner` interface that exposes normalized callbacks:
+
+```go
+type AgentChannel interface {
+ OnAskUser(question string, schema any) (answer string, err error)
+ OnReportSummary(text string)
+ OnSpawnSubtask(spec TaskSpec) (taskID string, err error)
+ OnRecordProgress(message string)
+}
+```
+
+Each runner picks its own wire:
+- **ClaudeRunner:** registers an in-process MCP server via `--mcp-config`. Tools (`ask_user`, `report_summary`, `spawn_subtask`, `record_progress`) bound to a per-task scoped token minted at subprocess spawn. Server derives `task_id` from token; never trusts agent-supplied IDs.
+- **GeminiRunner:** uses Gemini CLI tool-use registration (or direct API if CLI tool-use proves unworkable — to determine during build).
+- **LocalRunner:** uses Ollama-style tool-use API. Falls back to file conventions only if a future runner lacks tool-use plumbing.
+
+**Safety tradeoffs accepted:**
+- Atomicity at run boundary: MCP calls land mid-run, file-based intent lands at exit. Mitigated by designing tools idempotent (`report_summary` upserts, `ask_user` creates a clarification event which is fine mid-run by design).
+- Blast radius: per-task token scopes the agent's access to one task's data.
+- Portability: per-runner transport means non-MCP runners stay supported via callback abstraction.
+
+### 4. Web UI = observability
+
+**In:** task tree view, live log tailing, accept/reject buttons, event-stream timeline per task, budget headroom display.
+
+**Out:** manual task creation form, elaborate textbox, stories dashboard.
+
+Chatbots create tasks via the MCP server. Humans visit the web UI to watch, intervene on `READY`, or answer a `BLOCKED` clarification when push notification pings them.
+
+### 5. Three runners + Classifier
+
+| Runner | Status | Transport | Resume mechanism |
+|---|---|---|---|
+| ClaudeRunner | exists, works | `claude -p` subprocess + MCP | provider-side via `--resume <session_id>` |
+| GeminiRunner | scratch rewrite (current stub deleted) | `gemini` CLI subprocess + tool-use registration | message-history re-feed |
+| LocalRunner | exists (Phase 1 of `local-oss-runner.md`), extend with agent-channel | OpenAI-compat HTTP + tool-use | message-history re-feed |
+
+Classifier stays. Its job becomes: given a task, choose Claude-cloud vs Gemini-cloud vs Local, and within Claude/Gemini, the specific model. **Budget state feeds this decision** (#7).
+
+### 6. Auth: loopback default + reverse proxy + scoped tokens
+
+- Server binds to `127.0.0.1` by default. Refuses to bind non-loopback unless `external_bind_allowed = true` is set in config — fail loud on misconfiguration.
+- External network access: user fronts with Tailscale / Cloudflare Access / Caddy. Out of our code, battle-tested.
+- Internal tokens, each scoped:
+ - **GitHub webhooks:** HMAC-SHA256 (already exists).
+ - **Chatbot MCP + web UI:** one shared bearer token from config; user pastes into MCP client config. One user, one credential.
+ - **Agent MCP back-channel:** per-task token minted at subprocess spawn; bound to single task ID; expires at task termination. Server resolves `task_id` from token — never accepts agent-supplied `task_id`.
+ - **Web push:** standard VAPID subscriptions.
+
+### 7. Budget — multi-dimensional
+
+- Per-task `max_budget_usd` (exists).
+- **Rolling 5-hour budget per provider** (Claude, Gemini) — track spend in a window. Dispatcher refuses to start a paid task that would breach the window. Classifier prefers local when headroom is thin.
+- Optional per-day / per-week hard ceiling in config.
+- Local runners bypass entirely — free.
+- UI surfaces headroom: e.g. "Claude: 67% remaining in current 5h window."
+
+This is genuinely new code: budget accountant, classifier hook, UI surface. Not throwaway.
+
+### 8. Resume = per-runner sovereign opaque state
+
+Each runner persists its own resume state as opaque JSON in `executions.runner_state_json`. The Runner interface:
+
+```go
+Resume(ctx context.Context, opaqueState []byte) (*Result, error)
+```
+
+- **ClaudeRunner** stores `{session_id}`, calls `--resume`. **Prompt caching survives** — this is the killer feature that justifies the special case. Cost can differ 10x on long resumed tasks.
+- **GeminiRunner** stores `{messages: [...]}`, re-feeds on resume.
+- **LocalRunner** stores `{messages: [...]}`, re-feeds on resume.
+
+The "specialness" of Claude is entirely internal to ClaudeRunner — the dispatcher and event store don't know. Cross-model resume is **not** a goal; if we ever want it, revisit then.
+
+### 9. Subscription model: push, agent-return-friendly
+
+Server broadcasts events on WebSocket (live UI) + web push (high-signal subset: `clarification_request`, terminal state changes, `summary`). Subscription record:
+
+```
+subscriptions: id, endpoint, kind_filter, task_filter, actor_label
+```
+
+Today's only subscribers are browser WebSocket clients and web-push endpoints. The model is flexible enough that adding an autonomous-chatbot subscriber later is one row: `{endpoint: chatbot_url, kinds: ["clarification_request"], actor_label: "auto-responder"}`. The `POST /api/tasks/{id}/answer` endpoint already accepts any caller; the `actor` field on the resulting `clarification_answer` event records who answered.
+
+---
+
+## What Gets Deleted
+
+| Surface | Reason |
+|---|---|
+| `stories` table + handlers + UI dashboard | Folded into events + task tree view |
+| `tasks.question_json`, `interactions_json`, `summary`, `elaboration_input` | Replaced by events table |
+| `POST /api/tasks/elaborate` endpoint | Chatbots elaborate in-conversation; no REST shell-out path |
+| `sanitizeElaboratedTask` keyword heuristics | Same |
+| Web UI: create-task form, elaborate textbox, stories dashboard | Out of scope for observability UI |
+| `CLAUDOMATOR_QUESTION_FILE`, `CLAUDOMATOR_SUMMARY_FILE` env conventions | Replaced by per-runner agent channel |
+| GeminiRunner stub (`internal/executor/gemini.go` simulated output) | Scratch rewrite as real runner |
+| Planning preamble's file-protocol section | Shrunk to "use your tools" — runners register tools per-transport |
+
+## What Gets Built
+
+| Surface | Notes |
+|---|---|
+| `internal/event` package | `Event` struct, kind constants, `EventStore` interface |
+| `events` table + scan/CRUD methods | Additive migration; indexed on `(task_id, seq)` |
+| Per-task scoped MCP server | Hosted inside claudomator process; tokens minted per subprocess spawn |
+| Chatbot-facing MCP server | Tools: `submit_task`, `list_tasks`, `get_task`, `get_events`, `answer_question`, `accept_task`, `reject_task`, `cancel_task` |
+| `AgentChannel` interface + per-runner implementations | Normalized callbacks; transport hidden in runner |
+| Real GeminiRunner | Subprocess + stream parser + sandbox integration; mirrors ClaudeRunner shape |
+| Budget accountant | Rolling 5h window per provider; classifier hook; UI surface |
+| Loopback-default binding + `external_bind_allowed` flag | Hard refusal on misconfig |
+| Per-task token minting + scoping | Auth context for agent MCP calls |
+| Web UI event-stream timeline component | Replaces ad-hoc question/summary panels |
+| `external_refs` field on tasks | For opportunistic tracker back-sync |
+
+---
+
+## Phasing
+
+These are sequencing notes, not commitments to PR boundaries. Each phase should be independently shippable.
+
+**Phase 1 — Events table + Runner interface refinement.** Add `events` table, `EventStore`, write paths for state changes and existing question/summary flows. Refactor `Runner` to expose `AgentChannel` callbacks; ClaudeRunner keeps file-based wire as transitional default. No user-visible behavior change yet.
+
+**Phase 2 — ClaudeRunner MCP agent channel.** Per-task MCP server, token minting, four agent tools. Delete file-based escape hatches. Shrink preamble. Migration: existing in-flight tasks finish on the old wire; new tasks use MCP.
+
+**Phase 3 — Chatbot MCP server + UI rebuild.** Chatbot-facing MCP server. Web UI gets event-stream timeline, drops create form / elaborate / stories. Elaborate REST endpoint removed.
+
+**Phase 4 — GeminiRunner scratch rewrite.** Real subprocess + stream parser + sandbox + tool-use registration. Plug into AgentChannel.
+
+**Phase 5 — LocalRunner agent channel.** Extend the existing LocalRunner (from `local-oss-runner.md`) to participate in AgentChannel via Ollama tool-use. Was previously fire-and-forget; now first-class for orchestration tasks.
+
+**Phase 6 — Budget accountant.** Rolling 5h window tracking, classifier integration, UI headroom display, dispatcher gating.
+
+**Phase 7 — Auth tightening.** Loopback default, `external_bind_allowed` flag, refuse-bind enforcement, chatbot bearer rotation flow.
+
+**Phase 8 — Cleanup pass.** Delete `stories`, deprecated columns, dead handlers, dead UI. Update CLAUDE.md and ADRs.
+
+---
+
+## Open Design Questions (resolve at phase entry)
+
+- **MCP tool schemas.** Exact arg shapes for `ask_user` (free text? JSON schema for typed answers?), `spawn_subtask` (how much of `TaskSpec` is exposed?), `submit_task` (how to handle `project_dir` resolution from chatbot side?). Defer to Phase 1/3 detailed design.
+- **Gemini transport for tool-use.** Whether `gemini` CLI's tool-use is reliable enough or we need to drop to the SDK. Decide in Phase 4 spike.
+- **Web push subset.** Final list of event kinds that trigger push vs WebSocket-only. Default: `clarification_request`, `state_change → READY/FAILED/BLOCKED`, `summary`. Tune in Phase 3.
+- **Single shared bearer token vs per-client.** If multi-device gets painful, revisit. Defer.
+- **Budget hard-ceiling defaults.** What's the user's actual daily Claude spend tolerance? Surface in config with no default; require explicit value.
+
+## Deferred (revisit only if pressure appears)
+
+- Cross-model resume.
+- Non-code-task UX (event types for "agent produced text", task-with-conversation-as-primary view). Shape must remain possible; no code or docs accommodate it.
+- Autonomous chatbot answering clarifications. Subscription model supports it; no implementation.
+- GitLab webhook handler (parallel to GitHub). Add when needed.
+- Todoist or similar personal-task-tracker bridges. Same shape as GitHub webhook; add when needed.