Universal Read Output Contracts

Tracker references: pm-hb7ug8, pm-cxr0jb, pm-hid9g1, and pm-sb0tns.

Agent Quick Context

Every built-in read surface uses four output dimensions: what to include, how much to return, how much the result may cost, and how to encode it. The same canonical controls work through the CLI, SDK, MCP, generated schemas, runtime contracts, and shell completions.

Dimension CLI SDK and MCP Meaning
Include --output-include <csv> outputInclude Retain named fields or top-level sections.
Amount --output-limit <n|unbounded> outputLimit Bound shared row collections.
Cost --output-budget <tokens> outputBudget Fail closed when even the compact result cannot fit.
Encoding --output-format <toon|json> outputFormat Select the CLI renderer and record the requested encoding.

The contract covers list, context, search, get, next, health, deps, graph, history, activity, validate, events, contracts, comments, notes, files, docs, stats, and aggregate, including list aliases and ctx.

Row shaping follows each envelope's row_contract.row_keys, including dot-delimited nested arrays and object maps such as graph.nodes. Include, amount, repeat suppression, and cost compaction therefore operate on the same machine-declared rows; they do not rely on command-specific top-level keys.

Cross-Call Context Sessions

--output-session <json> / outputSession composes the four per-call dimensions across a request group. The caller supplies versioned state and passes the returned read_session.next_state to the next read:

{
  "version": 1,
  "id": "orientation",
  "token_budget": 4000,
  "spent_tokens": 0,
  "seen_item_ids": []
}

The session ceiling and an explicit --output-budget both bind; the smaller remaining allowance wins. Rows for item facts already present in the caller's context become { "id": "pm-a1b2", "context_ref": "session:orientation:pm-a1b2" } instead of repeating prose. References retain stable item identity and can be restored with pm get <item-id> --brief when the prior context is unavailable. The receipt reports estimated and charged tokens separately when the remaining group allowance is smaller than the minimum control envelope, plus the accumulated spend, remaining capacity, newly served items, and suppressed repeats.

Session state is deliberately caller-carried: CLI processes, SDK clients, MCP hosts, and packages share the same deterministic primitive without a hidden daemon or mutable cache. Validation rejects unknown fields, invalid identifiers, unsupported schema versions, unsafe integers, and spend beyond the declared ceiling before a read executes.

The mandatory orientation calibration runs context, list, search, get, and next against both a two-item tracker and a 2,243-item tracker. Its cross-call ceilings are strict: complete serialized bytes and cumulative spend may only shrink, while repeat suppression may only hold or improve. The gate also fixes the expected unique-fact shape:

Tracker tier Group spend / budget Seen items Suppressed repeats Delivered bytes
2 items 3,820 / 20,000 2 3 15,274
2,243 items 10,156 / 20,000 106 7 40,614

These are deterministic synthetic-corpus measurements from scripts/release/context-intent-calibration.json; they contain no hosted tracker content.

Precedence and Compatibility

Resolution is deterministic: canonical controls win over command-local compatibility options, which win over intent defaults, which win over command defaults. Existing options such as --fields, --limit, --token-budget, --format, --brief, and --full remain accepted. Contract output marks them as hidden compatibility aliases and supplies a migration hint; traversal, cursor, side-effect, and streaming controls instead receive an explicit behavior-preservation hint because a static output control cannot replace their semantics. Callers that omit the canonical controls receive the byte-identical established result.

pm list-open --output-include id,title,status --output-limit 10
pm context --for orient --output-budget 900 --output-format toon
pm search "runtime contracts" --output-limit 5 --output-format json
pm contracts --full --json

Every projected result carries a read_output receipt with the requested dimensions, precedence, observed compatibility aliases, deterministic estimated token count, string/row compaction signals, and budget outcome. If no useful content can fit, PmReadOutputBudgetExceeded provides a discriminated omission result; use isReadOutputBudgetExceeded before accessing result-specific fields. Universal controls are rejected on mutation commands and on the mutation mode of hybrid commands such as comments, notes, files, and docs.

SDK and Package Usage

Typed PmClient read methods accept PmReadOutputOptions directly:

import { PmClient } from "@unbrained/pm-cli/sdk";

const pm = new PmClient({ pmRoot: ".agents/pm" });
const result = await pm.list({
  status: "open,in_progress",
  outputInclude: "id,title,status",
  outputLimit: 10,
  outputBudget: 800,
  outputSession: {
    version: 1,
    id: "orientation",
    token_budget: 4000,
    spent_tokens: 0,
    seen_item_ids: [],
  },
});

Package authors should use the exported read-output contracts instead of inventing package-local limit or projection vocabularies. PM_READ_OUTPUT_SURFACE_CONTRACTS is the machine-readable matrix and resolveReadOutputDimensions plus applyReadOutputDimensions are the shared execution primitives.

Drift Gates

The full runtime contract reports every surface and all four dimensions. Strict SDK/MCP schemas expose the canonical camelCase keys, and the generated contract fixture catches surface or schema drift:

pnpm contracts:check
node scripts/run-tests.mjs test -- tests/unit/sdk/read-output-contracts.spec.ts
node scripts/run-tests.mjs test -- tests/unit/commands/completion-command.spec.ts

Read_output_contracts remote
Report an issue