@kontourai/flow-agents 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.github/CODEOWNERS +8 -0
  2. package/.github/workflows/ci.yml +12 -0
  3. package/.github/workflows/trust-reconcile.yml +62 -4
  4. package/CHANGELOG.md +23 -0
  5. package/CONTEXT.md +21 -0
  6. package/build/src/cli/assignment-provider.d.ts +1 -0
  7. package/build/src/cli/assignment-provider.js +748 -0
  8. package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
  9. package/build/src/cli/effective-assignment-provider-settings.js +125 -0
  10. package/build/src/cli/validate-workflow-artifacts.js +5 -1
  11. package/build/src/cli/workflow-sidecar.js +157 -110
  12. package/build/src/cli.js +6 -0
  13. package/build/src/tools/validate-source-tree.js +1 -0
  14. package/context/contracts/artifact-contract.md +2 -0
  15. package/context/contracts/assignment-provider-contract.md +239 -0
  16. package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
  17. package/context/contracts/decision-registry-contract.md +2 -0
  18. package/context/contracts/delivery-contract.md +2 -0
  19. package/context/contracts/execution-contract.md +25 -0
  20. package/context/contracts/governance-adapter-contract.md +2 -0
  21. package/context/contracts/knowledge-store-contract.md +197 -0
  22. package/context/contracts/planning-contract.md +2 -0
  23. package/context/contracts/review-contract.md +2 -0
  24. package/context/contracts/sandbox-policy.md +2 -0
  25. package/context/contracts/standing-directives.md +13 -0
  26. package/context/contracts/verification-contract.md +2 -0
  27. package/context/contracts/work-item-contract.md +2 -0
  28. package/context/settings/assignment-provider-settings.json +33 -0
  29. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +180 -0
  30. package/docs/context-map.md +1 -0
  31. package/docs/decisions/index.md +3 -0
  32. package/docs/decisions/knowledge-store-provider.md +51 -0
  33. package/docs/decisions/model-routing.md +63 -0
  34. package/docs/decisions/standing-directives.md +66 -0
  35. package/docs/fixture-ownership.md +1 -0
  36. package/docs/workflow-shared-contracts.md +2 -1
  37. package/docs/workflow-usage-guide.md +6 -0
  38. package/evals/ci/run-baseline.sh +6 -0
  39. package/evals/fixtures/assignment-provider/actor-a.json +6 -0
  40. package/evals/fixtures/assignment-provider/actor-b.json +6 -0
  41. package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
  42. package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
  43. package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
  44. package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
  45. package/evals/integration/test_assignment_provider_github.sh +318 -0
  46. package/evals/integration/test_assignment_provider_local_file.sh +222 -0
  47. package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
  48. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  49. package/evals/integration/test_publish_delivery.sh +21 -4
  50. package/evals/integration/test_pull_work_assignment_join.sh +132 -0
  51. package/evals/integration/test_pull_work_liveness_preflight.sh +14 -6
  52. package/evals/integration/test_reconcile_soundness.sh +33 -9
  53. package/evals/integration/test_trust_reconcile.sh +9 -8
  54. package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
  55. package/evals/integration/test_workflow_sidecar_writer.sh +79 -0
  56. package/evals/run.sh +10 -0
  57. package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
  58. package/evals/static/test_knowledge_providers.sh +23 -0
  59. package/kits/builder/skills/deliver/SKILL.md +19 -0
  60. package/kits/builder/skills/pull-work/SKILL.md +78 -10
  61. package/kits/knowledge/kit.json +35 -0
  62. package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
  63. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
  64. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
  65. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
  66. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
  67. package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
  68. package/kits/knowledge/providers/conformance/suite.test.js +125 -0
  69. package/kits/knowledge/providers/git-repo/index.js +236 -0
  70. package/kits/knowledge/providers/health/health-pass.test.js +99 -0
  71. package/kits/knowledge/providers/health/index.js +153 -0
  72. package/kits/knowledge/providers/index.js +24 -0
  73. package/kits/knowledge/providers/lib/model.js +91 -0
  74. package/kits/knowledge/providers/lib/schema-validate.js +119 -0
  75. package/kits/knowledge/providers/markdown-vault/index.js +169 -0
  76. package/kits/knowledge/providers/work-item/index.js +204 -0
  77. package/package.json +4 -2
  78. package/schemas/assignment-provider-settings.schema.json +125 -0
  79. package/schemas/knowledge/edge.schema.json +54 -0
  80. package/schemas/knowledge/health-report.schema.json +45 -0
  81. package/schemas/knowledge/node.schema.json +49 -0
  82. package/schemas/knowledge/proposal.schema.json +53 -0
  83. package/scripts/ci/trust-reconcile.js +521 -24
  84. package/src/cli/assignment-provider.ts +845 -0
  85. package/src/cli/effective-assignment-provider-settings.ts +112 -0
  86. package/src/cli/validate-workflow-artifacts.ts +5 -1
  87. package/src/cli/workflow-sidecar.ts +147 -106
  88. package/src/cli.ts +6 -0
  89. package/src/tools/validate-source-tree.ts +1 -0
@@ -1,5 +1,7 @@
1
1
  # Workflow Artifact Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Workflow artifacts are the resumable local handoff surface between orchestrators, workers, verifiers, hooks, evals, and future sessions.
4
6
 
5
7
  ## Artifact Root
