Schema reference

The JSON Schemas that define workflows, activities, techniques, conditions, and session state. The Zod schemas in src/schema/ are the source of truth; the JSON files under schemas/ are generated from them (npm run build:schemas) and validate workflow definitions at authoring time.

The field tables below show each schema's top-level shape and are rendered from the checked-in schema files. For authoring guidance — how the pieces fit together when writing a workflow — read the schema guide.

activity.schema.json

Activity definition with ordered steps.

Edit source on GitHub

Top-level fields

FieldTypeRequiredDescription
idstringyesIdentifier of the activity, unique within a workflow and carried by its filename: 01-gather.yaml declares gather. A file whose id disagrees with its filename is excluded from the load.
versionstringyesSemantic version of the activity
namestringyesHuman-readable activity name
descriptionstringnoDetailed description of the activity
variablesobjectnoSession variable names required by this activity and declarations for the variables it writes.
techniquestechniqueReference[]noActivity-wide technique references.
bundleTechniquesobjectnoOptional character limit for inline step techniques.
steps(technique | action | checkpoint | loop | routine)[]noOrdered steps, with checkpoints and loops at their positions in the sequence.
exitsobject[]noNamed outcomes, one of which the activity takes when its steps end. Every exit is bound to a destination in the workflow graph, and an unbound exit fails the workflow load. An activity with two or more exits declares exactly one isDefault. Omission makes the activity terminal.
exits[].idstringyesKebab-case outcome name, unique within the activity and distinct from a destination activity identifier.
exits[].labelstringnoHuman-readable statement of the outcome.
exits[].whenwhenExpressionnoExpression selecting this exit, in the same dialect as a step when. Omitted on the default exit and on an exit only a checkpoint option selects.
exits[].isDefaulttruenoDeclare true for the exit taken when no when matches and no checkpoint option selects an exit, including when a checkpoint is dismissed because its condition was not met. Required on exactly one exit of an activity with two or more exits; false is rejected.
exits[].immediatetruenoDeclare true so that selecting this exit at a checkpoint ends the step sequence there, and the remaining steps do not run. Omission records the exit when chosen and takes it when the steps finish; false is rejected.
triggersobject[]noChild workflows to start from this activity.
triggers[].workflowstringyesID of the workflow to trigger
triggers[].descriptionstringnoWhen and why to trigger this workflow.
triggers[].passContextstring[]noContext variable names to pass to the child workflow.
outcomestring[]noExpected outcomes of successful activity completion.
requiredbooleannoWhether this activity is required in the workflow Default: true
rulesstring[]noActivity-level rules and constraints that agents must follow
artifactPrefixstringnoNumeric artifact filename prefix, taken from the activity filename (02 from 02-design-philosophy.yaml); omitted from authored activity definitions.

Shared definitions

DefinitionTypeDescription
whenExpressionstringInline boolean expression in the when dialect, evaluated against the current session variables. Grammar: orExpr := andExpr ('||' andExpr)*; andExpr := unary ('&&' unary)*; unary := '!' unary | primary; primary := IDENT | comparison | 'true' | 'false' | '(' orExpr ')'; comparison := IDENT ('==' | '!=' | '>' | '<' | '>=' | '<=') literal. Precedence, tightest first: (), comparisons, !, &&, ||; so !a == b reads as !(a == b), and a negated left side of a comparison is not expressible. Mixing && and || at the same nesting depth requires parentheses, so grouping is always explicit: (a && b) || c, never a && b || c. An IDENT is a variable name or a dotted path into one (execution_plan.status): a letter or underscore, then letters, digits, underscores and dots. A literal is true, false, null, a single- or double-quoted string (a backslash escapes the next character), an integer with an optional leading -, or a bare word, which is a string (analysis_type == completion). Decimals are not literals. A bare IDENT holds when its value is truthy. A variable that is absent is undefined: it is falsy, == matches nothing (not even null or false), and != matches every literal. == and != compare strictly, without type coercion. >, <, >= and <= compare numbers and strings that read as a finite number, as a structured condition does, and are false when either side is anything else, a boolean, null, an absent variable and a blank string included. An expression that does not parse evaluates to false. Examples: has_saved_state == true, remediation_round > 0, !is_review_mode, a == true && b != false, (a && b) || c, is_review_mode != true && (problem_complexity == "moderate" || problem_complexity == "complex").
conditionsimple | and | or | notVariable test or combination of conditions using and, or, or not.
techniqueReferencestringTechnique reference: [namespace::]technique[::nested…], segments separated by ::, none of them empty. When its leading segments spell a namespace the corpus holds and at least one segment follows them, the rest of the path resolves in that namespace and nowhere else. A namespace is spelled by its directory name, by the path from the corpus root that reaches it, or by as much of the end of that path as reaches it alone (gitnexus and support::gitnexus name the same one); the longest leading run that spells a namespace wins. Otherwise every segment is a path under the referring workflow's techniques/, the first naming a group and the last the technique, resolved in the referring workflow and then in meta; a borrowed activity's referring workflow is the one it is borrowed from. On a step, a one-segment reference resolves first as <activity-id>::<name>, in the group named after the activity, and as written where that group holds no such technique. A reference that reads both ways is refused, naming both files. The spelling namespace/technique names a namespace, whether or not the corpus holds one; a second slash, or a slash after a ::, is malformed. Where no nested technique matches, the last segment names a rule on the technique before it, and a group name {group} stands for every rule named {group}-*.

