@kontourai/flow-agents 2.4.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.
- package/.github/CODEOWNERS +8 -0
- package/.github/workflows/ci.yml +20 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/CHANGELOG.md +38 -0
- package/CONTEXT.md +88 -1
- package/README.md +4 -0
- package/build/src/cli/assignment-provider.d.ts +144 -0
- package/build/src/cli/assignment-provider.js +805 -0
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/validate-workflow-artifacts.js +5 -1
- package/build/src/cli/workflow-sidecar.d.ts +2 -2
- package/build/src/cli/workflow-sidecar.js +549 -145
- package/build/src/cli.js +6 -0
- 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 -0
- package/context/contracts/artifact-contract.md +2 -0
- package/context/contracts/assignment-provider-contract.md +248 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +2 -0
- package/context/contracts/decision-registry-contract.md +2 -0
- package/context/contracts/delivery-contract.md +2 -0
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +2 -0
- package/context/contracts/probe-docs-write-contract.md +187 -0
- package/context/contracts/review-contract.md +2 -0
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/contracts/work-item-contract.md +2 -0
- package/context/scripts/hooks/config-protection.js +14 -1
- package/context/scripts/hooks/lib/config-protection-remedies.js +9 -0
- package/context/scripts/hooks/stop-goal-fit.js +4 -2
- package/context/scripts/hooks/workflow-steering.js +42 -0
- package/context/settings/assignment-provider-settings.json +33 -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 +182 -0
- package/docs/adr/README.md +49 -0
- package/docs/adr/index.md +34 -0
- package/docs/context-map.md +1 -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 +22 -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/knowledge-store-provider.md +51 -0
- package/docs/decisions/kontour-resource-contract.md +18 -0
- package/docs/decisions/mcp-posture.md +18 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/standing-directives.md +66 -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 -0
- package/docs/workflow-shared-contracts.md +2 -1
- package/docs/workflow-usage-guide.md +7 -1
- package/evals/ci/run-baseline.sh +10 -0
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/hostile-effective-state.json +11 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- 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_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_gate_lockdown.sh +10 -0
- package/evals/integration/test_publish_delivery.sh +21 -4
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +20 -7
- package/evals/integration/test_reconcile_soundness.sh +33 -9
- package/evals/integration/test_trust_reconcile.sh +9 -8
- package/evals/integration/test_trust_reconcile_negatives.sh +608 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +91 -3
- package/evals/run.sh +14 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +6 -0
- package/evals/static/test_knowledge_providers.sh +32 -0
- package/evals/static/test_workflow_skills.sh +15 -2
- package/kits/builder/skills/deliver/SKILL.md +36 -0
- package/kits/builder/skills/design-probe/SKILL.md +37 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +4 -4
- package/kits/builder/skills/pull-work/SKILL.md +78 -10
- package/kits/knowledge/adapters/default-store/index.js +92 -4
- package/kits/knowledge/adapters/obsidian-store/index.js +69 -4
- package/kits/knowledge/adapters/shared/codec.js +141 -0
- package/kits/knowledge/docs/README.md +121 -2
- package/kits/knowledge/docs/store-contract.md +112 -4
- package/kits/knowledge/evals/contract-suite/suite.test.js +176 -0
- package/kits/knowledge/flows/promote.flow.json +84 -0
- package/kits/knowledge/kit.json +50 -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/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +143 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +25 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -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/kits/knowledge/providers/work-item/index.js +204 -0
- package/package.json +7 -3
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/scripts/README.md +1 -0
- package/scripts/ci/trust-reconcile.js +521 -24
- package/scripts/freeze-adrs.mjs +364 -0
- package/scripts/hooks/config-protection.js +14 -1
- 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 +4 -2
- package/scripts/hooks/workflow-steering.js +42 -0
- package/scripts/statusline/flow-agents-statusline.js +3 -1
- package/src/cli/assignment-provider.ts +927 -0
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/validate-workflow-artifacts.ts +5 -1
- package/src/cli/workflow-sidecar.ts +577 -139
- package/src/cli.ts +6 -0
- package/src/lib/flow-resolver.ts +35 -14
- package/src/tools/validate-source-tree.ts +2 -0
package/build/src/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { main as effectiveBacklogSettings } from "./cli/effective-backlog-settings.js";
|
|
4
|
+
import { main as effectiveAssignmentProviderSettings } from "./cli/effective-assignment-provider-settings.js";
|
|
5
|
+
import { main as assignmentProvider } from "./cli/assignment-provider.js";
|
|
4
6
|
import { main as consoleLearningProjection } from "./cli/console-learning-projection.js";
|
|
5
7
|
import { main as kit } from "./cli/kit.js";
|
|
6
8
|
import { main as fixtureRetirementAudit } from "./cli/fixture-retirement-audit.js";
|
|
@@ -24,6 +26,8 @@ const availableCommands = new Map([
|
|
|
24
26
|
["build-bundles", () => buildBundles()],
|
|
25
27
|
["console-learning-projection", consoleLearningProjection],
|
|
26
28
|
["context-map", contextMap],
|
|
29
|
+
["assignment-provider", assignmentProvider],
|
|
30
|
+
["effective-assignment-provider-settings", effectiveAssignmentProviderSettings],
|
|
27
31
|
["effective-backlog-settings", effectiveBacklogSettings],
|
|
28
32
|
["fixture-retirement-audit", fixtureRetirementAudit],
|
|
29
33
|
["kit", kit],
|
|
@@ -46,6 +50,8 @@ const aliases = new Map([
|
|
|
46
50
|
["flow-agents-build-bundles", "build-bundles"],
|
|
47
51
|
["flow-agents-console-learning-projection", "console-learning-projection"],
|
|
48
52
|
["flow-agents-context-map", "context-map"],
|
|
53
|
+
["flow-agents-assignment-provider", "assignment-provider"],
|
|
54
|
+
["flow-agents-effective-assignment-provider-settings", "effective-assignment-provider-settings"],
|
|
49
55
|
["flow-agents-effective-backlog-settings", "effective-backlog-settings"],
|
|
50
56
|
["flow-agents-fixture-retirement-audit", "fixture-retirement-audit"],
|
|
51
57
|
["flow-agents-kit", "kit"],
|
|
@@ -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,6 +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([
|
|
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" }],
|
|
96
98
|
["evals/fixtures/backlog-provider-settings", { owners: ["evals/integration/test_effective_backlog_settings.sh"], classification: "settings precedence fixtures" }],
|
|
97
99
|
["evals/fixtures/builder-kit-workflow-state", { owners: ["evals/static/test_workflow_skills.sh"], classification: "Builder Kit workflow-state fixtures" }],
|
|
98
100
|
["evals/fixtures/console-learning-projection", { owners: ["evals/integration/test_console_learning_projection.sh"], classification: "console learning projection fixtures" }],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Workflow Artifact Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
Workflow artifacts are the resumable local handoff surface between orchestrators, workers, verifiers, hooks, evals, and future sessions.
|
|
4
6
|
|
|
5
7
|
## Artifact Root
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Assignment Provider Contract
|
|
2
|
+
|
|
3
|
+
This contract defines the provider-neutral vocabulary for durable work-item ownership: who has
|
|
4
|
+
claimed a subject, how that claim is represented, and how it is read back safely. It formalizes
|
|
5
|
+
[ADR 0021: Assignment Leases and Stale-Claim Takeover](../../docs/adr/0021-assignment-leases-and-stale-claim-takeover.md)
|
|
6
|
+
as the third provider leg beside `WorkItemProvider` and `BoardProvider`
|
|
7
|
+
(`context/contracts/work-item-contract.md`) and `ChangeProvider`. GitHub is the first concrete
|
|
8
|
+
mapping for this contract, not the generic vocabulary — Linear, Jira, GitLab, and a local-file
|
|
9
|
+
provider for tracker-less repos and evals all map the same operations.
|
|
10
|
+
|
|
11
|
+
## Terminology: three distinct "claim" concepts
|
|
12
|
+
|
|
13
|
+
This repository uses the word "claim" for three unrelated things. Always qualify in prose; never
|
|
14
|
+
write bare "claim" where the meaning is not obvious from surrounding context:
|
|
15
|
+
|
|
16
|
+
| Term | Layer | Meaning | Where it lives |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| **assignment claim** | this contract | Durable, provider-recorded ownership of a work item | `AssignmentProvider.claim()`, this doc |
|
|
19
|
+
| **liveness claim** | ADR 0012 | Ephemeral, TTL-reaped presence/heartbeat event | `liveness claim` (`workflow-sidecar.ts`), `freshHolders` (`liveness-read.js`) |
|
|
20
|
+
| **trust claim** | ADR 0010 / ADR 0017 | A Hachure `trust.bundle` evidence claim (kit-typed, verification status) | `workflow-sidecar.ts claim <id> <dir>` (`claimLookup`) |
|
|
21
|
+
|
|
22
|
+
An assignment claim's *effective state* is never computed from the assignment layer alone — it is
|
|
23
|
+
always joined against a liveness claim's freshness (see the join table below).
|
|
24
|
+
|
|
25
|
+
## AssignmentProvider Operations
|
|
26
|
+
|
|
27
|
+
Provider-neutral interface, verbatim from ADR 0021 §2:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
AssignmentProvider:
|
|
31
|
+
claim(subjectId, actor, meta) # assign + attach machine-readable claim record
|
|
32
|
+
release(subjectId, actor, meta) # unassign + handoff note
|
|
33
|
+
supersede(subjectId, from, to, meta) # reassign with audit trail
|
|
34
|
+
status(subjectId) -> {assignee?, actor?, claimedAt?, meta?}
|
|
35
|
+
list(actor?) -> [subjectId]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Operation | Arguments | Effect | Returns |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| `claim` | `subjectId`, `actor`, `meta` | Records durable ownership: assignee (or local-file record), a claim marker, and a versioned claim record carrying the full actor struct. | void (caller re-reads via `status` to confirm) |
|
|
41
|
+
| `release` | `subjectId`, `actor`, `meta` | Clears durable ownership and leaves a handoff note. Normally invoked by the incumbent actor at clean session end. | void |
|
|
42
|
+
| `supersede` | `subjectId`, `from`, `to`, `meta` | Reassigns ownership from a lapsed actor to a successor, with an audit-trail note explaining why (staleness, explicit human confirmation). | void |
|
|
43
|
+
| `status` | `subjectId` | Reads current assignment state without joining liveness. | `{ assignee?, actor?, claimedAt?, meta? }` — the raw assignment-layer read |
|
|
44
|
+
| `list` | `actor?` (optional filter) | Enumerates subject ids currently claimed, optionally filtered to one actor. | `[subjectId]` |
|
|
45
|
+
|
|
46
|
+
`status()` alone is **assignment-layer truth only** — it does not tell a caller whether the claim
|
|
47
|
+
is fresh. Callers that need to decide "is this work available" must compute the join described
|
|
48
|
+
next, not call `status()` in isolation.
|
|
49
|
+
|
|
50
|
+
**Actor identity is runtime-agnostic**: `{ runtime, session_id, host, human? }`. Claude Code,
|
|
51
|
+
codex, opencode, and pi sessions, and humans, are all actors. Reuse the exact struct
|
|
52
|
+
`scripts/hooks/lib/actor-identity.js`'s `resolveActor`/`serializeActor` already define — do not
|
|
53
|
+
fork a second actor concept. `subject_id` reuses `workItemSlug`'s `owner/repo#id` convention
|
|
54
|
+
(the same string the deterministic session slug derives from) rather than inventing a second
|
|
55
|
+
identifier scheme.
|
|
56
|
+
|
|
57
|
+
## The assignment ⋈ liveness join
|
|
58
|
+
|
|
59
|
+
Effective claim state is always a **join** of two layers with distinct jobs. Neither layer is
|
|
60
|
+
trusted alone:
|
|
61
|
+
|
|
62
|
+
| Layer | Records | Lifetime | Medium |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| **Assignment** | intent / ownership | durable, survives crashes | provider (issue assignee/label/comment, or local-file record) |
|
|
65
|
+
| **Liveness** | presence / freshness | ephemeral, TTL-reaped | ADR 0012 claim stream (`freshHolders`) |
|
|
66
|
+
|
|
67
|
+
Readers compute the join from `{ assignment: AssignmentStatus, freshHolders: FreshHolder[] }` to
|
|
68
|
+
one of five effective states:
|
|
69
|
+
|
|
70
|
+
| Assignment | Liveness | Effective state | `pull-work` treatment |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| assigned | fresh heartbeat | **held** | excluded |
|
|
73
|
+
| assigned | stale / absent | **reclaimable** | offered, via takeover protocol (ADR 0021 §5, out of this contract's scope) |
|
|
74
|
+
| assigned (human) | n/a (humans don't heartbeat) | **human-held** | surfaced, never auto-taken (see below) |
|
|
75
|
+
| unassigned | fresh (claim only) | **held** (assignment lagging) | excluded |
|
|
76
|
+
| unassigned | absent | **free** | offered |
|
|
77
|
+
|
|
78
|
+
This join rule is what makes lost locks structurally impossible: staleness — not assignment — is
|
|
79
|
+
what excludes, so an orphaned assignee/label/record from a dead session can never gate work.
|
|
80
|
+
|
|
81
|
+
The join is a pure function; reuse `scripts/hooks/lib/liveness-read.js`'s exported
|
|
82
|
+
`freshHolders(events, slug, selfActor, nowMs)` for the liveness half rather than re-implementing
|
|
83
|
+
TTL/staleness arithmetic. The join table itself (assignment status × liveness freshness →
|
|
84
|
+
effective state) is the only new logic — it has no other implementation in this repository.
|
|
85
|
+
|
|
86
|
+
## Lazy-correction transition table
|
|
87
|
+
|
|
88
|
+
Nothing updates the provider on a timer; every mutation has a responsible actor, invoked at a
|
|
89
|
+
specific lifecycle moment (ADR 0021 §4). This table names every transition for completeness; it
|
|
90
|
+
also records which issue implements each row so a future reader does not assume any single issue
|
|
91
|
+
delivers all four:
|
|
92
|
+
|
|
93
|
+
| Transition | Mutator | When | Owning issue |
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
|
+
| `claim` | the claiming session | at selection | **#290 (this issue)** |
|
|
96
|
+
| `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 |
|
|
98
|
+
| `supersede` | the successor, inside the takeover protocol | after the grace beat | #294 |
|
|
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) |
|
|
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
|
+
|
|
108
|
+
Lazy correction is *safe* by the join rule above (stale assignment excludes nothing), but leaves
|
|
109
|
+
the human-visible board stale in the crash case until the next claim attempt or a janitor sweep
|
|
110
|
+
corrects it.
|
|
111
|
+
|
|
112
|
+
## Human-assignee ask-first policy
|
|
113
|
+
|
|
114
|
+
Humans do not heartbeat, so `assigned-to-human + no-liveness` is **normal, not stale** — it must
|
|
115
|
+
never be treated as `reclaimable`. Agents surface idle human assignments ("assigned to `brian` 3
|
|
116
|
+
days ago, no linked activity — reclaim?") and act only on explicit confirmation.
|
|
117
|
+
|
|
118
|
+
- Default behavior: `ask_first`.
|
|
119
|
+
- The threshold (`idle_threshold_days`, default `3`, matching ADR 0021 §6's own illustrative
|
|
120
|
+
example) and behavior (`ask_first` | `never_reclaim`) are a policy knob in
|
|
121
|
+
`assignment-provider-settings.schema.json` (`policy.human_assignee_policy`), not a hardcoded
|
|
122
|
+
constant.
|
|
123
|
+
- **Non-goal:** never auto-supersede a human. The join function must gate on the actor struct's
|
|
124
|
+
`human` field being present (not absent/null) — never on a heuristic over the GitHub login name
|
|
125
|
+
— so a human assignee is always classified `human-held`, regardless of idle duration, and is
|
|
126
|
+
never silently reclaimed by any automated path.
|
|
127
|
+
- This contract and its settings schema define the policy knob and the read-side data a caller
|
|
128
|
+
needs to surface the question (assignee identity, idle duration). The actual "ask the user"
|
|
129
|
+
interaction belongs to whichever skill consumes the join result (`pull-work`), which records it
|
|
130
|
+
as an alignment question rather than a new UI surface.
|
|
131
|
+
|
|
132
|
+
## Versioned claim-record format
|
|
133
|
+
|
|
134
|
+
The durable claim record is a single JSON object, versioned via `schema_version` so a reader can
|
|
135
|
+
detect an incompatible future shape before parsing fields it does not understand:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"schema_version": "1.0",
|
|
140
|
+
"role": "AssignmentClaimRecord",
|
|
141
|
+
"subject_id": "kontourai/flow-agents#290",
|
|
142
|
+
"actor": { "runtime": "claude-code", "session_id": "...", "host": "...", "human": null },
|
|
143
|
+
"claimed_at": "2026-07-02T00:00:00Z",
|
|
144
|
+
"ttl_seconds": 1800,
|
|
145
|
+
"branch": "agent/<actor>/<slug>",
|
|
146
|
+
"artifact_dir": ".kontourai/flow-agents/<slug>",
|
|
147
|
+
"status": "claimed"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Field | Required | Description |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| `schema_version` | yes | Version of this record shape, `"1.0"`. |
|
|
154
|
+
| `role` | yes | Constant `"AssignmentClaimRecord"`, for readers scanning mixed content (e.g. a GitHub comment thread) for this record type. |
|
|
155
|
+
| `subject_id` | yes | The claimed work item, in `owner/repo#id` form — the same string `workItemSlug` derives the deterministic session slug from. |
|
|
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. |
|
|
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. |
|
|
159
|
+
| `ttl_seconds` | yes | Same field name/semantics as the liveness stream's `ttlSeconds` (default `1800`). |
|
|
160
|
+
| `branch` | yes | The branch this actor is working on, per the `agent/<actor>/<slug>` convention. |
|
|
161
|
+
| `artifact_dir` | yes | The deterministic workflow artifact directory for this subject, so a successor inherits the same session. |
|
|
162
|
+
| `status` | yes | `"claimed"` today; future transitions may add `"released"` / `"superseded"` values as the corresponding mutator (see the transition table) is implemented. |
|
|
163
|
+
|
|
164
|
+
**Versioning rule:** bump `schema_version` only on an incompatible change (matching
|
|
165
|
+
`artifact-contract.md`'s existing sidecar rule: "Keep `schema_version` at `1.0` until the schema
|
|
166
|
+
changes incompatibly"). Additive, optional fields do not require a bump.
|
|
167
|
+
|
|
168
|
+
### GitHub mapping
|
|
169
|
+
|
|
170
|
+
The GitHub implementation represents the record as:
|
|
171
|
+
|
|
172
|
+
- **assignee**: the notification/board hook.
|
|
173
|
+
- **a single `agent:claimed` label** (default name, configurable via
|
|
174
|
+
`policy.label_name`): the board filter.
|
|
175
|
+
- **a machine-readable claim comment**: human-readable prose above a fenced JSON block containing
|
|
176
|
+
the record above, located via a fixed marker (default
|
|
177
|
+
`<!-- flow-agents:assignment-claim -->`, configurable via `policy.claim_comment_marker`) so the
|
|
178
|
+
board stays legible to a human without tooling, while remaining machine-parseable.
|
|
179
|
+
|
|
180
|
+
The comment carries identity because the assignee field cannot: N agent sessions typically share
|
|
181
|
+
one GitHub account, so per-session identity lives in the attached record. Each provider decides
|
|
182
|
+
what it can natively represent versus what goes in the record.
|
|
183
|
+
|
|
184
|
+
### local-file mapping
|
|
185
|
+
|
|
186
|
+
For tracker-less repos and evals, the same record is written to a JSON file under the artifact
|
|
187
|
+
root (`<artifact-root>/assignment/<sanitized-subject-id>.json`), full read-modify-write, no
|
|
188
|
+
external process involved. This path is the one that does real I/O inside the CLI, since there is
|
|
189
|
+
no external mutation to defer to a skill.
|
|
190
|
+
|
|
191
|
+
**`--actor-json` asymmetry (fix-plan iteration 1, F5, intentional):** local-file `claim`/`release`
|
|
192
|
+
falls back to auto-resolving the actor from the live environment (`resolveActor(process.env)`) when
|
|
193
|
+
`--actor-json` is omitted; `render-claim`/`render-supersede` (GitHub) always require `--actor-json`
|
|
194
|
+
explicitly and fail loud without it. This is intentional, not a footgun to fix later — the
|
|
195
|
+
currently-wired `pull-work` skill always passes `--actor-json` for both provider kinds, so the
|
|
196
|
+
asymmetry has no effect on the shipped path today, but a future direct-CLI caller relying on
|
|
197
|
+
local-file `claim` to fail the same way `render-claim` does when the actor can't be determined
|
|
198
|
+
should pass `--actor-json` explicitly rather than relying on the auto-resolve fallback.
|
|
199
|
+
|
|
200
|
+
## Implementation Note
|
|
201
|
+
|
|
202
|
+
`schemas/assignment-provider-settings.schema.json` is the settings schema companion to this
|
|
203
|
+
contract — it configures `provider.kind` (`github` | `local-file`), the label name, the claim
|
|
204
|
+
comment marker, and the human-assignee policy knob described above.
|
|
205
|
+
|
|
206
|
+
The GitHub write path follows this repository's `publish-change-helper.ts` precedent — **render,
|
|
207
|
+
don't execute**: the CLI emits the exact, deterministic `gh` argv and comment body for a mutation
|
|
208
|
+
as a pure function; the calling skill runs that exact argv via its Bash tool (never freehand `gh`
|
|
209
|
+
text), then re-fetches and feeds the result back into the CLI's `status` parser to close the round
|
|
210
|
+
trip. This is a deliberate divergence from a literal "CLI shells out to `gh` itself" design: it
|
|
211
|
+
matches the one existing same-shaped precedent in this repository, keeps operational concerns
|
|
212
|
+
(auth, rate limiting, retries, partial-failure sequencing across separate `gh` calls) at the skill
|
|
213
|
+
layer where Bash-tool error handling already lives, and avoids introducing this repository's first
|
|
214
|
+
`execFileSync`-to-`gh` code path inside compiled code without a dedicated decision. A future
|
|
215
|
+
implementer of the GitHub mutation path should read this note, not re-derive the reasoning from
|
|
216
|
+
scratch — see the `#290` plan artifact's Design Decision 1 for the full evidence trail if more
|
|
217
|
+
detail is needed.
|
|
218
|
+
|
|
219
|
+
**`gh_commands` execution contract (fix-plan iteration 1, F4):** every `render-*` subcommand's
|
|
220
|
+
`gh_commands` field is a JSON array of **argv arrays** (one element per `gh` argument), not a
|
|
221
|
+
shell-command string. The calling skill MUST execute each entry as argv — every element passed as
|
|
222
|
+
its own separate Bash-tool argument — and MUST NOT concatenate the elements into a single shell
|
|
223
|
+
string or run them via `bash -c`/shell re-interpretation. This matters specifically because
|
|
224
|
+
`claim_comment_body` (and, for `render-supersede`, `previous_record`) can carry attacker-influenced
|
|
225
|
+
text: any GitHub user who can comment on the issue can post a forged claim-marker comment (see the
|
|
226
|
+
sanitization note below), and reconstructing a shell string from that content would reintroduce a
|
|
227
|
+
shell-injection surface the render/execute split is designed to avoid. This is stated explicitly in
|
|
228
|
+
`kits/builder/skills/pull-work/SKILL.md`'s "Assignment Claim On Selection" section as well — this
|
|
229
|
+
paragraph is the contract-side mirror of that instruction, not a duplicate source of truth to drift
|
|
230
|
+
from it.
|
|
231
|
+
|
|
232
|
+
**Untrusted claim-record fields (fix-plan iteration 1, F2):** `extractClaimRecord()` parses the
|
|
233
|
+
fenced JSON claim record out of *any* issue comment matching the claim marker — posting a comment
|
|
234
|
+
requires no elevated access, unlike the assignee/label mutations this contract otherwise gates. Its
|
|
235
|
+
returned record's string fields (`subject_id`, `branch`, `artifact_dir`, `actor.*`, including
|
|
236
|
+
`actor.human`, and each `audit_trail` entry's actor/`reason`) are therefore treated as untrusted
|
|
237
|
+
display input and are run through a control-character-strip + length-cap sanitizer (mirroring
|
|
238
|
+
`workflow-sidecar.ts`'s `stripControlCharsForDisplay`, the established #287/#320 mitigation for
|
|
239
|
+
this input class) before they leave the module in `status`/`list` output. This is display-only — it
|
|
240
|
+
never changes the assignment ⋈ liveness join's classification logic, only the surfaced string
|
|
241
|
+
content.
|
|
242
|
+
|
|
243
|
+
No part of this system writes to the provider on a timer or heartbeats to it (ADR 0021 §4/§7).
|
|
244
|
+
Every write is triggered by a specific lifecycle moment named in the transition table above. The
|
|
245
|
+
one optional exception is a comment-body refresh at an explicit phase transition
|
|
246
|
+
(`policy.comment_refresh_on_phase_transition`, default `false`) — cheap because it reuses the same
|
|
247
|
+
render path already built for `claim`, just called again with an updated `status`/`claimed_at`
|
|
248
|
+
field.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Builder Kit Workflow State Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
This contract defines the durable routing state for Builder Kit build-flow pickup and resume. It keeps `state.json` limited to canonical Flow Agents workflow routing and stores rich pickup decisions in a documented Probe record sidecar referenced by `state.json`, `handoff.json`, and the session artifact.
|
|
4
6
|
|
|
5
7
|
Builder Kit owns product-level build-flow coordination. Flow Agents owns the workflow harness, artifact conventions, primitive execution, and evidence gates. Flow remains the owner of Flow gate semantics. Builder Kit contracts may route to Flow Agents primitives, but must not redefine pass, fail, approval, or release authority semantics.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Decision Registry Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
Normative contract for the topic-keyed decision registry. This is the source of
|
|
4
6
|
truth for how NEW decisions are recorded in a Kontour repository. Numbered ADRs
|
|
5
7
|
under `docs/adr/` are frozen history and are never written for new decisions
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Delivery Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
Delivery chains planning, execution, review, verification, Goal Fit, and final acceptance until the user-facing goal is genuinely handled.
|
|
4
6
|
|
|
5
7
|
## Workflow
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Execution Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
Execution turns an approved plan artifact into code and local evidence while preserving parallel safety.
|
|
4
6
|
|
|
5
7
|
## Required Inputs
|
|
@@ -42,6 +44,29 @@ Execution may upgrade to a stronger mode when risk increases. Downgrades require
|
|
|
42
44
|
- Worker delegation must name the exact worker role (`tool-worker`) rather than spawning an unnamed/default implementation agent.
|
|
43
45
|
- After each wave, collect results, check conflicts, and update the session artifact before starting the next wave.
|
|
44
46
|
|
|
47
|
+
## Delegation: Model Routing
|
|
48
|
+
|
|
49
|
+
Model-routing policy is data, not code: `.datum/config.json` (schema:
|
|
50
|
+
`@kontourai/datum`, provider/role registry) is the source of truth for which
|
|
51
|
+
model backs which delegate role. This contract only says where to read it.
|
|
52
|
+
|
|
53
|
+
- When spawning a delegate, resolve its role via `npx @kontourai/datum resolve <role> --json`
|
|
54
|
+
(or the `@kontourai/datum` library's `resolve()`) and pass the resolved
|
|
55
|
+
`model` explicitly to the delegate invocation. Do not hardcode a model name
|
|
56
|
+
in a skill, agent definition, or generated file — the role name is the
|
|
57
|
+
stable reference; the model it resolves to can change without touching
|
|
58
|
+
delegation call sites.
|
|
59
|
+
- Role selection follows task shape, not delegate identity: fully-specified
|
|
60
|
+
mechanical tasks resolve `delegate-mechanical`; precisely-planned
|
|
61
|
+
implementation tasks resolve `delegate-implementation`; tasks needing design
|
|
62
|
+
latitude resolve `delegate-design`. The orchestrator's own model (planning,
|
|
63
|
+
gates, adversarial verification) resolves `orchestrator`, typically inherited
|
|
64
|
+
rather than overridden.
|
|
65
|
+
- When `datum` (the CLI/library) or `.datum/config.json` is absent or a role
|
|
66
|
+
fails to resolve, fall back to the runtime's inherited model and note the
|
|
67
|
+
fallback in the session/task artifact — do not block delegation on datum
|
|
68
|
+
being present.
|
|
69
|
+
|
|
45
70
|
## Completion Rules
|
|
46
71
|
|
|
47
72
|
Execution is complete only when:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Governance Adapter Contract
|
|
2
2
|
|
|
3
|
+
> Read [`context/contracts/standing-directives.md`](standing-directives.md) — ratified owner directives that override default engineering conservatism.
|
|
4
|
+
|
|
3
5
|
Governance adapters let Flow Agents ask an external tool for policy, proof, or trust evidence without baking repo-specific policy into Flow Agents core.
|
|
4
6
|
|
|
5
7
|
## Boundary
|