@kontourai/flow-agents 3.0.0 → 3.1.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 (131) hide show
  1. package/.github/workflows/ci.yml +8 -0
  2. package/CHANGELOG.md +15 -0
  3. package/CONTEXT.md +67 -1
  4. package/README.md +4 -0
  5. package/build/src/cli/assignment-provider.d.ts +143 -0
  6. package/build/src/cli/assignment-provider.js +106 -49
  7. package/build/src/cli/workflow-sidecar.d.ts +2 -2
  8. package/build/src/cli/workflow-sidecar.js +392 -35
  9. package/build/src/lib/flow-resolver.d.ts +12 -6
  10. package/build/src/lib/flow-resolver.js +30 -14
  11. package/build/src/tools/validate-source-tree.js +2 -1
  12. package/context/contracts/assignment-provider-contract.md +9 -0
  13. package/context/contracts/probe-docs-write-contract.md +187 -0
  14. package/context/scripts/hooks/config-protection.js +14 -1
  15. package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
  16. package/context/scripts/hooks/stop-goal-fit.js +4 -2
  17. package/context/scripts/hooks/workflow-steering.js +42 -0
  18. package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
  19. package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
  20. package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
  21. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  22. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  23. package/docs/adr/0006-typescript-first-source-policy.md +2 -0
  24. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  25. package/docs/adr/0007-skill-audit.md +2 -0
  26. package/docs/adr/0008-kit-operation-boundary.md +2 -0
  27. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
  28. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
  29. package/docs/adr/0011-mcp-posture.md +2 -0
  30. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
  31. package/docs/adr/0013-context-lifecycle.md +2 -0
  32. package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
  33. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
  34. package/docs/adr/0016-three-hard-boundary-model.md +2 -0
  35. package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
  36. package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
  37. package/docs/adr/0019-kit-dependency-ownership.md +2 -0
  38. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
  39. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
  40. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +2 -0
  41. package/docs/adr/README.md +49 -0
  42. package/docs/adr/index.md +34 -0
  43. package/docs/decisions/agent-coordination.md +20 -0
  44. package/docs/decisions/anti-gaming-trust-security.md +20 -0
  45. package/docs/decisions/context-lifecycle.md +18 -0
  46. package/docs/decisions/core-domain-kit-boundary.md +18 -0
  47. package/docs/decisions/flow-flow-agents-boundary.md +18 -0
  48. package/docs/decisions/flow-kit.md +20 -0
  49. package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
  50. package/docs/decisions/graph-knowledge-provider.md +63 -0
  51. package/docs/decisions/hook-core-kit-boundary.md +18 -0
  52. package/docs/decisions/index.md +19 -0
  53. package/docs/decisions/kit-dependency-ownership.md +18 -0
  54. package/docs/decisions/kit-operation-boundary.md +18 -0
  55. package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
  56. package/docs/decisions/kontour-resource-contract.md +18 -0
  57. package/docs/decisions/mcp-posture.md +18 -0
  58. package/docs/decisions/three-hard-boundary-model.md +18 -0
  59. package/docs/decisions/trust-reconcile.md +20 -0
  60. package/docs/decisions/typescript-source-policy.md +48 -0
  61. package/docs/decisions/workflow-enforcement.md +18 -0
  62. package/docs/decisions/workflow-trust-state.md +20 -0
  63. package/docs/fixture-ownership.md +1 -1
  64. package/docs/workflow-usage-guide.md +1 -1
  65. package/evals/ci/run-baseline.sh +4 -0
  66. package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
  67. package/evals/integration/test_current_json_per_actor.sh +516 -0
  68. package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
  69. package/evals/integration/test_gate_lockdown.sh +10 -0
  70. package/evals/integration/test_pull_work_liveness_preflight.sh +6 -1
  71. package/evals/integration/test_workflow_sidecar_writer.sh +12 -3
  72. package/evals/run.sh +4 -0
  73. package/evals/static/test_knowledge_providers.sh +13 -4
  74. package/evals/static/test_workflow_skills.sh +15 -2
  75. package/kits/builder/skills/deliver/SKILL.md +17 -0
  76. package/kits/builder/skills/design-probe/SKILL.md +37 -0
  77. package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
  78. package/kits/knowledge/adapters/default-store/index.js +92 -4
  79. package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
  80. package/kits/knowledge/adapters/shared/codec.js +141 -0
  81. package/kits/knowledge/docs/README.md +121 -2
  82. package/kits/knowledge/docs/store-contract.md +112 -4
  83. package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
  84. package/kits/knowledge/flows/promote.flow.json +84 -0
  85. package/kits/knowledge/kit.json +15 -0
  86. package/kits/knowledge/promote/distill.js +96 -0
  87. package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
  88. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
  89. package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
  90. package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
  91. package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
  92. package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
  93. package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
  94. package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
  95. package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
  96. package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
  97. package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
  98. package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
  99. package/kits/knowledge/promote/fixtures/session/state.json +19 -0
  100. package/kits/knowledge/promote/health.js +137 -0
  101. package/kits/knowledge/promote/index.js +176 -0
  102. package/kits/knowledge/promote/ingest.js +103 -0
  103. package/kits/knowledge/promote/lib.js +132 -0
  104. package/kits/knowledge/promote/link.js +84 -0
  105. package/kits/knowledge/promote/promote.test.js +174 -0
  106. package/kits/knowledge/providers/conformance/suite.test.js +18 -0
  107. package/kits/knowledge/providers/index.js +1 -0
  108. package/kits/knowledge/providers/neo4j/connection.js +121 -0
  109. package/kits/knowledge/providers/neo4j/cypher.js +190 -0
  110. package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
  111. package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
  112. package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
  113. package/kits/knowledge/providers/neo4j/index.js +280 -0
  114. package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
  115. package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
  116. package/kits/knowledge/providers/neo4j/sync.js +235 -0
  117. package/package.json +4 -2
  118. package/scripts/README.md +1 -0
  119. package/scripts/freeze-adrs.mjs +364 -0
  120. package/scripts/hooks/config-protection.js +14 -1
  121. package/scripts/hooks/evidence-capture.js +4 -1
  122. package/scripts/hooks/lib/config-protection-remedies.js +9 -0
  123. package/scripts/hooks/lib/current-pointer.js +123 -0
  124. package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
  125. package/scripts/hooks/stop-goal-fit.js +4 -2
  126. package/scripts/hooks/workflow-steering.js +42 -0
  127. package/scripts/statusline/flow-agents-statusline.js +3 -1
  128. package/src/cli/assignment-provider.ts +137 -55
  129. package/src/cli/workflow-sidecar.ts +430 -33
  130. package/src/lib/flow-resolver.ts +35 -14
  131. package/src/tools/validate-source-tree.ts +2 -1
