approval-flows — write-side walkthrough
The approval-flows template is the canonical Tasks × Apps Dockyard example. Two contract-first task-augmented tools driving a human-in-the-loop round-trip from inside an iframe. The write-side counterpart to analytics-widgets.
Scaffold
dockyard new my-approvals --template approval-flows
cd my-approvalsIf you installed Dockyard via go install …@latest, that's all — the generated go.mod pins the published module and resolves with no extra flag. If you built Dockyard from source, add --dockyard-path /path/to/dockyard so the generated go.mod and web/package.json point at your local checkout (D-080).
The scaffold produces a project with:
my-approvals/
├── README.md
├── dockyard.app.yaml # two tools (task_support: required) + one App
├── main.go # boots a real tasks.Engine (D-135)
├── internal/
│ ├── contracts/ # RequestApproval{Input,Output}, ProposeWithEdits…
│ └── handlers/ # handlers drive approval before or during a task
├── fixtures/ # six fixtures per tool
└── web/
└── src/
├── App.svelte # renders the approval card / edits form
└── …The two tools
| Tool | What it does |
|---|---|
request_approval | Pause for human approval of a single decision; returns approved/rejected + reason. |
propose_with_edits | Propose structured changes (fields with current + proposed values); the user edits and approves. |
Both declare task_support: required — durable approval work is the product, not an optional capability. The scaffolded main.go attaches a real tasks.Engine (decision D-135) so the tools run as durable tasks against the real runtime/server.
Run + inspect
dockyard new already ran go mod tidy and dockyard generate, so the project's Go dependencies and contract artifacts (JSON Schema + TS) are ready. (If you scaffolded with --no-postgen, run those two first.)
A template ships a Svelte UI, so two one-time steps come before the dev loop — skip them and dockyard dev fails with vite: command not found (web deps not installed) and open web/dist/index.html: file does not exist (the embedded bundle hasn't been built yet):
# 1. Install the web deps once (provides the Vite bundler):
(cd web && npm install)
# 2. Build once so the embedded UI bundle (web/dist) exists:
dockyard buildNow run the dev loop, which auto-attaches the inspector and prints its URL (cmd-click to open):
dockyard dev
# ...
# INFO inspector ready at http://127.0.0.1:54321Prefer a standalone inspector against a built server? Run it on HTTP in one terminal and attach in another:
DOCKYARD_TRANSPORT=http dockyard run # terminal 1
dockyard inspect --url http://127.0.0.1:8080 --dir . # terminal 2Fire request_approval from the Tools tab. The App renders an approval card; click Approve. Depending on when approval is needed, the inspector shows either a core MRTR retry before task creation or task input during the task lifecycle:

propose_with_edits renders a form whose proposed values are editable; approve with edits sends the user's values back through the bridge:

The Tasks panel renders each task's lifecycle as a Timeline:

How the round-trip works
There are two standards-based input lifecycles. Do not combine their state.
Approval before durable work: core MRTR
- The host calls
tools/calland receivesresultType: "input_required"withinputRequestsand optional opaquerequestState. - The host collects the response, then invokes
tools/callagain with a new JSON-RPC ID, the original arguments,inputResponses, and the echoedrequestState. - The retried call completes or creates the durable task. Core MRTR does not update an existing task and never uses
tasks/update.
Approval during durable work: task update
- The original
tools/callreturns aCreateTaskResult. - The handler requests input through its
TaskHandle;tasks/getthen reportsstatus: "input_required"with outstandinginputRequests. - The host collects the response and sends matching
inputResponsesthroughtasks/update; it does not retry the originaltools/calland there is no corerequestStateon the task. - The host resumes polling with
tasks/getuntil the task completes.
The Tasks panel renders the whole sequence as a Timeline so you can correlate UI events with task state transitions.
Dockyard-host-only
The App bridge notification that carries an inline response and live task progress are Dockyard extensions. The inspector translates the explicit operator action into the appropriate standard MCP operation: retry the original method for core MRTR, or call tasks/update for task input. A stock host that does not implement those bridge notifications ignores them. Design the App so its core value does not depend on this inline convenience. See the Tasks×Apps note in the UI-resources guide.
Capability degradation
If the host hasn't negotiated the Tasks capability, the handler returns a synchronous "requires interactive host" stub instead of crashing (RFC §7.5, the capability-driven rule from AGENTS.md §6). Flip the capability toggle in the inspector to test the degradation path.
Adapt it
- Replace the synthetic approval logic with your real decision flow — the handlers'
TaskHandle.RequestInputAPI is the integration point. - Wire to a real auth context — the scaffold opts
RequestorIdentifiable=falseon stdio (single-user); on HTTP plug a bearer-token resolver intoWithTasks. - Add more fields to the edits form — extend the
ProposeWithEditsInput.Fieldsslice; the App'sFieldDiffcomponent composes the editable current → proposed pair fromweb/ui/.
What next
analytics-widgetswalkthrough — the read-side example.- Inspector guide — Tasks panel deep dive.
test-with-the-inspectoragent skill — how an AI coding agent drives the inspector while building.