condition.schema.json

Condition expression schema

Edit source on GitHub

One of 4 variants: simple, and, or, not.

routine.schema.json

Reusable steps with declared inputs, outputs, and internals.

Edit source on GitHub

Top-level fields

FieldTypeRequiredDescription
idstringyesUnique kebab-case routine identifier, without ::.
versionstringyesSemantic version of the routine
namestringyesHuman-readable routine name
descriptionstringnoWhat the run does, and when to refer to it
inputsobject[]noParameters for every value the body reads without producing itself.
inputs[].idstring | "requirements" | "features" | "exclusions" | "dimensions" | "tasks" | "results" | "submodules" | "paths" | "fields" | "filters" | "stats" | "effects" | "substitutions" | "findings" | "assumptions" | "subsystems" | "transitions" | "options" | "branches" | "agents" | "changes" | "failures" | "items" | "files" | "gaps" | "outcomes" | "body" | "query" | "repo" | "owner" | "number" | "title" | "branch" | "diff" | "limit" | "name" | "sha" | "url" | "head" | "base" | "ref" | "labels" | "path" | "cursor" | "cql" | "jql" | "description" | "assignee" | "depth" | "direction" | "summary" | "state" | "target" | "adr" | "type" | "mode" | "kind"yesParameter name within the routine body.
inputs[].descriptionstringyesWhat the parameter is for, in the routine's own vocabulary.
inputs[].kind"technique"noMarks a technique-reference parameter, requiring a literal technique reference at each use or a declared default. A body technique step binding this parameter declares its own id, since a derived one would name the parameter.
inputs[].defaultstring | number | booleannoValue for an unbound argument; without a default, a value parameter uses the host variable of the same name.
outputsobject[]noProduced values, each declared with the type its bound session variable takes.
outputs[].id0 | 1yesOutput name within the routine body, bound to a session variable at each use.
outputs[].type"string" | "number" | "boolean" | "array" | "object"yesType of the output and its bound session variable.
outputs[].descriptionstringyesWhat the value is.
outputs[].valuesstring[]noComplete set of allowed values for a string output.
outputs[].optionaltruenoDeclare true to allow this output to remain unbound, where it stays local to that use of the routine; omission requires an output binding, save for an id that is also an input, which its argument binds.
internalsobject[]noNames for values shared between the routine's steps, local to each use.
internals[].id0 | 1yesName local to the routine body at each use, outside the workflow variable set.
internals[].descriptionstringyesWhat the value is, and which steps pass it.
steps(technique | action | checkpoint | loop | routine)[]yesNonempty ordered list of steps.

Shared definitions

