Dispatch Desk — API

Paste the failures — independent domains, one brief per agent, the conflicts to watch.

API tokens Open the app

Plan a parallel agent fan-out from your own scripts

Send a pile of work — a failing test run pasted straight out of the runner, a bug list, a task list, or the leftovers from a previous round of agents — and get back one JSON object: a posture saying whether fanning out is the right move at all, the independent problem domains with the reason each stands alone, one copy-ready agent brief per domain, the conflict watch list of files and resources two agents would both touch, the work held back for a later round, a reconciliation of the free client-side prescan, and the review-and-integrate checklist for when the agents come back. Everything this app does goes through the SkillSafe App API — plain JSON over HTTPS — so a red CI run can plan its own fan-out before anyone reads it. Wire it into whatever produces the work: a post-failure hook on your test job, a nightly triage of the open bug queue, a chat command that turns a paste into briefs. Pick a language once and the whole page follows.

Basics

Base URL: https://api.skillsafe.ai/v1/app-api, app slug dispatch-desk. Every request sends Authorization: Bearer <token> and JSON bodies with Content-Type: application/json. Responses are wrapped in an envelope: {"data": …} on success, {"error": {"code", "message"}} on failure. The plan itself is produced by the gpt-terra model. Estimates are free; runs are metered against your credit balance. There is a single run task — one work list in, one plan out, no follow-up calls and no session state to carry. A re-plan is just another run with round: "replan" and the agents' reports pasted into context.

StatusMeaning
400Malformed body, or a field the endpoint does not accept — check the input object against the table in step 3.
401Missing or expired token — create a new session.
402Not enough credits — top up at skillsafe.ai/account/billing. The app surfaces this before the run by comparing min_credits from /estimate against the balance from /me; do the same and you never see this code.
403The token isn't allowed to do this (e.g. a guest running a metered plan once the sponsored allowance is gone).
404Unknown job id, record id, or a collection this release does not declare.
409An Idempotency-Key replay whose body does not match the original request.
429Rate limited. Data endpoints share 120 requests/min; /collections/{name}/similar is 30/min per IP. Back off and retry.
5xxTransient platform error — retry with backoff, reusing the same Idempotency-Key.

Browsers enforce CORS for this API, so run these examples from a server, script or terminal — not from another website's frontend.

Step 1 — Get a token

POST /guest

A guest token lets you check balances and estimate costs for free. For metered plan runs billed to your own account, use your personal token: open the token page, sign in with SkillSafe, and press Copy shell export — it puts export SKILLSAFE_TOKEN="…" on your clipboard, which every example below reads. Treat the token like a password: it can spend your credits. For fully headless scripts — a CI job that has no browser to open — POST /guest mints a guest token with no human involved.

curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
  -H "Content-Type: application/json" \
  -d '{"slug":"dispatch-desk"}' | jq -r '.data.token'
import requests

API = "https://api.skillsafe.ai/v1/app-api"
res = requests.post(API + "/guest", json={"slug": "dispatch-desk"})
token = res.json()["data"]["token"]
const API = "https://api.skillsafe.ai/v1/app-api";
const res = await fetch(API + "/guest", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ slug: "dispatch-desk" }),
});
const { data } = await res.json();
const token = data.token;
body, _ := json.Marshal(map[string]string{"slug": "dispatch-desk"})
res, _ := http.Post("https://api.skillsafe.ai/v1/app-api/guest",
	"application/json", bytes.NewReader(body))
defer res.Body.Close()

var env struct {
	Data struct {
		Token string `json:"token"`
	} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&env)
token := env.Data.Token
var req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"slug\":\"dispatch-desk\"}"))
    .build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
// the guest token is at data.token in the returned envelope
require "net/http"
require "json"

API = "https://api.skillsafe.ai/v1/app-api"
res = Net::HTTP.post(URI(API + "/guest"),
                     { slug: "dispatch-desk" }.to_json,
                     "Content-Type" => "application/json")
token = JSON.parse(res.body)["data"]["token"]
<?php
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/guest");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Content-Type: application/json"],
    CURLOPT_POSTFIELDS     => json_encode(["slug" => "dispatch-desk"]),
]);
$token = json_decode(curl_exec($ch), true)["data"]["token"];
curl_close($ch);
using var http = new HttpClient();
var res = await http.PostAsJsonAsync(
    "https://api.skillsafe.ai/v1/app-api/guest", new { slug = "dispatch-desk" });
var env = await res.Content.ReadFromJsonAsync<JsonElement>();
var token = env.GetProperty("data").GetProperty("token").GetString();

The app stores this browser's token under the localStorage key skillsafe_app_token:dispatch-desk, on the app's own origin. The token page reads and manages it for you — you never need to open developer tools.

A tiny client

Every task below is a single HTTP call, so start with a short helper that adds the auth header, sends JSON and unwraps the data envelope. The later steps reuse it, and it is where the optional Idempotency-Key header from steps 4 and 5 gets threaded through.

export API="https://api.skillsafe.ai/v1/app-api"
export TOKEN="YOUR_TOKEN"      # from the token page, or the /guest call above

# every call looks like:
#   curl -s "$API/..." -H "Authorization: Bearer $TOKEN" [-d '{json}']
# jq is used below to pull fields out of the {"data": ...} envelope
import json, os, requests

API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = os.environ.get("SKILLSAFE_TOKEN", "YOUR_TOKEN")  # see above

def api(method, path, body=None, **headers):
    res = requests.request(method, API + path, json=body,
                           headers={"Authorization": f"Bearer {TOKEN}", **headers})
    payload = res.json()
    if not res.ok:
        raise RuntimeError(payload.get("error", {}).get("message", res.reason))
    return payload["data"]
// Node 18+ (built-in fetch)
const API = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // read it from your shell environment in real code

