What is supervision?

When you run hence agent spawn plan.spl, hence starts a supervisor loop that manages the full lifecycle of an agent executing a task. Rather than simply forking a process and walking away, the supervisor stays alive alongside the agent and handles everything that can go wrong:

  • Lease acquisition and renewal — the supervisor claims the task under a time-bounded lease and keeps renewing it as long as the agent is alive.
  • Liveness monitoring — the supervisor watches PTY output and worktree file changes to determine whether the agent is making progress.
  • Idle detection — if the agent goes quiet for too long, the supervisor marks the claim stale so another agent can take over.
  • Retries — failed or stale attempts are retried automatically up to a configurable limit, each in a fresh git worktree.
  • Evaluation — after completion an evaluator role assesses output quality and correctness.
  • Failure propagation — when a task definitively fails, all downstream dependents are automatically marked as blocked, transitively.

The result is a fully automated, fault-tolerant task execution loop. You spawn an agent and walk away; hence handles crashes, hangs, and cascading failures without any manual intervention.

Design principle
Supervision is built on hence's defeasible logic engine. Lease claims, staleness assertions, and failure conclusions are all ordinary plan facts that can be inspected with hence plan board or queried via hence query why-not. Nothing happens outside the plan's reasoning model.

The three roles

Each supervised execution involves three distinct logical identities. A single process may hold all three at different points in time, but they have separate public-key prefixes in the plan so their assertions are distinguishable and independently auditable.

Role Identity format Responsibility
Supervisor supervisor:ed25519:SHORTID Holds the task lease. Monitors agent liveness by observing PTY output and worktree changes. Renews the lease on each heartbeat cycle. If the agent goes idle beyond the idle timeout, asserts a stale-vN-TASK fact that defeats the active claim via defeasible superiority. Also coordinates retries and failure propagation.
Agent agent:ed25519:SHORTID (or agent:NAME when HENCE_AGENT is set) Performs the actual task work inside a git worktree — an isolated copy of the repository. The agent reads the plan to understand what to do but does not write to the plan directly; all plan mutations (claims, completions, findings) are submitted through the supervisor. This keeps the plan's reasoning model consistent even if the agent crashes mid-write.
Evaluator evaluator:ed25519:SHORTID Runs after the agent reports completion. Assesses output quality and correctness against the task's acceptance criteria. If evaluation fails, the supervisor can trigger a retry rather than accepting a low-quality result. Evaluator findings are recorded as plan facts and visible on the board.
Identity separation
The three-role split matters for multi-agent scenarios where a separate supervisor process oversees a remote agent. The supervisor's ed25519 key prefix lets the plan distinguish "the supervisor for attempt 2 of task deploy" from "the agent doing the work" — enabling fine-grained defeasible reasoning about whose assertions should win in a conflict.

Basic spawn usage

The entry point for supervised execution is hence agent spawn. In its simplest form, you hand it a plan file and it takes care of the rest:

# Spawn an agent on the next available task (auto-selected)
hence agent spawn plan.spl

# Target a specific task
hence agent spawn plan.spl --task mytask

# Specify which LLM backend to use
hence agent spawn plan.spl --agent claude --task mytask

# Remove the worktree after the run completes
hence agent spawn plan.spl --cleanup

# Emit the final result as JSON (useful in scripts)
hence agent spawn plan.spl --json

# Stream NDJSON events to a file while running
hence agent spawn plan.spl --events events.jsonl

# Stream NDJSON events to stdout
hence agent spawn plan.spl --events -

# Trust acceptance checks embedded in a remote plan
hence agent spawn https://plan.hence.run/xyz --trust-plan-checks

Task auto-selection

When --task is omitted, hence agent spawn calls the same logic as hence task next to pick the highest-priority unclaimed task assigned to (or compatible with) the current agent identity. If no task is available, the command exits immediately with a non-zero status so scripts can detect a no-op.

Git worktrees

