summaryrefslogtreecommitdiff
path: root/DESIGN.md
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-08-18 14:18:14 -0600
committert <t@tjp.lol>2026-08-18 14:20:28 -0600
commitcc69a2e431779528578211a5cb3385bb23e076bf (patch)
tree8833171e25bb3dfb71fc4a2e0b8fe583782da764 /DESIGN.md
parent372ef8ff40991644ec2654c61328f31779f4ad21 (diff)
Persist and replay subagent progress across sessions
Record durable child-turn metadata, restore bounded progress cards during startup replay, and keep settled cards attached to transcript entries. Add workflow-local Lua profiles and expose discovered agents and workflows in the session header.
Diffstat (limited to 'DESIGN.md')
-rw-r--r--DESIGN.md655
1 files changed, 0 insertions, 655 deletions
diff --git a/DESIGN.md b/DESIGN.md
deleted file mode 100644
index 1042885..0000000
--- a/DESIGN.md
+++ /dev/null
@@ -1,655 +0,0 @@
-# panto-subagents design
-
-## Product shape
-
-`panto-subagents` is a separate rock exposing one extension named
-`subagents`. Its model-facing tools use the `subagents.*` namespace. It does
-not shell out to `panto -p`; every child is an ordinary in-process
-libpantograph `Agent` driven by pantograph.
-
-The primary owns delegation. Children share its workspace, but not its
-dialogue, and cannot call `subagents.*`. Recursive delegation, worktree
-management, and merge orchestration are outside this design.
-
-## v0 tools
-
-### `subagents.run`
-
-One tool call starts or resumes one conversational child:
-
-```lua
--- Start a new child.
-{
- agent = "reviewer",
- prompt = "Review the authentication changes.",
- model = "anthropic:sonnet", -- optional; always provider:model
- reasoning = "high", -- optional
-}
-
--- Resume an existing child owned by this primary session.
-{
- id = "0198...",
- prompt = "Now focus on the missing tests.",
- model = "openai:gpt-5.6", -- optional per-turn change
- reasoning = "xhigh", -- optional per-turn change
-}
-```
-
-`prompt` must be non-empty. Exactly one of `agent` and `id` is required:
-`agent` selects the profile for a new child; `id` identifies a persisted child
-conversation. An ID cannot be resumed by a different primary session, and
-only one turn may be active for an ID.
-
-Once a valid new child has been allocated, every settled result includes its
-ID:
-
-```lua
-{
- id = "0198...",
- agent = "reviewer",
- status = "completed", -- completed, failed, or cancelled
- output = "...", -- present on success
- error = nil, -- present on failure
- resumable = true,
-}
-```
-
-Validation failures that happen before a child is allocated have no ID.
-Under the existing filesystem-store flush discipline, a new child that fails
-before producing its first assistant message has no durable file; such a
-result reports `resumable = false` rather than promising continuation.
-
-Parallel delegation uses ordinary model tool batching. The primary emits
-several `subagents.run` calls in one batch; pantograph starts them concurrently
-under one session-wide bound, initially four. A failure in one call does not
-discard successful siblings. There is no second `tasks` array, task ID, or
-extension-specific batch report format.
-
-### `subagents.models`
-
-The model catalog is too large to place in `subagents.run`'s description or
-JSON schema. A small query tool exposes it on demand:
-
-```lua
-subagents.models {}
--- Current inherited model/reasoning plus configured provider names and counts.
-
-subagents.models {
- provider = "anthropic",
- query = "sonnet",
- limit = 10,
-}
-
-subagents.models { model = "anthropic:sonnet" }
-subagents.models { agent = "reviewer" }
-```
-
-An exact model or agent lookup returns its effective model, default reasoning,
-and the reasoning levels known to be valid for it. A search returns only the
-requested bounded matches and says when more exist. The tool reuses
-pantograph's model registry and provider-specific reasoning-option logic,
-including effort choices reported dynamically by Lua protocols.
-
-The `subagents.run` description tells the primary to omit overrides normally
-and call `subagents.models` before choosing an unfamiliar model or reasoning
-level. Runtime validation remains authoritative when catalog capability data
-is incomplete.
-
-## Agent profiles
-
-Profiles are Markdown files with YAML frontmatter, compatible at the common
-format boundary used by other agent harnesses:
-
-```markdown
----
-name: reviewer
-description: Reviews changes for correctness and missing tests
-model: anthropic:sonnet
-reasoning: high
----
-
-You are a focused code reviewer.
-
-Report only concrete, evidence-backed findings.
-```
-
-They are discovered recursively from two layers, lowest precedence first:
-
-1. `${XDG_CONFIG_HOME:-$HOME/.config}/panto/agents/**/*.md`
-2. `<cwd>/.panto/agents/**/*.md`
-
-The project definition shadows a user definition with the same resolved name.
-`name` is optional and defaults to the extension-less filename.
-`description` is included with the discovered names in `subagents.run`'s tool
-description so the primary can choose a profile without loading its full
-prompt.
-
-The portable contract is `name`, `description`, and the Markdown body as the
-profile's system prompt. Panto additionally recognizes `model` only in full
-`provider:model` form and recognizes its existing reasoning labels. Unknown
-frontmatter is ignored. A foreign model spelling that is not valid Panto
-syntax is ignored with a warning so the prompt remains usable and inherits
-the primary model.
-
-For a new child, model and reasoning are resolved independently:
-
-```text
-model = tool.model ?? profile.model ?? primary.model
-reasoning = tool.reasoning ?? profile.reasoning ?? primary.reasoning
-```
-
-Pantograph resolves the selected model against the configured provider and
-model registries, applies the reasoning level, and fails before inference if
-the known combination is invalid. The Lua extension never receives provider
-credentials.
-
-The resolved model reference and reasoning label are recorded on each child
-turn. On continuation, omitted overrides keep the child's last effective
-values; an explicit `model` or `reasoning` changes that value for the new turn
-and for later continuations.
-
-## Conversation construction
-
-A new child receives a fresh `Conversation` containing:
-
-1. the primary's effective system and project context;
-2. a fixed child-role instruction;
-3. the selected profile's Markdown body as a separate system message; and
-4. the tool's `prompt` as its only initial user message.
-
-The parent dialogue is not copied. The primary must put task-specific context
-in the user prompt.
-
-The profile system message carries opaque, non-model-visible message metadata
-recording the immutable subagent manifest: its owning primary session ID and
-profile name. Each user message carries the full model reference and reasoning
-label effective for that turn. This avoids a mutable sidecar manifest: resume
-reads identity from the first system message and defaults from the latest user
-message. The existing filesystem-store load path must preserve per-message
-metadata when rebuilding a `Conversation`.
-
-The stored conversation is canonical on resume. Profile edits affect new
-children, not an existing child ID.
-
-Every child sees the parent's normal registered tools except `subagents.*`.
-Tools still run through the shared extension runtime. Children share the
-workspace, so concurrent writers can race or conflict; the primary must
-delegate independent work.
-
-## Durable child sessions
-
-Subagent conversations are ordinary `FileSystemJSONLStore` sessions, not an
-in-memory `id -> Conversation` map. The public subagent ID is the actual Panto
-session UUID and the JSONL filename stem.
-
-Each primary session owns a separate child catalog below its already-resolved
-per-cwd session directory:
-
-```text
-<session-dir>/
-├── <primary-session-id>.jsonl
-└── subagents/
- └── <primary-session-id>/
- ├── <subagent-id>.jsonl
- └── <subagent-id>.jsonl
-```
-
-This layout automatically follows `PANTO_SESSION_DIR` and the normal XDG
-fallback. The main store's `list` and `resolve` operations examine only direct
-`.jsonl` children, so nested subagent files never appear in `panto sessions`
-or the `/resume` picker.
-
-The primary session ID is the ownership boundary. After panto restarts and
-the user resumes a primary conversation, extension activation derives the
-same child-catalog path from that primary ID. A referenced child ID therefore
-resolves again. A different primary uses a different directory and cannot
-resolve it. There is no global child lookup and no cross-primary continuation.
-
-Starting and resuming use the ordinary session lifecycle:
-
-```text
-new:
- session = child_store.create()
- agent = Agent.init(session, no prior conversation)
- seed system/profile messages
- run prompt
-
-resume:
- session = child_store.resolve(id)
- conversation = session.load()
- agent = Agent.init(session, conversation)
- run prompt
-```
-
-The Agent appends through its `Session` handle, and the filesystem store syncs
-completed entries normally. The Agent may be destroyed after a turn; a later
-turn reconstructs the conversation from JSONL. Only active job handles,
-progress, and cancellation state remain in memory.
-
-## Foreground progress and cancellation
-
-Silent foreground waiting is not acceptable. Each child job has its ID and
-profile before inference starts and emits tagged progress events to the TUI.
-The user can see the child's assistant stream, tool calls, and tool results
-while the primary model remains blocked on the tool call. Concurrent children
-remain visually distinguishable.
-
-These nested events are presentation data, not partial tool results: they are
-not inserted into the primary model's conversation. `subagents.run` returns
-only the settled result, while the full child conversation is persisted under
-its ID for inspection and continuation.
-
-Escape cancels every still-running child belonging to the current foreground
-tool operation. Completed siblings remain successful. Cancellation targets
-the child streams and the protocol streams those turns opened, then lets
-every affected tool call settle coherently as `cancelled` rather than
-abandoning the parent's batch.
-
-## Generic pantograph child-job seam
-
-libpantograph remains unaware of Lua, extensions, and subagents. Its `Agent`
-and `SessionStore` abstractions already provide the conversation and
-persistence machinery, and no subagent scheduler or tree policy lives
-anywhere in Zig. The seam above it has three layers: a Lua binding
-(libpanto-lua) that adds generic job, metadata, and tool-control primitives
-usable by any embedder; a thin surface in pantograph itself (`panto.ext`) for
-the few things that need the running process or its credentials; and this
-rock, which is Lua policy written entirely on those two layers. Nothing
-subagent-specific exists below the rock.
-
-The binding's async job is the generic replacement for a hand-rolled worker
-thread:
-
-```lua
-local job = agent:run_async {
- prompt = "...", -- or blocks, mirroring agent:run
- metadata = { ... }, -- optional user-message metadata, a JSON
- -- object (array-shaped tables are refused)
- dispatch_tools = false, -- optional, default true: false ends the
- -- turn at the first assistant response
- -- instead of dispatching its tool calls
- wake_fd = fd, -- optional: one byte is written on every
- -- event and on settle
-}
-
-job:next_event() -- -> event table | nil (non-blocking; owned copy)
-job:result() -- -> settled result table | nil while running
-job:request_cancel()
-job:close() -- joins the pump and frees the settled result; a
- -- caller must read and cache job:result() first
-```
-
-A pump thread owned by the job, not by the caller, drives the blocking turn
-to completion, copies each event off the stream before the next pull frees
-it, and buffers it under a mutex. The `wake_fd` contract is deliberately
-loop-agnostic — a luv consumer arms `uv.new_poll(fd)`, any other reactor
-selects on it — so no loop dependency reaches into the binding. Tool dispatch
-inside the turn is unchanged libpantograph behavior: when the job's agent
-carries tools registered through pantograph's Lua tool source (the primary's
-own extension tools, copied over via `agent:set_tools`), `invoke_batch` posts
-to the Lua owner thread through the same multiplexed executor a primary's
-batch uses (below), bound to that job's agent as its lane. `dispatch_tools =
-false` is the generic knob that makes a one-shot structured worker possible
-without any host involvement: the settled result carries the assistant's
-tool calls (id, name, and raw input JSON) unresolved, and no second model
-round runs.
-
-Two more binding primitives complete the generic surface. Message metadata —
-`conv:message_metadata(i)` / `conv:set_message_metadata(i, tbl)`, plus an
-optional `metadata` argument on `add_system_message`/`add_user_message` —
-reads and writes the same JSON-object metadata the disk format already
-round-trips, on owned and borrowed conversations alike; the rock uses it for
-both the subagent manifest and the per-turn model/reasoning stamp, with no
-separate sidecar record. Tool control — `agent:tools()` returns a mutable
-copy of an agent's declarations, `agent:set_tools(decls)` replaces them, and
-`agent:set_config` additionally accepts `tool_choice` to force a named tool —
-lets the rock copy the primary's tools onto a child and drop `subagents.*`,
-or hand a child exactly one synthetic, call-refusing declaration for a
-one-shot worker's output tool. Dispatch is name-keyed on the shared runtime
-tool source, so a copied declaration reaches the same handler regardless of
-which agent it is registered on.
-
-pantograph adds only what requires the running process or its credentials.
-`panto.ext.resolve_model { model = "provider:alias", reasoning = label,
-tool_choice = ... }` returns an **opaque config userdata** accepted by
-`panto.agent { config = ... }` and `agent:set_config`; resolution reuses the
-existing provider/model registries and reasoning pipeline, fails before
-inference, and the userdata exposes only `model`/`reasoning`/`style`/
-`wire_model` — credentials never reach Lua. This is the one piece of the seam
-that could not be a binding API. It, `panto.ext.session_info()`, and
-`panto.ext.models` all read from one feature-neutral, read-only session
-record: the provider and model registries, the primary's already-credentialed
-live provider config, the current model label, registered extension
-protocols, and the session's id and directory. `panto.ext.on("turn_start" |
-"turn_interrupt" | "turn_end", fn)` fires around every turn on the primary
-session — interrupt before the pump parks on Escape or Ctrl+C, end on every
-exit path with a reason — generic and observe-only; the rock is one
-subscriber among any number that could exist, cancelling every live child job
-on interrupt and closing them on end. `event:set_component(tbl)` returns a
-handle (`h:invalidate()`, `h:alive()`) so a component that mutates after it is
-set — a progress card gaining a line — can ask for a repaint instead of
-rendering once and going stale.
-
-Everything else — the concurrency gate and queue, child store layout and
-directory creation on top of `SessionStore`, manifest and resume-default
-extraction from message metadata, tool filtering, one-shot worker
-construction, cancellation propagation, and progress rendering — is Lua
-policy in the rock, built entirely on the primitives above. A different
-extension could build an unrelated concurrent-job feature on exactly the same
-seam.
-
-## Shared Lua runtime executor
-
-All agents in one primary session share its Lua state, activated extensions,
-and libuv loop. Children do not create Lua states, reload rocks, or
-reactivate extensions.
-
-The runtime multiplexes tool batches rather than tracking one global
-in-flight batch — the primary's and one per in-flight child agent can be live
-at once, all executing on the single Lua owner thread:
-
-1. Parent tool handlers start child jobs and yield.
-2. Child workers drive ordinary libpantograph streams.
-3. Built-in providers run directly on those workers.
-4. A child Lua tool call is posted to a thread-safe runtime queue, waking the
- shared loop through a pantograph-owned `uv_async_t`. A Lua protocol call
- made on behalf of a child travels the same queue.
-5. The runtime executes a tool callback as a coroutine on the sole Lua owner
- thread, where async extension code may yield normally. A protocol callback
- is posted the same way but runs to completion under `pcall` on that
- thread: it cannot yield.
-6. Completion returns to the waiting child worker, while unrelated batches
- keep making progress.
-
-Tool batches carry explicit identity, so result recording always addresses
-the right batch rather than one global slot. Each batch is bound to the agent
-that raised it, and `panto.ext.agent` resolves to that Agent while the
-batch's coroutine runs. Protocol sources are not bound this way — one
-registered protocol serves every agent in the session, and its streams keep
-the turns apart. Module globals remain shared intentionally. A
-child's tool declarations are exactly whatever its own `agent:set_tools` call
-put there — there is no host-side filtered view to keep in sync.
-
-## Extension-provided protocol concurrency
-
-A registered protocol is process-wide, not per-child: the primary and every
-child job reach it through the one `ProtocolSource` the session installed.
-What keeps concurrent turns apart is the stream, not a per-job lane — each
-`open` returns its own. This remains a pantograph implementation detail
-rather than a libpantograph or workflow concept.
-
-Each call to an extension-provided protocol's `open` creates an independently
-managed stream, conceptually:
-
-```lua
-panto.ext.register_protocol {
- name = "external-runtime",
- open = function(request)
- local transport = open_transport(request)
- return {
- next = function() return transport:next() end,
- cancel_turn = function() transport:cancel_turn() end,
- close = function() transport:close() end,
- }
- end,
- close = function() -- runtime-wide teardown
- end,
-}
-```
-
-Mutable turn state belongs to the returned stream or its closure, never a
-module-global "current session." Multiple streams from one registered
-protocol may be open concurrently and interleave between calls. A protocol
-body — `open`, or a stream's `next`, `cancel_turn`, or `close` — runs to
-completion on the loop thread under `pcall`: it cannot yield, and it must not
-block, because that thread is the one every other stream and tool batch needs
-back. Awaiting belongs in tool handlers, which do run as coroutines and may
-park on luv work; the shared pantograph scheduler remains the sole event-loop
-owner.
-
-Cancellation and compaction are scoped to the stream, not the registration.
-Abandoning a turn calls that stream's own `cancel_turn`; a protocol that
-defines none falls back to the registration-level `cancel_turn`, which is
-process-wide by construction. A child compacting its own conversation does
-not reset protocol sessions the primary and its siblings are streaming on;
-only the session's own teardown runs a protocol's registration-level
-`close`.
-
-Every child turn, including a resumed child, receives a fresh protocol stream.
-The loaded Panto JSONL conversation is canonical and is present in the open
-request; extension protocols cannot depend on private transport history for
-continuation. A protocol may serialize the structured messages directly. If
-its transport only accepts a textual bootstrap, it can use the existing
-generic un-summarized transcript serializer, taking care not to duplicate
-system context supplied separately in the request.
-
-## Lua workflow API
-
-Workflows build on the same Agent jobs after v0. The canonical Lua API is
-callback- and await-based so a workflow can branch and fan out from structured
-child output:
-
-```lua
-return subagents.workflow(function(ctx, input)
- local split = ctx:agent {
- agent = "splitter",
- prompt = input,
- output = {
- description = "Return the independent work items.",
- schema = {
- type = "object",
- required = { "items" },
- properties = {
- items = {
- type = "array",
- items = { type = "string" },
- },
- },
- },
- },
- }:await()
-
- local jobs = {}
- for _, item in ipairs(split.output.items) do
- jobs[#jobs + 1] = ctx:agent {
- agent = "worker",
- prompt = "Process this item:\n" .. item,
- }
- end
-
- local results = ctx:await(jobs, "all")
- return results
-end)
-```
-
-`ctx:agent` starts immediately and returns a job handle. `handle:await()`
-waits for one. `ctx:await(handles, "all")` returns settled results in input
-order. `ctx:await(handles, "first")` returns the first settled result and the
-remaining handles. Child failures are result values so the callback can
-decide whether to continue, retry differently, or return an error. All jobs
-share pantograph's global concurrency bound.
-
-When `output` is present, the child is a one-shot structured worker:
-
-- it receives only one synthetic output tool;
-- tool choice is forced to that tool;
-- its first model response must call the tool exactly once;
-- the tool input is validated against `output.schema`;
-- no tool result is sent back and no second model round runs; and
-- `result.output` is the decoded tool input, not parsed assistant prose.
-
-One-shot structured workers are ephemeral and not resumable conversations.
-They exist to make reliable reducers, classifiers, planners, and dynamic
-fan-out inputs without inventing another structured-output protocol.
-
-The module is available directly to human-authored Panto extensions, which
-can register their own commands with `panto.ext.register_command`.
-
-## Model-authored transient workflows
-
-A later model-facing tool executes a transient Lua workflow without writing a
-definition to disk:
-
-```lua
-subagents.lua {
- prompt = "Review the changed subsystems.",
- source = "...Lua returning subagents.workflow(function(ctx, input) ... end)...",
-}
-```
-
-The source runs as a coroutine in Panto's existing Lua state, but under a
-restricted `_ENV` containing the workflow API and safe standard libraries.
-It cannot access `os`, `io`, `debug`, `package`, `require`, raw Panto runtime
-objects, or extension globals. An instruction budget between yields and a
-bounded number of jobs prevent generated Lua from wedging the owner thread or
-creating unbounded inference work. Human-authored extensions are trusted Lua
-and use the module directly without this restricted environment.
-
-No separate workflow-authoring skill ships initially. The tool schema and
-description are sufficient until actual traces show that longer guidance is
-needed.
-
-## TOML workflows and slash commands
-
-Persistent TOML workflows are the smaller, fully pre-specified DAG surface.
-They are discovered recursively from:
-
-1. `${XDG_CONFIG_HOME:-$HOME/.config}/panto/workflows/**/*.toml`
-2. `<cwd>/.panto/workflows/**/*.toml`
-
-Project workflows shadow user workflows by resolved name. A workflow may
-define independent roots, fixed dependencies, and per-step call overrides:
-
-```toml
-name = "review-chain"
-description = "Inspect a change from two angles, then synthesize."
-
-[[steps]]
-id = "correctness"
-agent = "reviewer"
-prompt = "Review for correctness and regressions."
-
-[[steps]]
-id = "tests"
-agent = "reviewer"
-prompt = "Review test coverage and validation gaps."
-reasoning = "high"
-
-[[steps]]
-id = "synthesis"
-agent = "reviewer"
-prompt = "Synthesize the findings and remove duplicates."
-needs = ["correctness", "tests"]
-```
-
-The parser validates duplicate IDs, unknown dependencies, and cycles before
-starting work, then lowers the definition onto the same Lua job/await
-primitives. Every step receives the original workflow input. A dependent also
-receives its dependencies' outputs, clearly labeled and ordered by `needs`.
-All ready steps start concurrently. A failed node skips its dependents while
-unrelated branches finish. Terminal-node results and failures are returned in
-declaration order.
-
-Every discovered workflow registers `/workflow:<name>`. The command tail—the
-equivalent of `$ARGUMENTS` in prompt commands—becomes the user prompt supplied
-to the DAG's head nodes. Human-authored Lua workflows register any desired
-slash command themselves.
-
-The model-facing `subagents.workflow` tool runs either a discovered workflow
-by name or a transient static step definition. Output-dependent branches and
-dynamic fan-out remain Lua features; TOML does not grow into a programming
-language.
-
-## Deferred model-facing background control
-
-Foreground tool batching and workflow-internal awaits cover the initial
-parallelism requirements. Background control can be added without changing
-the job model:
-
-```lua
-subagents.run {
- agent = "researcher",
- prompt = "Investigate this in the background.",
- await = false,
-}
--- { id = "0198...", status = "running" }
-
-subagents.await {
- ids = { "0198...", "0199..." },
- mode = "first", -- or "all"
-}
-```
-
-`first` means the first settled result, successful or not; `all` waits for
-every named child. IDs are explicit so a primary never waits on unrelated
-work. Results are not consumed by awaiting them. Adding this surface also
-requires background result retention, targeted cancellation/status, and TUI
-lifecycle behavior, so it is not part of v0.
-
-## Validation
-
-Pantograph checks must prove:
-
-- independent child Agents make concurrent progress under the global bound;
-- Lua tool batches from multiple children cannot corrupt results or enter the
- Lua state concurrently;
-- built-in and Lua protocol children can run together;
-- lane-local `panto.ext.agent` resolves to the correct child;
-- progress events remain tagged to the correct child;
-- cancellation settles every affected stream and tool batch coherently;
-- one failed child does not cancel successful siblings; and
-- child tool declarations omit `subagents.*` while shared extension
- activation happens exactly once.
-
-Session checks must prove:
-
-- successful child turns persist as normal JSONL sessions;
-- the child ID is the persisted session ID;
-- restarting and resuming a primary can continue its referenced child;
-- another primary cannot resolve that ID;
-- nested child files never appear in the primary session picker or listing;
-- the subagent manifest and full conversation round-trip through persistence;
- and
-- incomplete first turns do not falsely report durable resumability.
-
-Extension checks must cover profile discovery and shadowing, portable
-frontmatter parsing, independent model/reasoning precedence, catalog query
-bounding, new-versus-resume validation, and tool-batch concurrency.
-
-Workflow checks, when those phases land, must cover callback awaiting,
-structured-tool validation, output-driven fan-out, DAG validation, dependency
-prompt assembly, branch-local failure, deterministic terminal ordering, and
-generated slash-command registration.
-
-Extension-protocol checks must run multiple mocked streams concurrently and
-prove that startup, events, tool calls, cancellation, transcript bootstrap,
-and closure stay scoped to the stream that opened them — including that
-cancelling one turn leaves its siblings' streams untouched.
-
-## Delivery order
-
-1. Add generic concurrent Agent jobs, global bounding, multiplexed Lua
- dispatch, per-stream protocol concurrency, nested progress, and foreground
- cancellation.
-2. Add layered Markdown profiles, model/reasoning resolution, bounded catalog
- queries, durable tree-scoped child stores, continuation, and
- `subagents.run`/`subagents.models`.
-3. Add canonical-conversation bootstrap support for extension protocols whose
- transports start without history.
-4. Add the callback-based Lua workflow API and one-shot structured workers.
-5. Add the restricted model-authored Lua workflow tool.
-6. Add TOML DAG loading, `subagents.workflow`, and generated
- `/workflow:<name>` commands.
-7. Add model-facing background start/await/status/cancellation only when the
- foreground behavior is proven.
-
-## Not in v0
-
-- cross-primary subagent lookup or continuation;
-- recursive subagents;
-- background jobs or model-facing await/status tools;
-- worktree creation, merge orchestration, or write-conflict protection;
-- durable workflow-run state or artifact management;
-- agent/profile CRUD tools;
-- a workflow-authoring skill; or
-- Lua/TOML condition syntax beyond the callback and fixed-DAG surfaces above.