nextv2.0.0
Reference

Events

Every event type on a work item's timeline, its key payload fields, and the notification webhook that sends them.

Kraft records what happens to a work item as a list of events, its timeline. This page lists every event type, grouped as the contents list shows, and the notification webhook that sends them.

Every event has these fields:

FieldMeaning
seqThe event's number on the timeline.
work_item_idThe item it belongs to.
typeOne of the types below.
payloadA JSON object. Each type's key fields are in its row.
node_idThe node the event is about, or null for an item-level event (filing, a pause, a bulk action). It comes from the payload's node_id or node key. A gate's own events carry it under gate.
created_atWhen it happened.

Before 2.0, an event carried no node_id of its own. The upgrade filled it only from a payload's node_id or node key, so a gate's older events in an upgraded database keep null: they named their node under gate.

Read the events with kraft view events:

CommandWhat it shows
kraft view events IDThe whole timeline.
kraft view events ID --type gate_requestedOnly the events of that type.
kraft view events ID --after 120 -fOnly the events after seq 120, then follows the timeline.
kraft view events ID --jsonThe raw events.

The tables list each type and the payload fields you are most likely to use. A payload can carry more fields than these. Event types and payloads are not a stable interface: a release can add, rename or reshape them.

Work item

TypeWhenKey fields
work_item_createdThe item is filed.title, repo, chain_template (the chain the filing named, else the repo's default_chain; null when neither names a chain other than default); source, bead_id, priority when auto-intake filed it
work_item_attachmentsThe item is filed with a spec or plan.attachments
work_item_resumedA paused item starts or restarts.steer
pause_requestedSomeone pauses the item.sessions
work_item_queuedA start found every slot busy; the item waits for one. Written again when a server stop interrupts the start of a queued item, which goes back in its place.verb, from
work_item_dequeuedA queued or blocked item left the hold other than by the queue starting it. why is paused (by a person), refused, failed or superseded (detail says what; a superseded item is where to says), or dependency_abandoned.why, detail, to
work_item_storage_heldA start was queued because worktrees are over storage.worktrees.limit, not because a slot was busy. Written once each time the item is queued.used_bytes, limit_bytes
work_item_blockedA start was held: the item comes after something unfinished.verb, from, on
work_item_unblockedEverything the item came after completed; it joined the queue.
work_item_dependencies_droppedSomeone dropped what the item came after.dropped
work_item_blocked_by_dependencyAn open bead dependency paused the item before its next node.node_id, blocked_by
paused_by_broken_baseAnother item broke the base branch, so this one paused.broken_by, follow_up_bead
work_item_needs_humanThe item stopped for a person. See below.node_id, reason, kind
work_item_waitingThe item is parked on an external wait.node_id, retry_at
work_item_rate_limitedThe agent hit a rate limit; the item retries on its own.node_id, retry_at
work_item_retriedSomeone retried the item.node_id, steer, escalated (an escalation turn retried it)
work_item_completedThe item finished. Alone when its chain ended; after work_item_manually_completed when someone completed it by hand.none
work_item_manually_completedSomeone completed the item by hand. work_item_completed follows.reason, node_id
work_item_cancelledSomeone cancelled the item. work_item_abandoned follows.reason, node_id
work_item_abandonedThe item ended as abandoned. Alone for kraft item abandon; after work_item_cancelled for a cancel.none
work_item_archivedThe item left the Done column.by, kept_branch, unpushed_commits, rescued_branch, rescued_commits, worktree_kept, reason. See below.
work_item_restoredAn archived item came back.none
work_item_title_editedThe title changed.title
work_item_description_editedThe description changed.description
plan_progressA worker started a plan task.node_id, task, total, title
steer_context_setA steer was stored for the next agent.steer
steer_undeliveredA steer was stored but no agent received it.steer

work_item_archived

FieldMeaning
byyou or auto.
reasonstorage when automatic clean-up archived it for the worktree limit. Absent otherwise.
kept_branch, unpushed_commitsPresent when the branch stayed because it has commits nowhere else.
rescued_branch, rescued_commitsPresent when commits on a detached HEAD were kept on a new branch.
worktree_keptThe reason, when that branch could not be made and the worktree stayed.

work_item_needs_human

This is the event behind stop_reason in kraft view show --json, and behind "why did it stop" on the board. Its fields:

FieldMeaning
node_idThe node the item stopped on.
reasonWhat stopped it, in words. A reason that starts needs_context: is a question from the agent.
kindWhy, as one of nine values. See Stop kinds.
session_idThe last session of that node, when it failed or asked a question. kraft view logs reads its log.
capped{cycles, attempts} when a fix loop spent its attempts.
budget{scope, spent_usd, cap_usd} when a spend cap stopped the item.
stucktrue when the item stopped in a way an escalation turn can answer.
bundleWhat Kraft gathered about a stuck item when it gave up.
suggested_action{action, reason}, the next step Kraft suggests.
factsDetail that depends on the stop, when there is any. See The facts field.

Stop kinds

kind is also the value stop.kind takes on the HTTP API. Why did my item stop? gives each stop's fix.

KindMeaning
questionAn agent asked for context it needs (needs_context:).
capA loop or time cap ran out: a fix loop's attempts, a time cap, a gate's rejection cycles, a wait's timeout, or rate-limit retries.
budgetA spend cap, in dollars or tokens, refused the next launch.
failedA task failed, a changed-test scope ran red, or a read-only node changed the worktree.
conflictA merge or rebase conflict the conflict handler could not clear.
mr_closedSomeone closed the item's merge request.
configThe chain, the repo's configuration or a gate's document cannot run as it stands: a task that could not start, a missing base branch, a chain that changed under the walk.
infraKraft or what it depends on failed: a crash, git, the forge, CI infrastructure. facts.cause says which.
stuckA loop stopped making progress: the same findings across cycles, a judge's ruling to stop, or a repair that doubts itself. An escalation turn can answer it.

stop.kind takes three more values. They come from the item's status, not from this event:

KindMeaning
gateThe item waits at a pending gate (a gate_requested event).
waitThe item is parked on an external wait: CI or a review.
rate_limitAn agent rate limit holds the item, and it retries on its own.

The facts field

What facts carries depends on the stop:

Stopfacts carries
A conflict stopresolved and unresolved: the files the handler cleared and the ones still conflicted (git diff --diff-filter=U in the worktree).
A closed MRIts ref and url.
A failed stop on a red changed-test-scope runThe first failed command's command, scope, exit_code and session_id (area, and setup: true for an area's setup), and every failed one under failed_scopes when more than one went red.
A stop whose cause Kraft can namecause: see below.

