Skip to content

Lenses, program view, and workflows

Three surfaces turn the graph into something you can look at and act through: lenses (saved views), the program view (an auto-derived progress tree), and workflows (reviewable automation DAGs with tracked runs).

A lens is a lens- node whose body carries fenced blocks — the view definition is data in the graph, versioned and shareable like everything else:

---
id: lens-release-radar
type: lens
project: parcel
title: Release radar
summary: Open work gating the carrier rollout, grouped by status.
status: active
date: 2026-06-05
---
What still gates the rollout, at a glance.
## query
```json
{ "traverse": { "from": "task-carrier-rollout", "follow": ["blocks"],
"direction": "in", "depth": 2 } }
```
## render
```json
{ "as": "board", "group": "status" }
```

The ## query block selects and traverses; ## render chooses the shape (list, table, tree, board); an optional ## actions block declares write affordances bound to registry transitions (a “close” button is a declarative status change, gated exactly like any other write); and an optional ## custom block is a sandboxed render function for the rare view the declarative catalog cannot express. The JSON blocks are data — you can ask “which lenses select on this status?” — and running a lens is a pure function of the graph snapshot, so the same graph renders the same bytes.

Render a lens with spor lens <id>, the render_lens MCP tool (calling it with no id lists the catalog), or in a browser via the server’s render route. A read-only, expiring share link can be minted for teammates without a checkout (spor share <lens-id>); a shared link never carries a write-capable credential.

A workspace- node composes several lenses into one layout — a ## layout block naming lens slots — and renders as a single view tree, so a team dashboard is itself a node.

The program view: progress from blocks topology

Section titled “The program view: progress from blocks topology”

For “where does the workstream stand?”, no lens authoring is needed. Given any root node — an umbrella task, a milestone, anything other work blocks — the program view walks every node that blocks it, transitively, and derives each node’s bucket from the same truth the queue uses:

  • done — terminal status, superseded, or retired by a live resolves/answers edge (counted even while the status field lags);
  • blocked — live but gated by its own live unresolved blockers;
  • active — live, unblocked, started;
  • open — live, unblocked, not started.

The result is a progress bar plus a gating tree. Shared blockers render once and repeat as marked leaves, counted once; depth and size caps report what they skipped rather than truncating silently. A root that nothing blocks is a successful empty result telling you how to model the program: add blocks edges from the gating work.

A wf- node defines a repeatable automation as a DAG of steps, carried as a fenced JSON block (inputs, steps, concurrency) with an optional sandboxed routing function. Because a workflow is a node, it is versioned, attributed, and reviewed like everything else — and it is proposal-gated: created through the server it lands as proposed and inert, and a different identity must activate it. An agent may author a workflow; it may not deploy one.

Each execution is a run- node (performs → wf-...) recording state and lineage; triggered-by records what set it off. Starting a run (spor run <workflow-id> or the run_workflow MCP tool) only creates the run: workers — anything with a token — then claim ready steps over the claim API, do the work, and report a verdict. Step claims are leases like task claims: an expired lease frees the step, and a stale worker’s late report conflicts instead of overwriting the recorded outcome. Approval steps are excluded from worker-claimable work; they surface in the decision queue for a person. Live runs that get stuck surface in the queue too.

Run nodes are engine-managed: their state can only advance through the run engine’s claim/complete path, so a step cannot be hand-flipped to succeeded through the ordinary write API.