DefinitionTypeDescription
whenExpressionstringInline boolean expression in the when dialect, evaluated against the current session variables. Grammar: orExpr := andExpr ('||' andExpr)*; andExpr := unary ('&&' unary)*; unary := '!' unary | primary; primary := IDENT | comparison | 'true' | 'false' | '(' orExpr ')'; comparison := IDENT ('==' | '!=' | '>' | '<' | '>=' | '<=') literal. Precedence, tightest first: (), comparisons, !, &&, ||; so !a == b reads as !(a == b), and a negated left side of a comparison is not expressible. Mixing && and || at the same nesting depth requires parentheses, so grouping is always explicit: (a && b) || c, never a && b || c. An IDENT is a variable name or a dotted path into one (execution_plan.status): a letter or underscore, then letters, digits, underscores and dots. A literal is true, false, null, a single- or double-quoted string (a backslash escapes the next character), an integer with an optional leading -, or a bare word, which is a string (analysis_type == completion). Decimals are not literals. A bare IDENT holds when its value is truthy. A variable that is absent is undefined: it is falsy, == matches nothing (not even null or false), and != matches every literal. == and != compare strictly, without type coercion. >, <, >= and <= compare numbers and strings that read as a finite number, as a structured condition does, and are false when either side is anything else, a boolean, null, an absent variable and a blank string included. An expression that does not parse evaluates to false. Examples: has_saved_state == true, remediation_round > 0, !is_review_mode, a == true && b != false, (a && b) || c, is_review_mode != true && (problem_complexity == "moderate" || problem_complexity == "complex").
conditionsimple | and | or | notVariable test or combination of conditions using and, or, or not.
techniqueReferencestringTechnique reference: [namespace::]technique[::nested…], segments separated by ::, none of them empty. When its leading segments spell a namespace the corpus holds and at least one segment follows them, the rest of the path resolves in that namespace and nowhere else. A namespace is spelled by its directory name, by the path from the corpus root that reaches it, or by as much of the end of that path as reaches it alone (gitnexus and support::gitnexus name the same one); the longest leading run that spells a namespace wins. Otherwise every segment is a path under the referring workflow's techniques/, the first naming a group and the last the technique, resolved in the referring workflow and then in meta; a borrowed activity's referring workflow is the one it is borrowed from. On a step, a one-segment reference resolves first as <activity-id>::<name>, in the group named after the activity, and as written where that group holds no such technique. A reference that reads both ways is refused, naming both files. The spelling namespace/technique names a namespace, whether or not the corpus holds one; a second slash, or a slash after a ::, is malformed. Where no nested technique matches, the last segment names a rule on the technique before it, and a group name {group} stands for every rule named {group}-*.

session-file.schema.json

Session state recorded in session.

Edit source on GitHub

Top-level fields

