Design JSON schemas for BorgIQ — AI Actor outputSchema, AI Agent tool input schemas, Collection actor item schemas, Callable sub-flow contracts, and reusable actor.schemas.inputs. Use whenever a non-trivial structured data contract needs to be defined for any BorgIQ actor that consumes or produces typed data. Triggers on "JSON schema", "outputSchema", "structured output", "tool input schema", "collection schema", "callable response schema", "AI structured output", "agent output contract".
Design the JSON schemas that sit at every contract boundary in a BorgIQ workflow. This spoke is cross-cutting — other spokes hand off here when their schema work goes beyond the trivial. Pair with borgiq-builder (hub) for wiring; with borgiq-agent-builder for tool design; with borgiq-form-builder when fields cross between forms and back-end contracts.
Mental model
Schemas define the shape of data at every contract boundary in BorgIQ — where AI models produce output, where storage items take form, where agent tools receive parameters, where sub-flows declare contracts, where actor interfaces are reused. Tight schemas reduce errors, guide AI behavior, and document intent. Loose or missing schemas cause LLM hallucination, storage mismatches, and integration brittleness.
BorgIQ uses standard JSON Schema (draft 7 / 2020-12) with one local convention: when you need an object whose properties aren't statically known, use type: any rather than type: object with an empty properties map. (This is generation rule #12 in the hub SKILL.md.)
The six places schemas appear
Where
Purpose
Validated by
Guidance
AiActor outputSchema
Tells the LLM what JSON to produce
The model itself, via constrained decoding
Keep tight — every required field is something the model must produce. Use enums to constrain.
AiAgentActor tool schemas.inputs
Declares what each tool accepts; the agent reads this to choose tools
The agent, when selecting and invoking tools
Field description is read by the LLM — write it like docs the model will actually use.
CollectionActor item schema
Shape of stored items, queries, indexes
CollectionActor at putItem / updateItem; reads validate on retrieval
Design for query patterns first, not just current data shape. Plan key strategy upfront.
CallableTriggerActor schemas.inputs
Input contract of a sub-flow — the payload callers must send
Nothing at runtime — the editor generates the CallFlowActor payload form from it; the payload passes into the sub-flow unvalidated
The sub-flow's function signature. Mirror in configuration.inputs; keep the caller payload, this schema, and downstream msg.* reads in lockstep by discipline — drift fails silently, not fast.
Mirrors what inputs: declares; enables templatization and UI generation.
Structured output from an agent: AiAgentActor has no outputSchema — its done port carries result (free text) plus workspace/session zips. To get a structured contract out of an agent run, feed msg.<agent>.result into a downstream AiActor with an outputSchema (extraction pass), or have the agent write a JSON file to its workspace and parse it from outputZipFile.
Schema design principles — AI (AiActor, AiAgentActor)
Tight beats loose. Every required field is a chance for the LLM to fail. Start with the minimum required set; add optionals only if the task genuinely sometimes lacks them.
Enums over free strings.enum: [pending, processing, completed, failed] constrains the output space; type: string lets the model invent values.
Write field names and descriptions for the LLM. The model reads them. sentiment: { type: string, enum: [positive, negative, neutral], description: "Emotional tone of the text" } is dramatically clearer than s: { type: string }.
Stay flat. Deep nesting confuses LLMs and complicates Collection partial updates (which replace the entire top-level field). Two or three levels at most; use $ref to share shapes.
Use $ref for shared sub-schemas. Define an address once, reference it from shipping and billing. Less drift, consistent validation.
Don't over-engineer for hypothetical extensibility. Build for today's contract. Future fields cost more than they save if they're not currently used.
Set additionalProperties: false for output schemas. Without it (or with true), the LLM may invent extra fields. Lock the output surface.
Model for query patterns, not just current data. Before designing the schema, list every read pattern. Add label fields, sort prefixes, and TTL where needed. A schema that satisfies writes but doesn't support reads is a redesign waiting to happen.
Plan partition / sort key strategy upfront. Keys are lexicographically sorted. Hierarchical keys (user:U001:profile, user:U001:session:S123) enable cheap prefix scans.
Flat top-level fields for partial updates.updateItem replaces an entire top-level field if it's an object. Keep mutable fields flat.
TTL for ephemeral data. Set ttl: <seconds> on session tokens, callback IDs, transient state.
Denormalize for read efficiency. A query that needs name + email + status should find them in one item, not three.
Storage / API contract checklist — collection-backed apps
When an app (a React app frontend + Collection storage + backend endpoints) is on the table, define the contracts in this order, before picking actors:
Define endpoint request/response schemas first. For each endpoint the UI calls, write the request shape (query/body) and the response shape. These are the contract the frontend codes against — settle them before deciding whether the endpoint is a webhook-enabled UniversalTriggerActor or a WebhookTriggerActor flow. Actor choice follows the contract, not the other way around (see the hub's Universal Trigger vs Webhook Trigger matrix).
Define a Collection item schema and key strategy per read pattern. List every way the app reads the data (by id, by owner, by status, recent-first), then design the keys and label fields to serve those reads (see storage principles). One read pattern the keys don't support is a redesign waiting to happen.
Keep the AI output schema separate from the persisted item schema. When a generation endpoint feeds storage, the AiActor outputSchema (what the model must produce) and the Collection item schema (what's persisted) are different contracts. Map model output → stored item explicitly; don't reuse one schema for both. The model's schema stays tight for decoding; the stored schema carries keys, labels, TTL, and timestamps the model never produces.
The endpoint request/response schemas are the same contracts the borgiq-react-app-builder spoke wires with useEndpoint — design them together. Once the item schema and key strategy are set, they also define how each collection must be provisioned (created with the right labels, seeded with defaults) — see collection-migrations.md.
Common patterns
Tight AiActor outputSchema:
outputSchema:
type: object
additionalProperties: false
properties:
sentiment:
type: string
enum: [positive, negative, neutral]
description: Emotional tone of the text
confidence:
type: number
description: Confidence score 0.0–1.0
keywords:
type: array
items: { type: string }
description: 2–5 key themes
required: [sentiment, confidence]
Tool input schema for AiAgentActor:
schemas:
inputs:
type: object
properties:
query:
type: string
description: Specific search query — avoid vague terms
limit:
type: integer
default: 10
description: Max results to return
required: [query]
# Loose — model can invent values
status: { type: string }
# Tight — model is constrained
status:
type: string
enum: [pending, processing, completed, failed]
Anti-patterns
additionalProperties: true on AI output schemas. Lets the LLM invent fields. Set to false (or omit and rely on validator default) to lock the surface.
Marking every field optional then complaining the LLM skips important ones. Optional signals "model can skip"; required signals "model must produce". Match required to actual intent.
type: object with empty properties instead of type: any. The BorgIQ convention is type: any for truly open-ended objects (hub SKILL.md generation rule #12). Clearer to readers and validators.
oneOf: [{type: string}, {type: string}] where enum would do.oneOf is for shape unions; enum is for fixed-choice strings. Use the simpler construct.
Drift between tool schemas.inputs declaration and the tool's actual inputs: mapping. The agent reads the schema and infers what to pass; if the tool body uses different field names, calls silently misroute. Keep declaration and implementation in lockstep.
"Wire this schema into a tool / connect to an agent / design the agent loop"
borgiq-agent-builder
"Validate this form field" / "build a form that produces this shape"
borgiq-form-builder (form components have their own schema model)
"Webhook response contract for a custom UI"
borgiq-react-app-builder (this spoke for the schema; react-app-builder for the frontend)
msgVar wiring, inputs vs vars, deploy
Hub: borgiq-builder
1---2name: borgiq-json-schema-builder3description: Design JSON schemas for BorgIQ — AI Actor outputSchema, AI Agent tool input schemas, Collection actor item schemas, Callable sub-flow contracts, and reusable actor.schemas.inputs. Use whenever a non-trivial structured data contract needs to be defined for any BorgIQ actor that consumes or produces typed data. Triggers on "JSON schema", "outputSchema", "structured output", "tool input schema", "collection schema", "callable response schema", "AI structured output", "agent output contract".4---56# BorgIQ JSON Schema Builder78Design the JSON schemas that sit at every contract boundary in a BorgIQ workflow. This spoke is **cross-cutting** — other spokes hand off here when their schema work goes beyond the trivial. Pair with `borgiq-builder` (hub) for wiring; with `borgiq-agent-builder` for tool design; with `borgiq-form-builder` when fields cross between forms and back-end contracts.910## Mental model1112Schemas define the *shape* of data at every contract boundary in BorgIQ — where AI models produce output, where storage items take form, where agent tools receive parameters, where sub-flows declare contracts, where actor interfaces are reused. Tight schemas reduce errors, guide AI behavior, and document intent. Loose or missing schemas cause LLM hallucination, storage mismatches, and integration brittleness.1314BorgIQ uses standard JSON Schema (draft 7 / 2020-12) with one local convention: when you need an object whose properties aren't statically known, use **`type: any`** rather than `type: object` with an empty `properties` map. (This is generation rule #12 in the hub SKILL.md.)1516## The six places schemas appear1718| Where | Purpose | Validated by | Guidance |19|---|---|---|---|20| **AiActor `outputSchema`** | Tells the LLM what JSON to produce | The model itself, via constrained decoding | Keep tight — every `required` field is something the model must produce. Use enums to constrain. |21| **AiAgentActor tool `schemas.inputs`** | Declares what each tool accepts; the agent reads this to choose tools | The agent, when selecting and invoking tools | Field `description` is read by the LLM — write it like docs the model will actually use. |22| **CollectionActor item schema** | Shape of stored items, queries, indexes | CollectionActor at `putItem` / `updateItem`; reads validate on retrieval | Design for query patterns *first*, not just current data shape. Plan key strategy upfront. |23| **CallableTriggerActor `schemas.inputs`** | Input contract of a sub-flow — the payload callers must send | **Nothing at runtime** — the editor generates the CallFlowActor payload form from it; the payload passes into the sub-flow unvalidated | The sub-flow's function signature. Mirror in `configuration.inputs`; keep the caller `payload`, this schema, and downstream `msg.*` reads in lockstep by discipline — drift fails silently, not fast. |24| **CallableResponseActor response schema** | Output contract of a sub-flow | CallFlowActor when consuming the response | Parents depend on this. Enums + clear types reduce caller surprise. |25| **Actor-level `schemas.inputs`** | Reusable input interface for any actor | Framework at wire-in | Mirrors what `inputs:` declares; enables templatization and UI generation. |2627> **Structured output *from* an agent:** AiAgentActor has no `outputSchema` — its done port carries `result` (free text) plus workspace/session zips. To get a structured contract out of an agent run, feed `msg.<agent>.result` into a downstream **AiActor with an `outputSchema`** (extraction pass), or have the agent write a JSON file to its workspace and parse it from `outputZipFile`.2829## Schema design principles — AI (AiActor, AiAgentActor)30311. **Tight beats loose.** Every `required` field is a chance for the LLM to fail. Start with the minimum required set; add optionals only if the task genuinely sometimes lacks them.322. **Enums over free strings.** `enum: [pending, processing, completed, failed]` constrains the output space; `type: string` lets the model invent values.333. **Write field names and descriptions for the LLM.** The model reads them. `sentiment: { type: string, enum: [positive, negative, neutral], description: "Emotional tone of the text" }` is dramatically clearer than `s: { type: string }`.344. **Stay flat.** Deep nesting confuses LLMs and complicates Collection partial updates (which replace the entire top-level field). Two or three levels at most; use `$ref` to share shapes.355. **Use `$ref` for shared sub-schemas.** Define an `address` once, reference it from `shipping` and `billing`. Less drift, consistent validation.366. **Don't over-engineer for hypothetical extensibility.** Build for today's contract. Future fields cost more than they save if they're not currently used.377. **Set `additionalProperties: false` for output schemas.** Without it (or with `true`), the LLM may invent extra fields. Lock the output surface.3839## Schema design principles — storage (CollectionActor)40411. **Model for query patterns, not just current data.** Before designing the schema, list every read pattern. Add label fields, sort prefixes, and TTL where needed. A schema that satisfies writes but doesn't support reads is a redesign waiting to happen.422. **Plan partition / sort key strategy upfront.** Keys are lexicographically sorted. Hierarchical keys (`user:U001:profile`, `user:U001:session:S123`) enable cheap prefix scans.433. **Flat top-level fields for partial updates.** `updateItem` replaces an entire top-level field if it's an object. Keep mutable fields flat.444. **TTL for ephemeral data.** Set `ttl: <seconds>` on session tokens, callback IDs, transient state.455. **Denormalize for read efficiency.** A query that needs name + email + status should find them in one item, not three.4647## Storage / API contract checklist — collection-backed apps4849When an app (a React app frontend + Collection storage + backend endpoints) is on the table, define the contracts in this order, *before* picking actors:5051- [ ] **Define endpoint request/response schemas first.** For each endpoint the UI calls, write the request shape (query/body) and the response shape. These are the contract the frontend codes against — settle them before deciding whether the endpoint is a webhook-enabled UniversalTriggerActor or a WebhookTriggerActor flow. Actor choice follows the contract, not the other way around (see the hub's [Universal Trigger vs Webhook Trigger](../borgiq-builder/SKILL.md#universal-trigger-vs-webhook-trigger-http-endpoints) matrix).52- [ ] **Define a Collection item schema and key strategy per read pattern.** List every way the app reads the data (by id, by owner, by status, recent-first), then design the keys and label fields to serve those reads (see [storage principles](#schema-design-principles--storage-collectionactor)). One read pattern the keys don't support is a redesign waiting to happen.53- [ ] **Keep the AI output schema separate from the persisted item schema.** When a generation endpoint feeds storage, the AiActor `outputSchema` (what the model must produce) and the Collection item schema (what's persisted) are *different contracts*. Map model output → stored item explicitly; don't reuse one schema for both. The model's schema stays tight for decoding; the stored schema carries keys, labels, TTL, and timestamps the model never produces.5455The endpoint request/response schemas are the same contracts the `borgiq-react-app-builder` spoke wires with `useEndpoint` — design them together. Once the item schema and key strategy are set, they also define how each collection must be **provisioned** (created with the right labels, seeded with defaults) — see [collection-migrations.md](../borgiq-builder/references/collection-migrations.md).5657## Common patterns5859**Tight AiActor outputSchema:**60```yaml61outputSchema:62 type: object63 additionalProperties: false64 properties:65 sentiment:66 type: string67 enum: [positive, negative, neutral]68 description: Emotional tone of the text69 confidence:70 type: number71 description: Confidence score 0.0–1.072 keywords:73 type: array74 items: { type: string }75 description: 2–5 key themes76 required: [sentiment, confidence]77```7879**Tool input schema for AiAgentActor:**80```yaml81schemas:82 inputs:83 type: object84 properties:85 query:86 type: string87 description: Specific search query — avoid vague terms88 limit:89 type: integer90 default: 1091 description: Max results to return92 required: [query]93```9495**Collection item — flat, denormalized, query-friendly:**96```yaml97value:98 userId: user-00199 email: alice@example.com100 firstName: Alice101 status: active # label query: status=active102 lastLogin: 2026-03-19T10:00:00Z103```104105**`$ref` for a shared shape:**106```yaml107definitions:108 address:109 type: object110 required: [city, state, zip]111 properties:112 city: { type: string }113 state: { type: string }114 zip: { type: string }115properties:116 shipping: { $ref: '#/definitions/address' }117 billing: { $ref: '#/definitions/address' }118```119120**Enum vs free string — when in doubt, enum:**121```yaml122# Loose — model can invent values123status: { type: string }124125# Tight — model is constrained126status:127 type: string128 enum: [pending, processing, completed, failed]129```130131## Anti-patterns1321331. **`additionalProperties: true` on AI output schemas.** Lets the LLM invent fields. Set to `false` (or omit and rely on validator default) to lock the surface.1342. **Marking every field optional then complaining the LLM skips important ones.** Optional signals "model can skip"; required signals "model must produce". Match `required` to actual intent.1353. **`type: object` with empty `properties` instead of `type: any`.** The BorgIQ convention is `type: any` for truly open-ended objects (hub SKILL.md generation rule #12). Clearer to readers and validators.1364. **`oneOf: [{type: string}, {type: string}]` where `enum` would do.** `oneOf` is for shape unions; `enum` is for fixed-choice strings. Use the simpler construct.1375. **Drift between tool `schemas.inputs` declaration and the tool's actual `inputs:` mapping.** The agent reads the schema and infers what to pass; if the tool body uses different field names, calls silently misroute. Keep declaration and implementation in lockstep.138139## References140141| File | What's inside |142|---|---|143| [`references/ai-actor.md`](../borgiq-builder/references/ai-actor.md) | `outputSchema` examples, structured output patterns for code/HTML generation |144| [`references/ai-agent-actor.md`](../borgiq-builder/references/ai-agent-actor.md) | Tool input schemas, `${{aiInput}}` pattern |145| [`references/collection-actor.md`](../borgiq-builder/references/collection-actor.md) | Item schema patterns, queue/index designs, key strategies |146| [`references/collection-api.md`](../borgiq-builder/references/collection-api.md) | DynamoDB mapping, concurrent updates, nested-object replacement behavior |147| [`references/callable-response-actor.md`](../borgiq-builder/references/callable-response-actor.md) | Sub-flow response schema contracts |148149## When to hand off to other spokes150151| Customer ask | Hand off to |152|---|---|153| "Wire this schema into a tool / connect to an agent / design the agent loop" | `borgiq-agent-builder` |154| "Validate this form field" / "build a form that produces this shape" | `borgiq-form-builder` (form components have their own schema model) |155| "Webhook response contract for a custom UI" | `borgiq-react-app-builder` (this spoke for the schema; react-app-builder for the frontend) |156| msgVar wiring, `inputs` vs `vars`, deploy | Hub: `borgiq-builder` |
Run npx skillmds@latest add borgiq/borgiq-json-schema-builder in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Design JSON schemas for BorgIQ — AI Actor outputSchema, AI Agent tool input schemas, Collection actor item schemas, Callable sub-flow contracts, and reusable actor.schemas.inputs. Use whenever a non-trivial structured data contract needs to be defined for any BorgIQ actor that consumes or produces typed data. Triggers on "JSON schema", "outputSchema", "structured output", "tool input schema", "collection schema", "callable response schema", "AI structured output", "agent output contract". It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
BorgIQ (@borgiq) published this skill. Their other Agent Skills are listed on their SkillMD profile.