Workers are background Claude Code sessions that pick up and execute MC tickets.
Scheduler (every 1 min)
└── mc_pickup.py
├── Fetch todo tickets assigned to Luci
├── Check available worker slots (MAX_WORKERS = 2)
└── For each ticket:
├── Spawn: claude -p <prompt> --worktree mc-mc-<id> --effort max
├── Stream output via stream-json
├── Tee raw stream-json to mc-worker-<id>-stream.log (MC-747)
├── Parse DONE:/REVIEW:/QUESTION: prefix
├── Store tool_use/tool_result/text events in ticket_events table
├── Push ticket_event over SSE for live UI panel
├── Post result as comment on ticket
└── Update ticket status
~/workspace/mc_pickup.py — dispatcher + worker logic~/.claude/settings-worker.json — worker-specific Claude settings (no Telegram plugin)~/workspace/scripts/mc_hook.py — PostToolUse/Stop hooks for workers~/workspace/scripts/queue_reaper.py — reaper for stuck queued_messages (MC-462)~/workspace/tasks/queue-reaper.md — scheduler task: runs queue_reaper every 15 minLuci is anchored at ~/workspace, but workers are not all launched from there.
The dispatcher and scheduler use ~/workspace as the machine-level root; each
worker then resolves its own current working directory from the ticket project.
| Ticket/project type | Worker cwd | Why |
|---|---|---|
General, empty project, PKA |
~/workspace/PKA |
PKA repo-local instructions, skills, wiki, docs, and vault paths apply |
Mission Control, mission-control, MC |
~/workspace/mission-control |
MC app code, templates, static assets, tests, and mc.db live here |
Infrastructure, Life, Finance, Machine Ops |
~/workspace |
Machine-level scripts, tasks, logs, wiki, reports, and operational glue live here |
| Any project with a matching slug directory | ~/workspace/<project-slug> |
Lets project repos carry their own local context |
| Unknown project fallback | ~/workspace |
Safe machine-level fallback when no repo can be resolved |
This distinction matters because ~/workspace/wiki is Luci's operational wiki,
while ~/workspace/PKA and ~/workspace/mission-control are separate repos with
their own local instructions. Workers can still read wiki pages by absolute path
from any cwd, for example ~/workspace/wiki/02-mission-control/worker-system.md.
Mission Control itself is launched by systemd with
WorkingDirectory=/home/lucienne/workspace/mission-control, but app.py sets
WORKSPACE = Path.home() / "workspace". That is why /wiki renders
~/workspace/wiki, even though the Flask app process starts inside the MC repo.
ticket-pickup scheduler task runs mc_pickup.py every minutetodo tickets assigned to active MC worker roles
(luci, tessa, scott, atlas, plus lucienne as Luci alias), respects
MAX_WORKERS (2), and spawns or routes the relevant runtime--worktree mc-mc-<id>)--effort max for deepest reasoningdev-loop;
QA/research/architecture phases use their role workflow instead.| Prefix | Ticket Status | Meaning |
|---|---|---|
| DONE: | done | Work completed, ticket closed |
| REVIEW: | in_review | Needs Elmar's review before closing |
| QUESTION: | needs_input | Blocked, needs Elmar's input |
| (no prefix) | in_review | Default — needs manual review |
If a worker posts REVIEW: or QUESTION: mid-session but then outputs DONE:, the DONE is downgraded to REVIEW. The first "needs input" signal wins.
--resume <stored_session_id>session_id in the result stream, MC treats
that value as authoritative: it closes the old worker runtime row, opens a
new row keyed to the actual session id, and records a new ticket attempt with
the fork/cold-start reason. This prevents later replies from resuming the
wrong branch.Tickets assigned to Larry are dispatched via SSH:
ssh elmar@46.225.208.1 "cd /home/elmar/cowork/projects/legalmind-explorer && claude -p ..."
Larry is intentionally limited to claude or codex as first-class CLIs. The
post-completion review uses the selected Larry CLI/model instead of silently
hardcoding Claude. Clean or skipped review now queues Tessa validation via the
workflow action instead of marking the ticket done; critical review findings
return the ticket to Larry for fixes.
Workflow child tickets feed the parent ticket forward automatically instead of completing as isolated records:
send_to_tessa.send_to_luci_review.send_to_luci_review.DONE: APPROVE, REVIEW: RETURN_FOR_FIXES, or
REVIEW: SIGNOFF_REQUIRED to close, return to Larry, or queue Atlas.DONE: APPROVE or REVIEW: RETURN_FOR_FIXES.MC remains the workflow state machine. The tmux/CLI transcript is evidence and an attach surface, not the source of truth for parent/child workflow state.
ruff format on .py filesscripts/queue_reaper.py runs every 15 min via the queue-reaper scheduler task. It handles stuck queued_messages that workers failed to consume:
This prevents the "silent message death" problem where user replies like "yes, go ahead" would sit unprocessed indefinitely.
mc_tmux.start_session calls apply_oom_score() —
luci-persistent orchestrator gets oom_score_adj=-800 (killed last),
mc-* worker sessions get +600 (killed first). MC gunicorn carries
OOMScoreAdjust=-600 via systemd drop-in luci-dashboard.service.d/oom-protect.confWorkers emit structured events to ticket_events as they run, visible in the ticket detail UI:
content_block_stop with tool name, context string, and key argstool_result stream event (content truncated to 500 chars)Raw stream-json lines are also tee'd to ~/workspace/logs/mc-worker-<id>-stream.log for archival and debugging. The print-log (mc-worker-<id>.log) continues unchanged.
On the UI side: the ticket detail page calls GET /api/v1/tickets/<identifier>/events on load to replay history, then subscribes to the per-ticket SSE stream (/api/v1/tickets/<id>/stream) for live ticket_event messages. The panel is collapsed by default and auto-opens when the ticket is in_progress.
When a worker session is interrupted (e.g. context window exhaustion, OOM, rate-limit cascade), the scheduler detects the crash and auto-resumes:
session_id get --resume on next pickup cyclesession_id, MC records a fork/cold-start reasonWorker sessions spawned by mc_pickup.py now clear stale ANTHROPIC_* environment variables before launch. Previously, leftover API keys from prior sessions could leak into the worker subprocess, causing authentication confusion when the worker's intended provider was non-Anthropic (e.g. GLM, Kimi). The fix strips these vars in mc_pickup.py before Popen.
Orphaned interactive ticket runtimes (tmux sessions for in_progress tickets whose PID no longer exists) are now detected and cleaned up automatically. The reaper:
in_progress/proc/<pid>/The mc_request() helper in mc_pickup.py now handles transient gunicorn worker-recycle blips (connection refused, 502) with a short retry loop instead of immediately failing the pickup cycle. This prevents false-negative "MC unreachable" errors during gunicorn worker rotation, which was causing pickup to skip valid tickets.
~/workspace/PKA, MC work in ~/workspace/mission-control, and machine ops in ~/workspaceMission Control is the board for your delegated work: requests come in, Luci coordinates the next step, and evidence stays visible for review.
Luci is your always-on assistant for routing, status updates, and follow-through. Operators can still open deeper evidence when needed.