async function api(method, path, body, extraHeaders = {}) {
  const res = await fetch(API + path, {
    method,
    headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", ...extraHeaders },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const json = await res.json();
  if (!res.ok) throw new Error(json.error?.message ?? res.statusText);
  return json.data;
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const API = "https://api.skillsafe.ai/v1/app-api"

var token = os.Getenv("SKILLSAFE_TOKEN") // see above

func call(method, path string, body, out any, headers map[string]string) error {
	var buf bytes.Buffer
	if body != nil {
		json.NewEncoder(&buf).Encode(body)
	}
	req, _ := http.NewRequest(method, API+path, &buf)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	var env struct {
		Data  json.RawMessage `json:"data"`
		Error *struct{ Message string `json:"message"` } `json:"error"`
	}
	json.NewDecoder(res.Body).Decode(&env)
	if res.StatusCode >= 400 {
		return fmt.Errorf("api %s %s: %s", method, path, env.Error.Message)
	}
	if out == nil {
		return nil
	}
	return json.Unmarshal(env.Data, out)
}
// Java 17+, no dependencies. Pair with your JSON library (Jackson, Gson…)
// to read fields out of the returned envelope.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class SkillSafe {
    static final String API = "https://api.skillsafe.ai/v1/app-api";
    static final String TOKEN = System.getenv("SKILLSAFE_TOKEN"); // see above
    static final HttpClient HTTP = HttpClient.newHttpClient();

    static String api(String method, String path, String jsonBody, String idemKey) throws Exception {
        var b = HttpRequest.newBuilder(URI.create(API + path))
            .header("Authorization", "Bearer " + TOKEN)
            .header("Content-Type", "application/json")
            .method(method, jsonBody == null
                ? HttpRequest.BodyPublishers.noBody()
                : HttpRequest.BodyPublishers.ofString(jsonBody));
        if (idemKey != null) b = b.header("Idempotency-Key", idemKey);
        var res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
        if (res.statusCode() >= 400) throw new RuntimeException(res.body());
        return res.body(); // envelope: {"data": …}
    }
}
require "net/http"
require "json"

API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = ENV.fetch("SKILLSAFE_TOKEN", "YOUR_TOKEN") # see above

def api(method, path, body = nil, headers = {})
  uri = URI(API + path)
  req = Net::HTTP.const_get(method.capitalize).new(uri)
  req["Authorization"] = "Bearer #{TOKEN}"
  req["Content-Type"] = "application/json"
  headers.each { |k, v| req[k] = v }
  req.body = body.to_json if body
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
  payload = JSON.parse(res.body)
  raise (payload.dig("error", "message") || res.message) unless res.is_a?(Net::HTTPSuccess)
  payload["data"]
end
<?php
const API = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = getenv("SKILLSAFE_TOKEN") ?: "YOUR_TOKEN"; // see above

function api(string $method, string $path, ?array $body = null, array $extra = []): mixed {
    global $TOKEN;
    $ch = curl_init(API . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => array_merge([
            "Authorization: Bearer $TOKEN",
            "Content-Type: application/json",
        ], $extra),
        CURLOPT_POSTFIELDS     => $body === null ? null : json_encode($body),
    ]);
    $payload = json_decode(curl_exec($ch), true);
    $status  = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($status >= 400) {
        throw new Exception($payload["error"]["message"] ?? "HTTP $status");
    }
    return $payload["data"];
}
// .NET 8+
using System.Net.Http.Json;
using System.Text.Json;

static class SkillSafe
{
    const string Api = "https://api.skillsafe.ai/v1/app-api";
    static readonly HttpClient Http = new();

    static SkillSafe() =>
        Http.DefaultRequestHeaders.Authorization =
            new("Bearer", Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")); // see above

    public static async Task<JsonElement> ApiAsync(HttpMethod method, string path,
                                                   object? body = null, string? idemKey = null)
    {
        var req = new HttpRequestMessage(method, Api + path);
        if (body != null) req.Content = JsonContent.Create(body);
        if (idemKey != null) req.Headers.Add("Idempotency-Key", idemKey);
        var res = await Http.SendAsync(req);
        var json = await res.Content.ReadFromJsonAsync<JsonElement>();
        if (!res.IsSuccessStatusCode)
            throw new Exception(json.GetProperty("error").GetProperty("message").GetString());
        return json.GetProperty("data");
    }
}

Step 2 — Check who you are and your balance

GET /me

Returns subject_type ("user" or "guest"), subject_id and your credits balance, in credits (10,000 credits = 1 USD). A guest subject has no wallet worth speaking of: it exists so the free lanes — /estimate and the read-only endpoints — work without an account, and so a sponsored run can be attributed to somebody. Check this before planning a long work list, and check it against min_credits from step 3 rather than waiting for a 402.

curl -s "$API/me" -H "Authorization: Bearer $TOKEN" | jq '.data'

# {
#   "subject_type": "user",
#   "subject_id": "usr_...",
#   "credits": 48210
# }
me = api("GET", "/me")
print(me["subject_type"], me["credits"], "credits")
if me["subject_type"] != "user":
    print("guest session — metered runs need a signed-in token")
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits, "credits");
if (me.subject_type !== "user") {
  console.warn("guest session — metered runs need a signed-in token");
}
var me struct {
	SubjectType string `json:"subject_type"`
	SubjectID   string `json:"subject_id"`
	Credits     int64  `json:"credits"`
}
if err := call("GET", "/me", nil, &me, nil); err != nil {
	log.Fatal(err)
}
fmt.Printf("%s %s: %d credits\n", me.SubjectType, me.SubjectID, me.Credits)
String envelope = api("GET", "/me", null, null);
// data.subject_type, data.subject_id, data.credits
System.out.println(envelope);
me = api("GET", "/me")
puts "#{me["subject_type"]}: #{me["credits"]} credits"
warn "guest session — metered runs need a signed-in token" unless me["subject_type"] == "user"
$me = api("GET", "/me");
echo "{$me['subject_type']}: {$me['credits']} credits\n";
if ($me["subject_type"] !== "user") {
    fwrite(STDERR, "guest session — metered runs need a signed-in token\n");
}
var me = await SkillSafe.ApiAsync(HttpMethod.Get, "/me");
Console.WriteLine($"{me.GetProperty("subject_type")}: {me.GetProperty("credits")} credits");

Step 3 — Estimate the cost

POST /estimate

Send exactly the input you would send to /run; the response's hold_credits is the worst-case cost. Nothing is charged and no job is created, so estimating is free — useful when you are piping a whole runner log in and want a ceiling before spending credits. The app calls this on a 500 ms debounce while you type, which is also the sane cadence for a script that lets a human edit the work list before pressing go.

Response fieldTypeMeaning
modelstringThe concrete model that will run the plan, as shown in the app's price meter.
model_aliasstringThe stable alias the app pins. Dispatch Desk is built and tuned for gpt-terra; assert on this and fail loudly if it ever comes back as something else, because the output schema in step 6 is what this model was prompted for.
markup_bpsnumberThe app's markup over raw model cost, in basis points. It is already folded into hold_credits and min_credits — it is here so you can show a caller what they are paying for, not so you can add it again.
hold_creditsnumberThe worst-case cost, in credits, and the amount reserved when the run starts. You are charged only what the run actually uses; the remainder of the hold is released.
min_creditsnumberThe floor needed to start a run at all. Below this the app disables the run button and says how short you are. Between min_credits and hold_credits the run starts but may be truncated mid-plan — see the truncated flag in step 5.
sponsor_enabledbooleanTrue when the app is currently sponsoring runs for signed-out subjects. When it flips false mid-session the next run answers 403 with details.reason == "sponsor_exhausted", which is your cue to switch to a user token.

The input object

One flat JSON object. Only work is required; every other field has a default that matches the app's own form. This is the exact object the app builds in readForm(), so anything you send here behaves identically to using the web UI.

Input fieldTypeNotes
workstring, requiredThe pasted work, exactly as you have it: a Vitest/Jest FAIL block, a pytest report, a go test log, a TAP stream, a bulleted bug list, a numbered task list, or a mix. Stack traces are wanted, not noise — the file paths inside them are the strongest signal for telling independent work apart. Inputs longer than 100,000 characters are clipped middle-out on a line boundary, keeping the head and the tail, with a [... clipped N characters from the middle ...] marker showing where. The app refuses anything under 40 characters as too short to plan.
shapestringfailures | tasks | mixed | unknown. What you say the paste is. The model is told to trust the text over this label when the two disagree, so an honest unknown costs nothing.
agentsstring"auto" or "2""6" — a string, not a number, because it comes off a <select>. The hard cap on parallel briefs: the plan will never emit more briefs than this. "auto" means as many as there are genuinely independent domains. Work that does not fit under the cap is not dropped — it moves into sequential with after pointing at the round that frees it.
roundstringfirst (plan the initial fan-out) or replan (the agents came back; plan the next wave). On replan the model treats what context says the agents reported as fact: it will not re-dispatch finished work, and exec_summary says what changed since the last wave.
ownershipstringagents (may change production code) | tests-only (must not touch production code) | ask (must report findings and change nothing). Whichever you send appears in every brief's constraints, so it is the one field that reliably shapes what the agents are allowed to do.
contextstring, optionalFree text: repo layout, which subsystems are owned by whom, what changed just before this broke, whether the suite shares a database or a port, which files are off limits, how many agents you can really run — and, on a re-plan, what each agent reported and what is still red. Clipped at 20,000 characters, also middle-out.
prescan_factsobject, optionalWhat a client-side scanner mechanically matched in work: {"resources": [], "flags": []}, each entry {id, label}. See the two tables below. Optional for API callers — omit it, or send the two empty arrays — but supplying it is the only thing that makes coverage_check meaningful.
retry_notestring, optionalOnly set by the app's automatic reformat retry when a first reply was not valid JSON. Leave it out; the model is instructed to obey it and re-answer, which is not what you want on a first attempt.

prescan_facts — resources and flags

The app runs a free, offline prescan (DispatchScan.analyze) on every keystroke and sends the result along with the work. It splits the paste into items, pulls out the file paths, and raises a fixed checklist of mechanical independence risks. Nothing about it is AI: it is regular expressions over the text you already have, so an API caller can reproduce it, approximate it, or skip it.

Resources are the things the prescan found. Ids are res:item/<n>-<slug> for each split-out work item — e.g. res:item/3-should-track-pendingtoolcount — and res:file/<slug> for each file path, e.g. res:file/src-agents-abort-ts. Labels are Failure/…, Task/… or File/…. Every work item you send here must turn up somewhere in the plan — in inventory at minimum, and through it in a domain, a brief or sequential. Silently dropping pasted work is the failure mode this reconciliation exists to catch, because the user believes it was covered.

Flags are mechanical independence risks. Ids are <family>:<slug>, and there are eleven families:

Flag familyExample idWhat it means for the plan
shared-fileshared-file:src-batch-emitter-tsOne file appears under two or more items. Either those items are one domain, or the file gets exactly one owner and the other domain waits — the skill's rule is one agent per independent problem domain, and a shared file is not independence.
same-errorsame-error:typeerror-cannot-read-propertiesThe same error signature repeats across items. Identical failures in different files usually have one root cause; dispatching three agents at one bug burns three contexts and produces three conflicting fixes.
shared-fixtureshared-fixture:tests-conftest-pyA conftest, setup file, factory or test helper is in play. Shared setup is shared state by definition: name the single owner in exactly one brief's constraints, or forbid all of them from touching it.
ordering-depordering-dep:blocked-byThe text says "after", "depends on", "blocked by", "once X lands". That is a sequence, not a set of independent tasks — the later item belongs in sequential.
serialized-resourceserialized-resource:migrations-0042-orders-index-sqlA migration, schema file, lockfile, snapshot or generated artifact: single-writer by nature. Two agents both adding a migration or both regenerating a lockfile produce a conflict nobody wants to review.
shared-envshared-env:localhost-5432-app-testA shared port, database or environment variable. Agents that run tests concurrently against the same one interfere at runtime, not just in the diff — and the failures look like flakes.
thin-itemthin-item:fix-the-race-conditionAn item with no file, no error text and no acceptance criterion. This is the canonical "no context" bad brief: the agent spends its whole context rediscovering what you already knew.
too-broadtoo-broad:fix-all-the-testsAn item phrased too broadly to scope. "Fix all the tests" is the skill's canonical bad brief; it comes back as a sprawling diff.
timing-hinttiming-hint:race-conditionA timing, race, flake, timeout or deadlock hint. Any brief covering this item must carry the explicit constraint: do not just increase timeouts — find the real issue.
single-domainsingle-domain:floorEverything is one item, or everything is in one file. Below the floor for fanning out: one agent with the whole picture beats several with partial ones.
plain-secretplain-secret:hunter2abcdefA credential-looking literal is in the paste. It would otherwise be copied into a brief and into the saved plan. Redact it before you send.

Every flag id you send comes back in coverage_check exactly once — confirmed as accounted for, or set aside with a reason. Resource ids are not reconciled there; they shape inventory instead. Omit prescan_facts entirely and coverage_check comes back empty, with the rest of the plan unaffected.

cat > work.txt <<'WORK'
FAIL  src/agents/tool-abort.test.ts
  x  should abort a tool call and capture partial output (5023 ms)
     AssertionError: expected message to contain 'interrupted at'
       at src/agents/abort-controller.ts:118:9
FAIL  src/batch/completion.test.ts
  x  emits batch-complete once every tool settles
     TypeError: Cannot read properties of undefined (reading 'threadId')
       at src/batch/emitter.ts:57:31
FAIL  src/approval/race.test.ts
  x  runs an approved tool exactly once under concurrent approvals
     AssertionError: execution count expected 1, received 0
       at src/approval/queue.ts:88:5
WORK

jq -n --rawfile work work.txt \
  '{work: $work,
    shape: "failures",
    agents: "auto",
    round: "first",
    ownership: "agents",
    context: "Monorepo, Vitest, 28 test files. These went red after a refactor of the tool-execution pipeline that landed on main this morning; the three files share no imports beyond the types package.",
    prescan_facts: {resources: [], flags: []}}' > input.json

curl -s -X POST "$API/estimate" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d @input.json | jq '.data | {model, model_alias, markup_bps, hold_credits, min_credits, sponsor_enabled}'

# {
#   "model": "gpt-terra-2026-05",
#   "model_alias": "gpt-terra",
#   "markup_bps": 2000,
#   "hold_credits": 5400,
#   "min_credits": 900,
#   "sponsor_enabled": false
# }
WORK = """FAIL  src/agents/tool-abort.test.ts
  x  should abort a tool call and capture partial output (5023 ms)
     AssertionError: expected message to contain 'interrupted at'
       at src/agents/abort-controller.ts:118:9
FAIL  src/batch/completion.test.ts
  x  emits batch-complete once every tool settles
     TypeError: Cannot read properties of undefined (reading 'threadId')
       at src/batch/emitter.ts:57:31
FAIL  src/approval/race.test.ts
  x  runs an approved tool exactly once under concurrent approvals
     AssertionError: execution count expected 1, received 0
       at src/approval/queue.ts:88:5
"""

payload = {
    "work": WORK,
    "shape": "failures",
    "agents": "auto",
    "round": "first",
    "ownership": "agents",
    "context": "Monorepo, Vitest, 28 test files. These went red after a refactor of the "
               "tool-execution pipeline that landed on main this morning; the three files "
               "share no imports beyond the types package.",
    "prescan_facts": {"resources": [], "flags": []},
}

est = api("POST", "/estimate", payload)
assert est["model_alias"] == "gpt-terra", f'unexpected model: {est["model_alias"]}'
print("worst case:", est["hold_credits"], "credits; floor:", est["min_credits"])

me = api("GET", "/me")
if me["credits"] < est["min_credits"]:
    raise SystemExit(f'short by {est["min_credits"] - me["credits"]} credits')
const work = `FAIL  src/agents/tool-abort.test.ts
  x  should abort a tool call and capture partial output (5023 ms)
     AssertionError: expected message to contain 'interrupted at'
       at src/agents/abort-controller.ts:118:9
FAIL  src/batch/completion.test.ts
  x  emits batch-complete once every tool settles
     TypeError: Cannot read properties of undefined (reading 'threadId')
       at src/batch/emitter.ts:57:31
FAIL  src/approval/race.test.ts
  x  runs an approved tool exactly once under concurrent approvals
     AssertionError: execution count expected 1, received 0
       at src/approval/queue.ts:88:5
`;

const payload = {
  work,
  shape: "failures",
  agents: "auto",
  round: "first",
  ownership: "agents",
  context:
    "Monorepo, Vitest, 28 test files. These went red after a refactor of the " +
    "tool-execution pipeline that landed on main this morning; the three files " +
    "share no imports beyond the types package.",
  prescan_facts: { resources: [], flags: [] },
};

const est = await api("POST", "/estimate", payload);
if (est.model_alias !== "gpt-terra") throw new Error(`unexpected model: ${est.model_alias}`);
console.log("worst case:", est.hold_credits, "credits; floor:", est.min_credits);
const work = `FAIL  src/agents/tool-abort.test.ts
  x  should abort a tool call and capture partial output (5023 ms)
     AssertionError: expected message to contain 'interrupted at'
       at src/agents/abort-controller.ts:118:9
FAIL  src/batch/completion.test.ts
  x  emits batch-complete once every tool settles
     TypeError: Cannot read properties of undefined (reading 'threadId')
       at src/batch/emitter.ts:57:31
FAIL  src/approval/race.test.ts
  x  runs an approved tool exactly once under concurrent approvals
     AssertionError: execution count expected 1, received 0
       at src/approval/queue.ts:88:5
`

payload := map[string]any{
	"work":      work,
	"shape":     "failures",
	"agents":    "auto",
	"round":     "first",
	"ownership": "agents",
	"context": "Monorepo, Vitest, 28 test files. These went red after a refactor of the " +
		"tool-execution pipeline that landed on main this morning; the three files " +
		"share no imports beyond the types package.",
	"prescan_facts": map[string]any{
		"resources": []any{}, "flags": []any{},
	},
}

var est struct {
	Model          string `json:"model"`
	ModelAlias     string `json:"model_alias"`
	MarkupBps      int64  `json:"markup_bps"`
	HoldCredits    int64  `json:"hold_credits"`
	MinCredits     int64  `json:"min_credits"`
	SponsorEnabled bool   `json:"sponsor_enabled"`
}
if err := call("POST", "/estimate", payload, &est, nil); err != nil {
	log.Fatal(err)
}
if est.ModelAlias != "gpt-terra" {
	log.Fatalf("unexpected model: %s", est.ModelAlias)
}
fmt.Printf("worst case %d credits, floor %d\n", est.HoldCredits, est.MinCredits)
String work = """
    FAIL  src/agents/tool-abort.test.ts
      x  should abort a tool call and capture partial output (5023 ms)
         AssertionError: expected message to contain 'interrupted at'
           at src/agents/abort-controller.ts:118:9
    FAIL  src/batch/completion.test.ts
      x  emits batch-complete once every tool settles
         TypeError: Cannot read properties of undefined (reading 'threadId')
           at src/batch/emitter.ts:57:31
    FAIL  src/approval/race.test.ts
      x  runs an approved tool exactly once under concurrent approvals
         AssertionError: execution count expected 1, received 0
           at src/approval/queue.ts:88:5
    """;

