Agent runs and cancellation
Every long-running thing JobEmber does is an agent: the searcher, the employer-profiler, the cv-adapter, the rewriter, the AI-detector, the voice-creator, the LinkedIn composer, the evaluators. Most take 10s-3min. The active-runs indicator in the global header surfaces every in-flight run so you always know what’s still cooking.
What the indicator shows
A small pill with a blinking dot when at least one agent is running. Hover to expand a list of:
- Agent type (e.g. “Job search”, “Employer research”, “CV/CL batch”).
- A “Stop” button per row.
- A deep link back to the stage that owns the run.
If you closed the tab where you started the run, the indicator picks the run back up on the new tab — agent state is server-side, the UI just attaches to the existing stream via /api/runs/[id]/stream.
Cancelling
Click “Stop”. The route forwards to loomcycle’s cancel API, which terminates the agent process. Anything the agent had already written to the database stays (e.g., a half-finished employer-profiler that wrote 2 of 5 company profiles); anything in-flight is discarded.
Cancellation is idempotent — clicking twice or after a cancel-already-arrived doesn’t error.
When a run looks “stuck”
A run is “stuck” if loomcycle restarted while it was in flight — the local agent_runs row still says running but loomcycle no longer has the cancel registry entry. The Stop button shows “Stop (stuck)” disabled in this state; the heartbeat sweeper eventually marks it failed automatically.
What you don’t see in the indicator
Background work that isn’t an agent: PDF rendering, MCP DB writes, file uploads, Q&A persistence. These are sub-second and don’t need cross-tab persistence.
Cross-tab persistence (open in two tabs)
The activeRuns Zustand slice persists to localStorage. If you open JobEmber in a second tab while a run is in flight, the indicator shows it on both tabs. Cancel from either tab — both update.