@@ -14,6 +14,8 @@
14
14
  */
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
+ import { createRequire } from "node:module";
18
+ import { fileURLToPath } from "node:url";
17
19
  // ─── Security: Layer 1 traversal defense ─────────────────────────────────────
18
20
  //
19
21
  // Both kitId and flowName originate from agent-writable sources (active_flow_id
@@ -270,30 +272,44 @@ function findRepoRoot(startDir) {
270
272
  // Fallback: process.cwd() covers the common "run from repo root" case
271
273
  return process.cwd();
272
274
  }
275
+ /**
276
+ * Delegate to the shared pure-CJS per-actor current-pointer reader
277
+ * (scripts/hooks/lib/current-pointer.js), mirroring the exact createRequire idiom
278
+ * workflow-sidecar.ts already uses for its own cross-boundary CJS helper reuse (Wave 2 Task 2.2,
279
+ * #291). Deliberately NO inline duplicate fallback — this is the single choke point for the
280
+ * per-actor-first/legacy-fallback compat-shim rule; a second hand-rolled reader here would let it
281
+ * drift from every other consumer.
282
+ */
283
+ function loadCurrentPointerHelper() {
284
+ const _req = createRequire(import.meta.url);
285
+ const helperPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../scripts/hooks/lib/current-pointer.js");
286
+ return _req(helperPath);
287
+ }
273
288
  /**
274
289
  * Resolve the active flow step from current.json.
275
290
  *
276
- * Reads active_flow_id and active_step_id from <flowAgentsDir>/current.json.
277
- * If both are present, delegates to resolveFlowStep. The repoRoot is derived by
278
- * walking upward from flowAgentsDir to find the nearest ancestor containing kits/,
279
- * with a fallback to process.cwd(). This handles temp dirs, CI workspaces, and
280
- * subproject layouts without hardcoding the repo structure.
291
+ * Reads active_flow_id and active_step_id via the shared per-actor current-pointer helper
292
+ * (#291 Wave 2 Task 2.2): when `actorKey` is a resolved actor with its own
293
+ * `current/<actor>.json` projection, that file is preferred; otherwise (no actorKey, an
294
+ * unresolved actor, or no per-actor file yet) this falls straight back to the legacy global
295
+ * `<flowAgentsDir>/current.json` IDENTICAL to this function's pre-#291 behavior for every
296
+ * caller that does not pass `actorKey`. The repoRoot is derived by walking upward from
297
+ * flowAgentsDir to find the nearest ancestor containing kits/, with a fallback to
298
+ * process.cwd(). This handles temp dirs, CI workspaces, and subproject layouts without
299
+ * hardcoding the repo structure.
281
300
  *
282
301
  * @param flowAgentsDir Path to the runtime artifact root directory (contains current.json).
302
+ * @param actorKey Optional resolved actor identity (Wave 2 Task 2.1's `writeCurrent()` dual-write
303
+ * key) — when omitted, behavior is unchanged (legacy-file-only, exactly as before #291).
283
304
  * @returns ActiveFlowStep or null when fields are absent or resolution fails.
284
305
  */
