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
Handler — mobile app
A remote control for Handler: run many Claude Code agents across many projects, each isolated, each leaving a checkmark (current state) and an entry in the global log.
This is the React Native + Expo (iOS) implementation of the
Handler Mobile.dc.html design (turn 2a, the version the user committed to:
"Commit to 1a, wire-up the real screens"). It reproduces the interactive
prototype as a real app — six wired screens with the exact state logic from
the design.
Run it
cd app
npm install
npm run ios # opens the iOS simulator (requires Xcode)
# or: npm start then scan the QR code with Expo Go on a device
Screens
| Screen | File | What it does |
|---|---|---|
| Fleet (home) | src/screens/FleetScreen.tsx |
Tappable stat cards → filtered agent list, "Waiting on you" → Answer, "Recent checkmarks" → detail |
| Agent list | src/screens/AgentListScreen.tsx |
Every agent (checkmark or not) with All / Running / Waiting / Done filters → detail |
| Agent detail | src/screens/AgentDetailScreen.tsx |
Checkmark / Events / Log segmented control, live headless run event stream, meta table (incl. model backend + worker), Answer / Kill |
| Answer | src/screens/AnswerScreen.tsx |
Question, tappable quick replies, reply field + Send & resume |
| Spawn | src/screens/SpawnScreen.tsx |
Project select, model backend select, task field, Spawn |
| Schedules | src/screens/SchedulesScreen.tsx |
Recurring agent spawns: list, create (interval / role / model), pause, delete |
| Memory | src/screens/MemoryScreen.tsx |
The agent-memory note graph: kind filters, expandable notes, note authoring + deletion |
| Log | src/screens/LogScreen.tsx |
All / per-project / Errors filters over the global feed |
| Settings | src/screens/SettingsScreen.tsx |
Server info, Manage + Account entries, notification toggles, Sign out |
| Connect | src/screens/ConnectScreen.tsx |
Email sign-in (/auth/login), first-run admin setup, forgot-password, API-token fallback |
Management screens (Settings → Manage)
The full admin surface — everything the web dashboard can do, under
src/screens/manage/:
| Screen | What it does |
|---|---|
| Models | Model-backend CRUD: base URL, model ids, claude/pi harness, write-only API keys (set/clear), enable toggles |
| Skills | Create / toggle / delete managed skills, read SKILL.md, install-from-prompt via the command queue |
| Connectors | stdio / http / sse MCP servers with args, env, and header entry |
| Plugins | Marketplace plugins pinned to their repo |
| Permissions | Default permission mode + allow/deny/ask rules over the read-only env baseline |
| Claude login | Drive the worker's claude /login (authorize in browser, paste the code back) |
| Activity | The control-command queue: status filters, worker attribution, result/error detail, 5s auto-refresh |
| Repositories | Register repos (git-server or manual mode, optional mise-init bootstrap), sync, delete |
| Git servers | Forge hosts: encrypted tokens, generated deploy keys (public half copyable) |
| Approvals | Record operator approve / reject verdicts per project + branch |
| Shared context | Browse and set the cross-agent key/value store |
| Users | Invite (shareable links), promote / disable, mint reset links, delete |
| Account | Signed-in identity, change password, sign out (revokes the session server-side) |
The prototype navigates by swapping a single screen value (with working back
/ close controls) rather than a native stack, mirroring the design. Answering
agt-7a1d flips it Waiting → Running everywhere and clears it from the
Fleet waiting list — that cross-screen behavior lives in one shared store
(src/state/AppState.tsx, a direct port of the design's renderVals()).
Design system
The Leeworks tokens (project/_ds/.../tokens/*.css) are ported to typed RN
values in src/theme/tokens.ts; the components used by these screens
(Button, Badge, Icon, Switch, Select, Input, segmented control,
chip) are reimplemented in src/components/ from the design-system bundle.
- Fonts: Outfit (display), Figtree (body), Spline Sans Mono (data) via
@expo-google-fonts/*. - Colors: the warm-neutral ink ramp + muted status colors, light and dark.
Intentional deviations from the HTML prototype
The prototype drew a phone frame to make an HTML mock look like a device. A real iOS app is the device, so:
- The fake status bar (
9:41, signal, battery) and the home-indicator pill are dropped — the OS draws those. Screens use safe-area insets andexpo-status-barinstead. - Dark mode was a design-time prop; here it follows the system appearance
(
useColorScheme,userInterfaceStyle: "automatic"). - The
Selectuses a bottom-sheet picker (native<select>has no cross-platform styling in RN) — same field, real picking behavior.
All copy, spacing, colors, and interactions otherwise match the 2a design.