FieldTypeRequiredDescription
schemaVersion1yesSession file format version.
sessionIndexstringyesSix-character RFC 4648 base32 session identifier.
workflowIdstringyesNonempty workflow identifier.
workflowVersionstringyesWorkflow version in numeric major.minor.patch form.
agentIdstringyesNonempty identifier of the agent associated with this session.
seqintegeryesNonnegative integer revision number for the session state, increased by one with every state-changing tool call.
tsintegeryesTime of the latest state revision, in Unix epoch seconds.
startedAtstringyesISO 8601 timestamp when the session began.
frontierstring[]noActivities currently in progress. An instance fan's branches are named <activityId>#<index>, the zero-based collection index; a list fan's plain members appear under their bare identifier. Entries are distinct strings, so a bare activity identifier matches no instance of an instance fan. Empty before the first next_activity and after a transition to __terminal__; the call that retires a fan's last branch enters the join in the same revision, and a run that ends at an activity named complete keeps that activity. Default: []
currentTechniquestringnoCurrent technique reference, or an empty string when none is selected. Default: ""
exitstringnoName of the exit the previous activity took. Default: ""
activeCheckpointobjectnoOutstanding checkpoint and any ad hoc decision details.
variablesobjectnoCurrent values keyed by variable name. Default: {}
completedActivitiesstring[]noIdentifiers of completed activities. Default: []
checkpointResponsesobjectnoCheckpoint decisions keyed by activityId-checkpointId, holding the answers of each activity's current visit: entering an activity clears the ones an earlier visit recorded. A gate in a loop body is yielded under a discriminated id, so its key names the round and one round's answer replays only within that round. Default: {}
historyobject[]noChronological record of session progress. Default: []
history[].timestampstringyesISO 8601 timestamp of the event.
history[].type"workflow_started" | "workflow_completed" | "workflow_triggered" | "workflow_returned" | "activity_entered" | "activity_exited" | "step_started" | "step_completed" | "checkpoint_reached" | "checkpoint_response" | "checkpoint_replayed" | "variable_set" | "technique_fetched" | "resource_fetched" | "technique_bundled" | "variables_seeded" | "activity_usage" | "activity_dispatched" | "activity_redelivered" | "batch_refused" | "activity_outcome" | "progress_published" | "activity_delivered"yesCategory of the recorded session event.
history[].activitystringnoActivity identifier associated with the event.
history[].checkpointstringnoCheckpoint identifier associated with the event.
history[].dataobjectnoEvent-specific values, by type. workflow_triggered and workflow_returned: { workflowId, sessionIndex } of the child. activity_outcome: { outcome, exit? }. progress_published: { published }. step_started: { stepId, agentId }; step_completed: { stepId, agentId? }. checkpoint_response and checkpoint_replayed: { optionId }. variable_set: { name, value, source }, with source one of setVariable, checkpoint_reply, variables_changed, fan_enter or yield_checkpoint; a fan branch's write adds { key, instance, member, unit }, a type mismatch adds { declaredType, valueType, typeMismatch: true }, and a value outside the declared set adds { declaredValues, valueOutsideSet: true }. variables_seeded: { variables }, the whole seeded map. technique_fetched: { techniqueId, stepId?, agentId, chars, delivery }; technique_bundled: { techniqueId, stepId, agentId, chars, delivery }; resource_fetched: { resourceId, agentId, chars, delivery, bundled? }, with delivery full or unchanged for all three. activity_delivered: { agentId, delivery, resolved_techniques, provenance_passes, bundled_steps, spent_chars, eager_budget_chars, response_spent_chars, response_bound_chars, fixed_chars, worker_bundle_chars }, with delivery full or reference. activity_dispatched: { agentId, dispatch, chars? }, with dispatch fresh or resume. activity_redelivered: { agentId, priorAgentId, chars }. activity_usage: { usage, basis, agentId? }. batch_refused: { agentId, limit, activities, chars, maxActivities, budgetChars }, with limit activity_cap or delivery_budget. workflow_started, workflow_completed, activity_entered, activity_exited and checkpoint_reached carry none.
status"running" | "completed" | "aborted"noSession status, defaulting to running. Default: "running"
triggeredWorkflowsobject[]noChild workflows and their optional nested session state. Default: []
triggeredWorkflows[].workflowIdstringyesNonempty identifier of the child workflow.
triggeredWorkflows[].sessionIndexstringyesSix-character RFC 4648 base32 identifier of the child session.
triggeredWorkflows[].triggeredAtstringyesISO 8601 timestamp when the child workflow began.
triggeredWorkflows[].triggeredFromobjectyesActivity and optional step where the child workflow began.
triggeredWorkflows[].status"running" | "completed" | "aborted" | "error"yesCurrent status of the child workflow.
triggeredWorkflows[].completedAtstringnoISO 8601 timestamp when the child workflow completed.
triggeredWorkflows[].statesession-filenoFull nested state of the child session.
planningFolderPathstringnoAbsolute path of the planning folder containing the session file.
repostringnoTarget repository in owner/repo form. Fixed once bound: binding a different repository to the session is rejected.
contextMode"persistent" | "fresh"noAgent context model. persistent: content already delivered to an agent arrives as a hash reference. fresh, the default: content arrives in full unless a call asks for reference delivery, except that get_activity sends references for the blocks every activity repeats to an agent returning under the identity its dispatch minted.
executionPath"agent" | "runner"noExecution mode: agent by default, or runner.
deliveredContentobjectnoContent fingerprints grouped by agent identifier, then content key. A key names one whole item, prefixed by its kind: bundle:<technique-ref>, bundle:rules:<hash>, bundle:contract:<key>, activity_rules:<hash>, technique:<id>, workflow_bundle:<hash>, note:<id>:<hash>, or resource:<resource_id>. An item two calls can deliver keeps one key across both, so a delivery through either collapses the other.
declaredArtifactsobject[]noArtifacts declared during the session.
declaredArtifacts[].idstringyesNonempty artifact identifier.
declaredArtifacts[].namestringyesNonempty artifact filename.
declaredArtifacts[].pathstringnoLocation of the artifact file.

technique.schema.json

Technique capability with inputs, protocol, rules, and outputs.

Edit source on GitHub

