@kontourai/flow-agents 3.0.0 → 3.2.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.
- package/.github/workflows/ci.yml +12 -0
- package/CHANGELOG.md +32 -0
- package/CONTEXT.md +67 -1
- package/README.md +4 -0
- package/build/src/cli/assignment-provider.d.ts +188 -0
- package/build/src/cli/assignment-provider.js +193 -51
- package/build/src/cli/workflow-sidecar.d.ts +16 -6
- package/build/src/cli/workflow-sidecar.js +492 -45
- package/build/src/lib/flow-resolver.d.ts +12 -6
- package/build/src/lib/flow-resolver.js +30 -14
- package/build/src/tools/validate-source-tree.js +2 -1
- package/context/contracts/assignment-provider-contract.md +10 -1
- package/context/contracts/execution-contract.md +78 -0
- package/context/contracts/probe-docs-write-contract.md +187 -0
- package/context/scripts/hooks/config-protection.js +24 -4
- package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/context/scripts/hooks/stop-goal-fit.js +262 -5
- package/context/scripts/hooks/workflow-steering.js +42 -0
- package/docs/adr/0001-flow-agents-consumes-flow.md +2 -0
- package/docs/adr/0002-flow-kits-as-extension-unit.md +2 -0
- package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +2 -0
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0006-typescript-first-source-policy.md +2 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0007-skill-audit.md +2 -0
- package/docs/adr/0008-kit-operation-boundary.md +2 -0
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +2 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +2 -0
- package/docs/adr/0011-mcp-posture.md +2 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +2 -0
- package/docs/adr/0013-context-lifecycle.md +2 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +2 -0
- package/docs/adr/0016-three-hard-boundary-model.md +2 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +2 -0
- package/docs/adr/0018-freeze-local-shell-heuristics.md +2 -0
- package/docs/adr/0019-kit-dependency-ownership.md +2 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +2 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +3 -1
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +113 -0
- package/docs/adr/README.md +49 -0
- package/docs/adr/index.md +34 -0
- package/docs/decisions/agent-coordination.md +20 -0
- package/docs/decisions/anti-gaming-trust-security.md +20 -0
- package/docs/decisions/context-lifecycle.md +18 -0
- package/docs/decisions/core-domain-kit-boundary.md +18 -0
- package/docs/decisions/flow-flow-agents-boundary.md +18 -0
- package/docs/decisions/flow-kit.md +20 -0
- package/docs/decisions/flow-skill-kit-tool-boundary.md +20 -0
- package/docs/decisions/graph-knowledge-provider.md +63 -0
- package/docs/decisions/hook-core-kit-boundary.md +18 -0
- package/docs/decisions/index.md +19 -0
- package/docs/decisions/kit-dependency-ownership.md +18 -0
- package/docs/decisions/kit-operation-boundary.md +18 -0
- package/docs/decisions/knowledge-promote-sub-flow.md +59 -0
- package/docs/decisions/kontour-resource-contract.md +18 -0
- package/docs/decisions/mcp-posture.md +18 -0
- package/docs/decisions/three-hard-boundary-model.md +18 -0
- package/docs/decisions/trust-reconcile.md +20 -0
- package/docs/decisions/typescript-source-policy.md +48 -0
- package/docs/decisions/workflow-enforcement.md +18 -0
- package/docs/decisions/workflow-trust-state.md +20 -0
- package/docs/fixture-ownership.md +1 -1
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/ci/run-baseline.sh +6 -0
- package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
- package/evals/integration/test_checkpoint_signing.sh +4 -3
- package/evals/integration/test_current_json_per_actor.sh +516 -0
- package/evals/integration/test_ensure_session_ownership_guard.sh +435 -0
- package/evals/integration/test_gate_lockdown.sh +46 -0
- package/evals/integration/test_model_routing_escalation.sh +145 -0
- package/evals/integration/test_publish_delivery.sh +14 -6
- package/evals/integration/test_pull_work_liveness_preflight.sh +6 -1
- package/evals/integration/test_stop_hook_release.sh +552 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +170 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +12 -3
- package/evals/run.sh +10 -0
- package/evals/static/test_knowledge_providers.sh +13 -4
- package/evals/static/test_model_routing_hints.sh +107 -0
- package/evals/static/test_workflow_skills.sh +15 -2
- package/kits/builder/skills/builder-shape/SKILL.md +10 -0
- package/kits/builder/skills/deliver/SKILL.md +69 -11
- package/kits/builder/skills/design-probe/SKILL.md +47 -0
- package/kits/builder/skills/execute-plan/SKILL.md +13 -0
- package/kits/builder/skills/fix-bug/SKILL.md +17 -0
- package/kits/builder/skills/idea-to-backlog/SKILL.md +10 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
- package/kits/builder/skills/plan-work/SKILL.md +9 -0
- package/kits/builder/skills/pull-work/SKILL.md +10 -0
- package/kits/builder/skills/review-work/SKILL.md +11 -0
- package/kits/builder/skills/tdd-workflow/SKILL.md +17 -0
- package/kits/builder/skills/verify-work/SKILL.md +11 -0
- package/kits/knowledge/adapters/default-store/index.js +147 -18
- package/kits/knowledge/adapters/flow-runner/index.js +912 -16
- package/kits/knowledge/adapters/obsidian-store/index.js +97 -14
- package/kits/knowledge/adapters/shared/codec.js +265 -0
- package/kits/knowledge/docs/README.md +121 -2
- package/kits/knowledge/docs/store-contract.md +517 -7
- package/kits/knowledge/evals/audit-freshness/suite.test.js +92 -1
- package/kits/knowledge/evals/consolidate-incremental/suite.test.js +494 -0
- package/kits/knowledge/evals/consolidation/suite.test.js +1 -1
- package/kits/knowledge/evals/contract-suite/suite.test.js +212 -0
- package/kits/knowledge/evals/freshness/suite.test.js +339 -0
- package/kits/knowledge/evals/inbound-references/suite.test.js +351 -0
- package/kits/knowledge/evals/retirement/suite.test.js +1 -1
- package/kits/knowledge/evals/supersede-propagation/suite.test.js +384 -0
- package/kits/knowledge/flows/promote.flow.json +84 -0
- package/kits/knowledge/kit.json +15 -0
- package/kits/knowledge/promote/distill.js +96 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/CONTEXT.md +7 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction-policy.md +12 -0
- package/kits/knowledge/promote/fixtures/contradiction-registry/docs/decisions/cache-eviction.md +12 -0
- package/kits/knowledge/promote/fixtures/repo/CONTEXT.md +11 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/index.md +9 -0
- package/kits/knowledge/promote/fixtures/repo/docs/decisions/liveness-claim.md +12 -0
- package/kits/knowledge/promote/fixtures/session/acceptance.json +9 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-planner/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-verifier/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/agents/tool-worker-1-1/events.jsonl +1 -0
- package/kits/knowledge/promote/fixtures/session/kontourai-flow-agents-287--deliver.md +47 -0
- package/kits/knowledge/promote/fixtures/session/learning.json +35 -0
- package/kits/knowledge/promote/fixtures/session/state.json +19 -0
- package/kits/knowledge/promote/health.js +137 -0
- package/kits/knowledge/promote/index.js +176 -0
- package/kits/knowledge/promote/ingest.js +103 -0
- package/kits/knowledge/promote/lib.js +132 -0
- package/kits/knowledge/promote/link.js +84 -0
- package/kits/knowledge/promote/promote.test.js +174 -0
- package/kits/knowledge/providers/conformance/suite.test.js +18 -0
- package/kits/knowledge/providers/index.js +1 -0
- package/kits/knowledge/providers/neo4j/connection.js +121 -0
- package/kits/knowledge/providers/neo4j/cypher.js +190 -0
- package/kits/knowledge/providers/neo4j/fake-driver.js +123 -0
- package/kits/knowledge/providers/neo4j/fixtures/spike-ground-truth.json +29 -0
- package/kits/knowledge/providers/neo4j/graph-queries.js +210 -0
- package/kits/knowledge/providers/neo4j/index.js +280 -0
- package/kits/knowledge/providers/neo4j/integration.test.js +122 -0
- package/kits/knowledge/providers/neo4j/neo4j.test.js +219 -0
- package/kits/knowledge/providers/neo4j/sync.js +235 -0
- package/package.json +4 -2
- package/schemas/workflow-handoff.schema.json +6 -0
- package/scripts/README.md +1 -0
- package/scripts/ci/mint-attestation.js +33 -6
- package/scripts/ci/trust-reconcile.js +144 -26
- package/scripts/freeze-adrs.mjs +364 -0
- package/scripts/hooks/config-protection.js +24 -4
- package/scripts/hooks/evidence-capture.js +4 -1
- package/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/scripts/hooks/lib/current-pointer.js +123 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +75 -35
- package/scripts/hooks/stop-goal-fit.js +262 -5
- package/scripts/hooks/workflow-steering.js +42 -0
- package/scripts/statusline/flow-agents-statusline.js +3 -1
- package/src/cli/assignment-provider.ts +239 -59
- package/src/cli/workflow-sidecar.ts +529 -43
- package/src/lib/flow-resolver.ts +35 -14
- package/src/tools/validate-source-tree.ts +2 -1
|
@@ -72,16 +72,22 @@ export declare function resolvePhaseMap(flowId: string, repoRoot: string): Recor
|
|
|
72
72
|
/**
|
|
73
73
|
* Resolve the active flow step from current.json.
|
|
74
74
|
*
|
|
75
|
-
* Reads active_flow_id and active_step_id
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
75
|
+
* Reads active_flow_id and active_step_id via the shared per-actor current-pointer helper
|
|
76
|
+
* (#291 Wave 2 Task 2.2): when `actorKey` is a resolved actor with its own
|
|
77
|
+
* `current/<actor>.json` projection, that file is preferred; otherwise (no actorKey, an
|
|
78
|
+
* unresolved actor, or no per-actor file yet) this falls straight back to the legacy global
|
|
79
|
+
* `<flowAgentsDir>/current.json` — IDENTICAL to this function's pre-#291 behavior for every
|
|
80
|
+
* caller that does not pass `actorKey`. The repoRoot is derived by walking upward from
|
|
81
|
+
* flowAgentsDir to find the nearest ancestor containing kits/, with a fallback to
|
|
82
|
+
* process.cwd(). This handles temp dirs, CI workspaces, and subproject layouts without
|
|
83
|
+
* hardcoding the repo structure.
|
|
80
84
|
*
|
|
81
85
|
* @param flowAgentsDir Path to the runtime artifact root directory (contains current.json).
|
|
86
|
+
* @param actorKey Optional resolved actor identity (Wave 2 Task 2.1's `writeCurrent()` dual-write
|
|
87
|
+
* key) — when omitted, behavior is unchanged (legacy-file-only, exactly as before #291).
|
|
82
88
|
* @returns ActiveFlowStep or null when fields are absent or resolution fails.
|
|
83
89
|
*/
|
|
84
|
-
export declare function resolveActiveFlowStep(flowAgentsDir: string): ActiveFlowStep | null;
|
|
90
|
+
export declare function resolveActiveFlowStep(flowAgentsDir: string, actorKey?: string): ActiveFlowStep | null;
|
|
85
91
|
/** The resolved route-back policy for a phase transition. */
|
|
86
92
|
export type RouteBackPolicy = {
|
|
87
93
|
/** Maximum allowed route-back attempts for this transition key. */
|
|
@@ -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
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
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
|
|
289
|
-
|
|
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
|
-
| `
|
|
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** |
|
|
97
|
+
| `clean_release` | the incumbent (Stop hook / terminal `advance-state`) | session end — unassign + handoff comment | **#292** — the Stop hook (`scripts/hooks/stop-goal-fit.js`) on clean non-terminal session end: liveness release + local-file assignment release (`performLocalRelease`) run inline; for the github provider the hook cannot run `gh`, so it honestly defers via a `provider_release_pending` handoff intent rather than pretending to complete the release |
|
|
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. |
|
|
@@ -67,6 +67,84 @@ model backs which delegate role. This contract only says where to read it.
|
|
|
67
67
|
fallback in the session/task artifact — do not block delegation on datum
|
|
68
68
|
being present.
|
|
69
69
|
|
|
70
|
+
### Tier ladder
|
|
71
|
+
|
|
72
|
+
The delegate roles form a cost/capability ladder that both escalation and the
|
|
73
|
+
Goodhart guard below reference. Lowest to highest:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
delegate-mechanical < delegate-implementation < delegate-design
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`orchestrator` is off-ladder — it is the session's own model (planning, gates,
|
|
80
|
+
adversarial verification), typically inherited rather than overridden, and sits
|
|
81
|
+
above `delegate-design` for escalation purposes. `extraction-default` is a
|
|
82
|
+
non-delegate role (bulk extraction) and is not part of this ladder.
|
|
83
|
+
|
|
84
|
+
### Escalation on gate failure
|
|
85
|
+
|
|
86
|
+
Delegate at the step's hinted tier, then let the gates enforce cost safety:
|
|
87
|
+
|
|
88
|
+
- Dispatch the delegate at the tier its skill hint names (a cheap tier for
|
|
89
|
+
mechanical/fully-specified work).
|
|
90
|
+
- On a review or verify **gate FAILURE of that delegate's output**, re-dispatch
|
|
91
|
+
the **fix** one tier higher on the ladder — `delegate-mechanical` failures
|
|
92
|
+
escalate to `delegate-implementation`, `delegate-implementation` failures to
|
|
93
|
+
`delegate-design`, and a `delegate-design` failure escalates to the
|
|
94
|
+
`orchestrator` / a human decision. Do not re-dispatch the fix at the same
|
|
95
|
+
tier that already failed its gate.
|
|
96
|
+
- Record every escalation in the session artifact (see *Routing decisions in
|
|
97
|
+
the run artifact* below) with the tier it climbed **from**, so the run shows
|
|
98
|
+
why a more expensive model was used.
|
|
99
|
+
- This is the fail-closed cost story: cheap tiers are safe **because** the gates
|
|
100
|
+
catch their misses and escalate. Routing cheap-by-default never weakens
|
|
101
|
+
verification — an ungated cheap delegate is what this ladder exists to prevent.
|
|
102
|
+
|
|
103
|
+
### Goodhart guard (review/verify never cheaper than the work)
|
|
104
|
+
|
|
105
|
+
Review and verify roles resolve at a tier **greater than or equal to** the tier
|
|
106
|
+
of the work they check; they never auto-downgrade below it.
|
|
107
|
+
|
|
108
|
+
- If the checked work was produced at `delegate-design`, its reviewer/verifier
|
|
109
|
+
resolves `delegate-design` (or `orchestrator`) — never `delegate-implementation`
|
|
110
|
+
or `delegate-mechanical`.
|
|
111
|
+
- If the checked work was produced at `delegate-implementation`, its
|
|
112
|
+
reviewer/verifier resolves at `delegate-implementation` or higher.
|
|
113
|
+
- Rationale: a cheaper checker rubber-stamping a more capable worker's output
|
|
114
|
+
defeats the gate and turns the routing table into a Goodhart target (optimize
|
|
115
|
+
the cost metric, lose the thing the metric was a proxy for). The gate must be
|
|
116
|
+
at least as capable as the work, or the fail-closed escalation story above is
|
|
117
|
+
hollow.
|
|
118
|
+
|
|
119
|
+
### Routing decisions in the run artifact
|
|
120
|
+
|
|
121
|
+
Record each delegation's resolved role and model on the session artifact so a
|
|
122
|
+
downstream economics record (`flow-agents#349`) can price role assignments and a
|
|
123
|
+
baseline harness (`flow-agents#350`) can A/B tiers. Use the sidecar writer's
|
|
124
|
+
`record-agent-event` (additive `--role` / `--model` / `--escalated-from` flags):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# per-delegation routing decision
|
|
128
|
+
npm run workflow:sidecar -- record-agent-event \
|
|
129
|
+
--agent-id <delegate-id> --kind delegation --status active \
|
|
130
|
+
--role <resolved-role> --model "<resolved-model@provider>" \
|
|
131
|
+
--summary "<what was delegated>"
|
|
132
|
+
|
|
133
|
+
# an escalate-on-gate-failure re-dispatch
|
|
134
|
+
npm run workflow:sidecar -- record-agent-event \
|
|
135
|
+
--agent-id <delegate-id> --kind escalation --status active \
|
|
136
|
+
--role <higher-tier-role> --model "<resolved-model@provider>" \
|
|
137
|
+
--escalated-from <lower-tier-role> \
|
|
138
|
+
--summary "<gate that failed> failed at <lower-tier-role>; fix re-dispatched one tier higher"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
These land as top-level `role`, `model`, and (for escalations) `escalated_from`
|
|
142
|
+
fields on the JSONL event under `agents/<agent-id>/events.jsonl`. The shape is
|
|
143
|
+
additive: events without a routing decision are byte-identical to before, so no
|
|
144
|
+
existing consumer breaks. An economics consumer reads events where `role` is
|
|
145
|
+
present as one priced delegation each; `escalated_from` marks the entries that
|
|
146
|
+
cost more because a gate caught a cheaper tier.
|
|
147
|
+
|
|
70
148
|
## Completion Rules
|
|
71
149
|
|
|
72
150
|
Execution is complete only when:
|
|
@@ -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.
|
|
@@ -168,7 +180,9 @@ function checkProtectedPathPattern(filePath) {
|
|
|
168
180
|
// copy a forged bundle here to corrupt the CI trust check.
|
|
169
181
|
// SAFE: publishDelivery writes via fs.copyFileSync (not Write/Edit tool).
|
|
170
182
|
// RESIDUAL: runtime-constructed paths and fs writes are unaffected.
|
|
171
|
-
|
|
183
|
+
// #379: the optional (?:[^/]+\/)? segment also covers the per-session path
|
|
184
|
+
// delivery/<slug>/trust.bundle — the forgery surface moved with the write path.
|
|
185
|
+
if (/(?:^|\/)delivery\/(?:[^/]+\/)?trust\.bundle$/.test(norm)) {
|
|
172
186
|
return {
|
|
173
187
|
name: "delivery/trust.bundle",
|
|
174
188
|
reason: "an agent could write a forged bundle to corrupt the CI trust-reconcile anchor",
|
|
@@ -177,7 +191,8 @@ function checkProtectedPathPattern(filePath) {
|
|
|
177
191
|
|
|
178
192
|
// delivery/trust.checkpoint.json -- the signed checkpoint companion.
|
|
179
193
|
// SAFE: publishDelivery writes via fs.copyFileSync, NOT via Write/Edit tool.
|
|
180
|
-
|
|
194
|
+
// #379: optional (?:[^/]+\/)? segment also covers delivery/<slug>/trust.checkpoint.json.
|
|
195
|
+
if (/(?:^|\/)delivery\/(?:[^/]+\/)?trust\.checkpoint\.json$/.test(norm)) {
|
|
181
196
|
return {
|
|
182
197
|
name: "delivery/trust.checkpoint.json",
|
|
183
198
|
reason: "an agent could forge a signed delivery by writing a tampered checkpoint",
|
|
@@ -403,12 +418,15 @@ function checkCommandForBypass(command) {
|
|
|
403
418
|
* Regex that matches a normalized (forward-slash) path ending with a protected
|
|
404
419
|
* kill-switch file: shell profiles, .claude/settings*.json,
|
|
405
420
|
* .kontourai/flow-agents/current.json,
|
|
421
|
+
* .kontourai/flow-agents/current/<actor>.json (#291 per-actor projection),
|
|
406
422
|
* .kontourai/flow-agents/.goal-fit-block-streak.json,
|
|
407
423
|
* .kontourai/flow-agents/<slug>/state.json,
|
|
408
424
|
* .kontourai/flow-agents/<slug>/trust.bundle, and deprecated runtime-shaped
|
|
409
425
|
* .flow-agents equivalents.
|
|
410
426
|
*/
|
|
411
|
-
|
|
427
|
+
// #379: the delivery/ arms carry an optional (?:[^/]+\/)? segment so redirects/tee to the
|
|
428
|
+
// per-session path delivery/<slug>/trust.bundle (+ checkpoint) are caught, not just the flat path.
|
|
429
|
+
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
430
|
|
|
413
431
|
/**
|
|
414
432
|
* Return true when a token (an unquoted redirect target or tee argument) matches
|
|
@@ -556,8 +574,10 @@ function checkInterpreterWriteToProtected(command) {
|
|
|
556
574
|
* Delivery-protected path regex: delivery/trust.bundle and delivery/trust.checkpoint.json.
|
|
557
575
|
* These are the CI anchor files whose contents must not be agent-forged.
|
|
558
576
|
* Used by checkCopyMoveToProtected to catch `cp x delivery/trust.bundle`.
|
|
577
|
+
* #379: the optional (?:[^/]+\/)? segment also catches the per-session path
|
|
578
|
+
* `cp forged.json delivery/<slug>/trust.bundle`.
|
|
559
579
|
*/
|
|
560
|
-
const DELIVERY_COPY_PROTECTED_RE = /(?:^|\/)delivery\/trust\.bundle$|(?:^|\/)delivery\/trust\.checkpoint\.json$/;
|
|
580
|
+
const DELIVERY_COPY_PROTECTED_RE = /(?:^|\/)delivery\/(?:[^/]+\/)?trust\.bundle$|(?:^|\/)delivery\/(?:[^/]+\/)?trust\.checkpoint\.json$/;
|
|
561
581
|
|
|
562
582
|
/**
|
|
563
583
|
* Return true when a normalized token matches a delivery-protected path.
|
|
@@ -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'] },
|