String jsonPayload = """
    {"work": %s,
     "shape": "failures",
     "agents": "auto",
     "round": "first",
     "ownership": "agents",
     "context": "Monorepo, Vitest, 28 test files. These went red after a refactor of the tool-execution pipeline that landed on main this morning.",
     "prescan_facts": {"resources": [], "flags": []}}
    """.formatted(toJsonString(work));   // toJsonString = your JSON library's string encoder

String envelope = api("POST", "/estimate", jsonPayload, null);
// assert data.model_alias equals "gpt-terra", then read
// data.hold_credits (worst case) and data.min_credits (floor to start at all)
WORK = <<~LOG
  FAIL  src/agents/tool-abort.test.ts
    x  should abort a tool call and capture partial output (5023 ms)
       AssertionError: expected message to contain 'interrupted at'
         at src/agents/abort-controller.ts:118:9
  FAIL  src/batch/completion.test.ts
    x  emits batch-complete once every tool settles
       TypeError: Cannot read properties of undefined (reading 'threadId')
         at src/batch/emitter.ts:57:31
  FAIL  src/approval/race.test.ts
    x  runs an approved tool exactly once under concurrent approvals
       AssertionError: execution count expected 1, received 0
         at src/approval/queue.ts:88:5
LOG

payload = { work: WORK,
            shape: "failures",
            agents: "auto",
            round: "first",
            ownership: "agents",
            context: "Monorepo, Vitest, 28 test files. These went red after a refactor of " \
                     "the tool-execution pipeline that landed on main this morning.",
            prescan_facts: { resources: [], flags: [] } }

est = api("POST", "/estimate", payload)
raise "unexpected model: #{est["model_alias"]}" unless est["model_alias"] == "gpt-terra"
puts "worst case: #{est["hold_credits"]} credits, floor #{est["min_credits"]}"
$work = <<<'LOG'
FAIL  src/agents/tool-abort.test.ts
  x  should abort a tool call and capture partial output (5023 ms)
     AssertionError: expected message to contain 'interrupted at'
       at src/agents/abort-controller.ts:118:9
FAIL  src/batch/completion.test.ts
  x  emits batch-complete once every tool settles
     TypeError: Cannot read properties of undefined (reading 'threadId')
       at src/batch/emitter.ts:57:31
FAIL  src/approval/race.test.ts
  x  runs an approved tool exactly once under concurrent approvals
     AssertionError: execution count expected 1, received 0
       at src/approval/queue.ts:88:5
LOG;

$payload = [
    "work"          => $work,
    "shape"         => "failures",
    "agents"        => "auto",
    "round"         => "first",
    "ownership"     => "agents",
    "context"       => "Monorepo, Vitest, 28 test files. These went red after a refactor of "
                     . "the tool-execution pipeline that landed on main this morning.",
    "prescan_facts" => ["resources" => [], "flags" => []],
];

$est = api("POST", "/estimate", $payload);
if ($est["model_alias"] !== "gpt-terra") {
    throw new Exception("unexpected model: " . $est["model_alias"]);
}
echo "worst case: {$est['hold_credits']} credits, floor {$est['min_credits']}\n";
var work = """
    FAIL  src/agents/tool-abort.test.ts
      x  should abort a tool call and capture partial output (5023 ms)
         AssertionError: expected message to contain 'interrupted at'
           at src/agents/abort-controller.ts:118:9
    FAIL  src/batch/completion.test.ts
      x  emits batch-complete once every tool settles
         TypeError: Cannot read properties of undefined (reading 'threadId')
           at src/batch/emitter.ts:57:31
    FAIL  src/approval/race.test.ts
      x  runs an approved tool exactly once under concurrent approvals
         AssertionError: execution count expected 1, received 0
           at src/approval/queue.ts:88:5
    """;

var payload = new {
    work,
    shape = "failures",
    agents = "auto",
    round = "first",
    ownership = "agents",
    context = "Monorepo, Vitest, 28 test files. These went red after a refactor of the "
            + "tool-execution pipeline that landed on main this morning.",
    prescan_facts = new {
        resources = Array.Empty<object>(), flags = Array.Empty<object>(),
    },
};

var est = await SkillSafe.ApiAsync(HttpMethod.Post, "/estimate", payload);
if (est.GetProperty("model_alias").GetString() != "gpt-terra")
    throw new Exception("unexpected model");
Console.WriteLine($"worst case: {est.GetProperty("hold_credits")} credits, " +
                  $"floor {est.GetProperty("min_credits")}");

prescan_facts.flags is how you make the plan answer for the collisions you already know about. Send {"resources": [{"id": "res:file/src-batch-emitter-ts", "label": "File/src/batch/emitter.ts"}], "flags": [{"id": "shared-file:src-batch-emitter-ts", "label": "shared-file: src/batch/emitter.ts appears under 2 items"}]} and that id comes back in coverage_check — accounted for by a conflict row, a merged domain, a brief constraint or a held-back item, or explicitly set aside with the reason. Nothing you flag is silently dropped, which makes it the field to assert on in an automated gate.

Step 4 — Run the plan and wait for the result

POST /run
GET /jobs/{job_id}

/run takes the same input as /estimate, places a credit hold and returns a job_id. Poll /jobs/{job_id} every 1–2 seconds until status is succeeded or failed (a run typically takes 40–120 s, because every brief is a complete, self-contained prompt rather than a one-line summary). Always send an Idempotency-Key header so a network retry can't start a second, double-charged run. The plan is in output — usually nested as output.output, and as a JSON string, so parse defensively. The samples below print the posture, the domains, the briefs and the held-back work, then save the whole object to plan.json.

IDEM="dd-$(shasum -a 256 input.json | cut -c1-16)"

JOB_ID=$(curl -s -X POST "$API/run" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d @input.json | jq -r '.data.job_id')

while :; do
  JOB=$(curl -s "$API/jobs/$JOB_ID" -H "Authorization: Bearer $TOKEN")
  STATUS=$(echo "$JOB" | jq -r '.data.status')
  [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] && break
  sleep 2
done

# unwrap the plan once, then read it
echo "$JOB" | jq -r '.data.output.output' > plan.json

jq -r '
  "\(.plan_name) [\(.posture)]: \(.verdict)",
  "",
  "DOMAINS",
  (.domains[] | "  \(.id) \(.name) [\(.risk)] - \(.rationale)"),
  "",
  "BRIEFS",
  (.briefs[] | "  \(.id) -> \(.domain_id) \(.title)"),
  "",
  "CONFLICTS",
  (.conflicts[] | "  \(.resource) claimed by \(.domain_ids | join(", ")) - \(.mitigation)"),
  "",
  "HELD BACK",
  (.sequential[] | "  \(.item)\(if .after != "" then " (after " + .after + ")" else "" end) - \(.why)"),
  "",
  "COVERAGE",
  (.coverage_check[] | "  \(.id): \(if .addressed then "accounted for" else "SET ASIDE" end) - \(.note)"),
  "",
  "INTEGRATION",
  (.integration[] | "  - \(.)")' plan.json

# refuse to fan out when the plan says not to
jq -e '.posture != "sequential-first"' plan.json > /dev/null \
  || { echo "plan says do not fan out yet"; exit 1; }
import hashlib, time

idem = "dd-" + hashlib.sha256(
    json.dumps(payload, sort_keys=True).encode()).hexdigest()[:16]

job_id = api("POST", "/run", payload, **{"Idempotency-Key": idem})["job_id"]

while True:
    job = api("GET", f"/jobs/{job_id}")
    if job["status"] in ("succeeded", "failed"):
        break
    time.sleep(1.5)

if job["status"] == "failed":
    raise RuntimeError(job.get("error", "run failed"))

raw = job["output"]
if isinstance(raw, dict) and "output" in raw:
    raw = raw["output"]
plan = json.loads(raw) if isinstance(raw, str) else raw

print(f'{plan["plan_name"]} [{plan["posture"]}]: {plan["verdict"]}')
for d in plan["domains"]:
    print(f'  {d["id"]:<4} {d["name"]:<34} risk={d["risk"]:<6} {len(d["items"])} item(s)')
    print(f'       {d["rationale"]}')
for b in plan["briefs"]:
    print(f'  {b["id"]} -> {b["domain_id"]}  {b["title"]}')
    print(f'       goal: {b["goal"]}')
    print(f'       returns: {b["expected_output"]}')
for c in plan["conflicts"]:
    print(f'  conflict {c["resource"]} :: {", ".join(c["domain_ids"])} -> {c["mitigation"]}')
for s in plan["sequential"]:
    print(f'  held back: {s["item"]}' + (f' (after {s["after"]})' if s["after"] else ""))
for c in plan["coverage_check"]:
    print(f'  {c["id"]}: {"accounted for" if c["addressed"] else "SET ASIDE"} - {c["note"]}')
for step in plan["integration"]:
    print("  integrate:", step)

with open("plan.json", "w", encoding="utf-8") as fh:
    json.dump(plan, fh, indent=2)

if plan["posture"] == "sequential-first":
    raise SystemExit("plan says do not fan out yet")
import { writeFileSync } from "node:fs";
import { createHash } from "node:crypto";

const idem = "dd-" + createHash("sha256")
  .update(JSON.stringify(payload)).digest("hex").slice(0, 16);

const { job_id } = await api("POST", "/run", payload, { "Idempotency-Key": idem });

let job;
do {
  await new Promise((r) => setTimeout(r, 1500));
  job = await api("GET", `/jobs/${job_id}`);
} while (job.status !== "succeeded" && job.status !== "failed");

if (job.status === "failed") throw new Error(job.error ?? "run failed");

const raw = job.output?.output ?? job.output;
const plan = typeof raw === "string" ? JSON.parse(raw) : raw;

console.log(`${plan.plan_name} [${plan.posture}]: ${plan.verdict}`);
for (const d of plan.domains) {
  console.log(`  ${d.id} ${d.name} [${d.risk}] - ${d.rationale}`);
  for (const item of d.items) console.log(`      * ${item}`);
}
for (const b of plan.briefs) {
  console.log(`  ${b.id} -> ${b.domain_id}: ${b.title}`);
}
for (const c of plan.conflicts) {
  console.log(`  conflict ${c.resource} (${c.domain_ids.join(", ")}): ${c.mitigation}`);
}
for (const s of plan.sequential) {
  console.log(`  held back: ${s.item}${s.after ? ` after ${s.after}` : ""} - ${s.why}`);
}
for (const c of plan.coverage_check) {
  console.log(`  ${c.id}: ${c.addressed ? "accounted for" : "SET ASIDE"} - ${c.note}`);
}

writeFileSync("plan.json", JSON.stringify(plan, null, 2));

if (plan.posture === "sequential-first") process.exitCode = 1;
var started struct{ JobID string `json:"job_id"` }
idem := fmt.Sprintf("dd-%x", sha256.Sum256([]byte(fmt.Sprint(payload))))[:19]
if err := call("POST", "/run", payload, &started,
	map[string]string{"Idempotency-Key": idem}); err != nil {
	log.Fatal(err)
}

var job struct {
	Status string          `json:"status"`
	Error  string          `json:"error"`
	Output json.RawMessage `json:"output"`
}
for {
	if err := call("GET", "/jobs/"+started.JobID, nil, &job, nil); err != nil {
		log.Fatal(err)
	}
	if job.Status == "succeeded" || job.Status == "failed" {
		break
	}
	time.Sleep(1500 * time.Millisecond)
}

// job.Output is {"output": "<json string>"} — unwrap, then unmarshal:
type Plan struct {
	PlanName      string   `json:"plan_name"`
	Posture       string   `json:"posture"`
	Verdict       string   `json:"verdict"`
	ExecSummary   string   `json:"exec_summary"`
	Assumptions   []string `json:"assumptions"`
	OpenQuestions []string `json:"open_questions"`
	Inventory []struct {
		Kind, Name, Scope, Role string
	} `json:"inventory"`
	Domains []struct {
		ID, Name, Rationale, Risk string
		Items                     []string `json:"items"`
	} `json:"domains"`
	Briefs []struct {
		ID             string   `json:"id"`
		DomainID       string   `json:"domain_id"`
		Title          string   `json:"title"`
		Scope          string   `json:"scope"`
		Goal           string   `json:"goal"`
		Context        string   `json:"context"`
		Constraints    []string `json:"constraints"`
		ExpectedOutput string   `json:"expected_output"`
		Prompt         string   `json:"prompt"`
	} `json:"briefs"`
	Conflicts []struct {
		Resource   string   `json:"resource"`
		DomainIDs  []string `json:"domain_ids"`
		Mitigation string   `json:"mitigation"`
	} `json:"conflicts"`
	Sequential []struct {
		Item, After, Why string
	} `json:"sequential"`
	CoverageCheck []struct {
		ID, Note  string
		Addressed bool
	} `json:"coverage_check"`
	Integration []string `json:"integration"`
	Summary     string   `json:"summary"`
}
var wrapper struct{ Output string `json:"output"` }
json.Unmarshal(job.Output, &wrapper)
var plan Plan
json.Unmarshal([]byte(wrapper.Output), &plan)

