Settings → Manage → Activity lists every control command with status filter chips, when/where it ran (project, agent, claiming worker or 'unclaimed'), and tap-to-expand result/error text; a Sweep CI button enqueues the global poll. The list auto-refreshes every 5s while open, so a login_start or spawn can be watched to completion — this was the one web dashboard page missing from the phone, and exactly the view needed to diagnose a stuck command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
13 KiB
Changelog
All notable changes to handler are documented here. The format follows
Keep a Changelog; versions are the v* tags
the image workflows publish (plus latest from every push to main).
[Unreleased]
Added
- Activity screen in the mobile app (Settings → Manage → Activity): the
control-command queue with status filters, per-row worker attribution
(
on <worker>/unclaimed), expandable result/error text, a Sweep CI action, and a 5s auto-refresh — the screen that answers "why is my login/spawn/sync stuck" from the phone.
Fixed
- Untrusted-workspace wedge on headless runs. Phase 4's tmux-path deletion also
removed the only call to
claude_config.ensure_onboarded, so agent working dirs — every fresh worktree — were never pre-trusted in~/.claude.jsonand headlessclaude -pruns wedged or refused on the trust dialog with nobody at a TTY. Spawn and resume now re-seed onboarding + per-directory trust before every launch (resume included, so a cross-worker resume landing in a container that has never seen the working dir is covered). 2 regression tests.
Added — mobile app feature parity
The iOS app (app/) catches up with everything the backend and web dashboard gained
since its last release:
- Model backend picker on spawn: the spawn form now offers the registered model
backends (
/claude/models) next to the Claude subscription, matching the web dashboard's per-spawn dropdown; the agent detail meta card shows which backend an agent is pinned to, plus its supervising worker. - Headless run event stream: a new Events tab on the agent detail screen polls the
cursor-paged
/agents/{name}/eventsendpoint and renders the stream-json events live — assistant text, tool-call badges, run results with turns/cost, worker notices, raw lines. - Schedules tab: list, create (interval, role, model backend, prompt), pause/resume, and delete recurring agent spawns across all projects.
- Memory tab: the agent-memory note graph (
/memory/graph) with kind filters and expandable notes showing body, tags, and links — plus note authoring and deletion. - The full management surface (Settings → Manage): model backends (CRUD incl.
write-only API keys and the claude/pi harness pick), skills (incl.
install-from-prompt), MCP connectors, plugins, permission overrides, repository
registration (git-server + manual modes, mise-init), forge hosts (encrypted tokens,
generated deploy keys), branch approvals, shared context, and the worker's
claude /loginflow — the phone no longer needs a laptop nearby. - User accounts on mobile: the connect screen gains email sign-in against
/auth/login(session token stored like the legacy env token), first-run admin setup, forgot-password, and an API-token fallback (auto-selected for servers predating accounts); Settings gains an Account screen (identity, change password, sign out with server-side revocation) and Manage gains the admin Users screen (invite with shareable links, promote/disable, reset links, delete).
Added — built-in operator skills, pre-installed on every deployment
Eight skills now ship inside Handler (handler.builtin_skills) and are seeded into
the managed skill store on API startup, so every fresh install — and every existing
deployment on upgrade — starts with the judgment layer the hard gates can't enforce:
handler-quiet-output— work through tool calls, not prose: the transcript is not the deliverable. A minimizedNOTES.mdledger records what happened and how, problems go to memory, status goes to the checkpoint, and questions go through the question tool (push notification + answer prompt in the web/mobile apps) — never typed into the transcript.handler-gate-recovery— respond to a blocked completion/push gate by fixing the real failure; never delete/skip tests, weaken the misetesttask, or--no-verify.handler-testing— every behavior change lands with a test that fails without it; keep suites fast and deterministic.handler-checkpoints— checkpoints written for a phone-sized glance; questions only for operator-only decisions, with a recommended default.handler-memory— search before starting; save gotchas/decisions/runbooks, not narration or secrets.handler-mise-tasks—mise run testis the verification contract; never narrow it to get green.handler-scheduled-runs— the read-state-file → one increment → overwrite-state-file continuity pattern for recurring runs.handler-secrets— injected credentials stay out of logs, commits, PRs, and memory.
Seeding is idempotent by name: operator edits/disables survive every upgrade; deleting a built-in restores it (as shipped) on the next API start. 6 new tests (406 total).
Added — user accounts: email sign-in, invites, resets, per-user separation
- Email + password accounts replace "know the API key" for humans. First run shows a setup form and the first account created is the admin; every later account is invited by an admin through a one-shot set-password link. Passwords are scrypt (stdlib, self-describing hashes); sessions are opaque bearer tokens stored only as SHA-256 with a configurable TTL.
- Password reset by email (
POST /auth/forgot→ short-lived link, silent about account existence) via plain SMTP (SMTP_*settings). Email is optional: with SMTP unset, invite/reset links are shown to the admin in the dashboard to hand over out-of-band. Spending a link revokes the account's existing sessions. - Per-user separation of projects, skills, and tools. Projects, skills, MCP connectors, plugins, and model backends gain an owner; users see shared + their own (foreign resources 404 — existence isn't leaked), owners operate their own projects end-to-end without admin, shared (unowned) rows stay admin-managed and visible to all. Launches materialize only the project owner's skills/connectors, and private model backends can't be picked for someone else's spawns or schedules. Deleting a user reassigns their resources to shared; admins can reassign owners.
- Users page in the dashboard (admin-only): invite, admin/disable toggles, reset
links, delete. Sign-in page gains first-run setup, forgot-password, and a raw
API-token fallback;
/resetis the public landing page for invite/reset links. - Admin safety rails: the last active admin can't be demoted/disabled/deleted; no
self-deletion.
AUTH_TOKEN/ADMIN_TOKEN/SHARED_CONTEXT_WRITE_TOKENkeep their exact historical semantics for scripts/CI and break-glass. - 24 new tests (auth flows + separation matrix; 397 total).
Database (user accounts)
- Migration
0016_user_accounts: newusers,auth_sessions,auth_tokenstables plus a nullableowner_user_idonprojects,claude_skills,claude_connectors,claude_plugins,claude_models. Purely additive; existing rows have no owner (= shared) so an upgraded deployment behaves exactly as before until accounts are created.
Deployment notes (user accounts rollout)
- Apply migrations as usual (the API container runs them on start).
- Optionally set
SMTP_HOST/SMTP_PORT/SMTP_USERNAME/SMTP_PASSWORD/SMTP_FROM(+SMTP_STARTTLS/SMTP_SSL) andPUBLIC_BASE_URLfor emailed links; without them, invite/reset links appear in the dashboard instead. - Open the dashboard and create the first account — it becomes the admin. Existing
AUTH_TOKEN-based scripts keep working unchanged; the token can be rotated or dropped once accounts exist (keep one as break-glass if you like). - New TTL knobs (optional):
SESSION_TTL_DAYS=30,RESET_TOKEN_TTL_HOURS=2,INVITE_TOKEN_TTL_HOURS=168.
Added — the pi harness for local models (#29)
harnesson model backends (claude|pi, defaultclaude). A backend row can now run its agents through the lightweight pi coding agent instead of theclaudebinary. pi speaks the OpenAI Completions API natively, so a bare local endpoint (vLLM, llama.cpp, Ollama) works without a LiteLLM / claude-code-router translation proxy — and the loop is far lighter for slow local token throughput. Selectable in the dashboard's Claude → Models form and viaPOST /claude/models.- Full gate parity on pi via a bundled bridge extension (
pi_bridge.ts, generated into a per-agentPI_CODING_AGENT_DIRunder~/.handler-pi/, outside the repo tree). All gate logic stays in the same tested Python hooks:- Stop/completion gate (tests green + committed + pushed) re-prompts pi with blockers;
git pushruns the test → image-build → protected-branch approval chain and denies on failure;forge merge/mise run deployhit the approval gate;- questions defer through a new
ask_operatortool into the normal answer/resume flow; - memory recall injects at session start;
memory_search/get/save/linkare registered as native pi tools (pi has no MCP) throughpython -m handler.mcpserver --call.
- Web tools for agents:
web_searchandweb_fetch(handler.webtool), registered on pi-harness agents. Fetch is provider-free (HTML stripped to readable text, size-capped). Search resolvesSEARXNG_URL→BRAVE_SEARCH_API_KEY→ a zero-config DuckDuckGo fallback. - Full built-in tool surface on pi:
read,write,edit,bashplusgrep,find,ls(off by default in stock pi) — 14 tools total including the handler set. - Skills + prompts on pi: pi discovers the same web-managed
~/.claude/skillssync and the repo's committed.claude/skills(forge role skills); handler conventions are appended to pi's system prompt;AGENTS.md/CLAUDE.mdare read natively. - Cross-worker resume for pi sessions: single-JSONL transcripts pre-assigned by
handler, archived/materialized through the existing
session_archivesflow. PI_BINbinary override;SEARXNG_URL/BRAVE_SEARCH_API_KEYsettings; afake_pitest binary and 22 new tests (370 total).
Changed
- Control image: Node bumped from NodeSource 20 to 22 (pi requires ≥ 22.19; Claude
Code needs ≥ 18, unaffected) and
@earendil-works/pi-coding-agentis baked in alongside the Claude Code CLI. control.modelsrefactored:resolve_model()returns the row + decrypted key andharness_of()/claude_env()split harness selection from env building.resolve_model_env()keeps its signature (claude rows unchanged; pi rows return{}).- Dashboard Models form gained the harness selector and a
pi harnessbadge; docs (docs/local-models.md, README) describe both harnesses.
Database
- Migration
0015_model_harness: addsclaude_models.harness(NOT NULL DEFAULT 'claude'). Purely additive — every existing backend row keeps its current behavior. Applied automatically by the API container on start (RUN_MIGRATIONSstaysfalseon control, as before).
Deployment notes (for this release's rollout)
Merging to main publishes both images (docker.yml → ghcr.io/0xwheatyz/handler,
docker-control.yml → ghcr.io/0xwheatyz/handler/control). To roll out:
- Pull both images and restart API before control (compose already orders this):
the API applies
0015_model_harnesson boot; the control worker only needs the new column to exist when a pi backend is first selected. - The control image must be the new build before spawning any pi-harness agent —
it carries the
pibinary and Node 22. Older control containers refuse cleanly (launch fails loudly, no silent fallback to the subscription). - No env changes required. Optional:
SEARXNG_URLorBRAVE_SEARCH_API_KEYon the control container for a realweb_searchprovider (unset = DuckDuckGo fallback);PI_BINonly if pi lives off PATH. - Existing agents are untouched: subscription and claude-harness agents launch exactly as before; running agents and their resumes are unaffected by the migration.
- Rollback: reverting the images is safe — the
harnesscolumn is ignored by old code. Only agents already pinned to a pi backend would fail to resume until the new control image returns (alembic downgradewould drop the column; not needed for an image-level rollback). - Volume/layout invariants unchanged: same
PROJECTS_ROOT, sameHANDLER_SECRET_KEYeverywhere, no new shared filesystem. pi state lives under the worker's$HOME(~/.handler-pi/) and sessions ride the existing DB archive flow.
Verification
- 370 tests green (SQLite, real
alembic upgrade headper test), including the newfake_pirunner suite and mocked web-tool provider tests. - The bridge was validated live against pi 0.84.1 with a stub OpenAI endpoint: memory
injection, push-gate denial (including protected-branch approval), the stop-gate
block loop,
ask_operatorpause/resume, and the 14-tool surface all ran end to end through the real hooks and database.
Earlier work (pre-changelog)
Phases 1–2 plus the web dashboard, headless runner, credential store, schedules, model
backends, skill install-from-prompt, and the agent memory layer predate this changelog;
see docs/PLAN.md and the merged PR history for their details.