Commit Graph

9 Commits

Author SHA1 Message Date
Claude 8541b4b7c0 Agents can hand work to agents: dispatch_agent
A schedule is a time trigger, and only the first step of a pipeline is really
waiting on time — every later step waits on the previous step's result. Modeling
"watch a source -> write a spec -> implement it" as three schedules made each fire
blind: on a quiet day the coding agent still spawned, paid a full model run to find
there was nothing to do, and left an empty run in Activity.

So an agent can start the next step itself. `dispatch_agent` (a tool on the bundled
MCP server, and on the pi bridge through the same --call seam) enqueues an ordinary
spawn command in the agent's own project, tagged requested_by=agent:<id> — so a
handoff is visible in Activity with no new surface to build.

- Project-scoped by construction: project_id is read from the spawn environment and
  never from the tool arguments.
- Bounded rather than gated: MAX_DISPATCH_PER_RUN counts the command rows the agent
  already wrote; MAX_DISPATCH_DEPTH rides in the spawn payload and is recovered by
  spawn._dispatch_depth, so a chain keeps its place across a resume and a cycle
  terminates instead of fanning out.
- New scout and planner roles, with built-in skills (handler-scout, handler-planner,
  handler-dispatch) carrying the judgment code can't: dedupe against a memory-note
  watermark, treat "nothing new" as a complete run, and write a task the receiving
  cold-start agent can act on.
- A scout ending on a clean tree skips the test gate and records the new
  tests_status='skipped' (migration 0017, additive CHECK widening). The gate promises
  `done` means tests passed for the work that shipped; nothing shipped.

Rejected a `condition` field on schedules: "is this paper new and does it matter
here?" is a semantic judgment, so it belongs to a model, not a scheduler column. The
scout is the condition; dispatch is how it reports true — one mechanism that covers
future pipelines too.

426 tests (14 new for dispatch, 3 for the gate exemption).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HcbDevyMcJWE6qPA56C7mZ
2026-08-19 22:46:06 +00:00
Claude e5c8ebfd34 Add web_search / web_fetch tools for pi-harness agents
pi ships no web tools and claude's WebSearch/WebFetch are Anthropic-server-side
(absent on local endpoints), so these are handler-owned: a new handler.webtool
module (httpx, already a dependency) exposed through the bridge extension the
same way as the memory tools. web_fetch is provider-free — GET, HTML stripped
to readable text, size-capped. web_search resolves SEARXNG_URL, then
BRAVE_SEARCH_API_KEY, then falls back to DuckDuckGo's HTML endpoint with zero
config. Verified live: a pi agent now advertises all 14 tools (7 built-ins +
ask_operator + 4 memory + 2 web).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdGv3u3DfTsP1S188KDhVH
2026-08-12 18:41:41 +00:00
Claude 887576bf22 pi bridge: activate the full built-in tool set (grep/find/ls)
pi ships seven built-in tools but activates only read/write/edit/bash by
default. The --tools flag can't fix this (it is a strict allowlist that drops
extension tools — verified against a live endpoint), so the bridge calls
setActiveTools with everything registered at session start: all seven built-ins
plus ask_operator and the four memory tools. Verified live: the model now
receives all twelve tool definitions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdGv3u3DfTsP1S188KDhVH
2026-08-12 18:27:17 +00:00
Claude a3a5c272a2 Add the pi harness: lightweight local-model agents with full gate parity
Model backend rows gain a harness column (claude | pi). A pi-harness row runs
the agent through the pi coding agent instead of the claude binary — pi speaks
the OpenAI Completions API natively, so a bare vLLM/llama.cpp/Ollama endpoint
needs no LiteLLM/claude-code-router translation proxy, and the loop is far
lighter for slow local token throughput. The Claude subscription and existing
claude-harness backends are untouched.

Parity comes from generated per-agent artifacts under ~/.handler-pi (outside
the repo tree, so the clean-tree gate never trips): models.json + settings.json
render the row as a pi provider pinned as the default model; a bundled bridge
extension (pi_bridge.ts) adapts pi's events to the exact stdin/stdout contract
of `python -m handler.hooks` — the Stop/completion gate re-prompts pi with
blockers via a follow-up message, git push runs the test/build/approval gates
and denies on failure, questions defer through an ask_operator tool into the
normal answer/resume flow, and memory recall is injected at session start. The
memory tools are registered natively (pi has no MCP), shelling to a new
`python -m handler.mcpserver --call <tool>` seam that reuses the MCP server's
implementations. Skills reuse the same ~/.claude/skills sync (pi implements the
same SKILL.md standard) plus the repo's committed .claude/skills.

Sessions are single JSONL files pre-assigned via --session, so cross-worker
resume archives/materializes exactly like claude's; the prompt travels on stdin
(pi has no -- separator). The supervisor normalizes pi's event stream on the
fly: assistant message_end feeds last_output, the final agent_end becomes the
run result. The whole chain was validated live against pi 0.84.1 with a stub
OpenAI endpoint: memory injection, push-gate denial (including the protected-
branch approval gate), stop-gate block loop, and ask_operator pause all ran
end to end through the real hooks and DB.

