Skip to content

Design token-efficient schemas

Use this guide when an Orchard task passes structured data from one harness call or child task into another. The goal is not to make field names cryptic; it is to avoid feeding agents duplicate, deeply nested, or hard-to-scan context.

Principles

  • Shape data for the next decision. Keep task output complete for the current run, but create a smaller handoff schema for the next prompt when a downstream agent only needs selected relationships.
  • Prefer tables plus references for repeated entities. Put repeated evidence, files, symbols, or checklist definitions in one array with stable ids, then refer to those ids from findings.
  • Avoid duplicate aggregate views. Do not pass findings, answers, reviews, and unverified when every item in answers and reviews is already nested under a finding. Pick one canonical shape for the prompt.
  • Use homogeneous arrays of flat records. Row-like objects encode well in TOON and are easier for agents to scan than deeply nested object trees.
  • Keep meaningful field names. Prefer evidenceRefs over er; token efficiency should not make the schema ambiguous.
  • Do not trim by default. Reorganize first. Lossy caps, summaries, or sentence extraction can remove nuance and introduce report errors. Add them only when the workflow has an explicit budgeted mode.
  • Keep output schemas JSON-compatible. TOON is a prompt representation; the structured harness output should still be validated against Zod/JSON schema.

Pattern: organize before prompting

Keep full research output as the task result, then transform it into a prompt handoff that removes duplication and makes repeated evidence referable:

typescript
const evidenceSchema = z
  .object({
    id: z.string().min(1),
    path: z.string().min(1),
    detail: z.string().min(1),
  })
  .strict()

const reportContextFindingSchema = z
  .object({
    id: z.string().min(1),
    question: z.string().min(1),
    answer: z.string().min(1),
    evidenceRefs: z.array(z.string().min(1)),
    verification: z
      .object({
        correct: z.boolean(),
        complete: z.boolean(),
        issues: z.array(z.string()),
      })
      .strict(),
  })
  .strict()

const reportContextSchema = z
  .object({
    findings: z.array(reportContextFindingSchema),
    evidence: z.array(evidenceSchema),
  })
  .strict()

The agent sees each finding once and can resolve evidenceRefs against the single evidence table. This is usually smaller than embedding the same path and detail under every finding and then also passing separate answers or reviews arrays.

Prompt as TOON, return JSON

When a prompt contains structured context, encode that context as TOON while keeping the required answer as JSON:

typescript
import { encode as encodeToon } from '@toon-format/toon'

function promptDataBlock(label: string, value: unknown): string {
  return `${label} (TOON):\n${encodeToon(value, { keyFolding: 'safe' })}`
}

const prompt = [
  'Synthesize the verified findings into a report.',
  'Structured context blocks below are TOON, not JSON.',
  promptDataBlock('Report context', reportContext),
  'Return JSON matching the report schema.',
].join('\n\n')

This keeps the model-facing context compact without changing the structured output contract enforced by harnessCall. Orchard's CLI declares @toon-format/toon as a runtime dependency and links it when importing workflow artifacts, so generated artifacts may use this import directly.

Checklist for generated workflow schemas

Before adding or generating a schema that may be passed between harness calls:

  1. Is there one canonical representation, or are the same facts repeated in multiple arrays?
  2. Can repeated files, findings, checklist definitions, or evidence be stored in a table and referenced by id?
  3. Are arrays homogeneous records instead of ad hoc nested maps?
  4. Does the downstream prompt need the full task output, or only an organized handoff view?
  5. Are open questions and verification notes structured as data instead of mixed into prose blobs?
  6. Are you reorganizing before considering lossy trimming?

Example from question/research

The question/research example keeps complete research output as task data, then sends its reporter an organized reportContext:

  • findings[] contains one row per researched question.
  • each row stores the full answer, gaps, and verification result.
  • evidence[] stores deduplicated { id, path, detail } records.
  • findings point at evidence through evidenceRefs[].

In the Uniswap V2 research run used to validate this pattern, reorganizing the reporter context and encoding it as TOON reduced the reporter input by roughly half compared with raw research JSON, without trimming answer or evidence text.

Licensed under MIT