Running an engagement
Live progress, the event stream, and what the console shows while a run is in flight.
A submitted engagement goes onto a queue, waits for the scheduler to pick it up, and then streams events to the engagement page until it ends.
The queue is sequential¶
Submitting writes a job onto a Redis list. One process — the scheduler on the agent host — polls that list, and the poll loop takes one job, awaits the whole run, and only then looks for the next. There is no priority and no preemption. Five queued runs are five runs one after another.
The plan tier of the user who created a schedule caps how many engagements the
organization may have queued or running: free 1, pro 3, enterprise 10. An
unrecognised tier gets the free allowance rather than an unlimited one. The cap
governs how much may be in flight, not how fast the queue drains, and it is
checked only when a schedule fires: if the organization is at its limit, the
occurrence is skipped and the reason is recorded against the schedule. A
manually submitted engagement is not refused for the cap.
Caps and skips describes the cap and the skip,
and Plans and limits the tier.
The event stream¶
The engagement page opens a server-sent-events connection to
/api/engagements/{id}/progress. Events are not read from a live channel: the
scheduler appends each one to a per-engagement list in Redis, and the endpoint
reads that list by cursor, replaying everything recorded so far and then polling
for more once a second.
Two consequences follow. Reconnecting replays the run from its first event rather than resuming where you were, so closing the tab during a run loses nothing — as long as the run is still going when you reopen the page. If it ended meanwhile, the console will not open the stream at all, and the trace has to be fetched from the endpoint directly. And the trace is kept for 24 hours and capped at 5,000 events per engagement; past either limit the oldest events are gone, though the findings, artifacts and report are not affected.
The endpoint checks that the engagement belongs to your organization. Another organization's engagement returns 404 rather than 403.
What an event carries¶
Events the agent emits carry a type, the phase they came from, a human-readable
message, the engagement id and a UTC timestamp. Three further fields appear only
where they apply: command on a command event, severity on a finding event,
and tool.
The scheduler's own events are assembled separately and are thinner: a type, a
phase and a message, with no engagement id or timestamp of their own.
engagement_sealed is the exception in the other direction — it carries the seal
values in place of a message.
| Type | Emitted when |
|---|---|
job_started |
the scheduler is launching the container |
phase_change |
the run starts, a phase completes, a phase begins, or the rules-of-engagement document is rendered |
tool_execution |
a command has passed the scope guard and is about to run |
command_blocked |
the scope guard refused a command, or a tool call's arguments would not parse |
finding |
a finding was recorded, with its title and severity |
error |
a step raised, carrying the consecutive-failure count; the abort that follows the fifth failure is one more error event — the sixth — carrying no count |
artifact_captured |
an artifact was hashed and stored, with its hash, kind, size and sequence |
engagement_complete |
the agent finished its loop; the scheduler emits a second one after persisting results |
engagement_failed |
the container exited non-zero, produced no results, or the scheduler itself errored |
engagement_sealed |
the run was sealed, with the chain head, manifest hash, entry count and key id |
The stream carries the command text the agent runs. It does not carry command output: stdout and stderr go into the artifact, not into the event.
What the console renders¶
The engagement page opens the stream while the status is queued or running,
and not for a run that has already ended. Until the container starts there is
nothing in the trace, and the terminal shows waiting for events….
Two things are drawn. A six-stage pipeline — intel, threat model, vuln scan,
exploit, post-exploit, report — highlights the stage matching the most recent
phase value. Pre-engagement is not one of the six, because it renders the
rules-of-engagement document and advances without a model call. Below it, a
terminal renders phase_change, tool_execution, command_blocked, finding,
error and the two terminal events as colored lines.
artifact_captured and engagement_sealed are published to the stream but the
console does not render them. Evidence and seal details are read from the
engagement afterwards rather than watched live.
The console stops at the first engagement_complete or engagement_failed and
closes the connection. The agent emits engagement_complete itself, as its last
act before the container exits, so the work that follows — writing findings to the
database, sealing, and the scheduler's own completion event — happens after the
terminal has stopped. On engagement_complete the page refreshes findings and
requests a report; on engagement_failed it marks the engagement failed.
What you cannot do¶
There is no pause, no abort and no way to steer a run in flight. An engagement that has started ends on its own terms. The ways it can end are in Lifecycle and When a run fails.
Last updated 2026-09-23