The agent runs inside a git worktree — a parallel checkout of the repository at a separate filesystem path. This provides:

  • Isolation — the agent's file changes do not affect the main worktree or other concurrently-running agents.
  • Rollback — if the agent fails, the worktree can be discarded without leaving the main repo in a dirty state.
  • Auditability — the worktree path is recorded in the plan, so you can inspect exactly what the agent produced before it was merged.

By default the worktree is left on disk after the run so you can inspect or manually merge the results. Pass --cleanup to delete it automatically on success.

Worktree accumulation
If you run many agents without --cleanup, worktrees accumulate on disk. Use git worktree list to see them and git worktree remove to prune old ones. A future release will add hence agent prune-worktrees.

Lease-based claims

Task ownership in hence is expressed through lease-based claims. Rather than writing a permanent "I own this task" assertion, the supervisor writes a claim that is only valid within a time window:

; Illustrative schematic — the actual generated claims block looks like:
(claims supervisor:ed25519:a3f9b2c1
  :at "2026-02-24T10:00:00Z"
  ...)
Lease representation
Lease claims are written by the supervisor as claims blocks with a timestamp and a duration. The during/now/PT30M notation above is a schematic illustration of the temporal binding; the actual SPL written to the plan uses the claims form shown above.

The lease binds the claim to a temporal interval. At reference_time=now the claim is active; once PT30M (30 minutes) elapses without renewal, it automatically expires. This means:

  • If the supervisor process crashes, its lease expires on its own — no manual cleanup required.
  • Once the lease expires, another agent can claim the task cleanly without any coordination.
  • The supervisor renews the lease on each heartbeat cycle while the agent is alive.

How renewal works

On each heartbeat tick (default: every 60 seconds), the supervisor writes a fresh during fact that extends the lease window forward. The old fact remains in the plan but is superseded by the newer one — hence's defeasible reasoning selects the most recent valid claim when evaluating plan state.

If the supervisor itself hangs (e.g., due to a system suspend), the heartbeat stops, the most recent lease expires, and the task becomes available again. When the supervisor resumes it will find the task unclaimed and attempt to re-acquire it before resuming agent work.

No external lock server required
Lease expiry is evaluated lazily by the hence reasoner whenever any agent queries the plan. There is no background daemon, no lock file, and no external coordination service. The plan file itself is the single source of truth.

Liveness monitoring

The supervisor monitors agent liveness passively — without any cooperation from the agent process. It observes two signal sources:

  • PTY output — the agent runs under a pseudo-terminal. The supervisor reads the PTY stream and timestamps each byte of output. As long as output is flowing, the agent is considered alive.
  • Worktree file changes — the supervisor watches the agent's git worktree for filesystem modifications (writes, creates, deletes). File activity is treated as a liveness signal even if the agent's PTY output has gone quiet (e.g., the agent is running a long silent computation or waiting for a subprocess).

The supervisor tracks the last activity timestamp as max(last_pty_output, last_fs_change). If this timestamp is older than the configured idle timeout, the supervisor concludes the agent is stuck and asserts a staleness fact.

Staleness assertion

When idle timeout fires, the supervisor writes a versioned stale assertion to the plan:

; The supervisor writes a claims block recording the staleness observation.
; The reasoner then derives the stale-vN-TASK conclusion from those claims:
(claims supervisor:ed25519:a3f9b2c1
  :at "2026-02-24T10:05:00Z"
  ...)

; Derived conclusion (not a bare fact — inferred by the reasoner):
;   stale-v1-deploy  ← derived from the supervisor's claims
;
; This defeats the active claim via defeasible superiority:
(prefer (stale-v1-deploy) (claimed "deploy"))
Staleness conclusions are derived, not bare facts
stale-vN-TASK is a conclusion the reasoner derives from the claims the supervisor writes — it is not a bare top-level fact asserted directly. This means you can inspect the reasoning chain with hence query why-not to see exactly which supervisor claims led to the staleness conclusion.

