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.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Identifier 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. |
version | string | yes | Semantic version of the activity |
name | string | yes | Human-readable activity name |
description | string | no | Detailed description of the activity |
variables | object | no | Session variable names required by this activity and declarations for the variables it writes. |
techniques | techniqueReference[] | no | Activity-wide technique references. |
bundleTechniques | object | no | Optional character limit for inline step techniques. |
steps | (technique | action | checkpoint | loop | routine)[] | no | Ordered steps, with checkpoints and loops at their positions in the sequence. |
exits | object[] | no | Named 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[].id | string | yes | Kebab-case outcome name, unique within the activity and distinct from a destination activity identifier. |
exits[].label | string | no | Human-readable statement of the outcome. |
exits[].when | whenExpression | no | Expression 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[].isDefault | true | no | Declare 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[].immediate | true | no | Declare 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. |
triggers | object[] | no | Child workflows to start from this activity. |
triggers[].workflow | string | yes | ID of the workflow to trigger |
triggers[].description | string | no | When and why to trigger this workflow. |
triggers[].passContext | string[] | no | Context variable names to pass to the child workflow. |
outcome | string[] | no | Expected outcomes of successful activity completion. |
required | boolean | no | Whether this activity is required in the workflow Default: true |
rules | string[] | no | Activity-level rules and constraints that agents must follow |
artifactPrefix | string | no | Numeric artifact filename prefix, taken from the activity filename (02 from 02-design-philosophy.yaml); omitted from authored activity definitions. |
Shared definitions
| Definition | Type | Description |
|---|---|---|
whenExpression | string | Inline 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"). |
condition | simple | and | or | not | Variable test or combination of conditions using and, or, or not. |
techniqueReference | string | Technique 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
One of 4 variants: simple, and, or, not.
routine.schema.json
Reusable steps with declared inputs, outputs, and internals.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique kebab-case routine identifier, without ::. |
version | string | yes | Semantic version of the routine |
name | string | yes | Human-readable routine name |
description | string | no | What the run does, and when to refer to it |
inputs | object[] | no | Parameters for every value the body reads without producing itself. |
inputs[].id | string | "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" | yes | Parameter name within the routine body. |
inputs[].description | string | yes | What the parameter is for, in the routine's own vocabulary. |
inputs[].kind | "technique" | no | Marks 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[].default | string | number | boolean | no | Value for an unbound argument; without a default, a value parameter uses the host variable of the same name. |
outputs | object[] | no | Produced values, each declared with the type its bound session variable takes. |
outputs[].id | 0 | 1 | yes | Output name within the routine body, bound to a session variable at each use. |
outputs[].type | "string" | "number" | "boolean" | "array" | "object" | yes | Type of the output and its bound session variable. |
outputs[].description | string | yes | What the value is. |
outputs[].values | string[] | no | Complete set of allowed values for a string output. |
outputs[].optional | true | no | Declare 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. |
internals | object[] | no | Names for values shared between the routine's steps, local to each use. |
internals[].id | 0 | 1 | yes | Name local to the routine body at each use, outside the workflow variable set. |
internals[].description | string | yes | What the value is, and which steps pass it. |
steps | (technique | action | checkpoint | loop | routine)[] | yes | Nonempty ordered list of steps. |
Shared definitions
| Definition | Type | Description |
|---|---|---|
whenExpression | string | Inline 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"). |
condition | simple | and | or | not | Variable test or combination of conditions using and, or, or not. |
techniqueReference | string | Technique 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.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion | 1 | yes | Session file format version. |
sessionIndex | string | yes | Six-character RFC 4648 base32 session identifier. |
workflowId | string | yes | Nonempty workflow identifier. |
workflowVersion | string | yes | Workflow version in numeric major.minor.patch form. |
agentId | string | yes | Nonempty identifier of the agent associated with this session. |
seq | integer | yes | Nonnegative integer revision number for the session state, increased by one with every state-changing tool call. |
ts | integer | yes | Time of the latest state revision, in Unix epoch seconds. |
startedAt | string | yes | ISO 8601 timestamp when the session began. |
frontier | string[] | no | Activities 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: [] |
currentTechnique | string | no | Current technique reference, or an empty string when none is selected. Default: "" |
exit | string | no | Name of the exit the previous activity took. Default: "" |
activeCheckpoint | object | no | Outstanding checkpoint and any ad hoc decision details. |
variables | object | no | Current values keyed by variable name. Default: {} |
completedActivities | string[] | no | Identifiers of completed activities. Default: [] |
checkpointResponses | object | no | Checkpoint 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: {} |
history | object[] | no | Chronological record of session progress. Default: [] |
history[].timestamp | string | yes | ISO 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" | yes | Category of the recorded session event. |
history[].activity | string | no | Activity identifier associated with the event. |
history[].checkpoint | string | no | Checkpoint identifier associated with the event. |
history[].data | object | no | Event-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" | no | Session status, defaulting to running. Default: "running" |
triggeredWorkflows | object[] | no | Child workflows and their optional nested session state. Default: [] |
triggeredWorkflows[].workflowId | string | yes | Nonempty identifier of the child workflow. |
triggeredWorkflows[].sessionIndex | string | yes | Six-character RFC 4648 base32 identifier of the child session. |
triggeredWorkflows[].triggeredAt | string | yes | ISO 8601 timestamp when the child workflow began. |
triggeredWorkflows[].triggeredFrom | object | yes | Activity and optional step where the child workflow began. |
triggeredWorkflows[].status | "running" | "completed" | "aborted" | "error" | yes | Current status of the child workflow. |
triggeredWorkflows[].completedAt | string | no | ISO 8601 timestamp when the child workflow completed. |
triggeredWorkflows[].state | session-file | no | Full nested state of the child session. |
planningFolderPath | string | no | Absolute path of the planning folder containing the session file. |
repo | string | no | Target repository in owner/repo form. Fixed once bound: binding a different repository to the session is rejected. |
contextMode | "persistent" | "fresh" | no | Agent 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" | no | Execution mode: agent by default, or runner. |
deliveredContent | object | no | Content 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. |
declaredArtifacts | object[] | no | Artifacts declared during the session. |
declaredArtifacts[].id | string | yes | Nonempty artifact identifier. |
declaredArtifacts[].name | string | yes | Nonempty artifact filename. |
declaredArtifacts[].path | string | no | Location of the artifact file. |
technique.schema.json
Technique capability with inputs, protocol, rules, and outputs.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique technique identifier. |
version | string | yes | Version in numeric major.minor.patch form. |
capability | string | yes | Capability or result the technique provides. |
provenance_note | string | no | Input and output provenance annotation; omitted from authored technique files. |
rules | object | no | Rule names mapped to a directive or related directives. |
inputs | object[] | no | Named inputs accepted by the technique. |
inputs[].id | string | yes | Stable 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[].description | string | no | Input description, beginning with (optional) when the input is optional. |
inputs[].default | any | no | Default value when not supplied |
inputs[].components | object | no | Named parts of a composite input, authored as #### headings. |
inputs[].source | string | no | Input 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_inputs | object | no | Shared inputs from the workflow or group contract; omitted from authored technique files. |
protocol | object[] | no | Protocol blocks in execution order. |
protocol[].title | string | no | Block label, authored as a ### heading. |
protocol[].steps | string[] | yes | Ordered imperative step bullets for this block. |
outputs | object[] | no | Named outputs with optional descriptions and either components or list-entry fields. |
outputs[].id | string | yes | Stable output identifier: a snake_case noun phrase of at least two words, or a listed single-word exemption, distinct from its artifact filename. |
outputs[].description | string | no | Human-readable description of this output |
outputs[].components | components | no | Component identifiers mapped to descriptions or list-entry field definitions. |
outputs[].entry | object | no | List-entry fields under #### entry with ##### field headings; mutually exclusive with components. |
outputs[].artifact | object | no | Filename and create-or-update action for a persisted output. |
outputs[].audience | "human" | "agent" | no | Intended 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[].values | string[] | no | Complete set of allowed output values, authored under #### values. |
outputs[].fieldValues | object | no | Field names mapped to allowed value sets, authored as ##### headings under #### values. |
outputs[].destination | string | no | Output variable name annotation when different from its identifier; omitted from authored technique files. |
inherited_outputs | object | no | Shared 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.
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | no | URI of the JSON Schema for this workflow definition. |
id | string | yes | Unique workflow identifier |
version | string | yes | Semantic version |
title | string | yes | Human-readable workflow title |
description | string | no | Detailed workflow description |
author | string | no | Workflow author. |
tags | string[] | no | Labels for classifying the workflow. |
rules | object | no | Rules grouped by audience: workflow orchestrator, activity workers, or both. |
variables | object[] | no | Declarations 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[].name | string | "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" | yes | Snake_case noun phrase of at least two words, or a listed single-word exemption. |
variables[].type | "string" | "number" | "boolean" | "array" | "object" | yes | Declared 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[].description | string | no | Meaning and intended use of the variable. |
variables[].values | string[] | no | Complete 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[].defaultValue | any | no | Initial 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[].required | boolean | no | Marks the variable as expected to be set; no check reads it. Default: false |
techniques | object | no | Technique references grouped by scope: workflow orchestration or every activity. |
initialActivity | string | yes | Identifier 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. |
graph | object | no | Activity 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. |
activities | string[] | no | Activities 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
| Definition | Type | Description |
|---|---|---|
techniqueReference | string | Technique 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}-*. |