Skip to main content
Every board declares its own custom fields in field_schema, split into a task section and a comments section. A task’s values for those fields live in custom_data. Fields are the vocabulary your policies speak — a transition_guard checks field values to decide whether a move is allowed.

Defining a field

Declare fields directly in the board’s field_schema.task object. Here is an example covering every available type:

Field properties

string
required
The data type for this field. One of: string, number, boolean, enum, markdown, string_list.
string[]
The allowed values when type is enum. Required for enum fields — a board that declares an enum without values fails validation.
boolean
Marks the field as required. Enforcement is lazy: existing tasks are not retroactively rejected, but list_tasks accepts a missing_required_fields filter to surface tasks a tightened schema left incomplete.
string
A free-form format hint. Informational only — Substrate does not enforce it.
boolean
When true, agent MCP write tools refuse to set this field. Only substrate approve can write it. See Human-only fields below.

Validation is lazy

Substrate validates custom_data at write time, not retroactively. Changing a board’s schema never rejects data already stored — add, tighten, or remove a field without running a migration. Use the missing_required_fields filter on list_tasks to find tasks that a schema change left incomplete.

Changing a field

update_board with a full field_schema replaces the entire schema at once. To change a single field without touching the rest, use field_schema_patch — it merges the fields you supply, and setting a field to null deletes it:

Gate fields

A gate field is a boolean you agree to treat as an exit criterion — spec_approved, tests_passing, review_cleared. An agent flips it via update_task as each criterion is met, and a transition_guard policy requires it to be true before allowing a group move. Reference a gate field from a policy condition as task.<name>. A "*" → done guard that requires tests_passing: true is the idiomatic definition-of-done gate.

Human-only fields

A gate that an autonomous agent can set is a gate it can self-clear. If your workflow requires a genuine human decision, mark the gating field "human_only": true.
When a field carries "human_only": true:
  • create_task and update_task refuse to set it — the write returns forbidden.
  • update_board refuses to downgrade or delete the flag — an agent cannot strip its own restriction.
  • The only write channel is the CLI: substrate approve <task_id> <field>. The change is stamped human:<os-user> in the event log.
  • substrate unapprove revokes the approval by deleting the key, so both an exists check and an eq true check re-block correctly.
Two honest limits to understand:

Comments fields

human_only applies to task fields only. On comments fields it is currently inert.

Policy is still data

The guard policy is substrate-as-code. An agent could rewrite or archive_policy the guard — that’s a git-visible, event-logged act, but not structurally impossible.