Because defeasible logic allows more specific rules to override less specific ones, the stale-v1-deploy conclusion wins over the active claimed fact without needing to delete or retract it. The task reverts to unclaimed status and — if retries remain — the supervisor spawns a fresh agent in a new worktree.

No agent cooperation required
The agent does not need to implement heartbeats, check-ins, or any protocol with the supervisor. Liveness is inferred entirely from external observations. This means supervision works with any agent implementation — including agents that are completely unaware of the hence supervision layer.

Timeout parameters

Supervision behaviour is controlled by four timeout parameters. Each can be set at three levels with the following precedence: plan metadata overrides CLI flag overrides built-in default.

Parameter CLI flag Plan metadata key Default Effect
Lease TTL --lease-ttl lease-ttl 30 min (PT30M) How long each lease heartbeat is valid for. If the supervisor crashes, the lease expires after this duration and the task becomes reclaimable.
Idle timeout --idle-timeout idle-timeout 5 min (PT5M) Maximum time the agent can go without producing PTY output or touching the worktree before the supervisor declares it stale.
Wall-clock timeout --timeout timeout 60 min (PT60M) Hard upper bound on total agent run time, regardless of activity. Prevents runaway agents from holding a lease indefinitely on tasks that require many small steps.
Max retries max-retries 2 (3 total attempts) Number of retry attempts after the initial attempt fails or goes stale. Total attempts = max-retries + 1.

Duration format

All time-based parameters use ISO 8601 duration format. Examples:

  • PT5M — 5 minutes
  • PT30M — 30 minutes
  • PT2H — 2 hours
  • PT90S — 90 seconds
  • P1DT2H — 1 day and 2 hours
Lease TTL vs idle timeout
Set lease-ttl longer than idle-timeout. The idle timeout fires first (agent detected as stuck) and the supervisor immediately marks the task stale and retries. The lease TTL only matters if the supervisor itself dies unexpectedly — it bounds how long a dead supervisor's ghost lease blocks the task. A common pattern is idle-timeout = PT5M, lease-ttl = PT30M.

Per-task supervision config

You can configure supervision parameters on a per-task basis directly in the plan file using meta directives. These take precedence over any CLI flags:

; Standard task definition
(task task-deploy
  (depends-on task-build task-test)
  (assigned-to "deploy-agent"))

; Per-task supervision configuration
(meta task-deploy
  (description "Deploy to production")
  (acceptance   "Health checks pass in prod environment")
  (lease-ttl    "PT1H")
  (idle-timeout "PT10M")
  (timeout      "PT2H")
  (max-retries  1))

In this example, the deploy task is allowed up to 2 hours total, with a 10-minute idle window, a 1-hour lease TTL (appropriate if the deploy process has long silent phases like waiting for container health checks), and only 1 retry allowed (2 total attempts) because deploying twice without investigation is risky.

Why per-task config matters

Different tasks have vastly different runtime profiles. A code-generation task might produce output continuously for 5 minutes; a long-running integration test suite might take 45 minutes of mostly-silent execution. Rather than inflating global timeouts to accommodate the slowest task, per-task metadata lets you tune supervision precisely where it matters.

; Fast task — tight timeouts
(meta task-lint
  (idle-timeout "PT1M")
  (timeout      "PT5M")
  (max-retries  3))

; Integration test — long silent phases expected
(meta task-integration-tests
  (idle-timeout "PT20M")
  (timeout      "PT90M")
  (max-retries  1))

; Deployment — one careful attempt
(meta task-deploy
  (idle-timeout "PT10M")
  (timeout      "PT2H")
  (max-retries  0))

Retry logic

When an agent attempt fails (due to idle timeout, wall-clock timeout, or an explicit failure assertion), the supervisor checks whether any retries remain and — if so — spawns a fresh agent in a brand-new git worktree.

Attempt counting

The relationship between max-retries and total attempts is:

total_attempts = max_retries + 1