Top-level fields

FieldTypeRequiredDescription
idstringyesUnique technique identifier.
versionstringyesVersion in numeric major.minor.patch form.
capabilitystringyesCapability or result the technique provides.
provenance_notestringnoInput and output provenance annotation; omitted from authored technique files.
rulesobjectnoRule names mapped to a directive or related directives.
inputsobject[]noNamed inputs accepted by the technique.
inputs[].idstringyesStable input identifier: a snake_case noun phrase of at least two words (planning_folder_path), or a listed single-word exemption. It binds by name to the session variable of the same name.
inputs[].descriptionstringnoInput description, beginning with (optional) when the input is optional.
inputs[].defaultanynoDefault value when not supplied
inputs[].componentsobjectnoNamed parts of a composite input, authored as #### headings.
inputs[].sourcestringnoInput value source annotation: a step-binding value, workflow variable, prior step output, declared default, optional input, inherited context, or UNRESOLVED when nothing in the workflow supplies it. Omitted from authored technique files.
inherited_inputsobjectnoShared inputs from the workflow or group contract; omitted from authored technique files.
protocolobject[]noProtocol blocks in execution order.
protocol[].titlestringnoBlock label, authored as a ### heading.
protocol[].stepsstring[]yesOrdered imperative step bullets for this block.
outputsobject[]noNamed outputs with optional descriptions and either components or list-entry fields.
outputs[].idstringyesStable output identifier: a snake_case noun phrase of at least two words, or a listed single-word exemption, distinct from its artifact filename.
outputs[].descriptionstringnoHuman-readable description of this output
outputs[].componentscomponentsnoComponent identifiers mapped to descriptions or list-entry field definitions.
outputs[].entryobjectnoList-entry fields under #### entry with ##### field headings; mutually exclusive with components.
outputs[].artifactobjectnoFilename and create-or-update action for a persisted output.
outputs[].audience"human" | "agent"noIntended reader of this output or artifact: human by default, or agent. An output declaring an artifact declares its audience, and an agent artifact is JSON, so its filename (or a template's literal suffix) ends in .json; check-audience enforces both.
outputs[].valuesstring[]noComplete set of allowed output values, authored under #### values.
outputs[].fieldValuesobjectnoField names mapped to allowed value sets, authored as ##### headings under #### values.
outputs[].destinationstringnoOutput variable name annotation when different from its identifier; omitted from authored technique files.
inherited_outputsobjectnoShared outputs from the workflow or group contract; omitted from authored technique files.

workflow.schema.json

Workflow definition file; its activities are the files in its activities/ folder and the activities it borrows.

Edit source on GitHub

Top-level fields

FieldTypeRequiredDescription
$schemastringnoURI of the JSON Schema for this workflow definition.
idstringyesUnique workflow identifier
versionstringyesSemantic version
titlestringyesHuman-readable workflow title
descriptionstringnoDetailed workflow description
authorstringnoWorkflow author.
tagsstring[]noLabels for classifying the workflow.
rulesobjectnoRules grouped by audience: workflow orchestrator, activity workers, or both.
variablesobject[]noDeclarations of the session facts and policy this workflow file owns, spanning activities. A variable an activity writes is declared in that activity's variables.writes and contributed here when the activity joins the graph. Declarations of one name that each name a different type, starting value or value set fail the load; one silent about a starting value takes the value another names.
variables[].namestring | "requirements" | "features" | "exclusions" | "dimensions" | "tasks" | "results" | "submodules" | "paths" | "fields" | "filters" | "stats" | "effects" | "substitutions" | "findings" | "assumptions" | "subsystems" | "transitions" | "options" | "branches" | "agents" | "changes" | "failures" | "items" | "files" | "gaps" | "outcomes" | "body" | "query" | "repo" | "owner" | "number" | "title" | "branch" | "diff" | "limit" | "name" | "sha" | "url" | "head" | "base" | "ref" | "labels" | "path" | "cursor" | "cql" | "jql" | "description" | "assignee" | "depth" | "direction" | "summary" | "state" | "target" | "adr" | "type" | "mode" | "kind"yesSnake_case noun phrase of at least two words, or a listed single-word exemption.
variables[].type"string" | "number" | "boolean" | "array" | "object"yesDeclared variable type. A value written to the session, through a checkpoint setVariable effect or a reported variable change, is checked against it; a mismatch is stored as written with a warning, and a {name} template value is exempt. Declarations of one name, in the workflow or its activities, that name different types fail the workflow load.
variables[].descriptionstringnoMeaning and intended use of the variable.
variables[].valuesstring[]noComplete set of values the variable admits: nonempty, without repeats, and declared only on a string variable. A declared defaultValue must be one of them. A value written outside the set is stored as written with a warning, and a {name} template value is exempt. Declarations of one name that each name a value set, and name different ones, fail the workflow load.
variables[].defaultValueanynoInitial value, present from the start of every session and child session. Do not gate a defaulted variable with exists or notExists: the variable is always present, so the gate is constant, and check:variable-model rejects it. Declarations of one name that each name a starting value, and name different ones, fail the workflow load.
variables[].requiredbooleannoMarks the variable as expected to be set; no check reads it. Default: false
techniquesobjectnoTechnique references grouped by scope: workflow orchestration or every activity.
initialActivitystringyesIdentifier of the first activity to execute: the activity the first next_activity names, and the root the variable-reachability analysis walks from. Naming no activity of this workflow fails the load.
graphobjectnoActivity identifiers mapped to exit identifiers and their destinations. A destination is one of: an activity identifier, which the run enters next; __terminal__, which ends the run; a list of two or more members, each an activity identifier or an instance fan, run together with one worker each; or a single instance fan, which runs one activity once per element of a collection. A fan's width is the number of branches once every member is flattened, an instance fan counting its collection's length when entered; it is bounded by the server's ceiling and by any maxInstances an instance fan declares. Every exit of every branch names the fan's join, one single activity, and the load fails otherwise, so a branch cannot open a fan of its own; the run enters the join once, after the last branch returns. A fan also fails the load when an activity appears twice among its members, counting the activity an instance fan runs; when a member is __terminal__; when a branch binds no exit, or an exit back to itself; when the join is __terminal__ or the activity whose exit opens the fan; when a branch declares a checkpoint step; when a branch binds a planning README writer (workflow-engine::sync-progress-status, workflow-engine::prepare-engineering-commit) or workflow-engine::handle-sub-workflow, or a git:: technique without also binding git::create-worktree; when a branch's outputs key (<id>_outputs, its hyphens read as underscores) is not a legal variable name; and when an activity reads a slot of a fanned activity's outputs (review_unit_outputs.<n>) the fan can never fill, a plain member filling slot 0 alone and an instance fan filling as many as its maxInstances, or four. Each fanned activity's outputs land in an array variable named for it, its id with - replaced by _ and _outputs appended (review-unit lands in review_unit_outputs), one slot per branch in collection order, each carrying its unit's id and that branch's values. Every exit of every activity is bound here: an unbound exit, an unknown exit and an unknown destination each fail the load. Omitted only when no activity declares exits.
activitiesstring[]noActivities borrowed from other workflows, each named by its file; the workflow's own activities are the files in its activities/ folder. An activity identifier appears once in a workflow: two of the workflow's own files carrying one identifier, or a borrowed activity sharing its identifier with one of the workflow's own or with another borrowed activity, fails the workflow load.

Shared definitions

DefinitionTypeDescription
techniqueReferencestringTechnique reference: [namespace::]technique[::nested…], segments separated by ::, none of them empty. When its leading segments spell a namespace the corpus holds and at least one segment follows them, the rest of the path resolves in that namespace and nowhere else. A namespace is spelled by its directory name, by the path from the corpus root that reaches it, or by as much of the end of that path as reaches it alone (gitnexus and support::gitnexus name the same one); the longest leading run that spells a namespace wins. Otherwise every segment is a path under the referring workflow's techniques/, the first naming a group and the last the technique, resolved in the referring workflow and then in meta; a borrowed activity's referring workflow is the one it is borrowed from. On a step, a one-segment reference resolves first as <activity-id>::<name>, in the group named after the activity, and as written where that group holds no such technique. A reference that reads both ways is refused, naming both files. The spelling namespace/technique names a namespace, whether or not the corpus holds one; a second slash, or a slash after a ::, is malformed. Where no nested technique matches, the last segment names a rule on the technique before it, and a group name {group} stands for every rule named {group}-*.