Also: harness selector in the dashboard Models form, pi baked into the control
image (NodeSource 22 for pi's node >= 22.19 floor), PI_BIN override, docs in
docs/local-models.md, fake_pi fixture + 12 tests (361 total green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdGv3u3DfTsP1S188KDhVH
2026-08-12 18:21:35 +00:00
Claude 44632ea771 Extend the model picker to schedules
schedules.model_id (migration 0013) pins every fired run of a recurring spawn
to a registered model backend, exactly like a hand-spawned agent: the worker
copies it into each firing's spawn payload, the launched agent records the pin,
and resumes stay on the same backend. The Schedules form gets the same Model
dropdown as the spawn form (Claude subscription by default), with a badge in
the schedules table. Create/update routes fail fast on a missing or disabled
backend so a stale selection bounces immediately instead of every firing
failing asynchronously in Activity; a backend deleted later still fails each
firing visibly rather than silently falling back to the subscription.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzDofD7gP63WpeLG8vEdZu
2026-07-29 18:54:31 +00:00
Claude 2d5c0e34d7 Add local model backends: per-spawn dropdown pointing claude at alternative endpoints
Operators can register Anthropic-API-compatible endpoints (a local Qwen/Llama
behind LiteLLM or claude-code-router, an LLM gateway) on the dashboard's
Claude -> Models tab and pick one from a Model dropdown when spawning an agent.
The agent still launches as the same claude binary with the same hooks, skills,
connectors, plugins, and gates — only its ANTHROPIC_BASE_URL / ANTHROPIC_MODEL /
ANTHROPIC_AUTH_TOKEN env differs — and it stays pinned to its backend across
resumes. No selection keeps the worker's Claude subscription untouched.

- claude_models table (+ agents.model_id pin), migration 0012
- control.models resolves a row into the launch env (API keys Fernet-encrypted
  at rest, decrypted only in the control container; placeholder key when none is
  stored so the subscription OAuth token never reaches a local endpoint)
- /claude/models CRUD (admin-gated writes, key never returned), spawn route +
  worker + CLI (--model) pass the selection through, fail-fast on missing or
  disabled backends
- dashboard: Models tab, spawn-form dropdown, model badge in the agents table
- docs/local-models.md: why bare OpenAI-compatible servers break tool calling
  with Qwen-Coder, and working vLLM/LiteLLM/llama.cpp stacks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzDofD7gP63WpeLG8vEdZu
2026-07-29 18:36:16 +00:00
0xWheatyz bbb01d0882 feat(phase-3): web UI served same-origin by the API
Add a no-build, same-origin web frontend so an operator can open a URL,
see every agent's state, and answer a paused question with no terminal
(Phase 3 DoD). The UI is a client of the existing API — no endpoint,
schema, or auth change — so the 106 existing tests pass unchanged.

- app.py serves the bundled UI from / and /static, gated on UI_ENABLED
  (default on); optional CORS_ORIGINS (default empty => no middleware)
  for hosting the UI on a separate origin. Dedicated /static prefix +
  explicit / route so API routes are never shadowed. Zero new runtime
  deps (StaticFiles/CORSMiddleware ship with Starlette).
- static/: vanilla fetch + plain CSS + vendored alpine.min.js (v3.14.8,
  no CDN). Token captured once into localStorage; all API values render
  via x-text (never x-html) to block agent-authored markup injection.
  Project switcher, agent list, checkmark panel, paginated log, shared
  feed, and Answer / Answer & Resume. Polling scoped to the selected
  agent to avoid an N+1 over the fleet.
- config.py: ui_enabled, cors_origins (+ cors_origin_list); documented
  in .env.example.
- tests/test_api_ui.py: serving, unauthenticated shell, non-shadowing
  401 regression, CORS toggle, UI_ENABLED=false. 114 tests, ruff clean.

The static assets ship in the wheel by default (they live inside the
packaged src/handler tree) — no force-include needed.
2026-07-09 20:43:23 -04:00
0xWheatyz 6fb26115ce feat(phase-2): forge integration — credentials, role skills, approval gate, CI poller
Phase 2 configures forge for the agents (operator only sets a credential_ref +
optional version pin) and lets them drive a junior→senior→deploy workflow:

- Credential resolution/injection (control/credentials.py): credential_ref pointers
  (env:/file:/cmd:) resolved only at spawn, injected as FORGE_TOKEN + host var, with a
  forge-host-scoped git credential helper reading the token from env (never on disk / in
  the DB). Resolution is a fail-fast spawn gate.
- Role-based forge skills committed into the managed repo (control/skills_gen.py,
  `handler forge-init`): forge-junior/senior/deploy + a workflow overview.
- Hard approval gate (hooks/gate.py, approvals table, migration 0002): merge/deploy —
  and direct pushes to protected branches — are denied unless a DIFFERENT agent has an
  `approved` record for the branch, pinned to the reviewed commit (approved_sha). Senior
  records verdicts via `handler approve`/`reject`.
- forge/git seams (control/forge.py, control/gitops.py) matching the Phase 1 seam pattern.
- CI status poller (control/poller.py, `handler poll-ci [--watch]`) backfilling
  ci_status/ci_checked_at via `forge ci list`.
- Fix: migrations/env.py commits explicitly after run_migrations — pysqlite on Py 3.12+
  was rolling back the final migration's DDL + alembic_version stamp (latent in Phase 1).

Reviewed via a separate code-reviewer pass; gate-bypass and credential-scoping findings
addressed. 106 tests, ruff clean, verified end-to-end against real git + migrations.
2026-07-08 22:05:49 -04:00
Claude 15ff713417 docs: split MVP plan into docs/PLAN.md and rewrite README
Move the original plan-of-action out of README.md into docs/PLAN.md (verbatim,
with Phase 1 checkboxes ticked and MVP-resolved open questions annotated). Replace
the README with a proper project readme grounded in the implemented MVP: what it is,
architecture, install/configure/run, curl walkthrough, control CLI, API reference,
hooks, development, and layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5ZuS5pV1NS6eKsRZHXonY
2026-07-08 11:37:00 +00:00