; max-retries 0  →  1 attempt  (no retries)
; max-retries 1  →  2 attempts (1 retry)
; max-retries 2  →  3 attempts (2 retries, the default)

Each attempt increments the stale version counter in the staleness assertions, making the history legible in the plan and on the board:

; Attempt 1 went stale — reasoner derives this conclusion from supervisor claims:
;   stale-v1-deploy  (derived)

; Attempt 2 also went stale:
;   stale-v2-deploy  (derived)

; Attempt 3 failed explicitly — supervisor writes a claims block, reasoner derives:
;   failed-deploy    (derived)

; No more retries — terminal state asserted by the supervisor:
(permanently-failed-deploy)

Fresh worktrees on retry

Each retry spawns the agent in a completely fresh git worktree, checked out from the same base commit as the original attempt. The previous attempt's worktree is left intact (unless --cleanup was passed) so you can inspect what went wrong.

This means retries start from a clean slate — any partial work or corrupted state from the previous attempt does not carry over. If you want to carry state between retries, use plan facts: the agent can assert intermediate results to the plan, and subsequent attempts can read them.

Stale vs failed
Stale means the agent went quiet (idle timeout or wall-clock timeout fired). The work may have been in progress — perhaps the agent simply got stuck or hung. Failed means the agent explicitly concluded it could not complete the task (e.g., encountered an unrecoverable error and asserted a failure fact). Both trigger the retry logic, but failure provides a diagnostic reason visible on the board.

Failure propagation

When a task exhausts all retries and enters the permanently-failed state, hence automatically propagates this failure to all downstream dependents. This prevents agents from indefinitely waiting on tasks that will never complete.

Upstream-blocked

For each task that directly or transitively depends on a permanently-failed task, the reasoner concludes upstream-blocked-TASK:

; task-build permanently failed
(permanently-failed-task-build)

; task-test depends on task-build → gets blocked
(upstream-blocked-task-test
  (because task-build permanently-failed))

; task-deploy depends on task-test → also gets blocked
(upstream-blocked-task-deploy
  (because task-test upstream-blocked
           root-cause task-build))

Transitive propagation

Propagation is fully transitive. Consider a dependency chain:

task-setup ──→ task-build ──→ task-test ──→ task-deploy ──→ task-release ↑ permanently-failed

If task-build permanently fails, hence concludes:

  • upstream-blocked-task-test (direct dependent)
  • upstream-blocked-task-deploy (transitive, via task-test)
  • upstream-blocked-task-release (transitive, via task-deploy)

task-setup is not affected because it is an upstream dependency of task-build, not a downstream one.

Diamond dependencies

Propagation handles diamond-shaped dependency graphs correctly. If two tasks both depend on the same failed task, both are blocked:

task-build / \ task-test-unit task-test-int \ / task-package

If task-build fails, all three downstream tasks (task-test-unit, task-test-int, task-package) are marked upstream-blocked, and the board shows the root cause for each.

Permanent failure vs staleness

Only permanently-failed tasks propagate downstream blocks. Intermediate failures and stale states trigger retries first. Propagation fires only after all retries are exhausted, when the task truly cannot proceed:

; State machine per attempt:
;   claimed → stale  → retry (if retries remain)
;   claimed → failed → retry (if retries remain)
;   claimed → completed ✓
;
; After all retries exhausted:
;   → permanently-failed → propagate upstream-blocked downstream
Unblocking a permanently-failed task
permanently-failed is a terminal state that the reasoner does not automatically reverse. To retry after manual intervention, assert a (override-permanently-failed-TASK) fact with a higher defeasible priority than the failure conclusion. This is an intentional safeguard — automatic retry of a task that has already exhausted retries would create infinite loops.

Board and status integration

hence plan board plan.spl surfaces supervision state alongside regular task status. Each task row shows:

  • Attempt N of M — current attempt number and total allowed.
  • Stale/timeout history — which version numbers have already been stale (e.g., stale-v1, stale-v2).
  • Active lease status — whether the current supervisor's lease is valid, and how much time remains before it expires.
  • Upstream-blocked reason — which dependency failed (and why) when a task is blocked.
  • Evaluator verdict — post-completion quality assessment from the evaluator role.
