Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ reported. There is no `--update-baseline`: a verdict is a human judgement, which
retired baselines let a regenerate flag skip. `npx tsx scripts/check-binding-fidelity.ts
--emit-untriaged` prints the findings still needing one.

A ledger is the exception, not the shape a new guard starts from. `check:activity-variables` has
none: each of its findings named a definition defect, and the corpus was fixed rather than
classified.

`check:review-mode` follows the same shape with a smaller list —
`ACCEPTED_HEADLESS_AUTO_ADVANCE` in [`scripts/check-review-mode-gating.ts`](../scripts/check-review-mode-gating.ts),
one reason per accepted checkpoint.
Expand Down
4 changes: 3 additions & 1 deletion docs/state-management-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Ask a model what to do next and it will answer, but not always the same way twic

## Where variables come from

A workflow declares its state variables in `workflow.yaml`, and the server seeds every declared default into the session's variable bag when the session opens — at `start_session` for a top-level session, at `dispatch_child` for an embedded child, which seeds from the child workflow's own declarations. The seeded map is recorded as a single `variables_seeded` event.
A variable is declared where it is owned. `workflow.yaml` holds the facts a session starts with and the policy that spans its activities; everything an activity produces is declared by that activity, under `variables.writes` beside the reads it needs. Including an activity in a workflow's graph contributes its write declarations to that workflow, so the two lists are one variable set by the time the workflow loads — and an activity two workflows run states its needs once, in the file that holds it. Two declarations of one name that disagree on type or default describe two different variables under one name, so the workflow does not load and the disagreement is named.

The server seeds every declared default from that combined set into the session's variable bag when the session opens — at `start_session` for a top-level session, at `dispatch_child` for an embedded child, which seeds from the child workflow's own declarations. The seeded map is recorded as a single `variables_seeded` event.

Seeding at creation is what keeps the orchestrator's copy of the state and the server's bag in agreement from the first call, so `get_workflow_status` returns the seeded values rather than an empty map.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"check:anchors": "tsx scripts/check-resource-anchors.ts",
"check:technique-template": "tsx scripts/check-technique-template.ts",
"check:variable-model": "tsx scripts/check-variable-model.ts",
"check:activity-variables": "tsx scripts/check-activity-variables.ts",
"check:fragments": "tsx scripts/check-fragments.ts",
"check:review-mode": "tsx scripts/check-review-mode-gating.ts",
"check:checkpoint-entry": "tsx scripts/check-checkpoint-entry.ts",
Expand Down
4 changes: 2 additions & 2 deletions schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ The server enforces structure at load time plus a small runtime core; most schem