285
- export function resolveActiveFlowStep(flowAgentsDir) {
306
+ export function resolveActiveFlowStep(flowAgentsDir, actorKey) {
286
307
  if (!flowAgentsDir)
287
308
  return null;
288
- const currentFile = path.join(flowAgentsDir, "current.json");
289
- let current;
290
- try {
291
- const raw = fs.readFileSync(currentFile, "utf8");
292
- current = JSON.parse(raw);
293
- }
294
- catch {
309
+ const pointer = loadCurrentPointerHelper().readCurrentPointer(flowAgentsDir, actorKey);
310
+ if (pointer.source === "none" || !pointer.payload)
295
311
  return null;
296
- }
312
+ const current = pointer.payload;
297
313
  const flowId = typeof current["active_flow_id"] === "string" ? current["active_flow_id"] : null;
298
314
  const stepId = typeof current["active_step_id"] === "string" ? current["active_step_id"] : null;
299
315
  if (!flowId || !stepId)
@@ -83,6 +83,7 @@ const hookFilePolicies = new Map([
83
83
  ["scripts/hooks/lib/actor-identity.js", { category: "shared hook library", requiredNeedles: ["resolveActor", "FLOW_AGENTS_ACTOR"] }],
84
84
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
85
85
  ["scripts/hooks/lib/config-protection-remedies.js", { category: "shared hook library", requiredNeedles: ["SANCTIONED_REMEDIES", "REMEDY_COMMAND_CANDIDATES"] }],
86
+ ["scripts/hooks/lib/current-pointer.js", { category: "shared hook library", requiredNeedles: ["readCurrentPointer", "perActorCurrentFile"] }],
86
87
  ["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
87
88
  ["scripts/hooks/lib/liveness-heartbeat.js", { category: "shared hook library", requiredNeedles: ["maybeEmitHeartbeat", "resolveHeartbeatThrottleSeconds"] }],
88
89
  ["scripts/hooks/lib/liveness-policy.js", { category: "shared hook library", requiredNeedles: ["isLivenessEnabled", "resolveTtlSeconds"] }],
@@ -93,7 +94,7 @@ const hookFilePolicies = new Map([
93
94
  ["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
94
95
  ]);
95
96
  const fixtureOwnerPolicies = new Map([
96
- ["evals/fixtures/assignment-provider", { owners: ["evals/integration/test_assignment_provider_local_file.sh", "evals/integration/test_assignment_provider_github.sh", "evals/integration/test_pull_work_assignment_join.sh"], classification: "AssignmentProvider local-file and GitHub render/status fixtures (#290)" }],
97
+ ["evals/fixtures/assignment-provider", { owners: ["evals/integration/test_assignment_provider_local_file.sh", "evals/integration/test_assignment_provider_github.sh", "evals/integration/test_pull_work_assignment_join.sh", "evals/integration/test_ensure_session_ownership_guard.sh"], classification: "AssignmentProvider local-file and GitHub render/status fixtures (#290); hostile-effective-state.json is the #291 ensure-session ownership guard's AC9 sanitization fixture" }],
97
98
  ["evals/fixtures/backlog-provider-settings", { owners: ["evals/integration/test_effective_backlog_settings.sh"], classification: "settings precedence fixtures" }],
98
99
  ["evals/fixtures/builder-kit-workflow-state", { owners: ["evals/static/test_workflow_skills.sh"], classification: "Builder Kit workflow-state fixtures" }],
99
100
  ["evals/fixtures/console-learning-projection", { owners: ["evals/integration/test_console_learning_projection.sh"], classification: "console learning projection fixtures" }],
@@ -93,10 +93,18 @@ delivers all four:
93
93
  | Transition | Mutator | When | Owning issue |
94
94
  | --- | --- | --- | --- |
95
95
  | `claim` | the claiming session | at selection | **#290 (this issue)** |
96
+ | `claim` / `supersede` | `ensure-session`'s pre-entry ownership guard (a SECOND mutator, alongside `assignment-provider claim`) | on session entry, before any session directory is created — `free` establishes a claim, `reclaimable` requires explicit `--supersede-stale` | **#291** |
96
97
  | `clean_release` | the incumbent (Stop hook / terminal `advance-state`) | session end — unassign + handoff comment | #292 |
97
98
  | `supersede` | the successor, inside the takeover protocol | after the grace beat | #294 |
98
99
  | `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
 
101
+ `ensure-session`'s guard reuses this file's own `computeEffectiveState`/`performLocalClaim`/
102
+ `performLocalSupersede` (Wave 1 exports) rather than a parallel implementation — see
103
+ `docs/adr/0021-assignment-leases-and-stale-claim-takeover.md` §3 for why a second claim point
104
+ was needed (a session entered without going through `pull-work` previously got no durable claim
105
+ at all) and the canonical `actor_key` field (below) that keeps its self-recognition consistent
106
+ with every other tool that reads or writes a claim record.
107
+
100
108
  Lazy correction is *safe* by the join rule above (stale assignment excludes nothing), but leaves
101
109
  the human-visible board stale in the crash case until the next claim attempt or a janitor sweep
102
110
  corrects it.
@@ -146,6 +154,7 @@ detect an incompatible future shape before parsing fields it does not understand
146
154
  | `role` | yes | Constant `"AssignmentClaimRecord"`, for readers scanning mixed content (e.g. a GitHub comment thread) for this record type. |
147
155
  | `subject_id` | yes | The claimed work item, in `owner/repo#id` form — the same string `workItemSlug` derives the deterministic session slug from. |
148
156
  | `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. |
157
+ | `actor_key` | no (additive, #291) | The canonical `resolveActor(env).actor` string for the claiming actor — the same flat/bare token `liveness whoami`, `liveness claim --actor`, per-actor `current.json`, and pull-work's `--self-actor` all use. When present, `computeEffectiveState` compares against THIS (not a re-serialization of `actor`) for both self-recognition and the liveness join, because `serializeActor(actor)` and `resolveActor(env).actor` diverge for an explicit-override actor (a bare token vs. a `explicit-override:<value>:<host>` triple) while agreeing for a derived actor. Absent on any pre-#291 record or fixture — `computeEffectiveState` falls back to `serializeActor(actor)` in that case, reproducing pre-#291 behavior exactly. |
149
158
  | `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
159
  | `ttl_seconds` | yes | Same field name/semantics as the liveness stream's `ttlSeconds` (default `1800`). |
151
160
  | `branch` | yes | The branch this actor is working on, per the `agent/<actor>/<slug>` convention. |
@@ -0,0 +1,187 @@
1
+ # Probe Docs-Write Contract
2
+
3
+ > Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
4
+
5
+ Normative contract for how a Probe (`design-probe`, and its Builder Kit
6
+ `pickup-probe` specialization) writes durable documentation as understanding
7
+ crystallizes during probing. This is the docs-write half of the grill-with-docs
8
+ pattern: a Probe already interviews and aligns; this contract says what a Probe
9
+ must leave behind in durable docs, not just in the session artifact.
10
+
11
+ ## Why
12
+
13
+ Probe outcomes previously landed only in session artifacts (`.kontourai/flow-agents/<slug>/`):
14
+ vocabulary and decisions crystallized while probing had to be re-extracted later,
15
+ or were lost when the session archived. Grill-with-docs' whole point is that the
16
+ interview leaves living documentation behind, not session-local notes. This
17
+ contract makes that the default behavior for every Probe, not an occasional
18
+ manual follow-up.
19
+
20
+ ## Scope
21
+
22
+ Applies to `design-probe` (generic) and `pickup-probe` (the Builder Kit
23
+ pickup specialization) — see
24
+ [`kits/builder/skills/design-probe/SKILL.md`](../../kits/builder/skills/design-probe/SKILL.md)
25
+ and
26
+ [`kits/builder/skills/pickup-probe/SKILL.md`](../../kits/builder/skills/pickup-probe/SKILL.md).
27
+ It does not create a new skill; both existing Probe skills already own the
28
+ grill/grill-with-docs behavior (R1 in
29
+ [flow-agents#311](https://github.com/kontourai/flow-agents/issues/311)) — this
30
+ contract only normalizes what they write and where.
31
+
32
+ Out of scope: enforcement gating on Probe docs-writes (a separate issue),
33
+ `CONTEXT.md` restructuring beyond vocabulary sections, and repositories with no
34
+ `CONTEXT.md` (an open question the owner has not settled; non-blocking for
35
+ flow-agents, which always has one).
36
+
37
+ ## The Two-Delta Rule
38
+
39
+ **Per crystallized understanding** — a question the Probe asked and the user (or
40
+ discoverable local evidence) definitively answered, not a still-open question or
41
+ an accepted gap — the Probe emits, in the same motion:
42
+
43
+ 1. **A vocabulary delta** into `CONTEXT.md` (or the owning `CONTEXT-MAP.md`
44
+ context when one exists) — see [Vocabulary Delta](#1-vocabulary-delta-coin-first).
45
+ 2. **A decision delta** into `docs/decisions/<slug>.md` — see
46
+ [Decision Delta](#2-decision-delta-retrieval-at-write).
47
+ 3. **Transcript provenance** — the decision delta's `evidence[]` links the
48
+ session artifact that produced it — see
49
+ [Transcript Provenance](#3-transcript-provenance).
50
+
51
+ Not every Probe question needs both deltas. A question resolved by reading
52
+ existing docs/code needs neither (nothing crystallized — it was already
53
+ decided). A question about implementation detail, sequencing, or a transient
54
+ planning choice needs neither (it belongs in the workflow artifact, per
55
+ `context/contracts/artifact-contract.md`, not durable docs). The two-delta rule
56
+ applies specifically to a **decision subject** crystallizing: a durable,
57
+ reusable answer to a named domain question that other work will need to look up
58
+ later. If in doubt, ask: "will a future session need to know this answer without
59
+ re-deriving it?" — if yes, it is a decision subject and both deltas apply.
60
+
61
+ ### 1. Vocabulary delta (coin-first)
62
+
63
+ **A decision subject may only use subject terms present in (or added to)
64
+ `CONTEXT.md` in the same session** (R2). Vocabulary is the topic namespace the
65
+ decision registry's slugs are drawn from
66
+ (`context/contracts/decision-registry-contract.md` § Slug rules), so the term
67
+ must exist before the topic file can be named.
68
+
69
+ - If the crystallized understanding's subject noun is **already** a `CONTEXT.md`
70
+ glossary term, the vocabulary delta may be a **refinement** of that entry
71
+ rather than a new one — e.g. removing "subject open" language once a decision
72
+ ratifies it, tightening a definition, or adding an `_Avoid_` term. This still
73
+ counts as a vocabulary delta: the glossary is a living document, not
74
+ write-once.
75
+ - If the subject noun is **absent**, **coin it first**: add a tight one- or
76
+ two-sentence `### <Term>` entry to `CONTEXT.md`'s Glossary section, in the
77
+ same style as existing entries (definition, optional `_Avoid_` line). Do this
78
+ before creating the decision file — the term is the slug's namespace, not an
79
+ afterthought.
80
+ - Keep the vocabulary delta to glossary-style terminology. Implementation
81
+ detail, rationale, and derivation stay out of `CONTEXT.md` (unchanged from the
82
+ Probe skills' existing docs discipline).
83
+ - If a `CONTEXT-MAP.md` exists and a more specific context owns the term, update
84
+ that context instead of assuming the root glossary owns every term.
85
+
86
+ ### 2. Decision delta (retrieval-at-write)
87
+
88
+ Once the vocabulary exists, record the decision per
89
+ `context/contracts/decision-registry-contract.md` in full — this contract does
90
+ not repeat the frontmatter/slug/tombstone rules, only the Probe-specific
91
+ sequencing:
92
+
93
+ 1. **Consult `docs/decisions/index.md`** (the always-loaded slug + one-liner
94
+ index) before writing anything, and propose **revise-vs-create**:
95
+ - An existing topic slug already covers the subject (including a
96
+ `needs-decision` stub seeded by ADR-freeze tooling, issue #314) → **revise
97
+ that file in place.** Flip `needs-decision` → `current` (or edit a `current`
98
+ file's body/`decided` date for a superseding decision), add the new
99
+ `evidence[]` refs, do not create a second file for the same subject.
100
+ - No topic covers the subject → confirm the vocabulary delta above landed,
101
+ then **create** `docs/decisions/<slug>.md` with the frontmatter shape
102
+ `context/contracts/decision-registry-contract.md` defines.
103
+ 2. Run `npm run gen:decisions-index && npm run check:decisions` after every
104
+ decision-delta write. A Probe session is not done touching the registry until
105
+ both exit 0.
106
+ 3. Never propose a numbered ADR. `docs/adr/` is frozen history
107
+ (`docs/adr/README.md`); a Probe's decision delta always targets the
108
+ topic-keyed registry, never a new `docs/adr/NNNN-*.md` file.
109
+
110
+ ### 3. Transcript provenance
111
+
112
+ Every decision file a Probe creates or edits carries an `evidence[]` entry of
113
+ `kind: session-archive` pointing at the Probe's own session artifact (R3) — the
114
+ Markdown file recording the interview, decisions, and accepted gaps
115
+ (`.kontourai/flow-agents/<slug>/<slug>--design-probe.md`,
116
+ `<slug>--pull-work.md`, or the equivalent Probe record the active workflow
117
+ artifact uses).
118
+
119
+ - Record the **live session path** at write time, even though the topic file
120
+ and the decision-registry contract describe `session-archive` as pointing at
121
+ an *archived* artifact "promoted at retirement" — the Probe runs at shaping
122
+ time, before the session archives. This is a deliberate, documented seam: the
123
+ promote-then-archive gate (issue #312) is the retirement-time mechanism that
124
+ moves the session directory to `<artifact-root>/archive/<slug>/` and is
125
+ responsible for keeping already-written evidence refs accurate across that
126
+ move. The Probe's job is emitting the initial link, not the later archival
127
+ move; do not block a decision delta on the session having archived first.
128
+ - When the Probe is also citing a frozen ADR as prior provenance (e.g. upgrading
129
+ a `needs-decision` stub whose `evidence[]` already names one), keep that `adr`
130
+ evidence entry and add the `session-archive` entry alongside it — provenance
131
+ accumulates, it does not replace.
132
+ - Also add a `pr`/`issue` evidence entry once a PR or issue number exists for the
133
+ work, following the same pattern every other decision file in the registry
134
+ uses.
135
+
136
+ ## Worked Example: Upgrading a Needs-Decision Stub
137
+
138
+ The ADR-freeze cutover (issue #314, landed in #368) seeded 17 `needs-decision`
139
+ stubs, each naming a subject with provenance in a frozen ADR but no ratified
140
+ living decision. These are the natural first targets for this contract: a Probe
141
+ that settles one of these subjects from existing evidence (the frozen ADR
142
+ already states a decision that remains true — nothing new needs deciding, only
143
+ recording) does the following:
144
+
145
+ 1. **Read the stub** (`docs/decisions/<slug>.md`, `status: needs-decision`) and
146
+ its `evidence[]`-linked frozen ADR(s).
147
+ 2. **Vocabulary delta**: the subject noun is already a `CONTEXT.md` term (seeded
148
+ alongside the stub during the freeze cutover) — refine its definition to
149
+ state the decision is now living rather than "subject open in the Decision
150
+ Registry," and keep the link to `docs/decisions/<slug>.md`.
151
+ 3. **Decision delta**: consult `docs/decisions/index.md` — the slug already
152
+ exists (revise, not create). Edit `docs/decisions/<slug>.md`:
153
+ `status: needs-decision` → `current`, set `decided` to today, keep the
154
+ existing `adr` evidence ref(s), append a `session-archive` ref (this Probe's
155
+ session artifact) and a `pr` ref once the PR exists, and write the `# <Title>`
156
+ body stating the decision (drawn from the ADR) plus lean rationale.
157
+ 4. Run `npm run gen:decisions-index && npm run check:decisions`; both must exit
158
+ 0 before the Probe records the gate claim.
159
+ 5. **Transcript provenance**: the session-archive evidence ref from step 3 is the
160
+ provenance link (R3).
161
+
162
+ This exercises all three rules end-to-end without inventing a new decision —
163
+ the frozen ADR already made the call; the Probe's job is only recording that it
164
+ is still true and giving it a living home.
165
+
166
+ ## Relationship To Other Contracts
167
+
168
+ - **`context/contracts/decision-registry-contract.md`** is the normative
169
+ frontmatter/slug/tombstone/validation contract for every `docs/decisions/`
170
+ file, Probe-written or not. This contract only adds *when* and *in what
171
+ sequence* a Probe writes to that registry.
172
+ - **`context/contracts/artifact-contract.md`** governs the Probe's session
173
+ artifact itself (the thing this contract's transcript-provenance rule links
174
+ to) and the promote/archive lifecycle referenced above.
175
+ - **`docs/adr/README.md`** is where this contract is announced as the mechanism
176
+ that supersedes the prior "propose ADRs sparingly" Probe guidance — Probes
177
+ never write numbered ADRs; they write decision-registry deltas.
178
+
179
+ ## Validation
180
+
181
+ - `npm run check:decisions` / `npm run gen:decisions-index` — the decision
182
+ delta must pass the registry validator and keep the index current (wired into
183
+ the required `source-and-static` CI lane).
184
+ - `evals/static/test_workflow_skills.sh` — asserts `design-probe` and
185
+ `pickup-probe` reference this contract and describe the two-delta behavior.
186
+ - `evals/static/test_decisions.sh` — regression coverage for the validator and
187
+ generator this contract's decision deltas depend on.
@@ -131,6 +131,18 @@ function checkProtectedPathPattern(filePath) {
131
131
  };
132
132
  }
133
133
 
134
+ // .kontourai/flow-agents/current/<actor>.json (#291) — the per-actor projection of the same
135
+ // pointer above. An agent could forge active_flow_id/active_step_id here exactly as it could
136
+ // via the legacy global file, so this is protected identically (same reason text).
137
+ // SAFE: the workflow CLI writes this via writePerActorCurrent → fs.writeFileSync,
138
+ // NOT via the Write/Edit tool — blocking the tool path does not break legit sidecar.
139
+ if (/(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\/[^/]+\.json$/.test(norm)) {
140
+ return {
141
+ name: '.kontourai/flow-agents/current/<actor>.json',
142
+ reason: 'an agent could forge active_flow_id/active_step_id to route the gate to a permissive FlowDefinition',
143
+ };
144
+ }
145
+
134
146
  // .kontourai/flow-agents/.goal-fit-block-streak.json controls soft-block
135
147
  // release counting. An agent could force early advisory-gate release by
136
148
  // writing a high count.
@@ -403,12 +415,13 @@ function checkCommandForBypass(command) {
403
415
  * Regex that matches a normalized (forward-slash) path ending with a protected
404
416
  * kill-switch file: shell profiles, .claude/settings*.json,
405
417
  * .kontourai/flow-agents/current.json,
418
+ * .kontourai/flow-agents/current/<actor>.json (#291 per-actor projection),
406
419
  * .kontourai/flow-agents/.goal-fit-block-streak.json,
407
420
  * .kontourai/flow-agents/<slug>/state.json,
408
421
  * .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
409
422
  * .flow-agents equivalents.
410
423
  */
411
- const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
424
+ const REDIRECT_PROTECTED_RE = /(?:^|\/|~\/)(\.bash_profile|\.bashrc|\.profile|\.zprofile|\.zshrc)$|(?:^|\/)\.claude\/settings(?:\.local)?\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/current\/[^/]+\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/\.goal-fit-block-streak\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/state\.json$|(?:^|\/)(?:\.kontourai\/flow-agents|\.flow-agents)\/[^/]+\/trust\.bundle$|(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
412
425
 
413
426
  /**
414
427
  * Return true when a token (an unquoted redirect target or tee argument) matches
@@ -22,6 +22,8 @@ const SANCTIONED_REMEDIES = {
22
22
  'There is no sanctioned automated writer for this file. Ask a human maintainer to edit it directly. Never disable this hook to make the write.',
23
23
  '.kontourai/flow-agents/current.json':
24
24
  'Use `npm run workflow:sidecar -- ensure-session` (or `advance-state`), which writes this file for you. Never disable this hook to make the write.',
25
+ '.kontourai/flow-agents/current/<actor>.json':
26
+ 'Use `npm run workflow:sidecar -- ensure-session` (or `advance-state`), which writes this file for you. Never disable this hook to make the write.',
25
27
  '.kontourai/flow-agents/.goal-fit-block-streak.json':
26
28
  'This file is only mutated internally by the goal-fit Stop hook; there is no sanctioned agent writer. Never disable this hook to make the write.',
27
29
  '.kontourai/flow-agents/<slug>/state.json':
@@ -61,6 +63,13 @@ const REMEDY_COMMAND_CANDIDATES = [
61
63
  { name: '.kontourai/flow-agents/<slug>/trust.bundle', needles: ['trust.bundle'] },
62
64
  { name: '.kontourai/flow-agents/<slug>/state.json', needles: ['state.json'] },
63
65
  { name: '.kontourai/flow-agents/.goal-fit-block-streak.json', needles: ['.goal-fit-block-streak.json'] },
66
+ // #291: the per-actor projection (current/<actor>.json) is checked BEFORE the legacy
67
+ // current.json needle below — its needle is a directory-scoped substring
68
+ // ('flow-agents/current/') that never overlaps with the bare 'current.json' basename, so
69
+ // ordering between these two entries has no effect on real inputs, but keeping the more
70
+ // specific per-actor entry first mirrors this table's existing "specific before generic"
71
+ // convention.
72
+ { name: '.kontourai/flow-agents/current/<actor>.json', needles: ['flow-agents/current/', '.flow-agents/current/'] },
64
73
  { name: '.kontourai/flow-agents/current.json', needles: ['current.json'] },
65
74
  { name: '.claude/settings.local.json', needles: ['settings.local.json'] },
66
75
  { name: '.claude/settings.json', needles: ['.claude/settings.json'] },
@@ -42,6 +42,8 @@ const {
42
42
  flowAgentsArtifactRoot,
43
43
  flowAgentsArtifactRootsForRead,
44
44
  } = require('./lib/local-artifact-paths');
45
+ const { resolveActor } = require('./lib/actor-identity.js');
46
+ const { readCurrentPointer } = require('./lib/current-pointer.js');
45
47
 
46
48
  const MAX_STDIN = 1024 * 1024;
47
49
  const ACTIVE_STATUSES = new Set([
@@ -1531,7 +1533,7 @@ async function bundleEnforcement(artifactDir, activeFlowStep) {
1531
1533
  * to scanning all of .kontourai/flow-agents (newest-mtime).
1532
1534
  */
1533
1535
  function preferredArtifactDir(flowAgentsDir) {
1534
- const current = readJsonFile(path.join(flowAgentsDir, 'current.json'));
1536
+ const { payload: current } = readCurrentPointer(flowAgentsDir, resolveActor(process.env).actor);
1535
1537
  if (!current) return null;
1536
1538
  const slug = current.artifact_dir || current.active_slug;
1537
1539
  if (typeof slug !== 'string' || !slug.trim()) return null;
@@ -1549,7 +1551,7 @@ function hasSidecarPresence(artifactDir) {
1549
1551
  // return that slug so analyze() can log the staleness rather than silently falling back to
1550
1552
  // a global mtime scan that could resurface an abandoned/never-real session as active.
1551
1553
  function staleCurrentSlug(flowAgentsDir) {
1552
- const current = readJsonFile(path.join(flowAgentsDir, 'current.json'));
1554
+ const { payload: current } = readCurrentPointer(flowAgentsDir, resolveActor(process.env).actor);
1553
1555
  if (!current) return null;
1554
1556
  const slug = current.artifact_dir || current.active_slug;
1555
1557
  if (typeof slug !== 'string' || !slug.trim()) return null;
@@ -19,6 +19,7 @@ const path = require('path');
19
19
  const { readLivenessEvents, freshHolders } = require('./lib/liveness-read');
20
20
  const { resolveActor } = require('./lib/actor-identity');
21
21
  const { flowAgentsArtifactRootsForRead } = require('./lib/local-artifact-paths');
22
+ const { readCurrentPointer } = require('./lib/current-pointer');
22
23
 
23
24
  const STEERING = {
24
25
  'tool-planner': [
@@ -102,7 +103,48 @@ function readJson(file) {
102
103
  }
103
104
  }
104
105
 
106
+ /**
107
+ * #291 (Conflict #2, Wave 2 Task 2.4): actor-scoped preference consulted BEFORE the global
108
+ * newest-mtime scan below. For each read-root, resolve the "current" pointer via
109
+ * `readCurrentPointer` (per-actor `current/<actor>.json` preferred, legacy global
110
+ * `current.json` fallback — the same single choke point every other Wave 2 consumer uses) and,
111
+ * if it names a session dir whose `state.json` is still in an ACTIVE_STATE_STATUSES status,
112
+ * return that state immediately — so actor A's own ambient steering hint always prefers A's own
113
+ * current task over a globally-newer-mtime `state.json` some other actor (B) happened to touch
114
+ * more recently. Returns null (never throws) when no root yields an actor-scoped resolution,
115
+ * so the caller falls through to the unchanged global scan below — this is the compat-shim
116
+ * guarantee: when `actorKey` is empty/unresolved and/or no per-actor file exists yet, this
117
+ * degrades to the SAME legacy-`current.json`-or-nothing lookup the compat shim already performs
118
+ * for every other Wave 2 reader, so a single-session/CI/legacy-only fixture cannot fail to
119
+ * resolve here in a way it wouldn't already have resolved via the global scan.
120
+ *
121
+ * @param {string} root
122
+ * @param {string} actorKey
123
+ * @returns {{file: string, payload: object, mtimeMs: number}|null}
124
+ */
125
+ function actorScopedWorkflowState(root, actorKey) {
126
+ for (const flowAgentsDir of flowAgentsArtifactRootsForRead(root)) {
127
+ const { payload: current } = readCurrentPointer(flowAgentsDir, actorKey);
128
+ if (!current) continue;
129
+ const slug = current.artifact_dir || current.active_slug;
130
+ if (typeof slug !== 'string' || !slug.trim()) continue;
131
+ const safe = slug.replace(/\.\.+/g, '').replace(/^[/\\]+/, '');
132
+ const dir = path.join(flowAgentsDir, safe);
133
+ if (!dir.startsWith(flowAgentsDir + path.sep) || !fs.existsSync(dir)) continue;
134
+ const file = path.join(dir, 'state.json');
135
+ let stat;
136
+ try { stat = fs.statSync(file); } catch { continue; }
137
+ const payload = readJson(file);
138
+ if (!payload || !ACTIVE_STATE_STATUSES.has(payload.status)) continue;
139
+ return { file, payload, mtimeMs: stat.mtimeMs };
140
+ }
141
+ return null;
142
+ }
143
+
105
144
  function latestWorkflowState(root) {
145
+ const preferred = actorScopedWorkflowState(root, resolveActor(process.env).actor);
146
+ if (preferred) return preferred;
147
+
106
148
  const states = flowAgentsArtifactRootsForRead(root)
107
149
  .flatMap(artifactRoot => walkStateFiles(artifactRoot))
108
150
  .map(file => {
@@ -2,6 +2,8 @@
2
2
  title: ADR 0001: Flow Agents Consumes Flow For Workflow Enforcement
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0001: Flow Agents Consumes Flow For Workflow Enforcement
6
8
 
7
9
  Date: 2026-05-24
@@ -2,6 +2,8 @@
2
2
  title: ADR 0002: Flow Kits As The Extension Unit
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0002: Flow Kits As The Extension Unit
6
8
 
7
9
  Flow Agents will use **Flow Kit** as the product and implementation term for installable workflow bundles, replacing the older "pack" vocabulary. A Flow Kit may contain Flow Definitions, skills, adapters, provider contracts, docs, and evals; the Kit Catalog lists available kits and installable assets, but the workflow semantics live in kit-owned Flow Definition files. This avoids overloading packaging metadata with process semantics and gives custom workflow authoring a clearer product language.
@@ -2,6 +2,8 @@
2
2
  title: ADR 0003: Flow Agents Coordinates Kits And Adapters
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0003: Flow Agents Coordinates Kits And Adapters
6
8
 
7
9
  Flow Agents will not own the builder, or knowledge workflows as core behavior. Those out-of-the-box behaviors will be extracted into normal Flow Kits that use the same manifest, Flow Definition, skill, doc, provider, and eval contracts as third-party kits. Flow Agents owns kit validation, installation, runtime adapter selection, provider wiring, status/control commands, and runtime-specific export; Runtime Adapters own target-specific integration for local agent runtimes and API/framework agents.
@@ -2,6 +2,8 @@
2
2
  title: ADR 0004: Gates Expect Hachure Trust Bundles
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0004: Gates Expect Hachure Trust Bundles
6
8
 
7
9
  Flow-backed kits will model rich gate evidence as claim expectations using the Hachure trust.bundle format rather than provider-specific requirements. A gate expectation can require `kind: "trust.bundle"`, a domain claim type such as `builder.verify.tests`, accepted trust statuses such as `verified`, and whether the expectation blocks the transition; project or runtime config maps claim types to trusted Surface producers and authority traces. This lets the Builder Kit use repo governance, command checks, CI, human decisions, or future producers without naming a specific provider in the Flow Definition.
@@ -2,6 +2,8 @@
2
2
  title: ADR 0005: Kubernetes-Inspired Kontour Resource Contracts
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0005: Kubernetes-Inspired Kontour Resource Contracts
6
8
 
7
9
  Date: 2026-05-27
@@ -2,6 +2,8 @@
2
2
  title: ADR 0006: TypeScript-First Source Policy
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0006: TypeScript-First Source Policy
6
8
 
7
9
  Date: 2026-05-31
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0007: Flow / Skill / Kit / Tool Boundary"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0007: Flow / Skill / Kit / Tool Boundary
6
8
 
7
9
  **Date:** 2026-06-15
@@ -2,6 +2,8 @@
2
2
  title: "Skill Audit 2026-06-15: Flow / Skill / Kit / Tool Boundary"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # Skill Audit: Flow / Skill / Kit / Tool Boundary
6
8
 
7
9
  **Date:** 2026-06-15
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0008: Kit Operation Boundary"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0008: Kit Operation Boundary
6
8
 
7
9
  **Date:** 2026-06-15
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0009: Canonical Hook Core/Kit Boundary"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0009: Canonical Hook Core/Kit Boundary
6
8
 
7
9
  **Date:** 2026-06-23
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0010: Workflow Trust State as a Hachure Trust Bundle"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0010: Workflow Trust State as a Hachure Trust Bundle
6
8
 
7
9
  **Date:** 2026-06-23
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0011: MCP Posture — Enforcement Stays Hooks; Surface Owns MCP Projection; No Auto-Injected Config"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0011: MCP Posture
6
8
 
7
9
  **Date:** 2026-06-24
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0012: Agent Coordination as Hachure Liveness Claims"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0012: Agent Coordination as Hachure Liveness Claims
6
8
 
7
9
  **Date:** 2026-06-24
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0013: Context Lifecycle — Workflow-Boundary Compaction, Freshness-Gated Reuse, and the Learning Split"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0013: Context Lifecycle
6
8
 
7
9
  **Date:** 2026-06-25
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0014: Flow Agents core vs domain kits — the generic/kit boundary"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0014: Flow Agents core vs domain kits
6
8
 
7
9
  **Date:** 2026-06-25
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0015: Flow / Flow Agents Boundary Reconciliation"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0015: Flow / Flow Agents Boundary Reconciliation
6
8
 
7
9
  **Date:** 2026-06-25
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0016: The Three-Hard-Boundary Model — a FlowDefinition-Driven, Kit-Agnostic Core"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0016: The Three-Hard-Boundary Model — a FlowDefinition-Driven, Kit-Agnostic Core
6
8
 
7
9
  **Date:** 2026-06-26
@@ -2,6 +2,8 @@
2
2
  title: "ADR 0017: The Anti-Gaming Trust Security Model — Layered Defense + External CI Anchor"
3
3
  ---
4
4
 
5
+ > **FROZEN — immutable history.** Superseding/current decisions live in [`docs/decisions/`](../decisions/index.md). Do not edit.
6
+
5
7
  # ADR 0017: The Anti-Gaming Trust Security Model — Layered Defense + External CI Anchor
6
8
 
7
9
  Status: Accepted