$ hence plan board plan.spl
+--------------+--------------+--------------+--------------+--------------+
|  BACKLOG     |   READY      | IN PROGRESS  |  BLOCKED     |    DONE      |
+--------------+--------------+--------------+--------------+--------------+
|test-unit     |              |docs @claude  |              |setup         |
|test-int      |              |              |              |              |
|package       |              |              |              |              |
|deploy        |              |              |              |              |
|release       |              |              |              |              |
+--------------+--------------+--------------+--------------+--------------+

Tasks: 5 backlog, 0 ready, 1 in-progress, 0 blocked, 1 done

# build permanently-failed; test-unit, test-int, package, deploy, release
# are upstream-blocked. Inspect with:
$ hence query why-not ready-test-unit plan.spl
$ hence query explain upstream-blocked-test-unit plan.spl

You can query a specific task's supervision history in more detail with hence query why-not TASKID plan.spl, which prints the full defeasible derivation chain explaining why the task is in its current state.

Watch mode

hence agent watch is a longer-running companion to hence agent spawn. Rather than executing a single task, it watches the plan continuously and can spawn agents automatically as tasks become ready.

# Watch a plan — log events to stdout
hence agent watch plan.spl --events -

# Watch and spawn agents for ready tasks
hence agent watch plan.spl --spawn --agent claude

# Tag emitted events with a session name
hence agent watch plan.spl --session ci-run-42 --events events.jsonl

# Watch a remote plan
hence agent watch https://plan.hence.run/xyz --events -

What watch mode observes

The watcher polls the plan at a configurable interval and emits NDJSON events whenever:

  • A task transitions from blocked/pending to ready (all dependencies satisfied).
  • A task is claimed, completed, or failed.
  • A lease is renewed or expires.
  • A staleness event fires.
  • A new upstream-blocked conclusion is derived.

With --spawn, the watcher spawns a new agent process for each task that becomes ready, up to a configurable concurrency limit. This lets you drive an entire plan to completion with a single long-running command.

Session tagging
The --session NAME flag embeds a session identifier in every emitted event. This is useful when multiple watch processes run concurrently (e.g., in CI) and you want to correlate events from the same run in a centralized event log.

NDJSON event streaming

Both hence agent spawn and hence agent watch can emit newline-delimited JSON (NDJSON) events in real time. Use this for CI integration, dashboards, alerting, or audit logs.

# Write events to a file
hence agent spawn plan.spl --events events.jsonl

# Stream events to stdout (pipe-friendly)
hence agent spawn plan.spl --events -

# Process events in real time with jq
hence agent spawn plan.spl --events - | jq 'select(.type == "task_failed")'

Event types

Event type When emitted Key fields
task_claimed Supervisor successfully acquires a lease on a task. task, agent, attempt, lease_expires
lease_renewed Supervisor writes a fresh heartbeat fact extending the lease. task, supervisor, lease_expires
liveness_check Each liveness observation cycle (PTY + fs activity sampled). task, last_pty_output, last_fs_change, idle_for_secs
stale_detected Idle timeout fires; supervisor asserts stale-vN-TASK. task, stale_version, idle_for_secs, retries_remaining
task_completed Agent asserts completion and evaluator (if any) passes. task, agent, attempt, duration_secs
task_failed Agent explicitly fails or retries exhausted. task, reason, attempt, retries_remaining, permanent

Example event stream