fmt.Printf("%s [%s]: %s\n", plan.PlanName, plan.Posture, plan.Verdict)
for _, d := range plan.Domains {
	fmt.Printf("  %s %s [%s]: %s\n", d.ID, d.Name, d.Risk, d.Rationale)
}
for _, b := range plan.Briefs {
	fmt.Printf("  %s -> %s: %s\n", b.ID, b.DomainID, b.Title)
}
for _, c := range plan.Conflicts {
	fmt.Printf("  conflict %s %v: %s\n", c.Resource, c.DomainIDs, c.Mitigation)
}
os.WriteFile("plan.json", []byte(wrapper.Output), 0o644)
String envelope = api("POST", "/run", jsonPayload, "dd-" + jsonPayload.hashCode());
String jobId = /* data.job_id via your JSON library */;

while (true) {
    String job = api("GET", "/jobs/" + jobId, null, null);
    String status = /* data.status */;
    if (status.equals("succeeded") || status.equals("failed")) break;
    Thread.sleep(1500);
}
// The plan is at data.output.output as a JSON string — parse it again, then read
// plan_name, posture, verdict, exec_summary, assumptions[], open_questions[],
// inventory[] (kind/name/scope/role),
// domains[] (id/name/rationale/risk/items[]),
// briefs[] (id/domain_id/title/scope/goal/context/constraints[]/expected_output/prompt),
// conflicts[] (resource/domain_ids[]/mitigation),
// sequential[] (item/after/why), coverage_check[] (id/addressed/note),
// integration[] and summary.
// Finally keep the plan on disk:
//   Files.writeString(Path.of("plan.json"), planJson);
require "digest"

idem = "dd-" + Digest::SHA256.hexdigest(payload.to_json)[0, 16]
started = api("POST", "/run", payload, { "Idempotency-Key" => idem })

job = nil
loop do
  job = api("GET", "/jobs/#{started["job_id"]}")
  break if %w[succeeded failed].include?(job["status"])
  sleep 1.5
end
raise (job["error"] || "run failed") if job["status"] == "failed"

raw = job["output"].is_a?(Hash) ? job["output"].fetch("output", job["output"]) : job["output"]
plan = raw.is_a?(String) ? JSON.parse(raw) : raw

puts "#{plan["plan_name"]} [#{plan["posture"]}]: #{plan["verdict"]}"
plan["domains"].each { |d| puts "  #{d["id"]} #{d["name"]} [#{d["risk"]}] - #{d["rationale"]}" }
plan["briefs"].each  { |b| puts "  #{b["id"]} -> #{b["domain_id"]}: #{b["title"]}" }
plan["conflicts"].each do |c|
  puts "  conflict #{c["resource"]} (#{c["domain_ids"].join(", ")}): #{c["mitigation"]}"
end
plan["sequential"].each { |s| puts "  held back: #{s["item"]} - #{s["why"]}" }
plan["coverage_check"].each do |c|
  puts "  #{c["id"]}: #{c["addressed"] ? "accounted for" : "SET ASIDE"} - #{c["note"]}"
end
plan["integration"].each { |s| puts "  integrate: #{s}" }

File.write("plan.json", JSON.pretty_generate(plan))
exit 1 if plan["posture"] == "sequential-first"
$idem = "dd-" . substr(hash("sha256", json_encode($payload)), 0, 16);
$started = api("POST", "/run", $payload, ["Idempotency-Key: $idem"]);

do {
    sleep(2);
    $job = api("GET", "/jobs/" . $started["job_id"]);
} while (!in_array($job["status"], ["succeeded", "failed"]));

if ($job["status"] === "failed") {
    throw new Exception($job["error"] ?? "run failed");
}

$raw  = is_array($job["output"]) ? ($job["output"]["output"] ?? $job["output"]) : $job["output"];
$plan = is_string($raw) ? json_decode($raw, true) : $raw;

echo "{$plan['plan_name']} [{$plan['posture']}]: {$plan['verdict']}\n";
foreach ($plan["domains"] as $d) {
    echo "  {$d['id']} {$d['name']} [{$d['risk']}]: {$d['rationale']}\n";
}
foreach ($plan["briefs"] as $b) {
    echo "  {$b['id']} -> {$b['domain_id']}: {$b['title']}\n";
}
foreach ($plan["conflicts"] as $c) {
    echo "  conflict {$c['resource']} (" . implode(", ", $c["domain_ids"]) . "): {$c['mitigation']}\n";
}
foreach ($plan["sequential"] as $s) {
    echo "  held back: {$s['item']} - {$s['why']}\n";
}
foreach ($plan["coverage_check"] as $c) {
    echo "  {$c['id']}: " . ($c["addressed"] ? "accounted for" : "SET ASIDE") . "\n";
}

file_put_contents("plan.json", json_encode($plan, JSON_PRETTY_PRINT));
var idem = "dd-" + Convert.ToHexString(
    System.Security.Cryptography.SHA256.HashData(
        JsonSerializer.SerializeToUtf8Bytes(payload)))[..16];

var started = await SkillSafe.ApiAsync(HttpMethod.Post, "/run", payload, idem);
var jobId = started.GetProperty("job_id").GetString();

JsonElement job;
while (true)
{
    job = await SkillSafe.ApiAsync(HttpMethod.Get, $"/jobs/{jobId}");
    var status = job.GetProperty("status").GetString();
    if (status is "succeeded" or "failed") break;
    await Task.Delay(1500);
}

var rawText = job.GetProperty("output").GetProperty("output").GetString();
using var doc = JsonDocument.Parse(rawText!);
var plan = doc.RootElement;

Console.WriteLine($"{plan.GetProperty("plan_name")} " +
                  $"[{plan.GetProperty("posture")}]: {plan.GetProperty("verdict")}");
foreach (var d in plan.GetProperty("domains").EnumerateArray())
{
    Console.WriteLine($"  {d.GetProperty("id")} {d.GetProperty("name")} " +
                      $"[{d.GetProperty("risk")}]: {d.GetProperty("rationale")}");
}
foreach (var b in plan.GetProperty("briefs").EnumerateArray())
{
    Console.WriteLine($"  {b.GetProperty("id")} -> {b.GetProperty("domain_id")}: " +
                      $"{b.GetProperty("title")}");
}
foreach (var c in plan.GetProperty("conflicts").EnumerateArray())
{
    Console.WriteLine($"  conflict {c.GetProperty("resource")}: {c.GetProperty("mitigation")}");
}

await File.WriteAllTextAsync("plan.json", rawText!);