On an infra stop, cause is one of:

  • crash: the executor task crashed.
  • stranded: a claim that no walk took over.
  • ci_infra: CI infrastructure failed and retrying did not recover it.
  • git: the worktree refresh failed.
  • forge_auth: the forge refused the credential.
  • forge_unreachable: the forge could not be reached.

For forge_auth and forge_unreachable, the git or forge error says so. A forge task's own failure carries one of these two the same way, on its failed stop, or on its infra one in a node with a fix loop. Other infra stops name no cause.

Chain and nodes

TypeWhenKey fields
chain_loadedThe chain was built for the item.addons, only when the chain resolved through a Kraft add-on
worktree_preparedThe worktree was created and set up.report
node_startedA node began.node_id
node_completedA node finished.node_id
node_skippedA node or its gate was skipped.node_id, gate, note
scope_skippedA step or task inside a node was skipped.path, note
node_recovery_startedKraft reran a node's failed tasks.node_id, failed_tasks
run_forkedA retry started a new run from part of the old one.path, scope, reopened
base_change_restartThe base branch moved, so part of the chain reran.node_id, restart_from, nodes
worktree_rebase_verifiedA rebase reported by a worker matched the worktree.reported_head, worktree_head
read_only_violatedA read-only node or step changed the worktree.node_id, scope, files
fix_cycle_startedA fix loop started another attempt. max_attempts is how many the loop allows.node_id, cycle, max_attempts, failed_tasks
fix_cycle_finishedA fix attempt's repair ran. committed is whether it moved the branch head, null when git could not say.node_id, cycle, committed, head_sha
fix_cycle_refundedA fix attempt did not count, because the repair never ran.node_id, cycle, outcome
findings_measuredA node with a fix loop measured a round. measured_tasks are its measuring tasks with a session that measurement read, unmeasured_tasks the ones with none. cycle is -1 after a failed on_failure recovery.node_id, cycle, measured_tasks, unmeasured_tasks, findings
test_scopes_selectedA changed-test-scope task picked its commands for a repository, before it ran any. One per repository and round; a rerun emits another, and the latest counts.node_id, hook_point, repository, round, commands (each area's setup ahead of its first scope)
judge_verdictA judge ruled on findings.node_id, cycle, verdict, reasoning
sweep_failedKraft could not commit work an agent task left uncommitted. The work stays in the worktree. See below.node_id, task, error
sweep_left_outAfter an agent task, Kraft committed the work it left, but not some paths. See below.node_id, task, paths
bead_not_filedIntake could not file a bead.reason
beads_left_openThe item completed but its issues in beads stayed open.beads, reason

chain_loaded

addons maps each add-on namespace the chain resolved through to the version the item pinned: {id, commit, tree, source, digest, version}. commit and tree are null for an add-on of a directory collection. The item reads that add-on's skills and agent profiles from this version until it ends. See What a work item pins.

{"addons": {"release": {"id": "release@acme", "commit": "f4a11422cc8377a6ae61353897431dafa72b8ef5",
  "tree": "de1b745c4020ff2d468c14451da2f28a5fb56207", "source": "./addons/release",
  "digest": "sha256:f915f6266d319e9c0bc2bb2483683d74f307839138258901919e7d071fd37da4", "version": "1.0.0"}}}

sweep_failed

Kraft could not commit the work because:

  • the commit failed;
  • the worktree was not on the item's branch;
  • the worktree held a git operation Kraft did not start; or
  • the worktree held a repository Kraft did not create.

sweep_left_out

After an agent task, Kraft commits the work the task left, but not the installs the repo's setup made. The event names the paths it left out. It is written only when that set differs from the item's last sweep_left_out, not after every task.

Kraft leaves out:

  • a lockfile the repo's setup command wrote and nobody changed since (uv sync in a repo that commits no uv.lock);
  • a virtualenv or node_modules the repo does not ignore. A node_modules the setup command made counts whatever is in it: bun leaves no marker.

Kraft commits, as the agent's work:

  • a lockfile the agent writes, or an edit it makes to the setup's;
  • a node_modules the setup did not make with no package manager's marker in it (.package-lock.json, .modules.yaml, .pnpm, .yarn-integrity, .yarn-state.yml), even beside a package.json;
  • a pyvenv.cfg with no python beside it.

Gates and review

TypeWhenKey fields
gate_requestedA gate is waiting for a decision.gate, node_id
gate_approvedA gate was approved.gate, by
gate_rejectedA gate was rejected.gate, note, node, by
gate_reopenedAn approved gate is open again.gate, reason
gate_auto_review_startedA gate's review agent started.gate, session_id
gate_auto_review_skippedA gate's review agent did not run, or left the decision to a person.gate, reason; note says why, when the review gave a reason
gate_auto_review_discardedA review agent's verdict was not used.gate, verdict, reason
chain_revision_shownA person was shown a chain revision.gate, digest
chain_revision_unchangedA chain revision proposed no change, so its gate passed.gate, rationale
chain_revisedAn approved chain revision, or an applied item draft, replaced the chain.gate, changes, diff; source: "draft" and gate: null for an item draft
artifact_refusedKraft found a gate's document, or a document a task wrote, but would not serve it; once per refused request.path, reason
review_submittedA review was sent.review_id, gate, outcome
thread_updatedA review thread was resolved or reopened.thread_id, state
rewind_requestedA review asked for work to be redone.review_id, target, note
rewind_cancelledThat request was withdrawn.review_id
reply_agent_wroteAn agent answered review threads.gate, files
reply_agent_skippedNo agent answered review threads.gate, reason
reply_agent_failedThe reply agent failed.gate, error

by is human (the board or the kraft command), assistant (an agent through the MCP server), agent (a Kraft worker, such as a gate's review agent) or kraft (Kraft itself).

Agent sessions

TypeWhenKey fields
worker_session_createdA session was recorded, before launch.session_id, node_id, hook_point, attempt
worker_session_startedIts process started.session_id, node_id, hook_point, pid
worker_session_exitedIt ended.session_id, status, wall_ms, model, cost_usd, token counts
worker_session_pausedIt was stopped by a pause, skip or manual end.session_id
agent_session_resumedA session resumed the agent's earlier conversation.task, session_id
session_reattachedAfter a restart, Kraft found a session still running.session_id, pid
session_unknownAfter a restart, Kraft could not confirm a session.session_id, reason
background_jobs_abandonedA session left background jobs behind.reason
permission_decisionThe permission gate answered a tool call.session_id, node_id, tool, decision, reason, grant

Caps, budgets and limits

TypeWhenKey fields
time_cap_reachedA time cap ran out.scope, field, minutes, reason, node_id
scope_budget_reachedA scope's token or dollar cap refused a launch.the breach fields, task
budget_changedThe item's spend cap was set.budget_usd
budget_raisedThe cap was raised to continue a stopped item.budget_usd; key: "policy.budget_usd" when it was the item-wide budget_usd of the item's policy, not its own cap
spend_unpricedThe first session on the item whose harness reported no cost and whose model prices.json cannot price: the item and daily dollar caps count it as $0.session_id, harness, model, message
cap_counters_resetLoop counters were reset: by a retry a person asked for, or by a gate rejection for the fix loops it re-runs.by, counters
rate_limit_hitAn agent CLI refused a launch for a rate limit.node_id, rate_limit_type, resets_at_iso
launch_fallbackA launch moved to the next harness or model.node_id, task, from, to, reason
launch_fallback_exhaustedEvery fallback was limited.node_id, task, resets_at_iso

Escalation

TypeWhenKey fields
escalation_messageA message went into the escalation thread.session_id, message, auto, thread, turn
stuck_escalation_startedKraft started an escalation turn on a stuck item.node_id, task, reason, attempt
stuck_escalation_finishedThat turn ended.node_id, task, status
work_item_auto_escalate_skippedKraft did not escalate a stop.reason
work_item_auto_escalate_cappedAutomatic escalations hit their limit.cap, count
work_item_self_retry_requestedAn escalation turn asked to retry the item.node_id, path, restart, steer
work_item_self_retry_droppedThat retry did not happen.node_id, status

Forge, CI and waits

TypeWhenKey fields
mr_openedThe merge request was opened.number, url
mr_closedKraft closed the item's merge request because POST /api/work-items/{id}/cancel set close_mr: true. One someone else closed is a work_item_needs_human event of kind mr_closed.ref, url, by (always "cancel")
mr_reopenedPOST /api/work-items/{id}/reopen-mr reopened a closed merge request.ref, url
mr_labels_setA worker labeled the merge request.labels
external_wait_startedA wait on CI or a review began.task, node_id, kind, timeout_s, deadline
external_wait_observedKraft checked the wait.task, observation, state, result, next_observation_at
external_wait_reboundedA policy change moved the wait's limits.task, timeout_s, deadline
external_wait_endedThe wait settled, timed out or errored.task, outcome, result, observations
ci_infra_exhaustedCI failed for its own reasons, and retries did not help.node_id, reason
ci_run_abandonedA cancelled CI run has no successor.node_id, reason
ci_not_configuredThe repo sets ci_checks: false, so a CI wait, before or after the merge, passed without checking.node_id, task, repo
automated_review_not_configuredThe repo names no automated reviewer, so the wait settles without one.node_id, task, repo
automated_review_erroredThe automated reviewer failed.node_id, reason

Sandbox

TypeWhenKey fields
sandbox_egress_refusedThe sandbox proxy refused a host.session_id, host, port, phase, reason
sandbox_oom_killedThe container was killed under its memory limit. confirmed is false when the runtime never flagged the kill and Kraft counted exit 137 under the limit instead.session_id, memory, confirmed
sandbox_branch_not_syncedThe sandbox's branch did not reach the worktree.session_id, branch, reason
sandbox_kit_resolvedA task was dispatched under a Kit, once per item and Kit. skipped lists the optional capabilities Kraft does not enforce, and ignored those it accepts without applying (agent-sessions@1, a credential's oauth).kit, manifest, skipped, ignored

A workspace item records one sandbox_branch_not_synced for each repository whose branch did not move, and its reason names that repository.

Settings changed

TypeWhenKey fields
chain_template_changedThe item moved to another chain before it started.from, to
attachments_changedIts spec or plan changed before it started.from, to
agent_overrides_changedIts model or effort override changed.overrides
node_overrides_changedA node's override changed.overrides
policy_override_changedIts policy override changed.policy
notification_failedThe notification webhook failed twice.event_type, status, host, error

Before 2.0, a chain was a chain template. chain_template_changed, and the chain_template key of work_item_created, keep that name, and both hold chain ids.

Reading events as they happen

WS /api/ws/events

Streams every work item's events as they are stored. The board and kraft view watch read it. Unlike GET /api/work-items/{id}/events, it is not limited to one item. It first replays the stored events after after_seq, then sends each new event once, with none lost or repeated between the two.

QueryTypeDefaultMeaning
after_seqinteger0Replay every stored event with a greater seq first. 0 replays the whole timeline of every item, so reconnect with the last seq you saw.
livebooleanfalse1 or true also sends live messages.

Each frame is one JSON text message. An event frame has the fields in the table above, {seq, work_item_id, type, payload, node_id, created_at}. A live message has {"frame": "live", "type", "payload", "created_at"} and no seq.

A client authenticates as for the HTTP routes: who may call. A peer on this machine needs nothing while Kraft is bound to loopback. Otherwise send the MCP bearer token as Authorization: Bearer <token>, or the session cookie from the login page. The trigger token is not accepted. The Host and Origin rules of the HTTP routes apply too.

Refusal or closeWhen
403 on the handshakeKraft refused the connection before accepting it. See below.
Close code 1011The client fell behind: more than 1000 messages were waiting for it. Reconnect with after_seq set to the last seq you read.

A refused connection closes before it is accepted, so a real client sees the handshake fail with HTTP 403, not a close frame. A test client reports it as close code 1008. The causes are no valid credential, a remote caller while no password is set, a Host that is not allowed, and an Origin from another site.

Live messages

A client of WS /api/ws/events that connects with live=1 also receives messages that are not events. They are {"frame": "live", "type", "payload", "created_at"}, have no seq, are sent once to whoever is connected and are neither stored nor replayed after a reconnect. A client that does not ask for them never sees one.

TypeWhenKey fields
intake_checkedAn intake poll found a bead ready, started an item or left one alone. A poll that found nothing sends none.id, at, ready, started, skipped
apply_changedWhat is saved but not applied changed: a draft was published, a reload or an access save ran, or a file was edited on disk (checked every 5 seconds). Not sent when the pending items are the same.restart, reload (how many of each; see GET /api/apply)

Notifications

notify.yaml sends an HTTP POST to a webhook when an event you name happens. Set it on the Settings screen, or edit the file:

config/notify.yaml
enabled: true
url: https://hooks.example.com/abc123   # a secret; Kraft never shows it back
base_url: https://kraft.example.com     # where links in the message point
events: [gate_requested, work_item_needs_human]

notify.yaml lists the fields, and what events accepts. Get notified when an item needs you sets it up.

The body is JSON:

{
  "work_item_id": "<work-item-id>",
  "title": "Fix the flaky login test",
  "type": "gate_requested",
  "gate": "spec_approval",
  "url": "https://kraft.example.com/work-items/<work-item-id>"
}

gate is null for an event that is not about a gate.

  • Kraft sends at most one message per work item every 10 seconds. A second matching event in that window sends nothing.
  • A send that times out after 5 seconds, errors, or gets a status of 400 or more is tried once more after 2 seconds. If that fails too, Kraft records a notification_failed event on the item.
  • Events that happen while the server is down send nothing when it comes back.
  • The Settings screen's Send a test posts "type": "test" with a nullwork_item_id and gate.
Copyright © 2026