{"type":"task_claimed","task":"deploy","agent":"agent:ed25519:a3f9b2","attempt":1,"lease_expires":"2026-02-24T14:30:00Z"}
{"type":"liveness_check","task":"deploy","last_pty_output":"2026-02-24T14:01:23Z","idle_for_secs":0}
{"type":"lease_renewed","task":"deploy","supervisor":"supervisor:ed25519:a3f9b2","lease_expires":"2026-02-24T15:01:23Z"}
{"type":"liveness_check","task":"deploy","last_pty_output":"2026-02-24T14:04:55Z","idle_for_secs":147}
{"type":"stale_detected","task":"deploy","stale_version":1,"idle_for_secs":300,"retries_remaining":2}
{"type":"task_claimed","task":"deploy","agent":"agent:ed25519:b9e120","attempt":2,"lease_expires":"2026-02-24T15:10:00Z"}
{"type":"task_completed","task":"deploy","agent":"agent:ed25519:b9e120","attempt":2,"duration_secs":432}

Trust and evaluation

The evaluator role

After an agent reports a task complete, the supervisor can optionally run an evaluator to assess the quality and correctness of the output. The evaluator identity (evaluator:ed25519:SHORTID) is separate from the agent identity so its verdicts are distinguishable in the plan.

The evaluator reads the task's acceptance criteria from the plan metadata and examines the agent's worktree output. It asserts one of:

  • (evaluation-passed TASKID) — output meets acceptance criteria; task is considered complete.
  • (evaluation-failed TASKID REASON) — output does not meet criteria; supervisor triggers a retry if any remain.
; Acceptance criteria in plan metadata
(meta task-api-impl
  (acceptance "All endpoint tests pass. OpenAPI spec is valid. No TODO comments remain."))

; Evaluator records its verdict
(evaluation-passed "task-api-impl")

; Or, if it finds problems:
(evaluation-failed "task-api-impl"
  (reason "3 endpoint tests failing. /users POST returns 500."))

The --trust-plan-checks flag

When working with remote plans fetched from hence.run or another URL, the plan may embed acceptance checks and evaluation logic authored by a third party. By default, hence does not automatically trust and execute embedded checks from remote plans.

Pass --trust-plan-checks to opt in to executing the plan's embedded evaluation logic:

# Spawn with full trust in the remote plan's evaluation checks
hence agent spawn https://plan.hence.run/xyz --trust-plan-checks

# Without the flag, evaluation is skipped for remote plans
hence agent spawn https://plan.hence.run/xyz
Security consideration
--trust-plan-checks allows the plan to specify evaluation logic that will be executed on your machine. Only use this flag with plans from sources you control or trust. For local plans (.spl files on disk), trust is granted implicitly.

Supervision lifecycle: end-to-end

Putting it all together, here is the complete lifecycle of a supervised task execution:

  1. Task selection — the supervisor calls hence task next logic to find the highest-priority unclaimed task compatible with the current agent. If --task is specified, that task is used directly.
  2. Lease acquisition — the supervisor writes a during-bounded claim fact to the plan. If the claim fails (another agent already holds a valid lease), the supervisor backs off and tries again after a random jitter delay.
  3. Worktree creation — a fresh git worktree is created at a temporary path. The agent will do all its file work within this isolated checkout.
  4. Agent launch — the agent process is started under a PTY. The supervisor records the agent's PID and begins monitoring PTY output and worktree filesystem changes.
  5. Heartbeat loop — on each tick the supervisor: (a) checks liveness, (b) renews the lease, (c) checks wall-clock timeout, (d) emits a liveness_check event if streaming is enabled.
  6. Idle check — if last_activity < now - idle_timeout, the supervisor asserts stale-vN-TASK and terminates the agent process. If retries remain, the loop restarts from step 3 with a fresh worktree.
  7. Wall-clock check — if total elapsed time exceeds timeout, the supervisor asserts a timeout failure and terminates the agent. Retries apply here too.
  8. Completion — if the agent asserts a completed-TASK fact, the supervisor starts the evaluator role. If evaluation passes, the supervisor emits a task_completed event and exits. If evaluation fails, retries apply.
  9. Permanent failure — after all retries are exhausted without completion, the supervisor asserts permanently-failed-TASK. The reasoner then propagates upstream-blocked to all downstream dependents.
  10. Cleanup — if --cleanup was passed, the worktree(s) from successful or failed attempts are removed. Otherwise they are left for inspection.
