Activity
Overview
Activity lives at/activity, under the Run group of the sidebar. It answers the question people arrive with — what is happening, and where do I look — as a stream: a left rail listing what has been active in the loaded window, and a main column that changes with whatever the reader picked.
The page is built from two independent reads:
- The workflow index —
GET /api/v1/chains(hooks/use-chains.ts), one row per chain. A chain is every run sharing onechain_origin: the rule or person that started it, the routine runs it caused, and the agent work those dispatched. It is fetched on demand, not streamed. - The journal —
GET /api/v1/journal, which feeds the event list, the Overview dashboard and the opened-event detail. It streams over SSE and falls back to polling.
/activity used to be a single-canvas run trace: a rail of runs, one run’s execution chain on a ReactFlow canvas, a step side panel. That page is gone — with its saved views, heatmap toolbar, step selection and inline waitpoint buttons. A run’s execution was not lost; see Where the run trace went.When to use it
- Something ran and you want to know what it caused. A workflow row opens the whole chain — its runs, its causal graph, the sequence it happened in, and the issues and agents it touched.
- You want a routine’s runs, not its definition. The Routines lens opens a routine’s runs grouped by the hour, with each run’s own result on the row instead of its id. The definition stays on Routines.
- You want to know what your agents did. The Agents lens lists agents that took work in the window, how much, and in which workflows.
- An issue moved and you want to know what moved it. The Issues lens lists issues touched in the window with the routine and agent that reached each one.
- You are chasing a failure. The Failed status segment narrows both the rail and the feed.
/journal or the Crew Journal API.
Key concepts
The rail
One column, and it is navigation: a search box, a filter popover, four lens tabs, four status segments, then the list. Every narrowing lives in the popover; every place you can go is a row.The four lenses
Each lens lists only the members that were active in the loaded window. An issue nobody touched today is in Issues, not here.
Workflow rows are grouped into Active now, Today and Earlier. “Active now” is a state, not a time: a chain parked on a human approval since Tuesday is the most urgent row on the page and must not sort to the bottom by its timestamp.
Each workflow row wears its routine’s own icon and colour (the same derivation the Routines rail uses), a status dot notched into the corner, a halo while something is live, its sentence,
relative time · duration · what it touched, and its handle.
Status segments
All · Running · Waiting · Failed — mutually exclusive. Completed joins the line only while it is the current scope, because the Overview’s KPI cards can send the page into any scope and a control that cannot draw the state it was handed reads as broken. The counts are taken over the window with the search applied but not the segment, so a number survives its own selection: counting after the pick would render “Failed 3 · Waiting 0 · Running 0” — what is left rather than what there is to pick. A segment showing no number holds nothing in this window. The same choice narrows the journal query server-side (active/waiting become an entry_type filter, failed becomes severity=error); done is the one bucket with no server-side expression, so it is filtered client-side.
Waiting is narrowed twice, and the second pass is the one that matters. The journal is an event log, so the entry_type query returns ask-shaped rows — including an approval.requested whose approval was granted an hour ago, and a peer.escalation that is the resolution of one (ask and answer share that one entry type, separated only by payload.state). A row is only still waiting on somebody while its ask is open, so each ask is joined to its answer on kind + id over the loaded window, and the answered ones fall into Completed. An ask whose answer lies outside the window, or that carries no id to join on, stays under Waiting: over-reporting one row is a smaller failure than hiding something a person is blocking on. One function (scopeOf in lib/activity-stream.ts) does this for the segment count, the feed list, every row’s status dot and the Overview’s Waiting on you card, so those four cannot disagree.
Search and filters
The search box narrows two things at once: the rail’s workflow list (client-side, over the loaded index page — matching the routine’s name and slug, what started the chain, the handle, and the identifiers, titles and agent names it touched) and the journal query (server-side, asq).
The Filter trigger carries a count of active narrowings and opens a popover holding:
error is deliberately absent from Severity and human from Source: they are the Failed and Waiting segments, and one filter reachable from two controls is how the two quietly contradict each other. See lib/activity-rail.ts.emptyLensCopy), because they want different actions from the reader.
The main column
One selection, one column — never a stack.Overview (Workflows lens)
Four KPI cards — Running now, Waiting on you, Failures, Spend — where the first three are places to go rather than numbers to read (clicking one selects that status segment). Under them: Open asks and What is broken side by side with their evidence, then Latest activity and, only when the loaded window spans two days or more, a Failures · N days bar chart. Waiting on you and Open asks count asks that are still open — the same join the Waiting segment uses, not a second opinion about it. A zero says which zero it is: “nothing broke” is a claim about the world, and this window only ever knew about itself.Issues, Agents, Routines
Each lens owns the column, so pressing a tab changes what is shown and not only what is selected.- Issues — Touched / Created by agents / Changed / Agents involved, then one line per issue carrying the issue’s real status and priority glyph, with an indented
↳ routine → agentline that opens the workflow behind it. - Agents — Agents at work (of N hired) / Assignments / In workflows / Busiest, then one line per agent with a proportion bar and what it worked on. “In workflows” is the wall clock of the chains the agent worked in, not billed agent time — the index does not carry that.
- Routines — Routines that ran (of N in the catalogue) / Runs / Success / Failing, one line per routine with a success-rate bar, plus a Firing next card listing the schedules due next with their cron and a countdown.
A workflow page
Picking a workflow row opens it as a page, answering five questions in order:- What was this? — the sentence as the heading, who started it, the raw
triggered_via, when (absolute and relative), and the chain origin. Then a strip: Duration, Runs, Failed, Depth, Issues, Agents. - Which runs? — the Runs card, derived from the chain walk and filtered to this chain’s origin. Each row opens that run.
- How did it happen? — the topology card: the causal graph from
GET /api/v1/chains/{anchor}, drawn left to right. Nodes are clickable and walk one level down. - What happened, then? — What happened, in sequence, beside the graph: the same nodes as dated rows, indented by cause, time in a fixed left gutter and duration in a fixed right one.
- What did it touch? — the issues and agents, clickable.
Where a row leads
Each kind gets the surface that kind already has elsewhere, not a fifth rendering of the feed.A routine’s runs
Thirty runs of a one-minute routine are thirty rows that read the same, so this page makes three deliberate choices (all pure functions inlib/run-digest.ts):
- Absolute time, to the second — the reader already knows it was recent and is asking which one.
- The run’s own result instead of its id — the first line of its output (“3 tickets classified”), or the error for a failure, or a
slow — 4.2snote when the run is past 1.4× its peers’ median. An empty output renders as a dash rather than a stock phrase repeated on every row. - An hour header that summarises — “12 runs · all ok” is skippable; “3 runs · 1 failed” is where you stop. The header carries its date only when the page holds more than one day.
This page’s “failed” folds in
cancelled and interrupted, while a chain row’s failed_runs does not. That is not a bug to align: a chain row answers did this workflow fail, where a process killed by a deploy is the operator’s problem; this page answers which of these thirty runs did not produce the thing I came for, and a run killed mid-flight produced it exactly as little as one that threw.Opening one event
A row in the feed opens as a full-width page, not a side panel — an activity has a shape, and a 340px column is where that shape goes to die. It carries: four KPIs (this event, chain duration, chain cost, errors in chain), an Execution graph for the run behind the event, a topology card, the Chain (every journal entry under the same trace, oldest first), and the raw Record.The execution graph here is the same
TraceCanvas component the old trace page drew — but inline waitpoint decisions and heatmap shading are not wired to it. Decide waitpoints in Inbox.Where the run trace went
This is the question every reader of the old page arrives with. The trace canvas as a page — pick a run from a rail, read its steps in a side panel — no longer exists. What it showed is in three places:
There is no step object on this page: a run’s steps are its drill-down. Saved views, the heatmap toolbar, group-by axes and keyboard step selection went with the canvas and have no replacement.
Deep links
Fourteen places across the product link into/activity with a query string the trace canvas defined. Those links still work — the parameters are translated rather than dropped (lib/activity-deeplink.ts). They are read once, at mount; after that the page owns the selection.
Two rules are worth knowing:
- Order is by specificity, not by the URL.
?mission=then?pipeline=then?run=are pushed as stops in that order, so?pipeline=<slug>&run=<id>— the link routine cards build — lands on the run with the routine behind it, and Back returns to the routine rather than to the overview. - A status this page has no segment for is ignored rather than obeyed. A link written against some other vocabulary lands on everything, because a page that renders empty because it did not recognise a word is indistinguishable from a page with no data.
What the numbers can and cannot say
Every count on this page is a claim about a window, and the page says so where the numbers are. Repeated here so nothing is implied by omission:- One page of the index. The lens tabs, the status segments and every KPI on every lens dashboard are derived from the loaded chain index page — the newest 25 rows in the dashboard. When the workspace holds more, the rail prints “Newest N workflows. Every count on this page describes these, not the whole workspace.” (
has_morefrom the API; the client treats a missing field as “there are more”). The API itself defaults to 50 rows and caps at 200. - The Agents lens sees only routine-dispatched work. The index groups
pipeline_runs, so an agent whose work no routine dispatched has no chain to belong to and is not indexed. That is a hole in the index, not in the lens — and the empty state says it rather than reading as “nobody worked”. - The Issues lens is issues touched in the window, not the backlog. An issue with no activity has no row here at all.
- Issue writes an agent makes from inside its own assignment do not attach to the chain. Those journal entries carry the assignment’s own journal run id as their trace, and that id is not recorded on the assignments row, so nothing joins them back. The issues a chain’s routine runs touched are complete; the ones its agents touched are not reachable. Stated as a KNOWN GAP on
chainIssuesQueryininternal/api/chains_list.go. - Chains whose runs predate
chain_originare not in the index at all. The link was never written and cannot be reconstructed, so listing them would mean claiming each run was its own chain root. Their existence is reported instead — the rail prints “Older runs are not indexed here — the link that would group them was never written.” - A row’s issue and agent lists are capped at 5 each, with the exact totals alongside, so a cut list reads as cut (“+N more, not carried on this row”).
- A routine, run or step crumb narrows the loaded window only — those ids live inside the journal payload, which the API cannot filter on. The chip says
(loaded window)rather than implying a full-table match. - A workflow chip narrows nothing. The journal has no
chain_origincolumn, so a chain is not expressible as a filter over journal rows; picking one re-points the column. It is therefore not counted by the “none satisfies all N filters” banner. - The index is fetched, not streamed. A row can be picked and then swept from the list — the page says “This workflow is no longer in the index” rather than drawing a graph-shaped hole.
What earns the word “workflow”
The Workflows lens lists only chains that composed something: more than one run, a composed hop, an agent dispatched, or an issue touched. Plus two exceptions that are not about composition — a chain that failed, is running or is waiting on a person (that is why somebody opened this page), and a chain no other lens can list (one whose root run retention swept has no routine slug, so the Routines lens has no row for it). Nothing is deleted by this. A bare run of a known routine is still a run, and the rail says where it went: “N runs composed nothing and are listed under Routines.”Keyboard
j/k— step to the next or previous event in the feed, opening it. From nothing open,jopens the newest.Esc— close an opened event; with nothing open it steps one stop back up the trail, rather than doing nothing once you are already at the top.
Realtime
The journal feed subscribes to the workspace stream and prepends live entries; the badge next to the event count reads live (SSE), polling (the stream is unavailable, falling back to a 5s poll) or connecting. There is no Pause and no Refresh: the surface is always live, so a Refresh button would only advertise a staleness that is not there and a Pause is a way to be shown old numbers by accident. The chain index is not streamed. The rail is a place you return to between actions, not a tape you watch — one query per look, rather than one per event, and no re-sorting under the cursor.API and CLI
Activity is a shell over existing endpoints; each has a CLI command with the same shape.
Schemas:
/api-reference/chains, /api-reference/journal, /api-reference/runs. CLI pages: crewship chain, crewship activity.
The legacy /orchestration route still redirects to /activity so old bookmarks keep working.
Activity Bar (toolbar)
The/activity page answers “what happened” in depth; the Activity Bar answers “what is running right now” at a glance, from anywhere in the app. It is the Activity icon in the top toolbar next to the Inbox bell, with a badge counting live runs — amber the moment anything waits on a human approval, blue while routines run.
The dropdown holds LIVE (up to six active routine runs with their current step, elapsed time and cost, each with Review / Trace / Cancel, plus any in-flight agent runs) and RECENT (the last few terminal routine runs, so “did my run just finish?” costs no page hop). The footer opens /activity?status=active while anything is live, and /activity otherwise.
The server is the source of truth: GET /api/v1/runs?status=RUNNING for agent runs and GET /api/v1/workspaces/{ws}/pipelines/runs/active for routine runs, re-fetched on a short poll and immediately on assignment.updated, run.*, mission.updated, pipeline.run.* and pipeline.step.started — so a dropped frame cannot leave the badge stale. Nothing in flight, and the badge disappears.
Run activity rail
Individual issue and routine pages carry an inline Run activity rail — the readable, human-first timeline of what an agent actually did during a run, built from the journal. Each step is one line: a toned icon, the time, a plain-language title (“Ran command”, “Wrote file”), an optional detail line (the path, command or url) and right-aligned metadata (exit 0 · 1.2s, 412 B, $0.0021). Machine noise — output chunks, container metrics — is dropped so the rail stays the highlights reel.
- On an issue (
/issues/<id>): once the issue is started, a “Run activity” section appears below the description, scoped to that issue’s runs via the journal’smission_idfilter and narrowed to work entry types (run.*,assignment.*,exec.command,file.written,network.egress,llm.call,keeper.*,summary.generated). It is distinct from the issue’s lifecycle Activity feed below it, and hidden while the issue is still in Backlog or Todo. - On a routine (
/routines): right after you click Run or Test run, the rail shows the just-started run inline so status is visible immediately. - On a run drill-down here: the same component renders the Steps card, so a run reads the same wherever it is opened.
Common pitfalls
- “Agents 4” is a fact about the window, not the workspace. Read the line at the bottom of the rail before quoting a number from this page.
- An empty Agents lens does not mean nobody worked. Work no routine dispatched is not in the chain index at all.
- A short “what it touched” list is a cut list. The row carries at most five issues and five agents; the exact totals are on the workflow page’s strip.
?pipeline=<slug>narrows, it does not open the routine. To get a routine’s runs, switch the rail to the Routines lens and pick it there.- Two crumbs can intersect to the empty set. A run pinned from an event’s spine plus an issue focused in the rail is one click away; the page says the question is empty rather than reporting a quiet system.
- The workflow you picked can vanish. The index is fetched once per look, so a row can be swept between the click and the render. The runs it held are still in the feed.
- A server that records runs in the journal only has no per-run list. The routine runs page says so instead of rendering an empty table.
Related
- Inbox — where waitpoints, escalations and failed runs are actually decided.
- Routines — the definitions whose runs this page streams.
- Crew Journal — the event log Activity reads from, and how to query it programmatically.
- Orchestration — the engine behind the runs (lifecycle, retries, deadlock detection).
- Harbormaster — the approval gates that put a chain into the Waiting segment.
- Paymaster — the cost ledger behind the Spend KPI and a run’s cost.