NewWacht Bench is live — AI-assisted development for Wacht
GuidesTasks

Deliverables and the journal

Every task completion produces a structured handoff — in the deliverables array, on the assignment row, and in /task/JOURNAL.md.

When a coordinator marks a task completed, three things happen at once:

  1. An entry appends to the board item's deliverables array.
  2. The handoff fields merge into the assignment row's result_payload, and result_summary mirrors to its own column.
  3. An entry appends to /task/JOURNAL.md.

This page covers that handoff — what it carries, where it lands, and which surface to read for which purpose.

The handoff payload

The coordinator agent reports a completion by calling update_project_task with status="completed". The runtime validates the payload before accepting the transition:

FieldRequiredConstraint
result_summaryyes≥ 30 characters after trimming
artifactsyesat least one entry; every declared path must exist on disk
findingsoptional≤ 200 characters, single line
cautionsoptional≤ 200 characters, single line
nextoptional≤ 200 characters, single line

artifacts is a list of objects, each carrying a path. Existence is checked per path at completion time — a path that names a file the agent never wrote rejects the transition. Paths are conventionally under /task/artifacts/, but the check is existence, not prefix.

A payload that fails any constraint rejects the status change with a BadRequest. The agent sees the error and corrects it on the next iteration. The same validation applies to the other terminal statuses that require a summary (failed, blocked, rejected, needs_clarification); only completed also requires artifacts.

Where the handoff lands

The deliverables array on the board item

{
  "id": "board_item_id",
  "title": "30s teaser for launch",
  "status": "completed",
  "deliverables": [
    {
      "at": "2026-05-20T14:33:12Z",
      "assignment_id": "1234567890",
      "by_thread_id": "987654",
      "by_agent_name": "video-coordinator",
      "result_summary": "Rendered 30s teaser with 4 cuts and music bed.",
      "artifacts": [{ "path": "/task/artifacts/teaser-final.mp4" }],
      "findings": "Source clips needed -2dB normalization.",
      "cautions": "Audio drift after 25s — re-encode source if reusing.",
      "next": "Run color grade pass before publish."
    }
  ]
}

Each completion appends a new entry. Reopen and recomplete a task and you get multiple entries — the array is the audit trail. This is the surface your UI should read: stable, ordered, and queryable.

The assignment's result_payload

findings, cautions, and next also shallow-merge into the assignment row's result_payload JSONB, and result_summary mirrors to the assignment's result_summary column. Read these when you are querying assignment-level history — one row per executor run — rather than task-level deliverables.

/task/JOURNAL.md

A markdown entry appends to the journal on every completion:

## [2026-05-20T14:33:12Z] · video-coordinator · completed
outcome: Rendered 30s teaser with 4 cuts and music bed.
findings: Source clips needed -2dB normalization.
cautions: Audio drift after 25s — re-encode source if reusing.
artifacts: /task/artifacts/teaser-final.mp4
next: Run color grade pass before publish.
<!-- assignment:1234567890 -->

The <!-- assignment:N --> marker makes the append idempotent. If the same handoff fires twice, the second append short-circuits — the marker is already present.

The journal is the agent's working memory across iterations. At prompt-build time the runtime injects the tail of JOURNAL.md — the last 60 lines, drawn from the most recent 16 KB — so an agent with an active board item sees recent handoffs without querying. This is plumbing the agent reads, not a surface for your UI.

Surfacing deliverables in your UI

"use client";
import { useProjectTaskBoardItem } from "@wacht/nextjs";

export function DeliverablesPanel({ projectId, taskId }: { projectId: string; taskId: string }) {
  const { item } = useProjectTaskBoardItem(projectId, taskId);
  const deliverables = item?.deliverables ?? [];

  if (deliverables.length === 0) {
    return <div>No deliverables yet. The agent is still working.</div>;
  }

  return (
    <ol>
      {deliverables.map((d, i) => (
        <li key={i}>
          <header>
            <strong>{d.by_agent_name}</strong>
            <time>{d.at}</time>
          </header>
          <p>{d.result_summary}</p>
          {d.findings && <p><b>Findings:</b> {d.findings}</p>}
          {d.cautions && <p><b>Cautions:</b> {d.cautions}</p>}
          {d.next && <p><b>Next:</b> {d.next}</p>}
          <ul>
            {d.artifacts.map((a) => (
              <li key={a.path}>
                <a href={`/api/task/${taskId}/file?path=${encodeURIComponent(a.path)}`}>{a.path}</a>
              </li>
            ))}
          </ul>
        </li>
      ))}
    </ol>
  );
}

useProjectTaskBoardItem(projectId, taskId) revalidates on a 15-second interval, so a new deliverable surfaces within that window and the list re-renders. Each artifact is an object with a path — iterate over a.path, not the entry directly.

Reading the journal directly

To show the markdown narrative — a "history" tab, say — read /task/JOURNAL.md through the workspace file method on the same hook:

"use client";
import { useEffect, useState } from "react";
import { useProjectTaskBoardItem } from "@wacht/nextjs";

export function JournalView({ projectId, taskId }: { projectId: string; taskId: string }) {
  const { getTaskWorkspaceFile } = useProjectTaskBoardItem(projectId, taskId);
  const [text, setText] = useState<string | null>(null);

  useEffect(() => {
    getTaskWorkspaceFile("/task/JOURNAL.md").then((res) => setText(res.data?.content ?? ""));
  }, [getTaskWorkspaceFile]);

  if (text === null) return <div>Loading…</div>;
  return <pre>{text}</pre>;
}

getTaskWorkspaceFile is a method on the hook, not a hook of its own — call it on demand rather than subscribing. Render the raw text through react-markdown if you want formatting. Remember the journal is the agent's memory; don't put it where a user expects to see results.

Multi-agent handoffs

In a multi-lane flow an executor finishes its assignment without marking the task completed — other lanes may still be running. The executor's handoff records on its assignment row (result_payload), but it does not append to deliverables yet.

The deliverable lands on the array only when the coordinator (or a conversation thread) calls update_project_task(status="completed", ...). The coordinator's result_summary summarizes the whole task; per-lane summaries live on each assignment.

So:

  • deliverables[] — task-level summaries, one per round trip through completion.
  • result_payload on assignments — per-lane summaries, one per executor run.

Read deliverables[] for the headline. Read both when you want the full picture of who did what.

When to use which surface

You wantRead
The latest outputdeliverables[last].artifacts
Every completion, structureddeliverables (the array)
The narrative history/task/JOURNAL.md
Findings/cautions per assignmentresult_payload on the assignment row
The summary line in a task listresult_summary on the assignment row

The data is duplicated across these places on purpose — each is optimized for a different access pattern. Most UIs need only deliverables.

Where to go next

On this page