hence agent spawn plan.spl │ ▼ ┌─────────────┐ claim fails (race) │ Acquire │────────────────────────→ back off + retry │ lease │ └──────┬──────┘ │ claim succeeds ▼ ┌─────────────┐ │ Create git │ │ worktree │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ Launch │◄─────────────────────────────────────────┐ │ agent (PTY) │ │ └──────┬──────┘ │ │ │ ▼ │ ┌─────────────────────────────────────┐ │ │ Heartbeat loop │ │ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ Check liveness │ │ │ │ │ Renew lease │ │ │ │ │ Check idle / wall-clock │ │ │ │ └──────────────────────────────┘ │ │ │ │ │ │ idle timeout ──→ stale-vN ────────┼──── retry? ──────┘ │ wall-clock ──→ timeout ────────┤ │ agent exits ──→ check result ─────┤ └─────────────────────────────────────┘ │ │ agent reports done ▼ ┌─────────────┐ eval fails │ Run │──────────────────────────→ retry? │ evaluator │ └──────┬──────┘ │ eval passes ▼ ┌─────────────┐ │ completed │ permanently-failed │ │──────────────────────────→ propagate upstream-blocked downstream └─────────────┘

Common patterns and recipes

Single task with custom timeouts

# Override idle timeout and lease TTL via CLI flags
hence agent spawn plan.spl \
  --task task-integration-tests \
  --idle-timeout PT20M \
  --lease-ttl PT45M \
  --timeout PT2H \
  --agent claude

CI pipeline: drive a full plan to completion

# In CI: watch and spawn agents until all tasks complete or fail
hence agent watch plan.spl \
  --spawn \
  --agent claude \
  --session "ci-${CI_BUILD_ID}" \
  --events "ci-events-${CI_BUILD_ID}.jsonl" \
  --cleanup

Inspect the event log after a run

# Count events by type
jq -r .type events.jsonl | sort | uniq -c | sort -rn

# Show only stale and failure events
jq 'select(.type | test("stale|fail"))' events.jsonl

# Compute total duration per task
jq 'select(.type == "task_completed") | {task, duration_secs}' events.jsonl

Manually override a permanently-failed task

; Add this to the plan after fixing the root cause
; This defeats the permanently-failed conclusion so the task can be retried
(prefer
  (cleared-deploy)
  (permanently-failed-deploy))

(cleared-deploy)

Audit who held each lease

; The plan retains the full history of lease facts as ordinary SPL assertions
; Use hence plan status to inspect current task state, or hence query explain
; for a full defeasible derivation of the current conclusions
hence plan status plan.spl
hence query explain deploy plan.spl

Reference: spawn flags

Flag Type Description
--task ID string Target task ID. If omitted, auto-selects via hence task next logic.
--agent NAME string LLM backend to use (e.g. claude, codex, gemini). Falls back to HENCE_AGENT env var.
--lease-ttl DUR ISO 8601 Lease heartbeat TTL. Default: PT30M. Overridden by plan metadata.
--idle-timeout DUR ISO 8601 Max idle before staleness assertion. Default: PT5M. Overridden by plan metadata.
--timeout DUR ISO 8601 Hard wall-clock limit. Default: PT60M. Overridden by plan metadata.
--cleanup flag Remove the git worktree after the run completes (success or failure).
--json flag Emit final result as a JSON object to stdout instead of human-readable text.
--events PATH path or - Stream NDJSON supervision events to a file or stdout (-).
--session NAME string Tag all emitted events with this session name for log correlation.
--trust-plan-checks flag Execute evaluation logic embedded in remote plans. Required for remote plan acceptance checks.

See also: Agent Workflow for how agents interact with plans manually, Writing Plans for meta directive syntax, and CLI Reference for the full flag listing.