| Construct | Engine-enforced | Advisory (incl. warn-only checks) | Agent-interpreted |
|---|---|---|---|
| Workflow | `id` (file resolution); `techniques.workflow` / `techniques.activity` (bundle composition); `activities` / `activitiesDir` (assembly); `variables[].defaultValue` (seeded into the session variable bag at session creation, recorded as a `variables_seeded` history event) | `version` (mid-session drift warns); `title`, `description`, `tags`; `rules.*`; `variables[]` declarations (rendered in `get_workflow`); `initialActivity` (wrong first activity warns); `variables[].type` (checkpoint `setVariable` values validated warn-only — mismatches stored as written) | `author`; `variables[].required` (never checked — authoring metadata) |
| Activity | `id` (navigation key); `artifactPrefix` (server-computed from the filename; also orders activities); the composed artifact contract (synthesized from bound techniques' outputs); `techniques[]` (bundle); `bundleTechniques` (hybrid step-technique bundling in `get_activity`) | `name`, `description`, `required`, `rules[]`; `transitions[]` (legality warns only — `next_activity` moves anywhere); `decisions[]` (stringified for warn-only transition matching) | `triggers[]` / `passContext` (`dispatch_child` takes an explicit `workflow_id`; a child session's bag starts from the child workflow's own declared defaults); `outcome[]` (never reconciled against manifests) |
| Workflow | `id` (file resolution); `techniques.workflow` / `techniques.activity` (bundle composition); `activities` / `activitiesDir` (assembly); `variables[].defaultValue` (seeded into the session variable bag at session creation, recorded as a `variables_seeded` history event) | `version` (mid-session drift warns); `title`, `description`, `tags`; `rules.*`; `variables[]` declarations (the file's own, plus every `variables.writes` declaration the activities in its graph contribute; rendered in `get_workflow`); `initialActivity` (wrong first activity warns); `variables[].type` (checkpoint `setVariable` values validated warn-only — mismatches stored as written) | `author`; `variables[].required` (never checked — authoring metadata) |
| Activity | `variables.writes[]` (contributed to the including workflow's variable set at load; two declarations of one name that disagree on `type` or `defaultValue` fail the load); `id` (navigation key); `artifactPrefix` (server-computed from the filename; also orders activities); the composed artifact contract (synthesized from bound techniques' outputs); `techniques[]` (bundle); `bundleTechniques` (hybrid step-technique bundling in `get_activity`) | `variables.reads[]` (the names the activity needs the workflow to supply; `check:activity-variables` holds the graph to them); `name`, `description`, `required`, `rules[]`; `transitions[]` (legality warns only — `next_activity` moves anywhere); `decisions[]` (stringified for warn-only transition matching) | `triggers[]` / `passContext` (`dispatch_child` takes an explicit `workflow_id`; a child session's bag starts from the child workflow's own declared defaults); `outcome[]` (never reconciled against manifests) |
| Step (common) | `kind` (selects the per-kind closed contract); `id` (duplicate ids are a load error; the key for manifests and step-bound `get_technique`) | absence of a gated step from a `step_manifest` is accepted; ungated omissions warn | `when` / `condition` gates (the server never evaluates a condition; on a checkpoint step only `condition` enables `condition_not_met` dismissal); `required` (worker hint); `actions[]` (no verb has a server interpreter — `set` does not write the variable bag and is slated for removal at the next schema major, #166 B7/B12) |
| Checkpoint step | `options[]` (`option_id` hard-validated); `effect.setVariable` (applied to the session variable bag — the one engine-applied effect); `defaultOption` + `autoAdvanceMs` (the server enforces the full timer before `auto_advance`) | `effect.transitionTo` (recorded and returned; the orchestrator enacts it via `next_activity`); `effect.skipActivities` (recorded in `skippedActivities` bookkeeping) | `blocking` (orchestrator directive; the server's auto-advance gate does not consult it) |
| Loop step | body `steps[]` structure (id uniqueness per scope, flattened for lookups and artifact composition) | loop-body step ids are accepted in `step_manifest` but never required | `loopType` semantics, `variable` / `over`, `breakCondition`, `maxIterations` — iteration is executed and bounded entirely by the agent |
Expand Down
121 changes: 121 additions & 0 deletions schemas/activity.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,127 @@
"type": "string",
"description": "Detailed description of the activity"
},
"variables": {
"type": "object",
"properties": {
"reads": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string",
"pattern": "^[a-z][a-z0-9]*(_[a-z0-9]+)+$"
},
{
"type": "string",
"enum": [
"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"
]
}
],
"description": "Qualified snake_case noun phrase (>=2 words, AP-60), or an enumerated bare-word exemption."
},
"description": "Session variables this activity consults: gate and routing conditions, loop collections, prose interpolations, and the bound operations' own inputs it does not supply itself. A name written by an earlier step of the same activity is resolved internally and is not declared here."
},
"writes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"$ref": "#/definitions/activity/properties/variables/properties/reads/items"
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"array",
"object"
],
"description": "Declared type. The server validates checkpoint setVariable values against it, warn-only: a mismatch is stored as written and surfaced in _meta.validation and on the variable_set history event. Agents honor it for their own writes."
},
"description": {
"type": "string"
},
"defaultValue": {
"description": "Initial value the server seeds into the session variable bag at session creation (start_session fresh sessions and dispatch_child children), recorded as one variables_seeded history event. Do not gate a defaulted variable with exists/notExists — seeding makes the gate constant (check:variable-model enforces this)."
},
"required": {
"type": "boolean",
"default": false,
"description": "Authoring metadata; the server does not check that the variable is ever set."
}
},
"required": [
"name",
"type"
],
"additionalProperties": false
},
"description": "Session variables this activity puts into the bag: its bound operations' outputs (under their declared id or the step binding's remap target), its checkpoint setVariable effects and its `set` action targets. Contributed to the including workflow's variable set, defaultValue included — a loop variable is iteration state and is not declared here."
}
},
"additionalProperties": false,
"description": "The session variables this activity reads and writes. Its writes are declarations contributed to the including workflow's variable set; its reads are the names it needs that workflow to supply."
},
"techniques": {
"type": "array",
"items": {
Expand Down
23 changes: 22 additions & 1 deletion schemas/workflow.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@
],
"additionalProperties": false
},
"description": "Workflow-level variable declarations, rendered in get_workflow for agents. The session variable bag is seeded from each declaration's defaultValue at session creation; thereafter the server writes it through checkpoint setVariable effects and through the worker outputs an orchestrator relays as next_activity's variables_changed."
"description": "The variables this workflow file owns: facts about the session and policy spanning activities. A variable an activity writes is declared by that activity, under its own `variables.writes`, and contributed here when the activity joins this workflow's graph — get_workflow renders the whole set, and two declarations of one name that disagree on type or default fail the load. The session variable bag is seeded from each declaration's defaultValue at session creation; thereafter the server writes it through checkpoint setVariable effects and through the worker outputs an orchestrator relays as next_activity's variables_changed."
},
"techniques": {
"type": "object",
Expand Down Expand Up @@ -441,6 +441,27 @@
"type": "string",
"description": "Detailed description of the activity"
},
"variables": {
"type": "object",
"properties": {
"reads": {
"type": "array",
"items": {
"$ref": "#/definitions/workflow/properties/variables/items/properties/name"
},
"description": "Session variables this activity consults: gate and routing conditions, loop collections, prose interpolations, and the bound operations' own inputs it does not supply itself. A name written by an earlier step of the same activity is resolved internally and is not declared here."
},
"writes": {
"type": "array",
"items": {
"$ref": "#/definitions/workflow/properties/variables/items"
},
"description": "Session variables this activity puts into the bag: its bound operations' outputs (under their declared id or the step binding's remap target), its checkpoint setVariable effects and its `set` action targets. Contributed to the including workflow's variable set, defaultValue included — a loop variable is iteration state and is not declared here."
}
},
"additionalProperties": false,
"description": "The session variables this activity reads and writes. Its writes are declarations contributed to the including workflow's variable set; its reads are the names it needs that workflow to supply."
},
"techniques": {
"type": "array",
"items": {
Expand Down
Loading
Loading