@kontourai/flow-agents 3.2.0 → 3.4.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/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +173 -0
- package/CHANGELOG.md +54 -0
- package/CONTEXT.md +5 -1
- package/README.md +19 -8
- package/build/src/builder-flow-run-adapter.d.ts +80 -0
- package/build/src/builder-flow-run-adapter.js +241 -0
- package/build/src/builder-flow-runtime.d.ts +16 -0
- package/build/src/builder-flow-runtime.js +290 -0
- package/build/src/cli/assignment-provider.js +10 -1
- package/build/src/cli/builder-run.d.ts +1 -0
- package/build/src/cli/builder-run.js +27 -0
- package/build/src/cli/effective-backlog-settings.js +70 -2
- package/build/src/cli/init.d.ts +34 -0
- package/build/src/cli/init.js +341 -61
- package/build/src/cli/kit.js +55 -12
- package/build/src/cli/pull-work-provider.js +346 -5
- package/build/src/cli/skill-drift-check.d.ts +1 -0
- package/build/src/cli/skill-drift-check.js +165 -0
- package/build/src/cli/telemetry-doctor.d.ts +37 -0
- package/build/src/cli/telemetry-doctor.js +53 -6
- package/build/src/cli/validate-hook-influence.js +37 -7
- package/build/src/cli/workflow-artifact-cleanup-audit.js +418 -11
- package/build/src/cli/workflow-sidecar.d.ts +310 -1
- package/build/src/cli/workflow-sidecar.js +1914 -126
- package/build/src/cli.js +5 -0
- package/build/src/flow-kit/validate.d.ts +54 -34
- package/build/src/flow-kit/validate.js +237 -26
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/console-connect-options.d.ts +97 -0
- package/build/src/lib/console-connect-options.js +199 -0
- package/build/src/lib/console-telemetry-validate.d.ts +49 -0
- package/build/src/lib/console-telemetry-validate.js +91 -0
- package/build/src/lib/flow-resolver.d.ts +54 -1
- package/build/src/lib/flow-resolver.js +112 -5
- package/build/src/lib/fs.d.ts +17 -0
- package/build/src/lib/fs.js +172 -0
- package/build/src/lib/local-artifact-root.d.ts +44 -1
- package/build/src/lib/local-artifact-root.js +131 -3
- package/build/src/runtime-adapters.d.ts +39 -3
- package/build/src/runtime-adapters.js +77 -31
- package/build/src/tools/build-universal-bundles.js +40 -2
- package/build/src/tools/codex-agent-routing.d.ts +2 -0
- package/build/src/tools/codex-agent-routing.js +49 -0
- package/build/src/tools/generate-context-map.js +1 -0
- package/build/src/tools/validate-source-tree.js +30 -3
- package/context/contracts/artifact-contract.md +16 -2
- package/context/scripts/hooks/lib/kit-catalog.js +235 -0
- package/context/scripts/hooks/lib/runnable-command.js +177 -0
- package/context/scripts/hooks/stop-goal-fit.js +278 -48
- package/context/scripts/hooks/workflow-steering.js +194 -22
- package/context/scripts/package.json +3 -0
- package/context/scripts/telemetry/install-console-config.sh +25 -4
- package/context/scripts/telemetry/lib/config.sh +102 -12
- package/context/scripts/telemetry/lib/pricing.sh +50 -0
- package/context/scripts/telemetry/lib/session.sh +3 -0
- package/context/scripts/telemetry/lib/transport.sh +87 -0
- package/context/scripts/telemetry/lib/usage.sh +205 -4
- package/context/scripts/telemetry/telemetry.conf +6 -0
- package/context/scripts/telemetry/telemetry.sh +48 -0
- package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
- package/docs/agent-usage-feedback-loop.md +35 -0
- package/docs/architecture-engine-and-kits.md +110 -0
- package/docs/context-map.md +2 -0
- package/docs/coordination-guide.md +370 -0
- package/docs/decisions/agent-coordination.md +26 -9
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +5 -3
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/trust-reconcile.md +42 -9
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +6 -2
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +23 -9
- package/docs/integrations/flow-agents-console.md +275 -0
- package/docs/integrations/index.md +4 -0
- package/docs/kit-authoring-guide.md +52 -21
- package/docs/spec/builder-flow-runtime.md +80 -0
- package/docs/spec/runtime-hook-surface.md +45 -1
- package/docs/specs/economics-record-contract.md +270 -0
- package/docs/specs/harness-capability-matrix.md +74 -0
- package/docs/specs/learning-review-proposals-contract.md +340 -0
- package/docs/specs/routing-efficiency-review.md +59 -0
- package/docs/verifiable-trust.md +74 -25
- package/docs/workflow-artifact-lifecycle.md +38 -1
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +2 -0
- package/evals/ci/run-baseline.sh +78 -4
- package/evals/fixtures/economics/acceptance.json +12 -0
- package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
- package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
- package/evals/fixtures/economics/critique.json +22 -0
- package/evals/fixtures/economics/expected-record.json +71 -0
- package/evals/fixtures/economics/session-usage-event.json +1 -0
- package/evals/fixtures/economics/state.json +11 -0
- package/evals/fixtures/economics/transcript.jsonl +3 -0
- package/evals/fixtures/hook-influence/cases.json +7 -7
- package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
- package/evals/fixtures/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
- package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -0
- package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
- package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -0
- package/evals/integration/test_builder_step_producers.sh +18 -10
- package/evals/integration/test_bundle_install.sh +172 -0
- package/evals/integration/test_checkpoint_signing.sh +10 -2
- package/evals/integration/test_ci_actor_identity.sh +221 -0
- package/evals/integration/test_console_tenant_isolation.sh +167 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
- package/evals/integration/test_dual_emit_flow_step.sh +10 -4
- package/evals/integration/test_economics_record.sh +674 -0
- package/evals/integration/test_effective_backlog_settings.sh +1 -1
- package/evals/integration/test_evidence_capture_hook.sh +17 -2
- package/evals/integration/test_exemption_usage_review.sh +198 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +83 -0
- package/evals/integration/test_flowdef_session_activation.sh +0 -1
- package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
- package/evals/integration/test_gate_lockdown.sh +7 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
- package/evals/integration/test_goal_fit_hook.sh +2031 -0
- package/evals/integration/test_hook_category_behaviors.sh +8 -1
- package/evals/integration/test_hook_influence_cases.sh +25 -1
- package/evals/integration/test_install_merge.sh +227 -2
- package/evals/integration/test_kit_conformance_levels.sh +6 -6
- package/evals/integration/test_learning_review_proposals.sh +329 -0
- package/evals/integration/test_liveness_conflict_injection.sh +26 -22
- package/evals/integration/test_liveness_console_relay.sh +166 -0
- package/evals/integration/test_liveness_heartbeat.sh +17 -17
- package/evals/integration/test_liveness_worktree_root.sh +575 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
- package/evals/integration/test_publish_delivery.sh +389 -2
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_reconcile_preflight.sh +304 -0
- package/evals/integration/test_record_check.sh +378 -0
- package/evals/integration/test_routing_efficiency.sh +71 -0
- package/evals/integration/test_runtime_adapter_activation.sh +28 -0
- package/evals/integration/test_session_resume_roundtrip.sh +16 -19
- package/evals/integration/test_skill_drift_check.sh +870 -0
- package/evals/integration/test_takeover_protocol.sh +340 -0
- package/evals/integration/test_telemetry.sh +445 -0
- package/evals/integration/test_telemetry_doctor.sh +66 -0
- package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +121 -13
- package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
- package/evals/integration/test_usage_cost.sh +61 -0
- package/evals/integration/test_verify_hold.sh +910 -0
- package/evals/integration/test_veritas_governance_kit.sh +257 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
- package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
- package/evals/integration/test_workflow_steering_hook.sh +157 -16
- package/evals/integration/test_workspace_settings.sh +176 -0
- package/evals/lib/env.sh +26 -0
- package/evals/lib/node.sh +8 -0
- package/evals/run.sh +37 -0
- package/evals/static/test_ci_integration_coverage.sh +115 -0
- package/evals/static/test_declared_scope_forms_documented.sh +114 -0
- package/evals/static/test_universal_bundles.sh +34 -0
- package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
- package/evals/static/test_workflow_skills.sh +1 -1
- package/kits/builder/flows/build.flow.json +9 -18
- package/kits/builder/flows/publish-learn.flow.json +5 -1
- package/kits/builder/kit.json +120 -0
- package/kits/builder/skills/continue-work/SKILL.md +2 -0
- package/kits/builder/skills/deliver/SKILL.md +115 -0
- package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -0
- package/kits/builder/skills/learning-review/SKILL.md +51 -0
- package/kits/builder/skills/plan-work/SKILL.md +17 -20
- package/kits/builder/skills/pull-work/SKILL.md +33 -2
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +113 -7
- package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
- package/kits/veritas-governance/kit.json +19 -0
- package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
- package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
- package/package.json +2 -2
- package/packaging/manifest.json +29 -0
- package/schemas/backlog-provider-settings.schema.json +13 -0
- package/schemas/workflow-state.schema.json +44 -0
- package/scripts/README.md +4 -0
- package/scripts/check-content-boundary.cjs +8 -1
- package/scripts/ci/trust-reconcile.js +214 -253
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/actor-identity.js +82 -0
- package/scripts/hooks/lib/codex-exit-code.js +316 -0
- package/scripts/hooks/lib/kit-catalog.js +235 -0
- package/scripts/hooks/lib/liveness-write.js +28 -1
- package/scripts/hooks/lib/local-artifact-paths.js +97 -1
- package/scripts/hooks/lib/runnable-command.js +177 -0
- package/scripts/hooks/lib/skill-drift.js +350 -0
- package/scripts/hooks/stop-goal-fit.js +278 -48
- package/scripts/hooks/workflow-steering.js +194 -22
- package/scripts/install-codex-home.sh +97 -47
- package/scripts/install-merge.js +72 -14
- package/scripts/install-owned-files.js +178 -0
- package/scripts/lib/reconcile-shape.js +381 -0
- package/scripts/liveness/relay.sh +84 -0
- package/scripts/telemetry/economics-record.schema.json +145 -0
- package/scripts/telemetry/economics-record.sh +331 -0
- package/scripts/telemetry/install-console-config.sh +25 -4
- package/scripts/telemetry/learning-review-decide.sh +124 -0
- package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
- package/scripts/telemetry/learning-review-proposals.sh +484 -0
- package/scripts/telemetry/lib/config.sh +102 -12
- package/scripts/telemetry/lib/pricing.sh +14 -6
- package/scripts/telemetry/lib/session.sh +3 -0
- package/scripts/telemetry/lib/transport.sh +133 -15
- package/scripts/telemetry/lib/usage.sh +121 -28
- package/scripts/telemetry/routing-efficiency.sh +0 -0
- package/scripts/telemetry/telemetry.conf +6 -0
- package/scripts/telemetry/telemetry.sh +48 -0
- package/src/builder-flow-run-adapter.ts +357 -0
- package/src/builder-flow-runtime.ts +348 -0
- package/src/cli/assignment-provider.ts +12 -1
- package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
- package/src/cli/builder-flow-runtime.test.mjs +213 -0
- package/src/cli/builder-run.ts +28 -0
- package/src/cli/codex-agent-routing.test.mjs +44 -0
- package/src/cli/codex-exit-code.test.mjs +207 -0
- package/src/cli/console-connect-options.test.mjs +329 -0
- package/src/cli/console-telemetry-validate.test.mjs +157 -0
- package/src/cli/effective-backlog-settings.ts +68 -2
- package/src/cli/flow-resolver-composition.test.mjs +72 -0
- package/src/cli/init.test.mjs +161 -0
- package/src/cli/init.ts +407 -62
- package/src/cli/kit-metadata-security.test.mjs +443 -0
- package/src/cli/kit.ts +50 -12
- package/src/cli/pull-work-provider.ts +377 -3
- package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
- package/src/cli/skill-drift-check.ts +196 -0
- package/src/cli/telemetry-doctor.test.mjs +53 -0
- package/src/cli/telemetry-doctor.ts +50 -7
- package/src/cli/validate-hook-influence.ts +37 -6
- package/src/cli/workflow-artifact-cleanup-audit.ts +483 -10
- package/src/cli/workflow-sidecar.ts +1980 -119
- package/src/cli.ts +5 -0
- package/src/flow-kit/validate.ts +277 -38
- package/src/index.ts +19 -0
- package/src/lib/console-connect-options.ts +261 -0
- package/src/lib/console-telemetry-validate.ts +88 -0
- package/src/lib/flow-resolver.ts +117 -4
- package/src/lib/fs.ts +160 -0
- package/src/lib/local-artifact-root.ts +129 -3
- package/src/runtime-adapters.ts +113 -33
- package/src/tools/build-universal-bundles.ts +36 -2
- package/src/tools/codex-agent-routing.ts +48 -0
- package/src/tools/generate-context-map.ts +1 -0
- package/src/tools/validate-source-tree.ts +29 -3
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Parallel-Session Coordination Guide
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Parallel-Session Coordination Guide
|
|
6
|
+
|
|
7
|
+
This is the plain-language, end-to-end explanation of how Flow Agents lets **many sessions work the
|
|
8
|
+
same repository at once** without two of them silently taking the same work, redoing each other's
|
|
9
|
+
effort, or clobbering one another's changes at publish time.
|
|
10
|
+
|
|
11
|
+
> **Which doc do I want?** This page is the *living reference* for coordination — the model and the
|
|
12
|
+
> four guard points, as actually shipped. The frozen [ADRs](adr/) (0012, 0021, 0020, 0022) are the
|
|
13
|
+
> immutable record of *why* each decision was made; this guide is where you look to understand *what
|
|
14
|
+
> runs today*. The [System Guidebook](agent-system-guidebook.md) is the broader map of the whole
|
|
15
|
+
> operating layer. For the optional hosted tier that turns this local substrate into a team-wide
|
|
16
|
+
> fleet view, see [Flow Agents × Console](integrations/flow-agents-console.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The problem, in one paragraph
|
|
21
|
+
|
|
22
|
+
You want to run *X* parallel Builder sessions and never think about collisions. That means four
|
|
23
|
+
moments in a session's life must be fenced: **selection** (don't pick work someone already holds),
|
|
24
|
+
**entry** (don't re-enter a subject someone else owns), **mid-flight** (notice fast if an overlap
|
|
25
|
+
happened anyway, and correct it), and **publish** (never let a session that was superseded while it
|
|
26
|
+
slept push over the session that replaced it). Everything else is convenience; those four are the
|
|
27
|
+
safety. Flow Agents makes all four advisory-by-default and cheap, with exactly **one hard fence** —
|
|
28
|
+
at publish — because a false block anywhere else would just annoy you, while a missing block at
|
|
29
|
+
publish loses work.
|
|
30
|
+
|
|
31
|
+
## The mental model: two independent streams, joined
|
|
32
|
+
|
|
33
|
+
Coordination is not one lock. It's **two independent, append-only signals about a subject** (a work
|
|
34
|
+
item / slug), joined at read time into a single *effective state*:
|
|
35
|
+
|
|
36
|
+
| Stream | Question it answers | Lifetime | Where it lives |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| **Liveness** | "Is someone *actively working* right now?" | Ephemeral — TTL-reaped, heartbeat-refreshed | `liveness/` event stream ([ADR 0012](adr/0012-agent-coordination-as-liveness-claims.md)) |
|
|
39
|
+
| **Assignment** | "Who *durably owns* this subject?" | Durable — an explicit claim record / GitHub assignee | `AssignmentProvider` ([ADR 0021](adr/0021-assignment-leases-and-stale-claim-takeover.md)) |
|
|
40
|
+
|
|
41
|
+
Neither alone is enough. Liveness without assignment can't tell a crashed session from a finished
|
|
42
|
+
one. Assignment without liveness can't tell an actively-working owner from a stale lease nobody is
|
|
43
|
+
servicing. **Joined**, they answer the only question that matters — *can I safely take this?* — and
|
|
44
|
+
the answer degrades gracefully when one signal is missing.
|
|
45
|
+
|
|
46
|
+
Here is the lifecycle, mapped to the code that fences each moment:
|
|
47
|
+
|
|
48
|
+
| Session phase | Guard point | Command / hook | Effect |
|
|
49
|
+
| --- | --- | --- | --- |
|
|
50
|
+
| **Selection** | pull-work preflight | `pull-work` skill → `assignment-provider status` | Excludes `held`/`reclaimable`; asks first on `human-held` |
|
|
51
|
+
| **Entry** | ensure-session ownership guard | `workflow-sidecar ensure-session` | `free`→claim; `reclaimable`+`--supersede-stale`→take over; `human-held`→ask |
|
|
52
|
+
| **Mid-flight** | liveness heartbeat + supersession steering | Activity hooks + `workflow-steering.js` | Refreshes the claim; warns every turn if you were superseded |
|
|
53
|
+
| **Exit** | Stop-hook clean release | Stop hook → `liveness release` + assignment release | Frees both streams, leaves a handoff note |
|
|
54
|
+
| **Publish** | verify-hold gate (**the one hard fence**) | `workflow-sidecar verify-hold` inside `publishDelivery` | Blocks a superseded/zombie session from pushing |
|
|
55
|
+
|
|
56
|
+
The rest of this guide walks each layer bottom-up: first *who am I* (the actor model — the part that
|
|
57
|
+
caused the most bugs, so it goes first), then the two streams, then the join, then each guard point,
|
|
58
|
+
then how delivery itself is made tamper-resistant.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 1. The actor model — "who am I?"
|
|
63
|
+
|
|
64
|
+
Every claim, heartbeat, and ownership check is attributed to an **actor**. Getting actor identity
|
|
65
|
+
subtly wrong is what caused nearly every hard bug in this substrate (#291, #292, #293), so understand
|
|
66
|
+
this section before the rest.
|
|
67
|
+
|
|
68
|
+
### The resolution chain
|
|
69
|
+
|
|
70
|
+
`resolveActor(env)` (in `scripts/hooks/lib/actor-identity.js`, mirrored in `src/cli`) returns
|
|
71
|
+
`{ actor, source }` by trying four sources in strict priority order:
|
|
72
|
+
|
|
73
|
+
1. **`explicit-override`** — `FLOW_AGENTS_ACTOR` is set. The actor is the **bare token** you provided,
|
|
74
|
+
verbatim. Source string: `"explicit-override"`.
|
|
75
|
+
2. **`runtime-session-id:<runtime>`** — the host runtime exposes a native session id (e.g. Claude
|
|
76
|
+
Code). The actor is a **serialized triple** `runtime:session:host`. Source string:
|
|
77
|
+
`"runtime-session-id:<runtime>"`.
|
|
78
|
+
3. **`process-ancestry`** — no session id; identity is derived by walking the process tree. Also a
|
|
79
|
+
serialized triple. Source string: `"process-ancestry"`.
|
|
80
|
+
4. **`unresolved`** — nothing worked. Source string: `"unresolved"`.
|
|
81
|
+
|
|
82
|
+
The `"local"` literal default from the old design is **retired as an error, not a fallback** — a
|
|
83
|
+
shared default actor structurally defeats collision detection on the one machine (your laptop) where
|
|
84
|
+
co-located sessions are most likely.
|
|
85
|
+
|
|
86
|
+
### The seam that caused the bugs: flat token vs. serialized triple
|
|
87
|
+
|
|
88
|
+
Here is the trap. An **override** actor is a *bare token* (`alice`). A **derived** actor is a
|
|
89
|
+
*serialized triple* (`claude:sess-abc:host`). So there are two different string forms for "the
|
|
90
|
+
actor," and they **diverge for override actors but agree for derived actors**:
|
|
91
|
+
|
|
92
|
+
- `resolveActor(env).actor` → the canonical form (bare token for overrides; triple for derived).
|
|
93
|
+
- `serializeActor(actorStruct)` → **always** a triple `<runtime>:<session>:<host>`. For an override
|
|
94
|
+
actor the struct's `<runtime>` is whatever the reconstructing site fills in (`detectRuntime()`,
|
|
95
|
+
typically `unknown`, in `assignment-provider.ts`), so it serializes to something like
|
|
96
|
+
`unknown:alice:host` — **not** equal to the bare `alice` the rest of the system uses. The exact
|
|
97
|
+
prefix doesn't matter; what matters is that the serialized form is a triple and the canonical form
|
|
98
|
+
is a bare token, so the two **diverge for override actors** (and happen to agree for derived ones).
|
|
99
|
+
|
|
100
|
+
Every other surface — `liveness whoami`, `liveness claim --actor`, per-actor `current.json`,
|
|
101
|
+
pull-work's `--self-actor` — uses the canonical `resolveActor().actor` form. If a self-recognition
|
|
102
|
+
check compared the *serialized* form of a stored claim against the *canonical* self, an override
|
|
103
|
+
session would **fail to recognize its own claim** and either double-claim or false-block itself.
|
|
104
|
+
|
|
105
|
+
### The fix: a canonical `actor_key` on every record
|
|
106
|
+
|
|
107
|
+
Claim records carry an explicit `actor_key` field (schema ≥ 1.0): the canonical
|
|
108
|
+
`resolveActor(env).actor` string of the claiming actor. Self-recognition and the liveness join always
|
|
109
|
+
compare against **this**, with a backward-compatible fallback for pre-#291 records:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
holderActorKey = record.actor_key || serializeActor(record.actor)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A record written by a current session has `actor_key` and compares canonically; an old record without
|
|
116
|
+
it falls back to `serializeActor`, reproducing pre-#291 behavior exactly. This one field is why the
|
|
117
|
+
override/derived divergence no longer bites.
|
|
118
|
+
|
|
119
|
+
### Stable vs. unstable identity (this powers the publish gate)
|
|
120
|
+
|
|
121
|
+
The `source` matters beyond attribution. An identity is **stable** if it came from
|
|
122
|
+
`explicit-override` or `runtime-session-id:*` — those are reproducible across a session's lifetime. It
|
|
123
|
+
is **unstable** if it came from `process-ancestry` or `unresolved` — those can shift (e.g. a CI job
|
|
124
|
+
with a different process tree on each step). The publish gate (§8) *enforces* only for stable
|
|
125
|
+
identities and degrades to advisory for unstable ones, because a hard block keyed on an identity that
|
|
126
|
+
might not reproduce would false-block legitimate work. Making CI identity stable is the subject of the
|
|
127
|
+
forthcoming CI-runtime identity tier (#398).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 2. The liveness stream — "is someone working right now?"
|
|
132
|
+
|
|
133
|
+
Liveness ([ADR 0012](adr/0012-agent-coordination-as-liveness-claims.md)) is an **advisory,
|
|
134
|
+
append-only** stream of `claim` / `heartbeat` / `release` events, one file under `liveness/`, reaped
|
|
135
|
+
by TTL. It is computed, never authoritative:
|
|
136
|
+
|
|
137
|
+
- A session **claims** a subject when it starts working it, and **heartbeats** on activity to refresh
|
|
138
|
+
freshness. Auto-emit is wired into the lifecycle (default-on since #288).
|
|
139
|
+
- A holder is **fresh** if its most recent event is within the TTL window; **stale** otherwise.
|
|
140
|
+
- On clean exit the session emits **release**; on a crash it simply stops heartbeating and ages out.
|
|
141
|
+
|
|
142
|
+
Liveness answers "active *now*," cheaply and without provider mutation. It cannot, alone, distinguish
|
|
143
|
+
"crashed mid-work" from "finished and moved on" — that's what assignment adds.
|
|
144
|
+
|
|
145
|
+
Helpers: `scripts/hooks/lib/liveness-read.js` reads the stream; `liveness whoami` prints the current
|
|
146
|
+
actor; `workflow-sidecar liveness claim|heartbeat|release` manage events.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 3. The assignment layer — "who durably owns this?"
|
|
151
|
+
|
|
152
|
+
The `AssignmentProvider` ([ADR 0021](adr/0021-assignment-leases-and-stale-claim-takeover.md),
|
|
153
|
+
`src/cli/assignment-provider.ts`) is the durable half. It's an abstraction with two implementations
|
|
154
|
+
behind one contract ([`context/contracts/assignment-provider-contract.md`](https://github.com/kontourai/flow-agents/blob/main/context/contracts/assignment-provider-contract.md)):
|
|
155
|
+
|
|
156
|
+
- **`github`** — maps ownership onto native GitHub primitives: **assignee** (the durable claim),
|
|
157
|
+
**label** (the state), **comment** (the audit trail). This is the shared-truth provider for a team.
|
|
158
|
+
- **`local-file`** — a per-repo claim record on disk, for solo/offline use.
|
|
159
|
+
|
|
160
|
+
Operations: `claim`, `release`, `supersede`, `status`, `list`. A claim record is versioned
|
|
161
|
+
(`schema_version: "1.0"`) and carries the canonical `actor_key` (§1), a `claimed_at`, and an
|
|
162
|
+
`audit_trail` of `claim`/`release`/`supersede` transitions.
|
|
163
|
+
|
|
164
|
+
### Render-don't-execute
|
|
165
|
+
|
|
166
|
+
A crucial design rule ([ADR 0021](adr/0021-assignment-leases-and-stale-claim-takeover.md), Decision
|
|
167
|
+
1): **the CLI never runs `gh` itself.** For the `github` provider it emits the exact `gh` argv and
|
|
168
|
+
comment bodies as pure data; the skill layer executes them via the harness Bash tool (argv arrays,
|
|
169
|
+
never shell strings). No `gh` subprocess exists in `src/`. This keeps the trust boundary clean and the
|
|
170
|
+
provider testable.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 4. The join — assignment ⋈ liveness → effective state
|
|
175
|
+
|
|
176
|
+
`computeEffectiveState()` (`src/cli/assignment-provider.ts`) joins the two streams into one of four
|
|
177
|
+
**effective states**, each with a specific machine-readable `reason`. This table *is* the coordination
|
|
178
|
+
logic — everything downstream is a policy over these outcomes:
|
|
179
|
+
|
|
180
|
+
| effective_state | reason | Meaning |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| `free` | `no_assignment_no_liveness` | Nobody owns it and nobody is working it. Take it. |
|
|
183
|
+
| `held` | `self_is_holder` | *You* hold it (canonical `actor_key` matches self). Proceed. |
|
|
184
|
+
| `held` | `fresh_liveness_heartbeat` | Someone else holds it **and** is actively heartbeating. Hands off. |
|
|
185
|
+
| `held` | `liveness_claim_present_assignment_lagging` | Liveness present but **no durable assignment yet** — an actively-working session that hasn't recorded a durable claim. |
|
|
186
|
+
| `reclaimable` | `assignment_present_liveness_stale_or_absent` | A durable assignment exists but **nobody is heartbeating** — a stale lease / crashed or finished session. Eligible for takeover. |
|
|
187
|
+
| `human-held` | `assignee_is_human` | A human is assigned. **Ask first**, always. |
|
|
188
|
+
| `human-held` | `assignee_without_claim_record` | A human assignee with no agent claim record. **Ask first.** |
|
|
189
|
+
|
|
190
|
+
Two subtleties worth internalizing:
|
|
191
|
+
|
|
192
|
+
- **`held / liveness_claim_present_assignment_lagging`** is "working but not yet durably claimed." It
|
|
193
|
+
is a *hold* (don't barge in) but it is **not** an assignment-backed conflict — which is exactly why
|
|
194
|
+
the publish gate treats it as a pass (§8).
|
|
195
|
+
- **`reclaimable`** is the takeover-eligible state: the durable owner is gone (stale/absent liveness)
|
|
196
|
+
but left a lease. Reclaiming it is deliberate and gated (§7, §10), never automatic.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 5. Guard point 1 — pull-work selection
|
|
201
|
+
|
|
202
|
+
When a session selects work, the `pull-work` skill computes the join for each candidate and **excludes
|
|
203
|
+
anything not takeable**: `held` and `reclaimable` subjects are filtered out; `human-held` triggers an
|
|
204
|
+
ask-first prompt rather than a silent skip. Only after selection does it **emit** — first a liveness
|
|
205
|
+
claim, then (via render-don't-execute) the assignment claim. This is what stops *N* concurrent sessions
|
|
206
|
+
from all classifying the same issue "ready" and all taking it. Selection reads and writes both streams;
|
|
207
|
+
it never mutates a provider it hasn't been told to.
|
|
208
|
+
|
|
209
|
+
## 6. Guard point 2 — ensure-session ownership guard
|
|
210
|
+
|
|
211
|
+
`workflow-sidecar ensure-session` (`src/cli/workflow-sidecar.ts`, `enforceEnsureSessionOwnership`) is
|
|
212
|
+
the fence at **entry** — the moment a session commits to a subject. It computes the effective state and
|
|
213
|
+
branches:
|
|
214
|
+
|
|
215
|
+
- **`free`** → claim and enter.
|
|
216
|
+
- **`self_is_holder`** → already yours; enter.
|
|
217
|
+
- **`reclaimable`** → enter **only** with an explicit `--supersede-stale`; that performs a `supersede`
|
|
218
|
+
(recording the takeover in the audit trail) and enters. Without the flag, it stops and explains.
|
|
219
|
+
- **`held` (someone else, fresh)** → stop; someone is actively working it.
|
|
220
|
+
- **`human-held`** → stop and ask first.
|
|
221
|
+
|
|
222
|
+
It resolves *self* through the same `resolveActor` path and compares on the canonical `actor_key`, so
|
|
223
|
+
an override session recognizes its own in-progress claim (this is the seam from §1). Every untrusted
|
|
224
|
+
field it echoes back (holder actor, `last_at`, branch) is sanitized at construction — see §9.
|
|
225
|
+
|
|
226
|
+
## 7. Guard point 4 — Stop-hook clean release
|
|
227
|
+
|
|
228
|
+
On clean session end, the **Stop hook** releases *both* streams: `liveness release` plus an assignment
|
|
229
|
+
release, and writes a **handoff note** so the next session (human or agent) picks up from recorded
|
|
230
|
+
state rather than guesswork. A crash skips this — and that's fine: the liveness claim simply ages out
|
|
231
|
+
to `stale`, the assignment becomes `reclaimable`, and takeover (§10) handles it. Clean release just
|
|
232
|
+
makes the common case instant instead of TTL-delayed.
|
|
233
|
+
|
|
234
|
+
## 8. Guard point 3 — the verify-hold publish gate (the one hard fence)
|
|
235
|
+
|
|
236
|
+
This is the **only** place coordination *blocks*, and it earned three fix iterations, so its design is
|
|
237
|
+
the most carefully tuned in the system. It lives in `runVerifyHold` and composes into `publishDelivery`
|
|
238
|
+
(`src/cli/workflow-sidecar.ts`) as a distinct fail-closed tier.
|
|
239
|
+
|
|
240
|
+
**The scenario it exists for:** a session claims a subject, goes idle (laptop sleeps), gets
|
|
241
|
+
legitimately superseded by another session that finishes and merges. The first session wakes and tries
|
|
242
|
+
to push — over the top of the work that replaced it. Nothing upstream catches this, because the zombie
|
|
243
|
+
*was* the valid holder when it started. The publish gate is the backstop.
|
|
244
|
+
|
|
245
|
+
**The converged rule — enforce narrowly, degrade safely:**
|
|
246
|
+
|
|
247
|
+
> The gate **blocks (hard)** only when **(1) the session's identity is stable** (`explicit-override`
|
|
248
|
+
> or `runtime-session-id:*`, or an explicitly-passed `actorKey`) **AND (2) there is a durable
|
|
249
|
+
> assignment conflict** — the effective state is `reclaimable`, an assignment-backed `held`-by-another,
|
|
250
|
+
> or `human-held`. In every other case it **degrades to advisory** and passes.
|
|
251
|
+
|
|
252
|
+
Concretely:
|
|
253
|
+
|
|
254
|
+
- **Unstable identity** (`process-ancestry` / `unresolved`) → short-circuits to
|
|
255
|
+
`{ ok: true, effective_state: "not_evaluated", reason: "actor-identity-unstable-advisory-only" }`.
|
|
256
|
+
It never hard-blocks on an identity that might not reproduce. (This is why CI, which currently
|
|
257
|
+
resolves via ancestry, gets advisory treatment — and why #398, giving CI a stable identity, upgrades
|
|
258
|
+
it to enforcing.)
|
|
259
|
+
- **Liveness-only hold** (`held / liveness_claim_present_assignment_lagging`) → **passes.** There is no
|
|
260
|
+
durable assignment conflict; blocking here would false-block a legitimate publish.
|
|
261
|
+
- **Genuine durable conflict + stable identity** → throws `NotFreshHolderError`
|
|
262
|
+
(`.code === "VERIFY_HOLD_NOT_FRESH_HOLDER"`), which `publishDelivery` refuses to swallow.
|
|
263
|
+
|
|
264
|
+
**Why this doesn't weaken zombie protection:** a superseded session *always* leaves a durable
|
|
265
|
+
assignment record (supersede writes one), and a real interactive session *always* carries a stable
|
|
266
|
+
identity. So the exact case the gate must catch — a stable session whose durable claim was superseded —
|
|
267
|
+
always lands in the enforcing branch. The advisory degradations only ever apply to cases that were
|
|
268
|
+
never a real conflict.
|
|
269
|
+
|
|
270
|
+
**The load-bearing lesson** (captured as a learning + a deliver-guidance correction): a hard block
|
|
271
|
+
added to a *shared* code path (`publishDelivery` is traversed by every delivery and many tests) must
|
|
272
|
+
**default to advisory and enforce only on high-confidence signals.** Two of the three defects were
|
|
273
|
+
*false-blocks* invisible to all eight acceptance criteria and two review passes — because every AC
|
|
274
|
+
scenario naturally used a stable identity with an assignment record. They only surfaced when CI's broad
|
|
275
|
+
suite ran under a *neutral* (ancestry) identity. Targeted ACs and a broad-suite-under-neutral-identity
|
|
276
|
+
are **different safety nets**; a hard-block-on-shared-path change needs both.
|
|
277
|
+
|
|
278
|
+
## 9. The injection-safety invariant (everywhere untrusted fields are echoed)
|
|
279
|
+
|
|
280
|
+
Coordination reads fields written by *other* sessions — actor, holder, `last_at`, branch,
|
|
281
|
+
`artifact_dir`, reason. These are **untrusted** and flow into agent-facing output, so a hostile value
|
|
282
|
+
could inject instructions or blow up a display. The invariant, applied at *construction* (not at print
|
|
283
|
+
time), everywhere such a field crosses into output:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
stripControlCharsForDisplay(value).slice(0, 64) // (larger caps for known-long fields, e.g. reason → 240)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
This class recurred across #287/#320/#290/#291/#293. The subtlest miss: sanitizing only the
|
|
290
|
+
*discriminated* field (`actor`) while a sibling (`last_at`) slipped through raw — fixed by sanitizing
|
|
291
|
+
the *whole holder object* at construction. `safeStateText` in `workflow-steering.js` was found to only
|
|
292
|
+
collapse whitespace (not strip control chars) and was hardened. When you add a new field to any
|
|
293
|
+
coordination output, sanitize it at construction — no exceptions.
|
|
294
|
+
|
|
295
|
+
## 10. Takeover protocol (forthcoming — #294)
|
|
296
|
+
|
|
297
|
+
Takeover is the deliberate reclaiming of a `reclaimable` subject. The pieces already exist —
|
|
298
|
+
`reclaimable` detection (§4), `--supersede-stale` entry (§6), supersede audit records (§3), and the
|
|
299
|
+
publish gate that catches the woken zombie (§8). #294 is the remaining slice that ties them into a
|
|
300
|
+
first-class protocol: **stale detect → grace beat → supersede → resume the branch**, with the original
|
|
301
|
+
session caught cleanly at publish if it ever wakes. Until it lands, takeover works via the manual
|
|
302
|
+
`ensure-session --supersede-stale` path. *(This section describes intended behavior; #294 is not yet
|
|
303
|
+
shipped.)*
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## How delivery itself is made tamper-resistant
|
|
308
|
+
|
|
309
|
+
Coordination decides *who* may publish; the **delivery machinery** makes the publish itself honest and
|
|
310
|
+
non-colliding. Three mechanisms
|
|
311
|
+
([ADR 0020](adr/0020-trust-reconcile-manifest-and-claim-classification.md),
|
|
312
|
+
[ADR 0022](adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md)):
|
|
313
|
+
|
|
314
|
+
**Per-session delivery paths (#379).** Each session writes its bundle under `delivery/<slug>/` instead
|
|
315
|
+
of a shared `delivery/trust.bundle`. Two sessions delivering concurrently no longer overwrite each
|
|
316
|
+
other; CI selects prefer-newest. (A recurring publish trap: after a soft-reset, `git add -A` would
|
|
317
|
+
stage the *deletion* of other sessions' `delivery/<slug>/` dirs — always restore sibling delivery dirs
|
|
318
|
+
from `origin/main` before committing.)
|
|
319
|
+
|
|
320
|
+
**Three-tier fail-closed `publishDelivery`.** Publishing passes through, in order:
|
|
321
|
+
1. **fail-soft** — absent/repo-root bundle is tolerated (nothing to publish).
|
|
322
|
+
2. **shape gate (#356)** — `InvalidBundleShapeError` (`.code === "RECONCILE_PREFLIGHT_INVALID_SHAPE"`):
|
|
323
|
+
an invalid *bundle shape* is refused before anything is copied into `delivery/`.
|
|
324
|
+
3. **hold gate (#293)** — `NotFreshHolderError` (§8): a valid-shaped bundle from a superseded holder is
|
|
325
|
+
refused.
|
|
326
|
+
|
|
327
|
+
These are **distinct error types**, asserted by `.code`/`instanceof` (not message text) so a future
|
|
328
|
+
refactor can't silently collapse them.
|
|
329
|
+
|
|
330
|
+
**reconcile-preflight (#356).** A local, pre-push mirror of the CI reconcile check, sharing
|
|
331
|
+
`scripts/lib/reconcile-shape.js` so it can never drift from CI. It runs the *same* shape classification
|
|
332
|
+
CI enforces, catching an invalid bundle on your laptop instead of in a red CI run. Its one deliberate
|
|
333
|
+
difference is the `onUnderivable` mode: CI passes `'fail'` (fail-closed — an underivable status fails
|
|
334
|
+
the run); the local preflight may pass `'reduce'` (trust a session-local self-reported status when
|
|
335
|
+
re-derivation is unavailable). The default is `'fail'`, so nothing degrades to fail-open by accident.
|
|
336
|
+
|
|
337
|
+
**Staleness binding (ADR 0022).** A checkpoint's `commit_sha` must be an ancestor of `HEAD`, achieved
|
|
338
|
+
by *sealing at the parent commit*: soft-reset `HEAD~1` → seal → publish → recommit. This binds a bundle
|
|
339
|
+
to the exact history it attests, so a stale bundle can't be replayed onto a moved branch.
|
|
340
|
+
|
|
341
|
+
The canonical publish sequence that ties coordination + delivery together:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
rebase early → seal at parent (commit_sha = HEAD~1) → reconcile-preflight (local backstop)
|
|
345
|
+
→ restore sibling delivery/<slug>/ dirs → verify-hold → push
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Where this goes next: the Console tier
|
|
351
|
+
|
|
352
|
+
Everything above runs **locally** and needs no server — that's the design invariant (the Console is
|
|
353
|
+
never required). The optional [Kontour Console](integrations/flow-agents-console.md) tier turns this
|
|
354
|
+
per-repo substrate into a **team-wide fleet view**: a liveness relay so you can see every teammate's
|
|
355
|
+
active sessions, project/team economics and insight views alongside your own, an in-hub janitor that
|
|
356
|
+
reaps stale claims centrally, and — eventually — a Console-backed `AssignmentProvider` so assignment is
|
|
357
|
+
authoritative across machines. It also makes CI a first-class, attributable participant (paired with
|
|
358
|
+
#398). See the [Flow Agents × Console integration doc](integrations/flow-agents-console.md) for the
|
|
359
|
+
phased plan.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Provenance
|
|
364
|
+
|
|
365
|
+
This guide is the living reference. The decisions behind it are recorded, immutable, in the ADRs it
|
|
366
|
+
links; the ratified subject decisions live in [`docs/decisions/`](decisions/index.md)
|
|
367
|
+
([agent-coordination](decisions/agent-coordination.md), [trust-reconcile](decisions/trust-reconcile.md)).
|
|
368
|
+
Shipped across issues #287 (actor identity), #288 (liveness), #289 (branches), #166 (pull-work), #320
|
|
369
|
+
(overlap), #290 (AssignmentProvider), #291 (ensure-session guard), #292 (stop-release), #356
|
|
370
|
+
(reconcile-preflight), #293 (verify-hold); #294 (takeover) and #398 (CI identity) forthcoming.
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
---
|
|
2
|
-
status:
|
|
2
|
+
status: current
|
|
3
3
|
subject: Agent coordination
|
|
4
|
-
decided: 2026-07-
|
|
4
|
+
decided: 2026-07-04
|
|
5
5
|
evidence:
|
|
6
6
|
- kind: adr
|
|
7
7
|
ref: docs/adr/0012-agent-coordination-as-liveness-claims.md
|
|
8
8
|
- kind: adr
|
|
9
9
|
ref: docs/adr/0021-assignment-leases-and-stale-claim-takeover.md
|
|
10
|
+
- kind: doc
|
|
11
|
+
ref: docs/coordination-guide.md
|
|
10
12
|
---
|
|
11
13
|
# Agent coordination
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
(`
|
|
16
|
-
|
|
15
|
+
**Decision.** Parallel-session coordination is a **two-stream join**: an ephemeral,
|
|
16
|
+
TTL-reaped **liveness** stream ("who is working now") and a durable **assignment** record
|
|
17
|
+
("who owns this") are joined at read time by `computeEffectiveState()` into one of four
|
|
18
|
+
effective states — `free`, `held`, `reclaimable`, `human-held`. Selection, entry, and publish
|
|
19
|
+
are policies over that join. The model is **advisory by default with exactly one hard fence**
|
|
20
|
+
(the verify-hold publish gate), which itself enforces only for stable identities against durable
|
|
21
|
+
conflicts and degrades to advisory otherwise.
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
This is the **living reference**, kept current: [`docs/coordination-guide.md`](../coordination-guide.md).
|
|
24
|
+
The frozen ADRs ([0012](../adr/0012-agent-coordination-as-liveness-claims.md),
|
|
25
|
+
[0021](../adr/0021-assignment-leases-and-stale-claim-takeover.md)) remain immutable provenance
|
|
26
|
+
for *why* each decision was made.
|
|
27
|
+
|
|
28
|
+
**Rationale.** Two independent signals degrade gracefully when one is missing (liveness alone
|
|
29
|
+
can't tell a crash from a finish; assignment alone can't tell an active owner from a stale
|
|
30
|
+
lease), and an advisory-by-default posture keeps false blocks cheap while concentrating the one
|
|
31
|
+
place a false *miss* is expensive — publish — into a single, carefully-tuned gate.
|
|
32
|
+
|
|
33
|
+
**Shipped as** #287 (actor identity), #288 (liveness default-on), #289 (branches), #166
|
|
34
|
+
(pull-work), #320 (overlap detect-and-correct), #290 (AssignmentProvider), #291 (ensure-session
|
|
35
|
+
ownership guard), #292 (stop-hook clean release), #293 (verify-hold publish gate). Forthcoming:
|
|
36
|
+
#294 (takeover protocol), #398 (CI-runtime actor identity). The optional fleet tier is
|
|
37
|
+
[Flow Agents × Console](../integrations/flow-agents-console.md).
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: needs-decision
|
|
3
|
+
subject: Embeddable engine and adapter model
|
|
4
|
+
decided: 2026-07-07
|
|
5
|
+
evidence:
|
|
6
|
+
- kind: doc
|
|
7
|
+
ref: docs/spec/runtime-hook-surface.md
|
|
8
|
+
- kind: doc
|
|
9
|
+
ref: docs/decisions/flow-flow-agents-boundary.md
|
|
10
|
+
- kind: doc
|
|
11
|
+
ref: docs/decisions/trust-ledger-retention.md
|
|
12
|
+
- kind: pr
|
|
13
|
+
ref: https://github.com/kontourai/flow-agents/pull/497
|
|
14
|
+
- kind: issue
|
|
15
|
+
ref: https://github.com/kontourai/flow-agents/issues/410
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Embeddable engine and adapter model
|
|
19
|
+
|
|
20
|
+
> Status: **proposed** direction (shaped with Brian Anderson, 2026-07-07). Not yet
|
|
21
|
+
> fully built. Ratify + carry forward as the runtime is factored. This record is
|
|
22
|
+
> the north star the adapter/port refactors implement against; the linked backlog
|
|
23
|
+
> issues are the work.
|
|
24
|
+
|
|
25
|
+
**Decision.** Flow Agents is one **runtime-agnostic engine** with **thin
|
|
26
|
+
adapters** on the edges. A development harness (Claude Code, Codex, opencode, pi,
|
|
27
|
+
Kiro) and a customer agent built on a framework/SDK (AWS Strands, VoltAgent,
|
|
28
|
+
LangGraph, OpenAI Agents SDK) are the **same shape**: both are adapters that feed
|
|
29
|
+
canonical Flow events into the one engine and honor the same contracts. A Flow or
|
|
30
|
+
Flow-Agent-Kit implementation should behave **consistently** across development
|
|
31
|
+
workflows and customer agent implementations, with **explicit callouts** wherever
|
|
32
|
+
a runtime genuinely cannot offer a Flow feature — never silent divergence.
|
|
33
|
+
|
|
34
|
+
Five rules follow.
|
|
35
|
+
|
|
36
|
+
1. **One core, adapters at the edge (DRY by construction).** The engine owns
|
|
37
|
+
everything runtime-independent: the canonical event vocabulary, the redaction
|
|
38
|
+
contract, project/session/actor attribution, dual-channel transport, trust
|
|
39
|
+
bundles, gate/claim semantics, and state projection. An adapter's only job is
|
|
40
|
+
to translate a runtime's native surface into canonical events and to invoke
|
|
41
|
+
engine decisions — it holds **no policy of its own**. Today the harness
|
|
42
|
+
adapters already prove this: one shared shell core
|
|
43
|
+
(`scripts/telemetry/telemetry.sh` + `lib/*.sh`) with thin per-runtime JS
|
|
44
|
+
adapters (~130 lines each) that normalize hook input and shell into the core.
|
|
45
|
+
Duplicated logic across adapters is a bug against this record.
|
|
46
|
+
|
|
47
|
+
2. **Harness adapters and framework adapters are the same category.** A CLI
|
|
48
|
+
harness emits events from shell hooks; an in-process framework emits the same
|
|
49
|
+
canonical events from a language-native package — no shelling out, honoring the
|
|
50
|
+
same redaction contract in-process. The difference is the **transport into the
|
|
51
|
+
core**, not the **contract or the semantics**. `context.project`,
|
|
52
|
+
`context.cwd` redaction, actor identity, and dual-channel routing are derived
|
|
53
|
+
the same way everywhere (see `docs/spec/runtime-hook-surface.md`, which is now
|
|
54
|
+
the canonical adapter surface, and PR #497 which made `context.project` a
|
|
55
|
+
canonical field). Where the shell core cannot be reused verbatim in-process,
|
|
56
|
+
the engine's runtime-agnostic logic is extracted to a language port the
|
|
57
|
+
framework adapter calls; the shell adapter becomes one caller of that port, not
|
|
58
|
+
the definition of it.
|
|
59
|
+
|
|
60
|
+
3. **Flow Agents is an "SDK" engine layer, and Console is its remote backend.**
|
|
61
|
+
The same engine that instruments a dev session is the layer a customer embeds
|
|
62
|
+
in their own agent to get Flow's trust/state/economics for free. When embedded,
|
|
63
|
+
the **Console is the remote trust and state backend** for those SDK-embedded
|
|
64
|
+
agents — the same projection surface described in
|
|
65
|
+
`docs/decisions/trust-ledger-retention.md` (git/CI authoritative, console a
|
|
66
|
+
rebuildable projection), now fed by customer agents as well as dev harnesses.
|
|
67
|
+
The engine does not care whether the events came from a terminal or from a
|
|
68
|
+
long-running service.
|
|
69
|
+
|
|
70
|
+
4. **Cooperative in-process, authoritative at the boundary ("agent proposes, CI
|
|
71
|
+
disposes").** A framework/SDK adapter runs inside the customer's process, which
|
|
72
|
+
the engine does not control, so in-process enforcement is **cooperative**: the
|
|
73
|
+
embedded engine emits claims and honors gates as a good citizen. Authority
|
|
74
|
+
lives at the trust boundary the customer does not own — the **Console ingest
|
|
75
|
+
and CI trust-reconcile** re-verify what the agent claimed against independent
|
|
76
|
+
results (the fail-closed reconciliation from
|
|
77
|
+
`docs/adr/0022`). An adapter can be sloppy or hostile and the boundary still
|
|
78
|
+
holds the line. This is the same posture that already governs harness delivery;
|
|
79
|
+
it generalizes unchanged to embedded agents.
|
|
80
|
+
|
|
81
|
+
5. **Consistency is guaranteed by a conformance suite, not by hope
|
|
82
|
+
(OpenTelemetry model).** Every adapter — shell harness or language framework —
|
|
83
|
+
must pass a shared **conformance suite** that asserts the canonical contracts:
|
|
84
|
+
event shape, redaction defaults (full local path never leaves the machine),
|
|
85
|
+
attribution precedence, dual-channel routing, and gate/claim behavior. A new
|
|
86
|
+
adapter is "done" when it passes the suite, exactly as an OpenTelemetry SDK is
|
|
87
|
+
conformant when it passes the spec's tests. Feature gaps a runtime cannot close
|
|
88
|
+
are declared **explicitly** in the adapter's conformance report, not left for a
|
|
89
|
+
user to discover.
|
|
90
|
+
|
|
91
|
+
## Consequences / required refactors
|
|
92
|
+
|
|
93
|
+
The backlog issues that carry this direction:
|
|
94
|
+
|
|
95
|
+
- **Extract a runtime-agnostic core** (#500). The engine logic currently expressed
|
|
96
|
+
in shell (`scripts/telemetry/lib/*`) must be factored so its policy — event
|
|
97
|
+
canonicalization, redaction, attribution precedence, transport routing — is a
|
|
98
|
+
reusable port with at least a shell binding (today) and a language binding
|
|
99
|
+
(first framework adapter). No new policy may be added to an adapter that isn't
|
|
100
|
+
in the core.
|
|
101
|
+
- **The artifact/state store becomes a pluggable port** (#501). Trust bundles,
|
|
102
|
+
delivery records, and state projection are written today against a filesystem +
|
|
103
|
+
git + Console assumption. For embedded agents that seam must be an interface
|
|
104
|
+
(local-fs, git, Console-remote, customer-supplied) so the engine backend is
|
|
105
|
+
swappable without touching adapters. This is the storage-port dependency of the
|
|
106
|
+
Console-as-backend rule (3).
|
|
107
|
+
- **An adapter conformance suite** (#502) makes rule 5 checkable — one shared spec
|
|
108
|
+
every adapter (harness or framework) must pass, with explicit gap declarations.
|
|
109
|
+
- **A first framework adapter proves the model** (#503). AWS Strands (the
|
|
110
|
+
non-terminal runtime that motivated this) is the reference in-process adapter:
|
|
111
|
+
it must emit canonical events in-process, honor redaction without a shell, and
|
|
112
|
+
pass the conformance suite. Its explicit callouts define the template for "what
|
|
113
|
+
a framework cannot do that a harness can."
|
|
114
|
+
- **`runtime-hook-surface.md` is promoted from a harness spec to the adapter
|
|
115
|
+
contract** every adapter category conforms to.
|
|
116
|
+
|
|
117
|
+
## Rationale
|
|
118
|
+
|
|
119
|
+
The owner's model — "harness adapters and framework adapters would work in a very
|
|
120
|
+
similar if not exactly the same way, such that a Flow or Flow-Agent-Kit
|
|
121
|
+
implementation is consistent across development workflows and customer agent
|
|
122
|
+
implementations" — only holds if there is a single engine and the runtimes are
|
|
123
|
+
adapters over it. The alternative (per-runtime reimplementations that happen to
|
|
124
|
+
agree) drifts the moment two runtimes are maintained by different hands, which is
|
|
125
|
+
precisely the failure the numbered-ADR redesign already diagnosed elsewhere in
|
|
126
|
+
this portfolio. DRY here is not a style preference; it is what makes "Flow behaves
|
|
127
|
+
the same everywhere" a checkable property (rule 5) instead of a marketing claim.
|
|
128
|
+
|
|
129
|
+
Making Console the remote backend for embedded agents (rule 3) is the same
|
|
130
|
+
git-authoritative / console-projection split already ratified for trust retention;
|
|
131
|
+
it costs nothing new conceptually and turns the dogfood console into the customer
|
|
132
|
+
product surface. The cooperative-vs-authoritative split (rule 4) is the only
|
|
133
|
+
honest enforcement story for code running in a process we do not own, and it is
|
|
134
|
+
already how delivery reconciliation works — so embedding does not weaken the trust
|
|
135
|
+
model, it inherits it.
|
|
136
|
+
|
|
137
|
+
The explicit-callout requirement is the guard against the seductive failure mode:
|
|
138
|
+
quietly letting a framework adapter skip a Flow feature because it was hard,
|
|
139
|
+
leaving customers with an inconsistent product and no signal. A conformance report
|
|
140
|
+
that names the gap keeps the promise of consistency honest.
|
|
141
|
+
|
|
142
|
+
## Open questions
|
|
143
|
+
|
|
144
|
+
- **Language of the first extracted core port** (TypeScript is the source-policy
|
|
145
|
+
default; the shell core stays as a binding). Tracked in the extract-core issue.
|
|
146
|
+
- **Conformance-suite substrate** — reuse the existing `evals/integration`
|
|
147
|
+
harness vs a new adapter-conformance package.
|
|
148
|
+
- **Storage-port interface shape** and whether the Console-remote binding reuses
|
|
149
|
+
the existing telemetry ingest or a dedicated port.
|
|
150
|
+
|
|
151
|
+
These are implementation decisions for the linked backlog; the direction above is
|
|
152
|
+
the fixed part.
|
package/docs/decisions/index.md
CHANGED
|
@@ -10,11 +10,12 @@ Numbered ADRs under `docs/adr/` are frozen history and are not listed here.
|
|
|
10
10
|
|
|
11
11
|
| Topic | Status | Decision |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| [agent-coordination](./agent-coordination.md) |
|
|
13
|
+
| [agent-coordination](./agent-coordination.md) | current | Agent coordination |
|
|
14
14
|
| [anti-gaming-trust-security](./anti-gaming-trust-security.md) | needs-decision | Anti-gaming trust security |
|
|
15
15
|
| [context-lifecycle](./context-lifecycle.md) | needs-decision | Context lifecycle |
|
|
16
16
|
| [core-domain-kit-boundary](./core-domain-kit-boundary.md) | needs-decision | Core vs domain kit boundary |
|
|
17
17
|
| [decision-records](./decision-records.md) | current | Decision records |
|
|
18
|
+
| [embeddable-engine](./embeddable-engine.md) | needs-decision | Embeddable engine and adapter model |
|
|
18
19
|
| [flow-flow-agents-boundary](./flow-flow-agents-boundary.md) | needs-decision | Flow / Flow Agents boundary |
|
|
19
20
|
| [flow-kit](./flow-kit.md) | needs-decision | Flow Kit |
|
|
20
21
|
| [flow-skill-kit-tool-boundary](./flow-skill-kit-tool-boundary.md) | needs-decision | Flow / Skill / Kit / Tool boundary |
|
|
@@ -30,7 +31,8 @@ Numbered ADRs under `docs/adr/` are frozen history and are not listed here.
|
|
|
30
31
|
| [promotion-gate](./promotion-gate.md) | current | Promotion gate |
|
|
31
32
|
| [standing-directives](./standing-directives.md) | current | Standing directives |
|
|
32
33
|
| [three-hard-boundary-model](./three-hard-boundary-model.md) | needs-decision | Three-hard-boundary model |
|
|
33
|
-
| [trust-
|
|
34
|
+
| [trust-ledger-retention](./trust-ledger-retention.md) | needs-decision | Trust-ledger retention and console-as-projection |
|
|
35
|
+
| [trust-reconcile](./trust-reconcile.md) | current | Trust-reconcile and delivery reconciliation |
|
|
34
36
|
| [typescript-source-policy](./typescript-source-policy.md) | current | TypeScript-first source policy |
|
|
35
|
-
| [workflow-enforcement](./workflow-enforcement.md) |
|
|
37
|
+
| [workflow-enforcement](./workflow-enforcement.md) | current | Workflow Enforcement |
|
|
36
38
|
| [workflow-trust-state](./workflow-trust-state.md) | needs-decision | Workflow trust state |
|