The model is asked for one JSON object and nothing else, but a stray code fence or preamble is always possible. Strip a leading ```json fence, take the text between the first { and the last }, and only then parse — that is exactly what the app's parseResult() does before it falls back to a retry_note reformat run. Step 6 has the full recovery recipe.

Step 5 — Stream the plan as it is written

POST /run-stream

/run-stream takes exactly the same body as /run but answers with server-sent events, so you can show progress instead of a spinner. This is the app's default lane: a plan with four complete agent briefs is a long reply, and the app advances its step list by watching for the section keys as they arrive rather than making the user stare at a bar that means nothing. Events are separated by a blank line; each has an event: line and a data: line carrying JSON.

EventPayloadMeaning
job{job_id, status}Sent once, when the job is accepted — show "starting".
delta{text}A chunk of the reply, in order. Append it; the accumulated length is your only progress signal (the total is not known in advance). The app watches the accumulated text for "plan_name", "posture", "domains", "briefs", "conflicts", "sequential", "coverage_check", "integration" and "summary" and lights up a six-step list as each key appears.
done{job_id, status, charged_credits, output, truncated}The final, authoritative result — read the plan from output.output rather than trusting concatenated deltas, and the settled price from charged_credits. truncated: true means the balance ran out mid-plan: what arrived is real and worth keeping, but sections are missing.
error{code, message, details}Replaces done when the run fails. details.reason == "sponsor_exhausted" means the free allowance for signed-out subjects is used up for today.

Idempotency

Both /run and /run-stream accept an Idempotency-Key request header; some clients prefer the equivalent idempotency_key field in the JSON body, and the two are interchangeable. Derive the key from the input, not from a clock: the app builds dispatch-desk:<fnv1a-hash-of-the-input>:<length>:a<attempt>, where the hash covers work, shape, agents, round, ownership and context. Two consequences worth internalising:

# -N disables buffering so events print as they arrive
IDEM="dd-$(shasum -a 256 input.json | cut -c1-16):a1"

curl -N -s -X POST "$API/run-stream" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d @input.json

# event: job
# data: {"job_id":"job_...","status":"running"}
#
# event: delta
# data: {"text":"{\"plan_name\":\"Tool-pipeline refactor"}
# ...
# event: done
# data: {"job_id":"job_...","status":"succeeded","charged_credits":3180,"output":{"output":"{...}"}}

# A retry after a dropped connection reuses $IDEM and returns the SAME job:
curl -N -s -X POST "$API/run-stream" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" -d @input.json
import hashlib, json, requests

idem = "dd-" + hashlib.sha256(
    json.dumps(payload, sort_keys=True).encode()).hexdigest()[:16] + ":a1"

result = None
with requests.post(
    API + "/run-stream",
    headers={"Authorization": f"Bearer {TOKEN}",
             "Idempotency-Key": idem},
    json=payload,
    stream=True,
) as r:
    r.raise_for_status()
    event = None
    for line in r.iter_lines(decode_unicode=True):
        if not line:
            continue
        if line.startswith("event:"):
            event = line[len("event:"):].strip()
        elif line.startswith("data:"):
            data = json.loads(line[len("data:"):].strip())
            if event == "delta":
                print(".", end="", flush=True)          # live progress
            elif event == "done":
                result = data
            elif event == "error":
                raise RuntimeError(data.get("message", "run failed"))

plan = json.loads(result["output"]["output"])            # authoritative
print("\ncharged:", result["charged_credits"], "-", plan["plan_name"])
print("posture:", plan["posture"])
if result.get("truncated"):
    print("WARNING: cut short by the available balance — sections are missing")
for b in plan["briefs"]:
    print(f'  {b["id"]} -> {b["domain_id"]}: {b["title"]}')
with open("plan.json", "w", encoding="utf-8") as fh:
    json.dump(plan, fh, indent=2)
const idem = "dd-" + createHash("sha256")
  .update(JSON.stringify(payload)).digest("hex").slice(0, 16) + ":a1";

const res = await fetch(API + "/run-stream", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": idem,
  },
  body: JSON.stringify(payload),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "", done = null, acc = "";

for (;;) {
  const chunk = await reader.read();
  if (chunk.done) break;
  buf += decoder.decode(chunk.value, { stream: true });
  const frames = buf.split("\n\n");
  buf = frames.pop();
  for (const frame of frames) {
    const name = /^event:\s*(.+)$/m.exec(frame)?.[1];
    const body = /^data:\s*(.+)$/m.exec(frame)?.[1];
    if (!name || !body) continue;
    const data = JSON.parse(body);
    if (name === "delta") {
      acc += data.text;                                  // section-key progress
      if (acc.includes('"briefs"')) process.stdout.write("b");
      else process.stdout.write(".");
    }
    if (name === "done") done = data;
    if (name === "error") throw new Error(data.message ?? "run failed");
  }
}

const plan = JSON.parse(done.output.output);
console.log(`\n${done.charged_credits} credits - ${plan.plan_name} [${plan.posture}]`);
if (done.truncated) console.warn("cut short by the available balance");
for (const b of plan.briefs) console.log(`  ${b.id} -> ${b.domain_id}: ${b.title}`);
writeFileSync("plan.json", JSON.stringify(plan, null, 2));
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", API+"/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idem) // same key on every retry of this input

res, err := http.DefaultClient.Do(req)
if err != nil {
	log.Fatal(err)
}
defer res.Body.Close()

var event string
var final map[string]any
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
for sc.Scan() {
	line := sc.Text()
	switch {
	case strings.HasPrefix(line, "event:"):
		event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
	case strings.HasPrefix(line, "data:"):
		var data map[string]any
		json.Unmarshal([]byte(strings.TrimPrefix(line, "data:")), &data)
		switch event {
		case "delta":
			fmt.Print(".") // live progress
		case "done":
			final = data
		case "error":
			log.Fatal(data["message"])
		}
	}
}
// final["output"].(map[string]any)["output"].(string) is the plan JSON —
// unmarshal it into the Plan struct from step 4, then write it to plan.json.
// Java 17+ — read the stream line by line instead of buffering the body.
var req = HttpRequest.newBuilder(URI.create(API + "/run-stream"))
    .header("Authorization", "Bearer " + TOKEN)
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", idem)   // unchanged across retries of this input
    .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
    .build();

var res = HTTP.send(req, HttpResponse.BodyHandlers.ofLines());
String event = null, done = null;
for (String line : (Iterable<String>) res.body()::iterator) {
    if (line.startsWith("event:")) {
        event = line.substring(6).trim();
    } else if (line.startsWith("data:")) {
        String data = line.substring(5).trim();
        if ("delta".equals(event)) System.out.print(".");   // live progress
        else if ("done".equals(event)) done = data;
        else if ("error".equals(event)) throw new RuntimeException(data);
    }
}
// parse `done`, then parse data.output.output again — it is a JSON string holding
// plan_name, posture, verdict, domains[], briefs[], conflicts[], sequential[],
// coverage_check[], integration[] and the rest.
require "net/http"
require "json"

uri = URI(API + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = idem   # same key on every retry of this input
req.body = payload.to_json

event = nil
done = nil
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  http.request(req) do |res|
    res.read_body do |chunk|
      chunk.each_line do |line|
        line = line.strip
        if line.start_with?("event:")
          event = line.delete_prefix("event:").strip
        elsif line.start_with?("data:")
          data = JSON.parse(line.delete_prefix("data:").strip)
          case event
          when "delta" then print "."           # live progress
          when "done"  then done = data
          when "error" then raise (data["message"] || "run failed")
          end
        end
      end
    end
  end
end

plan = JSON.parse(done["output"]["output"])
puts "\n#{done["charged_credits"]} credits - #{plan["plan_name"]} [#{plan["posture"]}]"
warn "cut short by the available balance" if done["truncated"]
plan["briefs"].each { |b| puts "  #{b["id"]} -> #{b["domain_id"]}: #{b["title"]}" }
File.write("plan.json", JSON.pretty_generate(plan))
$event = null;
$done  = null;

$ch = curl_init(API . "/run-stream");
curl_setopt_array($ch, [
    CURLOPT_POST       => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $TOKEN",
        "Content-Type: application/json",
        "Idempotency-Key: $idem",
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$event, &$done) {
        foreach (explode("\n", $chunk) as $line) {
            $line = trim($line);
            if (str_starts_with($line, "event:")) {
                $event = trim(substr($line, 6));
            } elseif (str_starts_with($line, "data:")) {
                $data = json_decode(trim(substr($line, 5)), true);
                if ($event === "delta") { echo "."; }        // live progress
                elseif ($event === "done") { $done = $data; }
                elseif ($event === "error") { throw new Exception($data["message"] ?? "run failed"); }
            }
        }
        return strlen($chunk);
    },
]);
curl_exec($ch);
curl_close($ch);

$plan = json_decode($done["output"]["output"], true);
echo "\n{$done['charged_credits']} credits - {$plan['plan_name']} [{$plan['posture']}]\n";
foreach ($plan["briefs"] as $b) {
    echo "  {$b['id']} -> {$b['domain_id']}: {$b['title']}\n";
}
file_put_contents("plan.json", json_encode($plan, JSON_PRETTY_PRINT));
var req = new HttpRequestMessage(HttpMethod.Post, Api + "/run-stream") {
    Content = JsonContent.Create(payload),
};
req.Headers.Add("Idempotency-Key", idem);  // same key on every retry of this input

using var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res.Content.ReadAsStreamAsync());

string? evt = null, done = null;
while (await reader.ReadLineAsync() is { } line)
{
    if (line.StartsWith("event:")) evt = line[6..].Trim();
    else if (line.StartsWith("data:"))
    {
        var data = line[5..].Trim();
        if (evt == "delta") Console.Write(".");            // live progress
        else if (evt == "done") done = data;
        else if (evt == "error") throw new Exception(data);
    }
}

using var final = JsonDocument.Parse(done!);
var text = final.RootElement.GetProperty("output").GetProperty("output").GetString();
using var planDoc = JsonDocument.Parse(text!);
var plan = planDoc.RootElement;
Console.WriteLine($"{plan.GetProperty("plan_name")} [{plan.GetProperty("posture")}]");
foreach (var b in plan.GetProperty("briefs").EnumerateArray())
    Console.WriteLine($"  {b.GetProperty("id")} -> {b.GetProperty("domain_id")}: {b.GetProperty("title")}");
await File.WriteAllTextAsync("plan.json", text!);

In a browser, the native EventSource only speaks GET, and this endpoint is a POST — read the fetch response body incrementally, as the JavaScript sample above does. On an idempotent replay the server may answer with a plain JSON envelope instead of an event stream; check the Content-Type before you start parsing frames. And keep the accumulated delta text even when the stream dies: the app recovers a partial plan from it by closing the open JSON and re-parsing, which is far better than losing a run you already paid for.

Step 6 — Parse the result

One JSON object, always the same shape, whichever lane produced it. The plan is grounded in the paste alone: domains, briefs and conflicts cite only items, files, fixtures and subsystems that actually appear in work or context, and anything the model would need but was not given goes into open_questions rather than being invented. Where the paste is silent on something that changes the verdict you get an entry in assumptions. Refusing to parallelize is a successful answer, not a failure: a sequential-first plan with zero briefs and a strong sequential list is complete and valid.

FieldTypeMeaning
plan_namestringA short name for this dispatch, taken from the work's own vocabulary — e.g. Tool-pipeline refactor fallout — 3-way split. The app falls back to Untitled dispatch plan if it is missing, and uses it for the export filename and the history list.
posturestringparallel-ready | partial-parallel | sequential-first. The headline verdict. Anything unrecognised is coerced to partial-parallel. See the table below.
verdictstringOne sentence: fan out or not, and the single most important reason. This is the line to put in a CI comment.
exec_summarystringTwo to four short paragraphs separated by blank lines: what the work is, how it decomposes (or why it does not), the biggest collision risk, and what to do first. The app splits it on \n\n and renders one paragraph per block.
assumptionsstring[]What had to be assumed because the paste did not say. Read these first — a wrong assumption invalidates the split built on it.
open_questionsstring[]What the planner would ask before dispatching. On a low-detail paste this is often the most valuable field.
inventoryarray{kind, name, scope, role} — every Failure, Task, File, Fixture and Resource the plan parsed out of the paste, and where it ended up. role names the domain it belongs to and why, or says held back or shared - single owner. This is the field that proves nothing was dropped. Entries with neither kind nor name are discarded by the client.
domainsarray{id, name, rationale, risk, items[]} — the independent problem domains, in dispatch order. Columns below. At least one entry is required: the app throws domains must contain at least one entry and triggers its reformat retry if this is empty.
briefsarray{id, domain_id, title, scope, goal, context, constraints[], expected_output, prompt} — one copy-ready agent brief per parallel domain. Columns below. Legitimately empty when posture is sequential-first.
conflictsarray{resource, domain_ids[], mitigation} — one row per file, fixture, migration, lockfile, port or database claimed by more than one domain, or by a domain and the held-back work. mitigation names the single owner or the way to serialize it. Rows with no resource are discarded.
sequentialarray{item, after, why} — the work explicitly not dispatched this round. after is what it waits on, or an empty string when it is simply not parallelizable. Rows with no item are discarded. If briefs is empty this must not also be empty.
coverage_checkarray{id, addressed, note} — one entry per prescan_facts.flags id you sent, each appearing exactly once. Semantics below.
integrationstring[]The review-and-integrate steps for when the agents come back, made concrete for this split: read each summary, check whether two agents edited the same code, run the full suite, spot-check for systematic errors, plus whatever this particular fan-out makes risky.
summarystringTwo or three sentences a reader can act on immediately.

The three posture values:

postureWhat it means
parallel-readyTwo or more genuinely independent domains and no unresolved collision. Dispatch every brief in one message and go. The app labels this Parallel ready.
partial-parallelSome of the work fans out now, some has to wait — a shared migration, an ordering dependency, an item too thin to brief. briefs covers the first group, sequential the second. This is also the fallback the client applies to an unrecognised value.
sequential-firstDo not fan out yet: one root cause wearing several hats, one domain, an unresolved shared resource, or too little detail to brief anyone. Expect zero or very few briefs and a substantial sequential list. This is a correct, useful answer — the reason not to spend four agent contexts is the product.

Each entry in domains:

ColumnMeaning
idD1, D2, … in dispatch order — the handle referenced by briefs[].domain_id and conflicts[].domain_ids. The client fills in D<n> if it is missing.
nameA short domain name, e.g. Tool abort lifecycle.
rationaleWhy this stands alone — specifically what it does not share with the others. This is the sentence to disagree with if you think the split is wrong.
risklow | medium | high, the collision risk of handing this domain to its own agent. Anything else is coerced to medium. The app sorts briefs by this — highest risk first — on screen and in every export, so the brief most likely to need judgement is read while the reader is still paying attention.
itemsThe work items this domain covers, quoted from the paste.

Each entry in briefs:

ColumnMeaning
idSequential AB-001, AB-002, … The client fills in the next id if it is missing.
domain_idThe domains[].id this brief serves. If it names a domain that does not exist, the client re-points it at the domain in the same position rather than dropping the brief.
titleWhat this agent is for, in a few words.
scopeThe opening line of the prompt: exactly what this agent owns.
goalThe finish condition, stated so it can be tested.
contextEverything the agent needs and cannot infer: the concrete error text, the test names, the file paths, what changed. Agents get isolated context — they never inherit your session — so a brief that assumes prior knowledge is a broken brief.
constraintsstring[]. Always carries the ownership rule you sent. Any brief covering an item that raised a timing-hint flag also carries the explicit "do NOT just increase timeouts — find the real issue" constraint.
expected_outputWhat the agent must return, specifically. "Fix it" is the canonical bad brief; this field is the antidote.
promptThe complete, self-contained brief as one block of text, ready to paste as an agent's whole task. If the model leaves it empty the client rebuilds it from scope, goal, context, constraints and expected_output, in that order — do the same rather than dispatching a blank.

coverage_check semantics:

CaseWhat you get
Every flag id you sentEach prescan_facts.flags id appears in coverage_check exactly once. Nothing you flagged is silently dropped, which makes this the field to assert on in an automated gate. Ids in prescan_facts.resources are not reconciled here — they must show up in inventory instead.
addressed: trueThe plan actually accounts for the flag: a conflict row, two items merged into one domain, a constraint in a brief, or an item held back. note says which.
addressed: falseThe flag was deliberately set aside; note gives the reason — a shared file that only one of the two items really writes to, a timing word inside a test name rather than a symptom. Setting a flag aside is legitimate; ignoring it is not.
An id you sent that is missing entirelyTreat it as an unaddressed collision, not as an implicit pass. The app says so in its reconciliation line, and a gate should fail on it.
Nothing sentOmit prescan_facts, or send the two empty arrays, and coverage_check comes back empty. The rest of the plan is unaffected.

Defensive parsing, the way the app does it

The model is asked for exactly one JSON object and nothing else, and it almost always complies. The app still assumes it might not, in three layers:

A small, realistic result for the three-cluster paste above, trimmed for length:

{
  "plan_name": "Tool-pipeline refactor fallout — 3-way split",
  "posture": "parallel-ready",
  "verdict": "Three failure clusters sit in three subsystems with no shared file or fixture —
              fan out to three agents in one message.",
  "exec_summary": "Six tests went red in three files after this morning's tool-execution
                   refactor. The abort-lifecycle failures all bottom out in
                   src/agents/abort-controller.ts, the batch-completion failures in
                   src/batch/emitter.ts, and the approval race in src/approval/queue.ts. No file
                   appears under more than one cluster and no fixture is shared, so these are
                   three problems, not one wearing three hats.

                   The one thing to watch is the shared types package: all three subsystems
                   import from it, and if the refactor changed a type there, three agents will
                   each try to patch it. Nobody owns it in this plan — see the conflict row.

                   Start all three at once. The approval race is the highest-risk brief and the
                   one most likely to come back with questions, so read that summary first.",
  "assumptions": [
    "The refactor is the cause: everything else in the suite is green, per the context.",
    "The three subsystems have separate owners, so three concurrent edits are reviewable."
  ],
  "open_questions": [
    "Did the refactor change any exported type in the shared types package?",
    "Is the 5-second duration on the abort tests a real timeout, or just a slow test?"
  ],
  "inventory": [
    { "kind": "Failure", "name": "should abort a tool call and capture partial output",
      "scope": "src/agents/tool-abort.test.ts", "role": "D1 — abort lifecycle" },
    { "kind": "Failure", "name": "emits batch-complete once every tool settles",
      "scope": "src/batch/completion.test.ts", "role": "D2 — batch completion" },
    { "kind": "Failure", "name": "runs an approved tool exactly once under concurrent approvals",
      "scope": "src/approval/race.test.ts", "role": "D3 — approval race" },
    { "kind": "File", "name": "src/agents/abort-controller.ts", "scope": "src/agents",
      "role": "D1 only — no other cluster touches it" },
    { "kind": "Resource", "name": "shared types package", "scope": "workspace",
      "role": "shared - single owner (see conflicts)" }
  ],
  "domains": [
    { "id": "D1", "name": "Tool abort lifecycle",
      "rationale": "Both failures trace into src/agents/abort-controller.ts and nothing else
                    imports it; the batch and approval clusters never appear in these traces.",
      "risk": "medium",
      "items": ["should abort a tool call and capture partial output",
                "should track pendingToolCount across an abort"] },
    { "id": "D2", "name": "Batch completion events",
      "rationale": "One TypeError in src/batch/emitter.ts explains both batch failures; the
                    emitter is not referenced by the abort or approval traces.",
      "risk": "low",
      "items": ["emits batch-complete once every tool settles"] },
    { "id": "D3", "name": "Concurrent approval execution",
      "rationale": "Isolated to src/approval/queue.ts, and it is a concurrency bug rather than a
                    signature change, so it needs a different kind of attention.",
      "risk": "high",
      "items": ["runs an approved tool exactly once under concurrent approvals"] }
  ],
  "briefs": [
    { "id": "AB-003", "domain_id": "D3",
      "title": "Fix the double/zero execution under concurrent approvals",
      "scope": "You own src/approval/queue.ts and src/approval/race.test.ts. Nothing else.",
      "goal": "src/approval/race.test.ts passes, and an approved tool executes exactly once
               when two approvals arrive concurrently.",
      "context": "After a refactor of the tool-execution pipeline, this test fails with
                  'AssertionError: execution count expected 1, received 0' at
                  src/approval/race.test.ts:52:20, with the trace passing through
                  src/approval/queue.ts:88:5. The count is 0, not 2, so the likely shape is a
                  dropped execution rather than a duplicated one.",
      "constraints": [
        "Agents may change production code.",
        "Do NOT touch src/agents/ or src/batch/ — other agents own those this round.",
        "Do NOT just increase timeouts or add sleeps — find the real ordering issue.",
        "Do not edit the shared types package; report it instead if you need a change there."
      ],
      "expected_output": "A summary naming the root cause in one sentence, the diff you applied,
                          and the command you ran to prove the test passes.",
      "prompt": "You own src/approval/queue.ts and src/approval/race.test.ts. Nothing else.\n\n
                 Goal: src/approval/race.test.ts passes, and an approved tool executes exactly
                 once when two approvals arrive concurrently.\n\nContext:\n…" },
    { "id": "AB-001", "domain_id": "D1", "title": "Restore abort lifecycle semantics",
      "scope": "You own src/agents/abort-controller.ts and src/agents/tool-abort.test.ts.",
      "goal": "Both abort tests pass; partial output is captured and pendingToolCount is
               accurate across an abort.",
      "context": "…", "constraints": ["Agents may change production code.", "…"],
      "expected_output": "…", "prompt": "…" },
    { "id": "AB-002", "domain_id": "D2", "title": "Fix the undefined threadId in the emitter",
      "scope": "You own src/batch/emitter.ts and src/batch/completion.test.ts.",
      "goal": "…", "context": "…", "constraints": ["…"],
      "expected_output": "…", "prompt": "…" }
  ],
  "conflicts": [
    { "resource": "shared types package",
      "domain_ids": ["D1", "D2", "D3"],
      "mitigation": "No agent may edit it. If a type change is needed, the agent reports it and
                     you make the single edit after all three return." }
  ],
  "sequential": [],
  "coverage_check": [
    { "id": "timing-hint:timed-out", "addressed": true,
      "note": "AB-003 carries the do-not-raise-the-timeout constraint." }
  ],
  "integration": [
    "Read all three summaries before merging anything.",
    "Diff the three branches against each other for edits to the shared types package.",
    "Run the full suite once, not three partial suites — the interaction is the risk.",
    "If two agents independently changed the same type, treat both fixes as suspect."
  ],
  "summary": "Dispatch AB-003, AB-001 and AB-002 in a single message. Nobody owns the shared
              types package this round; collect type change requests and make them yourself
              afterwards. Read the approval-race summary first — it is the only genuine
              concurrency bug in the set."
}

The dispatch block — one message, not three

This is the whole point of the skill and the one thing to get right. Multiple dispatch calls in one message run in parallel; one call per message runs sequentially. So the plan is written to be pasted as a single block: every brief inline, the held-back work explicitly marked do-not-dispatch, and the integration steps at the bottom. The app derives this in the browser from the result already on screen, and so should you — it costs no run.

Two details that matter. Sort the briefs by their domain's risk — high, then medium, then low — and break ties by id; use that same order everywhere, because a brief list sorted by risk on screen and by id in the file you paste is how an agent ends up on the wrong task. And fall back to building prompt out of the other brief fields when the model left it empty.

# Build the dispatch block from plan.json, highest-risk domain first.
jq -r '
  (reduce .domains[] as $d ({}; .[$d.id] = ({high:0, medium:1, low:2}[$d.risk] // 1))) as $rank
  | (.briefs | sort_by($rank[.domain_id] // 1, .id)) as $bs
  | "Dispatch these \($bs | length) agents in a single message so they run in parallel.",
    "",
    ( $bs
      | to_entries[]
      | "--- Agent \(.key + 1) of \($bs | length): \(.value.title // .value.id) ---",
        "",
        ( .value.prompt
          // ([ .value.scope,
                "",
                "Goal: " + .value.goal,
                "",
                "Context:", .value.context,
                "",
                "Constraints:",
                (.value.constraints | map("- " + .) | join("\n")),
                "",
                "Return: " + .value.expected_output ] | join("\n")) ),
        "" ),
    (if (.sequential | length) > 0 then
       "--- Do NOT dispatch these in this round ---", "",
       (.sequential[] | "- \(.item)\(if .after != "" then " (after: " + .after + ")" else "" end) - \(.why)"),
       ""
     else empty end),
    (if (.integration | length) > 0 then
       "--- When they return ---", "", (.integration[] | "- \(.)"), ""
     else empty end)' plan.json > dispatch.txt

# dispatch.txt is now ONE message. Paste it whole; do not send it brief by brief.
wc -l dispatch.txt
RISK_ORDER = {"high": 0, "medium": 1, "low": 2}

def brief_prompt(b):
    """Rebuild a complete prompt when the model left `prompt` empty."""
    parts = []
    if b.get("scope"):
        parts.append(b["scope"])
    if b.get("goal"):
        parts += ["", "Goal: " + b["goal"]]
    if b.get("context"):
        parts += ["", "Context:", b["context"]]
    if b.get("constraints"):
        parts += ["", "Constraints:"] + ["- " + c for c in b["constraints"]]
    if b.get("expected_output"):
        parts += ["", "Return: " + b["expected_output"]]
    return "\n".join(parts)

def sort_briefs(plan):
    rank = {d["id"]: RISK_ORDER.get(d["risk"], 1) for d in plan["domains"]}
    return sorted(plan["briefs"], key=lambda b: (rank.get(b["domain_id"], 1), b["id"]))

def dispatch_block(plan):
    briefs = sort_briefs(plan)
    out = [f"Dispatch these {len(briefs)} agents in a single message so they run in parallel.", ""]
    for i, b in enumerate(briefs, 1):
        out += [f"--- Agent {i} of {len(briefs)}: {b['title'] or b['id']} ---", "",
                b.get("prompt") or brief_prompt(b), ""]
    if plan["sequential"]:
        out += ["--- Do NOT dispatch these in this round ---", ""]
        for s in plan["sequential"]:
            after = f" (after: {s['after']})" if s["after"] else ""
            out.append(f"- {s['item']}{after} - {s['why']}")
        out.append("")
    if plan["integration"]:
        out += ["--- When they return ---", ""] + [f"- {s}" for s in plan["integration"]] + [""]
    return "\n".join(out)

block = dispatch_block(plan)
with open("dispatch.txt", "w", encoding="utf-8") as fh:
    fh.write(block)

# ONE message = parallel. Send `block` as a single turn to your agent runner;
# sending one brief per turn silently serializes the whole plan.
print(block[:400], "...")
const RISK_ORDER = { high: 0, medium: 1, low: 2 };

function briefPrompt(b) {
  const L = [];
  if (b.scope) L.push(b.scope);
  if (b.goal) L.push("", "Goal: " + b.goal);
  if (b.context) L.push("", "Context:", b.context);
  if (b.constraints?.length) L.push("", "Constraints:", ...b.constraints.map((c) => "- " + c));
  if (b.expected_output) L.push("", "Return: " + b.expected_output);
  return L.join("\n");
}

function sortBriefs(plan) {
  const rank = Object.fromEntries(
    plan.domains.map((d) => [d.id, RISK_ORDER[d.risk] ?? 1]));
  return [...plan.briefs].sort((a, b) =>
    (rank[a.domain_id] ?? 1) - (rank[b.domain_id] ?? 1) || a.id.localeCompare(b.id));
}

function dispatchBlock(plan) {
  const briefs = sortBriefs(plan);
  const L = [`Dispatch these ${briefs.length} agents in a single message so they run in parallel.`, ""];
  briefs.forEach((b, i) => {
    L.push(`--- Agent ${i + 1} of ${briefs.length}: ${b.title || b.id} ---`, "",
           b.prompt || briefPrompt(b), "");
  });
  if (plan.sequential.length) {
    L.push("--- Do NOT dispatch these in this round ---", "");
    for (const s of plan.sequential) {
      L.push(`- ${s.item}${s.after ? ` (after: ${s.after})` : ""} - ${s.why}`);
    }
    L.push("");
  }
  if (plan.integration.length) {
    L.push("--- When they return ---", "", ...plan.integration.map((s) => "- " + s), "");
  }
  return L.join("\n");
}

const block = dispatchBlock(plan);
writeFileSync("dispatch.txt", block);
// ONE message = parallel. One brief per message = sequential. Send `block` whole.
var riskOrder = map[string]int{"high": 0, "medium": 1, "low": 2}

func briefPrompt(b Brief) string {
	var L []string
	if b.Scope != "" {
		L = append(L, b.Scope)
	}
	if b.Goal != "" {
		L = append(L, "", "Goal: "+b.Goal)
	}
	if b.Context != "" {
		L = append(L, "", "Context:", b.Context)
	}
	if len(b.Constraints) > 0 {
		L = append(L, "", "Constraints:")
		for _, c := range b.Constraints {
			L = append(L, "- "+c)
		}
	}
	if b.ExpectedOutput != "" {
		L = append(L, "", "Return: "+b.ExpectedOutput)
	}
	return strings.Join(L, "\n")
}

func dispatchBlock(p Plan) string {
	rank := map[string]int{}
	for _, d := range p.Domains {
		r, ok := riskOrder[d.Risk]
		if !ok {
			r = 1
		}
		rank[d.ID] = r
	}
	briefs := append([]Brief(nil), p.Briefs...)
	sort.SliceStable(briefs, func(i, j int) bool {
		if rank[briefs[i].DomainID] != rank[briefs[j].DomainID] {
			return rank[briefs[i].DomainID] < rank[briefs[j].DomainID]
		}
		return briefs[i].ID < briefs[j].ID
	})

	var L []string
	L = append(L, fmt.Sprintf(
		"Dispatch these %d agents in a single message so they run in parallel.", len(briefs)), "")
	for i, b := range briefs {
		title := b.Title
		if title == "" {
			title = b.ID
		}
		prompt := b.Prompt
		if prompt == "" {
			prompt = briefPrompt(b)
		}
		L = append(L, fmt.Sprintf("--- Agent %d of %d: %s ---", i+1, len(briefs), title), "", prompt, "")
	}
	if len(p.Sequential) > 0 {
		L = append(L, "--- Do NOT dispatch these in this round ---", "")
		for _, s := range p.Sequential {
			line := "- " + s.Item
			if s.After != "" {
				line += " (after: " + s.After + ")"
			}
			L = append(L, line+" - "+s.Why)
		}
		L = append(L, "")
	}
	if len(p.Integration) > 0 {
		L = append(L, "--- When they return ---", "")
		for _, s := range p.Integration {
			L = append(L, "- "+s)
		}
		L = append(L, "")
	}
	return strings.Join(L, "\n")
}

os.WriteFile("dispatch.txt", []byte(dispatchBlock(plan)), 0o644)
// ONE message = parallel; one brief per message = sequential.
// Sort briefs by their domain's risk (high, medium, low), tie-break on id, then
// emit ONE block. One message = parallel; one brief per message = sequential.
Map<String, Integer> riskOrder = Map.of("high", 0, "medium", 1, "low", 2);
Map<String, Integer> rank = new HashMap<>();
for (Domain d : plan.domains) rank.put(d.id, riskOrder.getOrDefault(d.risk, 1));

List<Brief> briefs = new ArrayList<>(plan.briefs);
briefs.sort(Comparator
    .comparingInt((Brief b) -> rank.getOrDefault(b.domainId, 1))
    .thenComparing(b -> b.id));

StringBuilder sb = new StringBuilder();
sb.append("Dispatch these ").append(briefs.size())
  .append(" agents in a single message so they run in parallel.\n\n");
for (int i = 0; i < briefs.size(); i++) {
    Brief b = briefs.get(i);
    sb.append("--- Agent ").append(i + 1).append(" of ").append(briefs.size())
      .append(": ").append(b.title.isEmpty() ? b.id : b.title).append(" ---\n\n")
      .append(b.prompt.isEmpty() ? rebuildPrompt(b) : b.prompt).append("\n\n");
}
if (!plan.sequential.isEmpty()) {
    sb.append("--- Do NOT dispatch these in this round ---\n\n");
    for (Seq s : plan.sequential) {
        sb.append("- ").append(s.item)
          .append(s.after.isEmpty() ? "" : " (after: " + s.after + ")")
          .append(" - ").append(s.why).append('\n');
    }
    sb.append('\n');
}
if (!plan.integration.isEmpty()) {
    sb.append("--- When they return ---\n\n");
    for (String s : plan.integration) sb.append("- ").append(s).append('\n');
}
Files.writeString(Path.of("dispatch.txt"), sb.toString());

// rebuildPrompt(b) = scope, blank line, "Goal: " + goal, blank line, "Context:" + context,
// blank line, "Constraints:" with one "- " line each, blank line, "Return: " + expectedOutput.
RISK_ORDER = { "high" => 0, "medium" => 1, "low" => 2 }.freeze

def brief_prompt(b)
  l = []
  l << b["scope"] unless b["scope"].to_s.empty?
  l += ["", "Goal: #{b["goal"]}"] unless b["goal"].to_s.empty?
  l += ["", "Context:", b["context"]] unless b["context"].to_s.empty?
  unless (b["constraints"] || []).empty?
    l += ["", "Constraints:"] + b["constraints"].map { |c| "- #{c}" }
  end
  l += ["", "Return: #{b["expected_output"]}"] unless b["expected_output"].to_s.empty?
  l.join("\n")
end

def dispatch_block(plan)
  rank = plan["domains"].to_h { |d| [d["id"], RISK_ORDER.fetch(d["risk"], 1)] }
  briefs = plan["briefs"].sort_by { |b| [rank.fetch(b["domain_id"], 1), b["id"]] }

  l = ["Dispatch these #{briefs.size} agents in a single message so they run in parallel.", ""]
  briefs.each_with_index do |b, i|
    title = b["title"].to_s.empty? ? b["id"] : b["title"]
    l += ["--- Agent #{i + 1} of #{briefs.size}: #{title} ---", "",
          b["prompt"].to_s.empty? ? brief_prompt(b) : b["prompt"], ""]
  end
  unless plan["sequential"].empty?
    l += ["--- Do NOT dispatch these in this round ---", ""]
    plan["sequential"].each do |s|
      after = s["after"].to_s.empty? ? "" : " (after: #{s["after"]})"
      l << "- #{s["item"]}#{after} - #{s["why"]}"
    end
    l << ""
  end
  unless plan["integration"].empty?
    l += ["--- When they return ---", ""] + plan["integration"].map { |s| "- #{s}" } + [""]
  end
  l.join("\n")
end

File.write("dispatch.txt", dispatch_block(plan))
# ONE message = parallel; one brief per message = sequential.
const RISK_ORDER = ["high" => 0, "medium" => 1, "low" => 2];

function briefPrompt(array $b): string {
    $l = [];
    if ($b["scope"]) { $l[] = $b["scope"]; }
    if ($b["goal"]) { $l[] = ""; $l[] = "Goal: " . $b["goal"]; }
    if ($b["context"]) { $l[] = ""; $l[] = "Context:"; $l[] = $b["context"]; }
    if (!empty($b["constraints"])) {
        $l[] = ""; $l[] = "Constraints:";
        foreach ($b["constraints"] as $c) { $l[] = "- $c"; }
    }
    if ($b["expected_output"]) { $l[] = ""; $l[] = "Return: " . $b["expected_output"]; }
    return implode("\n", $l);
}

function dispatchBlock(array $plan): string {
    $rank = [];
    foreach ($plan["domains"] as $d) {
        $rank[$d["id"]] = RISK_ORDER[$d["risk"]] ?? 1;
    }
    $briefs = $plan["briefs"];
    usort($briefs, function ($a, $b) use ($rank) {
        return [$rank[$a["domain_id"]] ?? 1, $a["id"]] <=> [$rank[$b["domain_id"]] ?? 1, $b["id"]];
    });

    $n = count($briefs);
    $l = ["Dispatch these $n agents in a single message so they run in parallel.", ""];
    foreach ($briefs as $i => $b) {
        $title = $b["title"] ?: $b["id"];
        $l[] = "--- Agent " . ($i + 1) . " of $n: $title ---";
        $l[] = "";
        $l[] = $b["prompt"] ?: briefPrompt($b);
        $l[] = "";
    }
    if ($plan["sequential"]) {
        $l[] = "--- Do NOT dispatch these in this round ---";
        $l[] = "";
        foreach ($plan["sequential"] as $s) {
            $after = $s["after"] ? " (after: {$s['after']})" : "";
            $l[] = "- {$s['item']}$after - {$s['why']}";
        }
        $l[] = "";
    }
    if ($plan["integration"]) {
        $l[] = "--- When they return ---";
        $l[] = "";
        foreach ($plan["integration"] as $s) { $l[] = "- $s"; }
    }
    return implode("\n", $l);
}

file_put_contents("dispatch.txt", dispatchBlock($plan));
// ONE message = parallel; one brief per message = sequential.
var riskOrder = new Dictionary<string, int> { ["high"] = 0, ["medium"] = 1, ["low"] = 2 };

var rank = plan.GetProperty("domains").EnumerateArray().ToDictionary(
    d => d.GetProperty("id").GetString()!,
    d => riskOrder.GetValueOrDefault(d.GetProperty("risk").GetString() ?? "", 1));

var briefs = plan.GetProperty("briefs").EnumerateArray()
    .OrderBy(b => rank.GetValueOrDefault(b.GetProperty("domain_id").GetString() ?? "", 1))
    .ThenBy(b => b.GetProperty("id").GetString(), StringComparer.Ordinal)
    .ToList();

var sb = new System.Text.StringBuilder();
sb.AppendLine($"Dispatch these {briefs.Count} agents in a single message so they run in parallel.");
sb.AppendLine();
for (var i = 0; i < briefs.Count; i++)
{
    var b = briefs[i];
    var title = b.GetProperty("title").GetString();
    if (string.IsNullOrEmpty(title)) title = b.GetProperty("id").GetString();
    var prompt = b.GetProperty("prompt").GetString();
    if (string.IsNullOrEmpty(prompt)) prompt = RebuildPrompt(b);   // scope/goal/context/…
    sb.AppendLine($"--- Agent {i + 1} of {briefs.Count}: {title} ---");
    sb.AppendLine();
    sb.AppendLine(prompt);
    sb.AppendLine();
}
foreach (var s in plan.GetProperty("sequential").EnumerateArray())
{
    var after = s.GetProperty("after").GetString();
    sb.AppendLine($"- {s.GetProperty("item")}" +
                  (string.IsNullOrEmpty(after) ? "" : $" (after: {after})") +
                  $" - {s.GetProperty("why")}");
}
foreach (var s in plan.GetProperty("integration").EnumerateArray())
    sb.AppendLine($"- {s.GetString()}");

await File.WriteAllTextAsync("dispatch.txt", sb.ToString());
// ONE message = parallel; one brief per message = sequential.

This is AI-generated planning from pasted text, not a project manager: it sees only what you sent, never the repository, the real ownership map or how the last fan-out went. Read assumptions and open_questions before you dispatch, check that every brief's constraints really carry your ownership rule, and never hand an agent a brief you have not read yourself.

Step 7 — Store and search past plans

POST /collections/plans/query
POST /collections/plans/records
POST /collections/plans/similar

The app declares one collection, plans, with acl_read: owner and acl_write: user — records belong to the calling identity, so an API caller using your personal token reads and writes the same history the web app shows. A collection is used deliberately rather than a single data key: GET /v1/app-api/data/{key} caches per subject and key for roughly 90 seconds, so a one-document history silently stops updating after the first read, while POST /query returns fresh rows immediately after a write.

FieldTypeWhat the app puts there
uidstringThe client-side identity of a run, used to union the account rows with this device's rows without duplicating.
titlestring, embeddedplan_name.
posturestringparallel-ready | partial-parallel | sequential-first — the field to filter on.
summarystring, embeddedThe plan's verdict, first 400 characters.
domains_textstring, embeddedThe domain names joined with commas, first 400 characters. This is what makes "the one where we split auth from billing" findable, which a title alone never would.
input_hashstringThe hash of the input that produced this plan — the cheap way to spot that you are about to pay for a plan you already have.
domains_count, briefs_countnumberCounts, for filtering and for the history line.
ran_attimestampISO-8601. Sort on this, descending.
entryobject{plan, meta, input} — the whole plan object from step 6, the run metadata, and the input that produced it. It round-trips intact but is not filterable.

The embed set is title, summary and domains_text, chosen once and never widened, because the platform does not backfill vectors: records written before an embed field existed are never vectorized. Documents are capped at 64 KB, so the app trims before writing — the pasted work list goes first, then the long-form brief prompts, and the plan itself is kept last because it is what the user came back for.

# Exact filter: every plan that said do-not-fan-out, newest first. `where` values
# must be operator OBJECTS - a bare value is rejected. Ordering is the `sort`
# object; `order_by` is accepted and then silently ignored.
curl -s -X POST "$API/collections/plans/query" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"where":{"posture":{"eq":"sequential-first"}},
       "sort":{"field":"ran_at","dir":"desc"},"limit":20}' \
  | jq -r '.data.records[] | "\(.record_id) \(.doc.title) - \(.doc.summary)"'

# Create a record. Note the path: /records, not the collection root, and the
# document goes inside a {"doc": ...} wrapper.
curl -s -X POST "$API/collections/plans/records" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "$(jq -n --slurpfile plan plan.json \
        '{doc: {uid: "cli-\(now|floor)",
                title: $plan[0].plan_name,
                posture: $plan[0].posture,
                summary: ($plan[0].verdict[0:400]),
                domains_text: ([$plan[0].domains[].name] | join(", ")),
                input_hash: "cli",
                domains_count: ($plan[0].domains | length),
                briefs_count: ($plan[0].briefs | length),
                ran_at: (now | todate),
                entry: {plan: $plan[0], meta: {model: "gpt-terra"}, input: null}}}')" \
  | jq -r '.data.record.record_id'

# Semantic search over title, summary and domains_text. 30 req/min per IP and
# about ten times the cost of the filter above - use `where` when an exact match
# would do.
curl -s -X POST "$API/collections/plans/similar" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"text":"the one where we split auth from billing","limit":8}' \
  | jq -r '.data.records[] | "\(.score) \(.doc.title)"'
# Exact filter - cheap, and the right tool whenever the question has an exact answer.
page = api("POST", "/collections/plans/query", {
    "where": {"posture": {"eq": "sequential-first"}},
    "sort": {"field": "ran_at", "dir": "desc"},
    "limit": 20,
})
for rec in page["records"]:
    doc = rec["doc"]                      # records nest under `doc` — never read fields flat
    print(rec["record_id"], doc["title"], "|", doc["domains_text"])

# Write one. The path ends in /records, and the document goes inside {"doc": ...}.
created = api("POST", "/collections/plans/records", {"doc": {
    "uid": f"py-{int(time.time())}",
    "title": plan["plan_name"],
    "posture": plan["posture"],
    "summary": plan["verdict"][:400],
    "domains_text": ", ".join(d["name"] for d in plan["domains"])[:400],
    "input_hash": idem,
    "domains_count": len(plan["domains"]),
    "briefs_count": len(plan["briefs"]),
    "ran_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    "entry": {"plan": plan, "meta": {"model": "gpt-terra"}, "input": payload},
}})
print("stored as", created["record"]["record_id"])

# Semantic search. Debounce it: 30/min per IP, and vector indexing is async, so a
# `similar` call immediately after a write can lag by a few seconds.
hits = api("POST", "/collections/plans/similar",
           {"text": "the one where we split auth from billing", "limit": 8})
for rec in (hits if isinstance(hits, list) else hits["records"]):
    print(round(rec["score"], 3), rec["doc"]["title"])
const page = await api("POST", "/collections/plans/query", {
  where: { posture: { eq: "sequential-first" } },
  sort: { field: "ran_at", dir: "desc" },
  limit: 20,
});
for (const rec of page.records) {
  const doc = rec.doc;            // records nest under `doc` — never read fields flat
  console.log(rec.record_id, doc.title, "|", doc.domains_text);
}

// The document goes inside a { doc: … } wrapper; the reply carries `record`.
const created = await api("POST", "/collections/plans/records", {
  doc: {
    uid: `js-${Date.now()}`,
    title: plan.plan_name,
    posture: plan.posture,
    summary: plan.verdict.slice(0, 400),
    domains_text: plan.domains.map((d) => d.name).join(", ").slice(0, 400),
    input_hash: idem,
    domains_count: plan.domains.length,
    briefs_count: plan.briefs.length,
    ran_at: new Date().toISOString(),
    entry: { plan, meta: { model: "gpt-terra" }, input: payload },
  },
});
console.log("stored as", created.record.record_id);

// similar() over the SDK resolves to the record ARRAY; the REST call returns
// {records}. Accept either shape rather than trusting one.
const hits = await api("POST", "/collections/plans/similar",
  { text: "the one where we split auth from billing", limit: 8 });
for (const rec of Array.isArray(hits) ? hits : hits.records) {
  console.log(rec.score.toFixed(3), rec.doc.title);
}
query := map[string]any{
	"where": map[string]any{"posture": map[string]any{"eq": "sequential-first"}},
	"sort":  map[string]any{"field": "ran_at", "dir": "desc"},
	"limit": 20,
}
var page struct {
	Records []struct {
		RecordID string         `json:"record_id"`
		Doc      map[string]any `json:"doc"` // records nest under doc
	} `json:"records"`
}
if err := call("POST", "/collections/plans/query", query, &page, nil); err != nil {
	log.Fatal(err)
}
for _, r := range page.Records {
	fmt.Println(r.RecordID, r.Doc["title"], "|", r.Doc["domains_text"])
}

// Create: the path ends in /records, not at the collection root, and the
// document is wrapped in {"doc": ...}. The reply is {"record": {...}}.
names := make([]string, 0, len(plan.Domains))
for _, d := range plan.Domains {
	names = append(names, d.Name)
}
record := map[string]any{
	"uid":           fmt.Sprintf("go-%d", time.Now().Unix()),
	"title":         plan.PlanName,
	"posture":       plan.Posture,
	"summary":       plan.Verdict,
	"domains_text":  strings.Join(names, ", "),
	"input_hash":    idem,
	"domains_count": len(plan.Domains),
	"briefs_count":  len(plan.Briefs),
	"ran_at":        time.Now().UTC().Format(time.RFC3339),
	"entry":         map[string]any{"plan": plan, "meta": map[string]any{"model": "gpt-terra"}},
}
var created struct {
	Record struct {
		RecordID string `json:"record_id"`
	} `json:"record"`
}
call("POST", "/collections/plans/records",
	map[string]any{"doc": record}, &created, nil)
fmt.Println("stored as", created.Record.RecordID)

// Semantic search - note the /similar path and the 30 req/min per-IP limit.
var hits json.RawMessage
call("POST", "/collections/plans/similar",
	map[string]any{"text": "the one where we split auth from billing", "limit": 8}, &hits, nil)
fmt.Println(string(hits))
// Filter. Every `where` entry must be an operator object.
String q = "{\"where\":{\"posture\":{\"eq\":\"sequential-first\"}},"
    + "\"sort\":{\"field\":\"ran_at\",\"dir\":\"desc\"},\"limit\":20}";
String page = api("POST", "/collections/plans/query", q, null);
// data.records[] — each row is {record_id, doc:{title, posture, summary, domains_text, …}}
// Read fields off row.doc, never off the row itself.
System.out.println(page);

// Create: the path ends in /records, not at the collection root, and the
// document goes inside {"doc": …}. The reply is {"data":{"record":{"record_id":…}}}.
String record = "{\"doc\":{\"uid\":\"java-" + System.currentTimeMillis() + "\","
    + "\"title\":\"Tool-pipeline refactor fallout — 3-way split\","
    + "\"posture\":\"parallel-ready\","
    + "\"summary\":\"Three failure clusters in three subsystems — fan out to three agents.\","
    + "\"domains_text\":\"Tool abort lifecycle, Batch completion events, Concurrent approval execution\","
    + "\"input_hash\":\"" + idem + "\","
    + "\"domains_count\":3,\"briefs_count\":3,"
    + "\"ran_at\":\"2026-08-12T09:00:00Z\",\"entry\":{\"plan\":" + planJson + "}}}";
api("POST", "/collections/plans/records", record, null);

// Semantic search over title, summary and domains_text — 30/min per IP.
System.out.println(api("POST", "/collections/plans/similar",
    "{\"text\":\"the one where we split auth from billing\",\"limit\":8}", null));
page = api("POST", "/collections/plans/query", {
  "where" => { "posture" => { "eq" => "sequential-first" } },
  "sort" => { "field" => "ran_at", "dir" => "desc" },
  "limit" => 20
})
page["records"].each do |r|
  doc = r["doc"]        # records nest under `doc` — never read fields flat
  puts "#{r["record_id"]} #{doc["title"]} | #{doc["domains_text"]}"
end

# The document goes inside {"doc" => ...}; the reply carries "record".
created = api("POST", "/collections/plans/records", { "doc" => {
  "uid" => "rb-#{Time.now.to_i}",
  "title" => plan["plan_name"],
  "posture" => plan["posture"],
  "summary" => plan["verdict"][0, 400],
  "domains_text" => plan["domains"].map { |d| d["name"] }.join(", ")[0, 400],
  "input_hash" => idem,
  "domains_count" => plan["domains"].size,
  "briefs_count" => plan["briefs"].size,
  "ran_at" => Time.now.utc.iso8601,
  "entry" => { "plan" => plan, "meta" => { "model" => "gpt-terra" }, "input" => payload }
} })
puts "stored as #{created["record"]["record_id"]}"

hits = api("POST", "/collections/plans/similar",
           { "text" => "the one where we split auth from billing", "limit" => 8 })
records = hits.is_a?(Array) ? hits : hits["records"]
records.each { |r| puts "#{r["score"].round(3)} #{r["doc"]["title"]}" }
$page = api("POST", "/collections/plans/query", [
    "where" => ["posture" => ["eq" => "sequential-first"]],
    "sort"  => ["field" => "ran_at", "dir" => "desc"],
    "limit" => 20,
]);
foreach ($page["records"] as $rec) {
    $doc = $rec["doc"];   // records nest under `doc` — never read fields flat
    echo $rec["record_id"], " ", $doc["title"], " | ", $doc["domains_text"], "\n";
}

// The document goes inside ["doc" => ...]; the reply carries "record".
$created = api("POST", "/collections/plans/records", ["doc" => [
    "uid"           => "php-" . time(),
    "title"         => $plan["plan_name"],
    "posture"       => $plan["posture"],
    "summary"       => substr($plan["verdict"], 0, 400),
    "domains_text"  => substr(implode(", ", array_column($plan["domains"], "name")), 0, 400),
    "input_hash"    => $idem,
    "domains_count" => count($plan["domains"]),
    "briefs_count"  => count($plan["briefs"]),
    "ran_at"        => gmdate("c"),
    "entry"         => ["plan" => $plan, "meta" => ["model" => "gpt-terra"], "input" => $payload],
]]);
echo "stored as ", $created["record"]["record_id"], "\n";

$hits = api("POST", "/collections/plans/similar",
    ["text" => "the one where we split auth from billing", "limit" => 8]);
foreach ($hits["records"] ?? $hits as $rec) {
    printf("%.3f %s\n", $rec["score"], $rec["doc"]["title"]);
}
var page = await SkillSafe.ApiAsync(HttpMethod.Post, "/collections/plans/query", new
{
    where = new { posture = new { eq = "sequential-first" } },
    sort = new { field = "ran_at", dir = "desc" },
    limit = 20
});
foreach (var rec in page.GetProperty("records").EnumerateArray())
{
    var doc = rec.GetProperty("doc");   // records nest under `doc`
    Console.WriteLine($"{rec.GetProperty("record_id")} {doc.GetProperty("title")}");
}

// The document goes inside a { doc = … } wrapper; the reply carries `record`.
var created = await SkillSafe.ApiAsync(HttpMethod.Post, "/collections/plans/records", new
{
    doc = new
    {
        uid = $"cs-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}",
        title = plan.GetProperty("plan_name").GetString(),
        posture = plan.GetProperty("posture").GetString(),
        summary = plan.GetProperty("verdict").GetString(),
        domains_text = string.Join(", ", plan.GetProperty("domains").EnumerateArray()
            .Select(d => d.GetProperty("name").GetString())),
        input_hash = idem,
        domains_count = plan.GetProperty("domains").GetArrayLength(),
        briefs_count = plan.GetProperty("briefs").GetArrayLength(),
        ran_at = DateTime.UtcNow.ToString("o"),
        entry = new { plan, meta = new { model = "gpt-terra" } }
    }
});
Console.WriteLine($"stored as {created.GetProperty("record").GetProperty("record_id")}");

var hits = await SkillSafe.ApiAsync(HttpMethod.Post, "/collections/plans/similar",
    new { text = "the one where we split auth from billing", limit = 8 });
Console.WriteLine(hits.ToString());

Four traps worth knowing before you write the integration. Records nest under docquery, get and similar all return {record_id, doc: {…}}, so reading rec.title gets you undefined and no error. Every where entry must be an operator object: {"posture":"sequential-first"} is rejected, {"posture":{"eq":"sequential-first"}} is right. Ordering is the sort object; order_by is accepted and then silently ignored, leaving you with created_at desc. And record creation posts to /collections/plans/records, not to the collection root, with the document inside a {"doc": …} wrapper — the reply is {"data":{"record":{"record_id":"rec_…"}}}, so the new id is at data.record.record_id and not one level up.

Data endpoints share 120 requests/min; /collections/{name}/similar is 30/min per IP and costs roughly an order of magnitude more than a where filter — use the filter whenever an exact match would do, and never fire a similarity query per keystroke. Vector indexing is asynchronous, so a similar call immediately after a write can lag by seconds. There is no backfill: records written before an embed field existed are never vectorized. Storage quotas that matter here: 64 KB per document, 10,000 records per collection, 1,000 records per owner.

A plan you already paid for is the cheapest plan there is. Before running, hash the input the way step 5 does and query input_hash for an exact match — if the work list has not changed, restore the stored entry.plan instead of spending credits on the same answer twice.

A closing word on prescan_facts

prescan_facts is optional for API callers. Omit it, or send {"resources": [], "flags": []}, and the plan comes back just as well reasoned — only coverage_check arrives empty. But that field is the one part of the reply that is checkable rather than merely readable, and it exists solely because you gave it something to reconcile. Supplying prescan_facts is what turns "the plan looks sensible" into "the plan accounted for all eleven things my scanner found, and said in writing why it set two of them aside". Every flag id you send comes back in coverage_check exactly once, confirmed or explicitly set aside; every work item you send in resources has to turn up in inventory, and through it in a domain, a brief or sequential.

The full semantics, with an example id for each family, are in the two tables in step 3. The eleven flag families, for reference while you are writing the matcher:

Ids are <family>:<slug>, the slug being a lowercased, hyphenated form of whatever the family matched. You do not have to reproduce the app's slugs exactly; you have to be consistent, because the id you send is the id that comes back. A useful CI gate is then three assertions: every id you sent appears in coverage_check, no id you consider blocking came back addressed: false, and posture is not sequential-first — a check that refuses to fan out work the planner said should not be fanned out.