@@ -0,0 +1,239 @@
1
+ # Assignment Provider Contract
2
+
3
+ This contract defines the provider-neutral vocabulary for durable work-item ownership: who has
4
+ claimed a subject, how that claim is represented, and how it is read back safely. It formalizes
5
+ [ADR 0021: Assignment Leases and Stale-Claim Takeover](../../docs/adr/0021-assignment-leases-and-stale-claim-takeover.md)
6
+ as the third provider leg beside `WorkItemProvider` and `BoardProvider`
7
+ (`context/contracts/work-item-contract.md`) and `ChangeProvider`. GitHub is the first concrete
8
+ mapping for this contract, not the generic vocabulary — Linear, Jira, GitLab, and a local-file
9
+ provider for tracker-less repos and evals all map the same operations.
10
+
11
+ ## Terminology: three distinct "claim" concepts
12
+
13
+ This repository uses the word "claim" for three unrelated things. Always qualify in prose; never
14
+ write bare "claim" where the meaning is not obvious from surrounding context:
15
+
16
+ | Term | Layer | Meaning | Where it lives |
17
+ | --- | --- | --- | --- |
18
+ | **assignment claim** | this contract | Durable, provider-recorded ownership of a work item | `AssignmentProvider.claim()`, this doc |
19
+ | **liveness claim** | ADR 0012 | Ephemeral, TTL-reaped presence/heartbeat event | `liveness claim` (`workflow-sidecar.ts`), `freshHolders` (`liveness-read.js`) |
20
+ | **trust claim** | ADR 0010 / ADR 0017 | A Hachure `trust.bundle` evidence claim (kit-typed, verification status) | `workflow-sidecar.ts claim <id> <dir>` (`claimLookup`) |
21
+
22
+ An assignment claim's *effective state* is never computed from the assignment layer alone — it is
23
+ always joined against a liveness claim's freshness (see the join table below).
24
+
25
+ ## AssignmentProvider Operations
26
+
27
+ Provider-neutral interface, verbatim from ADR 0021 §2:
28
+
29
+ ```
30
+ AssignmentProvider:
31
+ claim(subjectId, actor, meta) # assign + attach machine-readable claim record
32
+ release(subjectId, actor, meta) # unassign + handoff note
33
+ supersede(subjectId, from, to, meta) # reassign with audit trail
34
+ status(subjectId) -> {assignee?, actor?, claimedAt?, meta?}
35
+ list(actor?) -> [subjectId]
36
+ ```
37
+
38
+ | Operation | Arguments | Effect | Returns |
39
+ | --- | --- | --- | --- |
40
+ | `claim` | `subjectId`, `actor`, `meta` | Records durable ownership: assignee (or local-file record), a claim marker, and a versioned claim record carrying the full actor struct. | void (caller re-reads via `status` to confirm) |
41
+ | `release` | `subjectId`, `actor`, `meta` | Clears durable ownership and leaves a handoff note. Normally invoked by the incumbent actor at clean session end. | void |
42
+ | `supersede` | `subjectId`, `from`, `to`, `meta` | Reassigns ownership from a lapsed actor to a successor, with an audit-trail note explaining why (staleness, explicit human confirmation). | void |
43
+ | `status` | `subjectId` | Reads current assignment state without joining liveness. | `{ assignee?, actor?, claimedAt?, meta? }` — the raw assignment-layer read |
44
+ | `list` | `actor?` (optional filter) | Enumerates subject ids currently claimed, optionally filtered to one actor. | `[subjectId]` |
45
+
46
+ `status()` alone is **assignment-layer truth only** — it does not tell a caller whether the claim
47
+ is fresh. Callers that need to decide "is this work available" must compute the join described
48
+ next, not call `status()` in isolation.
49
+
50
+ **Actor identity is runtime-agnostic**: `{ runtime, session_id, host, human? }`. Claude Code,
51
+ codex, opencode, and pi sessions, and humans, are all actors. Reuse the exact struct
52
+ `scripts/hooks/lib/actor-identity.js`'s `resolveActor`/`serializeActor` already define — do not
53
+ fork a second actor concept. `subject_id` reuses `workItemSlug`'s `owner/repo#id` convention
54
+ (the same string the deterministic session slug derives from) rather than inventing a second
55
+ identifier scheme.
56
+
57
+ ## The assignment ⋈ liveness join
58
+
59
+ Effective claim state is always a **join** of two layers with distinct jobs. Neither layer is
60
+ trusted alone:
61
+
62
+ | Layer | Records | Lifetime | Medium |
63
+ | --- | --- | --- | --- |
64
+ | **Assignment** | intent / ownership | durable, survives crashes | provider (issue assignee/label/comment, or local-file record) |
65
+ | **Liveness** | presence / freshness | ephemeral, TTL-reaped | ADR 0012 claim stream (`freshHolders`) |
66
+
67
+ Readers compute the join from `{ assignment: AssignmentStatus, freshHolders: FreshHolder[] }` to
68
+ one of five effective states:
69
+
70
+ | Assignment | Liveness | Effective state | `pull-work` treatment |
71
+ | --- | --- | --- | --- |
72
+ | assigned | fresh heartbeat | **held** | excluded |
73
+ | assigned | stale / absent | **reclaimable** | offered, via takeover protocol (ADR 0021 §5, out of this contract's scope) |
74
+ | assigned (human) | n/a (humans don't heartbeat) | **human-held** | surfaced, never auto-taken (see below) |
75
+ | unassigned | fresh (claim only) | **held** (assignment lagging) | excluded |
76
+ | unassigned | absent | **free** | offered |
77
+
78
+ This join rule is what makes lost locks structurally impossible: staleness — not assignment — is
79
+ what excludes, so an orphaned assignee/label/record from a dead session can never gate work.
80
+
81
+ The join is a pure function; reuse `scripts/hooks/lib/liveness-read.js`'s exported
82
+ `freshHolders(events, slug, selfActor, nowMs)` for the liveness half rather than re-implementing
83
+ TTL/staleness arithmetic. The join table itself (assignment status × liveness freshness →
84
+ effective state) is the only new logic — it has no other implementation in this repository.
85
+
86
+ ## Lazy-correction transition table
87
+
88
+ Nothing updates the provider on a timer; every mutation has a responsible actor, invoked at a
89
+ specific lifecycle moment (ADR 0021 §4). This table names every transition for completeness; it
90
+ also records which issue implements each row so a future reader does not assume any single issue
91
+ delivers all four:
92
+
93
+ | Transition | Mutator | When | Owning issue |
94
+ | --- | --- | --- | --- |
95
+ | `claim` | the claiming session | at selection | **#290 (this issue)** |
96
+ | `clean_release` | the incumbent (Stop hook / terminal `advance-state`) | session end — unassign + handoff comment | #292 |
97
+ | `supersede` | the successor, inside the takeover protocol | after the grace beat | #294 |
98
+ | `crash_no_successor` | nobody, initially; corrected by the next actor to want the subject, or the janitor | lazily, or on a janitor sweep | out of scope — the Console relay's first cross-machine duty (ADR 0021 §4/§7) |
99
+
100
+ Lazy correction is *safe* by the join rule above (stale assignment excludes nothing), but leaves
101
+ the human-visible board stale in the crash case until the next claim attempt or a janitor sweep
102
+ corrects it.
103
+
104
+ ## Human-assignee ask-first policy
105
+
106
+ Humans do not heartbeat, so `assigned-to-human + no-liveness` is **normal, not stale** — it must
107
+ never be treated as `reclaimable`. Agents surface idle human assignments ("assigned to `brian` 3
108
+ days ago, no linked activity — reclaim?") and act only on explicit confirmation.
109
+
110
+ - Default behavior: `ask_first`.
111
+ - The threshold (`idle_threshold_days`, default `3`, matching ADR 0021 §6's own illustrative
112
+ example) and behavior (`ask_first` | `never_reclaim`) are a policy knob in
113
+ `assignment-provider-settings.schema.json` (`policy.human_assignee_policy`), not a hardcoded
114
+ constant.
115
+ - **Non-goal:** never auto-supersede a human. The join function must gate on the actor struct's
116
+ `human` field being present (not absent/null) — never on a heuristic over the GitHub login name
117
+ — so a human assignee is always classified `human-held`, regardless of idle duration, and is
118
+ never silently reclaimed by any automated path.
119
+ - This contract and its settings schema define the policy knob and the read-side data a caller
120
+ needs to surface the question (assignee identity, idle duration). The actual "ask the user"
121
+ interaction belongs to whichever skill consumes the join result (`pull-work`), which records it
122
+ as an alignment question rather than a new UI surface.
123
+
124
+ ## Versioned claim-record format
125
+
126
+ The durable claim record is a single JSON object, versioned via `schema_version` so a reader can
127
+ detect an incompatible future shape before parsing fields it does not understand:
128
+
129
+ ```json
130
+ {
131
+ "schema_version": "1.0",
132
+ "role": "AssignmentClaimRecord",
133
+ "subject_id": "kontourai/flow-agents#290",
134
+ "actor": { "runtime": "claude-code", "session_id": "...", "host": "...", "human": null },
135
+ "claimed_at": "2026-07-02T00:00:00Z",
136
+ "ttl_seconds": 1800,
137
+ "branch": "agent/<actor>/<slug>",
138
+ "artifact_dir": ".kontourai/flow-agents/<slug>",
139
+ "status": "claimed"
140
+ }
141
+ ```
142
+
143
+ | Field | Required | Description |
144
+ | --- | --- | --- |
145
+ | `schema_version` | yes | Version of this record shape, `"1.0"`. |
146
+ | `role` | yes | Constant `"AssignmentClaimRecord"`, for readers scanning mixed content (e.g. a GitHub comment thread) for this record type. |
147
+ | `subject_id` | yes | The claimed work item, in `owner/repo#id` form — the same string `workItemSlug` derives the deterministic session slug from. |
148
+ | `actor` | yes | `{ runtime, session_id, host, human? }` — the exact struct `actor-identity.js` defines. `human` is set (non-null) only for a human assignee; its presence, not a username heuristic, gates the human-held join state. |
149
+ | `claimed_at` | yes | ISO-8601 timestamp the claim was recorded. Mirrors the liveness stream's own claim-event field so a reader compares freshness with one mental model across both layers, even though the two are stored in different media. |
150
+ | `ttl_seconds` | yes | Same field name/semantics as the liveness stream's `ttlSeconds` (default `1800`). |
151
+ | `branch` | yes | The branch this actor is working on, per the `agent/<actor>/<slug>` convention. |
152
+ | `artifact_dir` | yes | The deterministic workflow artifact directory for this subject, so a successor inherits the same session. |
153
+ | `status` | yes | `"claimed"` today; future transitions may add `"released"` / `"superseded"` values as the corresponding mutator (see the transition table) is implemented. |
154
+
155
+ **Versioning rule:** bump `schema_version` only on an incompatible change (matching
156
+ `artifact-contract.md`'s existing sidecar rule: "Keep `schema_version` at `1.0` until the schema
157
+ changes incompatibly"). Additive, optional fields do not require a bump.
158
+
159
+ ### GitHub mapping
160
+
161
+ The GitHub implementation represents the record as:
162
+
163
+ - **assignee**: the notification/board hook.
164
+ - **a single `agent:claimed` label** (default name, configurable via
165
+ `policy.label_name`): the board filter.
166
+ - **a machine-readable claim comment**: human-readable prose above a fenced JSON block containing
167
+ the record above, located via a fixed marker (default
168
+ `<!-- flow-agents:assignment-claim -->`, configurable via `policy.claim_comment_marker`) so the
169
+ board stays legible to a human without tooling, while remaining machine-parseable.
170
+
171
+ The comment carries identity because the assignee field cannot: N agent sessions typically share
172
+ one GitHub account, so per-session identity lives in the attached record. Each provider decides
173
+ what it can natively represent versus what goes in the record.
174
+
175
+ ### local-file mapping
176
+
177
+ For tracker-less repos and evals, the same record is written to a JSON file under the artifact
178
+ root (`<artifact-root>/assignment/<sanitized-subject-id>.json`), full read-modify-write, no
179
+ external process involved. This path is the one that does real I/O inside the CLI, since there is
180
+ no external mutation to defer to a skill.
181
+
182
+ **`--actor-json` asymmetry (fix-plan iteration 1, F5, intentional):** local-file `claim`/`release`
183
+ falls back to auto-resolving the actor from the live environment (`resolveActor(process.env)`) when
184
+ `--actor-json` is omitted; `render-claim`/`render-supersede` (GitHub) always require `--actor-json`
185
+ explicitly and fail loud without it. This is intentional, not a footgun to fix later — the
186
+ currently-wired `pull-work` skill always passes `--actor-json` for both provider kinds, so the
187
+ asymmetry has no effect on the shipped path today, but a future direct-CLI caller relying on
188
+ local-file `claim` to fail the same way `render-claim` does when the actor can't be determined
189
+ should pass `--actor-json` explicitly rather than relying on the auto-resolve fallback.
190
+
191
+ ## Implementation Note
192
+
193
+ `schemas/assignment-provider-settings.schema.json` is the settings schema companion to this
194
+ contract — it configures `provider.kind` (`github` | `local-file`), the label name, the claim
195
+ comment marker, and the human-assignee policy knob described above.
196
+
197
+ The GitHub write path follows this repository's `publish-change-helper.ts` precedent — **render,
198
+ don't execute**: the CLI emits the exact, deterministic `gh` argv and comment body for a mutation
199
+ as a pure function; the calling skill runs that exact argv via its Bash tool (never freehand `gh`
200
+ text), then re-fetches and feeds the result back into the CLI's `status` parser to close the round
201
+ trip. This is a deliberate divergence from a literal "CLI shells out to `gh` itself" design: it
202
+ matches the one existing same-shaped precedent in this repository, keeps operational concerns
203
+ (auth, rate limiting, retries, partial-failure sequencing across separate `gh` calls) at the skill
204
+ layer where Bash-tool error handling already lives, and avoids introducing this repository's first
205
+ `execFileSync`-to-`gh` code path inside compiled code without a dedicated decision. A future
206
+ implementer of the GitHub mutation path should read this note, not re-derive the reasoning from
207
+ scratch — see the `#290` plan artifact's Design Decision 1 for the full evidence trail if more
208
+ detail is needed.
209
+
210
+ **`gh_commands` execution contract (fix-plan iteration 1, F4):** every `render-*` subcommand's
211
+ `gh_commands` field is a JSON array of **argv arrays** (one element per `gh` argument), not a
212
+ shell-command string. The calling skill MUST execute each entry as argv — every element passed as
213
+ its own separate Bash-tool argument — and MUST NOT concatenate the elements into a single shell
214
+ string or run them via `bash -c`/shell re-interpretation. This matters specifically because
215
+ `claim_comment_body` (and, for `render-supersede`, `previous_record`) can carry attacker-influenced
216
+ text: any GitHub user who can comment on the issue can post a forged claim-marker comment (see the
217
+ sanitization note below), and reconstructing a shell string from that content would reintroduce a
218
+ shell-injection surface the render/execute split is designed to avoid. This is stated explicitly in
219
+ `kits/builder/skills/pull-work/SKILL.md`'s "Assignment Claim On Selection" section as well — this
220
+ paragraph is the contract-side mirror of that instruction, not a duplicate source of truth to drift
221
+ from it.
222
+
223
+ **Untrusted claim-record fields (fix-plan iteration 1, F2):** `extractClaimRecord()` parses the
224
+ fenced JSON claim record out of *any* issue comment matching the claim marker — posting a comment
225
+ requires no elevated access, unlike the assignee/label mutations this contract otherwise gates. Its
226
+ returned record's string fields (`subject_id`, `branch`, `artifact_dir`, `actor.*`, including
227
+ `actor.human`, and each `audit_trail` entry's actor/`reason`) are therefore treated as untrusted
228
+ display input and are run through a control-character-strip + length-cap sanitizer (mirroring
229
+ `workflow-sidecar.ts`'s `stripControlCharsForDisplay`, the established #287/#320 mitigation for
230
+ this input class) before they leave the module in `status`/`list` output. This is display-only — it
231
+ never changes the assignment ⋈ liveness join's classification logic, only the surfaced string
232
+ content.
233
+
234
+ No part of this system writes to the provider on a timer or heartbeats to it (ADR 0021 §4/§7).
235
+ Every write is triggered by a specific lifecycle moment named in the transition table above. The
236
+ one optional exception is a comment-body refresh at an explicit phase transition
237
+ (`policy.comment_refresh_on_phase_transition`, default `false`) — cheap because it reuses the same
238
+ render path already built for `claim`, just called again with an updated `status`/`claimed_at`
239
+ field.
@@ -1,5 +1,7 @@
1
1
  # Builder Kit Workflow State Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  This contract defines the durable routing state for Builder Kit build-flow pickup and resume. It keeps `state.json` limited to canonical Flow Agents workflow routing and stores rich pickup decisions in a documented Probe record sidecar referenced by `state.json`, `handoff.json`, and the session artifact.
4
6
 
5
7
  Builder Kit owns product-level build-flow coordination. Flow Agents owns the workflow harness, artifact conventions, primitive execution, and evidence gates. Flow remains the owner of Flow gate semantics. Builder Kit contracts may route to Flow Agents primitives, but must not redefine pass, fail, approval, or release authority semantics.
@@ -1,5 +1,7 @@
1
1
  # Decision Registry Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Normative contract for the topic-keyed decision registry. This is the source of
4
6
  truth for how NEW decisions are recorded in a Kontour repository. Numbered ADRs
5
7
  under `docs/adr/` are frozen history and are never written for new decisions
@@ -1,5 +1,7 @@
1
1
  # Delivery Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Delivery chains planning, execution, review, verification, Goal Fit, and final acceptance until the user-facing goal is genuinely handled.
4
6
 
5
7
  ## Workflow
@@ -1,5 +1,7 @@
1
1
  # Execution Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Execution turns an approved plan artifact into code and local evidence while preserving parallel safety.
4
6
 
5
7
  ## Required Inputs
@@ -42,6 +44,29 @@ Execution may upgrade to a stronger mode when risk increases. Downgrades require
42
44
  - Worker delegation must name the exact worker role (`tool-worker`) rather than spawning an unnamed/default implementation agent.
43
45
  - After each wave, collect results, check conflicts, and update the session artifact before starting the next wave.
44
46
 
47
+ ## Delegation: Model Routing
48
+
49
+ Model-routing policy is data, not code: `.datum/config.json` (schema:
50
+ `@kontourai/datum`, provider/role registry) is the source of truth for which
51
+ model backs which delegate role. This contract only says where to read it.
52
+
53
+ - When spawning a delegate, resolve its role via `npx @kontourai/datum resolve <role> --json`
54
+ (or the `@kontourai/datum` library's `resolve()`) and pass the resolved
55
+ `model` explicitly to the delegate invocation. Do not hardcode a model name
56
+ in a skill, agent definition, or generated file — the role name is the
57
+ stable reference; the model it resolves to can change without touching
58
+ delegation call sites.
59
+ - Role selection follows task shape, not delegate identity: fully-specified
60
+ mechanical tasks resolve `delegate-mechanical`; precisely-planned
61
+ implementation tasks resolve `delegate-implementation`; tasks needing design
62
+ latitude resolve `delegate-design`. The orchestrator's own model (planning,
63
+ gates, adversarial verification) resolves `orchestrator`, typically inherited
64
+ rather than overridden.
65
+ - When `datum` (the CLI/library) or `.datum/config.json` is absent or a role
66
+ fails to resolve, fall back to the runtime's inherited model and note the
67
+ fallback in the session/task artifact — do not block delegation on datum
68
+ being present.
69
+
45
70
  ## Completion Rules
46
71
 
47
72
  Execution is complete only when:
@@ -1,5 +1,7 @@
1
1
  # Governance Adapter Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Governance adapters let Flow Agents ask an external tool for policy, proof, or trust evidence without baking repo-specific policy into Flow Agents core.
4
6
 
5
7
  ## Boundary
@@ -0,0 +1,197 @@
1
+ # Knowledge Store Provider Contract
2
+
3
+ Normative contract for the Knowledge Kit's typed graph MODEL and its pluggable
4
+ storage/sync PROVIDERS. The model (nodes, edges, provenance) is owned by the Kit;
5
+ storage and synchronisation are provider concerns. The same ingest/link/health
6
+ verbs run over every provider, so backlog hygiene is a knowledge-health pass over
7
+ the work-item provider rather than a bespoke tool.
8
+
9
+ Machine-checkable structure lives in `schemas/knowledge/*.schema.json`
10
+ (`node`, `edge`, `proposal`, `health-report`). Reference implementations live in
11
+ `kits/knowledge/providers/` (markdown-vault, git-repo, work-item) with health
12
+ verbs in `kits/knowledge/providers/health/` and the conformance suite in
13
+ `kits/knowledge/providers/conformance/`.
14
+
15
+ This contract applies the portfolio provider pattern (hachure format/adapters;
16
+ Builder Kit work-item contract/providers) to knowledge, which until now was fused
17
+ to one storage shape (the markdown vault).
18
+
19
+ ## 1. Graph model
20
+
21
+ ### 1.1 Node
22
+
23
+ A node is a typed, identified unit of knowledge. Validated by
24
+ `schemas/knowledge/node.schema.json`.
25
+
26
+ | Field | Required | Meaning |
27
+ | --- | --- | --- |
28
+ | `id` | yes | Provider-scoped stable id, unique within a single `readGraph` result. |
29
+ | `type` | yes | Node type (see §1.3). EXTENSIBLE. |
30
+ | `title` | yes | Human-readable title; the basis for duplicate detection. |
31
+ | `body` | no | Primary content; format is provider/type-specific. |
32
+ | `attributes` | no | Provider-specific structured fields (issue state/labels, decision status, note category/tags). |
33
+ | `provenance` | yes | Where this node was read from (§1.4). |
34
+
35
+ ### 1.2 Edge
36
+
37
+ An edge is a typed, directed relationship. Validated by
38
+ `schemas/knowledge/edge.schema.json`.
39
+
40
+ | Field | Required | Meaning |
41
+ | --- | --- | --- |
42
+ | `id` | yes | Provider-scoped stable edge id. |
43
+ | `type` | yes | One of the CLOSED edge vocabulary (§1.3). |
44
+ | `from` | yes | Source node id. |
45
+ | `to` | yes | Target node id. |
46
+ | `resolved` | no | `true` (default) means `to` is expected to resolve to a node in the same graph (an internal dependency link); `false` marks a deliberate external reference (e.g. `evidence-of` a PR outside the store). |
47
+ | `attributes` | no | Provider-specific structured fields. |
48
+ | `provenance` | yes | Where this edge was derived from (§1.4). |
49
+
50
+ ### 1.3 Vocabularies
51
+
52
+ **Node types are EXTENSIBLE.** The recommended core vocabulary is `note`,
53
+ `decision`, `issue`, `session`, `person`. Providers MAY emit additional types;
54
+ consumers MUST NOT reject unknown node types.
55
+
56
+ **Edge types are CLOSED** (schema-enforced enum):
57
+
58
+ | Edge type | Direction | Meaning |
59
+ | --- | --- | --- |
60
+ | `supersedes` | from → to | `from` replaces `to`. |
61
+ | `merged-into` | from → to | `from` was folded into `to`. |
62
+ | `blocks` | from → to | `from` must complete before `to`. |
63
+ | `evidence-of` | from → to | `from` is provenance/evidence for `to`. |
64
+ | `mentions` | from → to | `from` references `to` in prose. |
65
+ | `relates` | from → to | General association. |
66
+
67
+ Adding an edge type is a contract change (schema + this table), because health
68
+ verbs reason over the closed vocabulary (e.g. `blocks` is the dependency-link
69
+ relation).
70
+
71
+ ### 1.4 Provenance
72
+
73
+ Provenance appears on BOTH nodes and edges. Every assertion in the graph is
74
+ traceable back to the store it came from — no orphan facts.
75
+
76
+ | Field | Required | Meaning |
77
+ | --- | --- | --- |
78
+ | `provider` | yes | Provider id that produced the element. |
79
+ | `source` | yes | Concrete origin: a file path, an issue ref, or a URL. |
80
+ | `locator` | no | Finer locator (line, anchor, frontmatter field, link kind). |
81
+ | `retrieved_at` | yes | ISO-8601 read timestamp. |
82
+ | `agent` | no | Agent/process id that performed the read. |
83
+
84
+ ## 2. Provider interface
85
+
86
+ A provider is an ESM module exporting a default class constructed with a
87
+ provider-specific options object. Every provider implements the READ interface
88
+ and the PROPOSALS-ONLY write interface.
89
+
90
+ ### 2.1 Read
91
+
92
+ | Method | Signature | Behaviour |
93
+ | --- | --- | --- |
94
+ | `capabilities()` | `() => Capabilities` | Static description (§2.3). Synchronous. |
95
+ | `readNodes(options?)` | `(o?) => Promise<Node[]>` | All nodes; `options.type` filters by node type. Every node schema-valid. |
96
+ | `readEdges()` | `() => Promise<Edge[]>` | All edges; every edge schema-valid and in the closed vocabulary. |
97
+ | `queryByType(type)` | `(t) => Promise<Node[]>` | Convenience: the nodes of one type (a consistent subset of `readNodes`). |
98
+ | `readGraph()` | `() => Promise<{nodes, edges}>` | The full graph. |
99
+
100
+ ### 2.2 Write — proposals only
101
+
102
+ Knowledge stores are HUMAN-CURATED. No provider auto-writes to a store. The only
103
+ write surface is:
104
+
105
+ | Method | Signature | Behaviour |
106
+ | --- | --- | --- |
107
+ | `proposeWrite(intent)` | `(i) => Promise<Proposal>` | Returns a `Proposal` (§2.4) describing an intended change. MUST NOT mutate the store, disk, or the remote board. |
108
+
109
+ **How proposals-only is expressed structurally:** the `Proposal` schema fixes
110
+ `status` to the const `"proposed"`. A provider can never return an applied
111
+ proposal, and the conformance suite asserts that `readNodes()` is byte-stable
112
+ across a `proposeWrite` call. Enactment happens DOWNSTREAM of the provider:
113
+
114
+ - **git-repo** renders a decision-registry topic file (frontmatter compatible
115
+ with `schemas/decision-record.schema.json`) shaped for the **promote sub-flow**
116
+ (`docs/decisions/promotion-gate.md`). The promote step is the enactor.
117
+ - **work-item** renders a draft `gh issue comment` / `gh issue edit --add-label`
118
+ the operator files. The provider never runs a mutating `gh` command.
119
+ - **markdown-vault** renders a note in native frontmatter + `[[wikilink]]` form
120
+ for the Knowledge Kit ingest/compile flow to file.
121
+
122
+ This is the same proposals-only discipline as the store contract's
123
+ `propose → apply/reject` gate and the ADR-0003-style "proposals, never silent
124
+ writes to a curated store" precedent. A provider is the propose half; a human or
125
+ a gated flow is the apply half.
126
+
127
+ ### 2.3 Capabilities
128
+
129
+ `capabilities()` returns `{ id, node_types, edge_types, writable, write_mode,
130
+ proposal_targets, source_of_truth }`. `writable` is always `false` and
131
+ `write_mode` is always `"proposals-only"` in this version — a graph-database
132
+ provider that owns its store is a separate, gated spike (issue #318), not a
133
+ loophole here.
134
+
135
+ ### 2.4 Proposal
136
+
137
+ Validated by `schemas/knowledge/proposal.schema.json`.
138
+
139
+ | Field | Required | Meaning |
140
+ | --- | --- | --- |
141
+ | `schema_version` | yes | `"1.0"`. |
142
+ | `provider` | yes | Emitting provider id. |
143
+ | `kind` | yes | `create-node` \| `update-node` \| `add-edge` \| `comment` \| `label` \| `decision-topic`. |
144
+ | `target` | yes | Where the change would land (descriptive only). |
145
+ | `payload` | no | The structured node/edge/field proposed. |
146
+ | `rendered` | no | The provider-native form an enactor would apply. |
147
+ | `status` | yes | The const `"proposed"`. |
148
+ | `rationale` | no | Why the change is proposed. |
149
+ | `provenance` | yes | §1.4. |
150
+
151
+ ## 3. Health verbs
152
+
153
+ Health verbs are PROVIDER-AGNOSTIC pure functions over a graph
154
+ (`{ nodes, edges }`) — they never inspect provider internals, which is what lets
155
+ one health command run identically over any provider (R4). Each returns a
156
+ schema-valid Knowledge Health Report
157
+ (`schemas/knowledge/health-report.schema.json`). Reference verbs live in
158
+ `kits/knowledge/providers/health/`.
159
+
160
+ | Verb | Report `check` | Behaviour |
161
+ | --- | --- | --- |
162
+ | `detectDuplicates(graph, o?)` | `duplicate-detection` | Flags node pairs whose titles match / are highly similar (token Jaccard ≥ `threshold`, default 0.7; same-type by default). |
163
+ | `checkDependencyLinkIntegrity(graph, o?)` | `dependency-link-integrity` | Flags dependency edges (default type `blocks`) whose endpoints are missing from the graph, unless the edge is `resolved:false` (deliberate external ref). |
164
+
165
+ Every finding cites its evidence (the compared titles + similarity, or the edge
166
+ + which endpoint is missing). A report's `summary.finding_count` MUST equal
167
+ `findings.length`.
168
+
169
+ ## 4. Reference providers
170
+
171
+ | Provider | Reads | Node types | Write proposal |
172
+ | --- | --- | --- | --- |
173
+ | `markdown-vault` | The existing Knowledge Kit store (markdown + frontmatter + `[[wikilinks]]` + graph index) via an existing store adapter — thin wrapper, existing skills unaffected. | `note`, `person` | `create-node` rendered as a native vault note. |
174
+ | `git-repo` | `docs/decisions/*` (tombstones → `supersedes`/`merged-into`; `evidence[]` → `evidence-of`), CONTEXT.md glossary, `docs/learnings/*`. | `decision`, `note` | `decision-topic` shaped for the promote sub-flow. |
175
+ | `work-item` | GitHub issues via an INJECTABLE runner (default `gh`), `flow-agents:work-item-metadata` blockers + prose refs → `blocks`/`relates`. | `issue` | `comment` / `label` draft. |
176
+
177
+ ## 5. Conformance
178
+
179
+ `kits/knowledge/providers/conformance/suite.test.js` is the parameterized
180
+ conformance suite EVERY provider must pass (issue #317, AC3). It asserts:
181
+ capabilities declare proposals-only/non-writable; `readNodes` returns unique,
182
+ schema-valid nodes with provider provenance; `readEdges` returns schema-valid
183
+ edges in the closed vocabulary; `queryByType` is a consistent subset of
184
+ `readNodes`; `readGraph` returns `{nodes, edges}`; and `proposeWrite` returns a
185
+ schema-valid `"proposed"` proposal that mutates nothing.
186
+
187
+ `kits/knowledge/providers/health/health-pass.test.js` covers AC1 (the same
188
+ health command over vault + git-repo yields schema-valid reports) and AC2 (a
189
+ backlog pass over the work-item adapter, driven by a SEEDED fixture, flags a
190
+ duplicate and a broken blocker link — never the real board).
191
+
192
+ ## 6. Non-goals
193
+
194
+ Graph-database provider (issue #318 spike; adoption gated on a real query failing
195
+ over files); embeddings; auto-writes to any human-curated store; personal-vault
196
+ feature changes. This contract adds a model + read/propose interface over stores
197
+ operated today; it does not change how those stores are curated.
@@ -1,5 +1,7 @@
1
1
  # Planning Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Planning turns a user goal into an executable implementation plan without writing production code.
4
6
 
5
7
  ## Required Inputs
@@ -1,5 +1,7 @@
1
1
  # Review Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Review is report-only critique. It asks whether the changed code is maintainable, secure, consistent with project standards, and structurally sound before verification proves behavior.
4
6
 
5
7
  ## Purpose
@@ -1,5 +1,7 @@
1
1
  # Sandbox Policy Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Workflow artifacts must name the execution boundary required for the work. The sandbox mode is a planning and delegation contract, not a substitute for the runtime permission model.
4
6
 
5
7
  ## Canonical Modes
@@ -0,0 +1,13 @@
1
+ # Standing Directives Contract
2
+
3
+ Ratified owner directives are corrections and policies the owner has explicitly settled across sessions. They override default engineering conservatism (e.g. "keep the old path working just in case") wherever they apply. Each directive below is a durable, quotable statement — cite the number, not a paraphrase from memory. This file exists so a delegated agent that only sees one task in a plan still receives the same standing policy the orchestrator ratified; correction that lives only in one session's ephemeral context does not reach the agents who need it.
4
+
5
+ Read this file alongside the phase contract for the work you are doing. When a standing directive and a phase contract's default guidance conflict, the standing directive wins; if you believe a directive does not fit the situation, stop and report the conflict rather than silently picking a side.
6
+
7
+ ## Directives
8
+
9
+ 1. **No legacy fallbacks: migrate hard.** A compatibility path that keeps defective or superseded behavior reachable is prohibited regardless of how short-lived or self-healing it is; the remedy for old artifacts is a loud typed error naming the regeneration command. (Ratified July 2026. Evidence: [datum#3](https://github.com/kontourai/datum/pull/3) hard cutover to `.datum/config.json`; the fallback-removal follow-up to [flow-agents#344](https://github.com/kontourai/flow-agents/pull/344), tracked as [flow-agents#347](https://github.com/kontourai/flow-agents/issues/347).)
10
+ 2. **Durable vs runtime paths: never invert them.** `.kontourai/` is gitignored runtime state; `.<product>/` (e.g. `.veritas/`, `.datum/`) is a product's durable, committed directory in consuming repos. (Ratified July 2026. Evidence: `.veritas/` precedent and the `.kontourai/` vs `.veritas/` distinction in [docs/repository-structure.md](../../docs/repository-structure.md); [datum#3](https://github.com/kontourai/datum/pull/3) `.datum/config.json`; [surface#110](https://github.com/kontourai/surface/issues/110), [surface#113](https://github.com/kontourai/surface/pull/113).)
11
+ 3. **Evidence must be executable.** Check summaries/excerpts are runnable-from-repo-root commands (`true # ...` for prose that has no command); probe scripts live inside the session dir; reconcilable claims use manifest-exact bare labels. (Ratified July 2026.)
12
+ 4. **Standalone-first, then integrate.** Build pieces to provide value alone, then push them through a real product integration to pressure-test the abstractions; file upstream issues for friction found during integration rather than working around it. (Ratified July 2026.)
13
+ 5. **Regression rule for cutovers.** Proper before/after tests are required for any cutover; large regressions (e.g. more than 40% record loss, or a whole field-class disappearing) are surfaced as an explicit owner decision with data, never papered over — but a large regression does not block the cutover by default. (Ratified July 2026.)
@@ -1,5 +1,7 @@
1
1
  # Verification Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  Verification is report-only. It proves whether the implementation satisfies the plan, the Definition Of Done, and the original user outcome.
4
6
 
5
7
  ## Required Inputs
@@ -1,5 +1,7 @@
1
1
  # Work Item Contract
2
2
 
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
3
5
  This contract defines the provider-neutral vocabulary for selecting, planning, and handing off backlog work. It is the source shape for provider-backed workflows such as `pull-work`; provider-specific adapters map into this model without making GitHub, Jira, Linear, or any other provider the generic language.
4
6
 
5
7
  ## Provider Roles
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "../../schemas/assignment-provider-settings.schema.json",
3
+ "schema_version": "1.0",
4
+ "projects": [
5
+ {
6
+ "project": {
7
+ "repo": {
8
+ "owner": "kontourai",
9
+ "name": "flow-agents",
10
+ "url": "https://github.com/kontourai/flow-agents"
11
+ }
12
+ },
13
+ "provider": {
14
+ "kind": "github",
15
+ "repo": {
16
+ "owner": "kontourai",
17
+ "name": "flow-agents",
18
+ "url": "https://github.com/kontourai/flow-agents"
19
+ },
20
+ "capabilities": ["assignees", "labels", "comments"]
21
+ },
22
+ "policy": {
23
+ "label_name": "agent:claimed",
24
+ "claim_comment_marker": "<!-- flow-agents:assignment-claim -->",
25
+ "human_assignee_policy": {
26
+ "behavior": "ask_first",
27
+ "idle_threshold_days": 3
28
+ },
29
+ "comment_refresh_on_phase_transition": false
30
+ }
31
+ }
32
+ ]
33
+ }