@kontourai/flow-agents 3.3.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 +161 -0
- package/CHANGELOG.md +41 -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/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-sidecar.d.ts +93 -8
- package/build/src/cli/workflow-sidecar.js +1175 -158
- 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 +27 -1
- 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 +121 -21
- 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/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +3 -1
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +3 -0
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +19 -9
- package/docs/integrations/flow-agents-console.md +167 -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-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +72 -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/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_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 +331 -1
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- 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_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 +30 -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_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 +29 -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/deliver/SKILL.md +42 -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 +21 -0
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +35 -7
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/kit.json +14 -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 +136 -0
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- 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 +121 -21
- 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/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/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-sidecar.ts +1150 -151
- 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 +26 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# `kontour.learning-review-proposals` — kit/gate tuning proposal contract (v0.1)
|
|
2
|
+
|
|
3
|
+
**Status:** ratified (flow-agents #352). **Kind:** `kontour.learning-review-proposals`.
|
|
4
|
+
**Version:** `0.1`.
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
|
|
8
|
+
Turn already-accumulated `kontour.console.economics` records (fa#349) into **advisory,
|
|
9
|
+
evidence-cited** kit/gate tuning proposals — the same "turn recorded outcomes into a proposal
|
|
10
|
+
a human ratifies" shape as `routing-efficiency.sh` (#415), generalized from per-`(role,model)`
|
|
11
|
+
to **per-kit** and **per-gate**. `scripts/telemetry/learning-review-proposals.sh` reads a
|
|
12
|
+
window of `economics.jsonl`, joins each record's kit/gate identity from already-shipped
|
|
13
|
+
sidecar shapes, computes aggregates, and emits proposals into a durable, idempotent ledger. A
|
|
14
|
+
companion script, `scripts/telemetry/learning-review-decide.sh`, records the human
|
|
15
|
+
ratify/reject/defer decision against a proposal id. Nothing here is ever auto-applied:
|
|
16
|
+
`learning-review` step 2b (see `kits/builder/skills/learning-review/SKILL.md`) surfaces the
|
|
17
|
+
output for review; a human decides.
|
|
18
|
+
|
|
19
|
+
This is a pure **consumer**: it makes zero edits to `economics-record.sh`,
|
|
20
|
+
`economics-record.schema.json`, or `gate-review`'s `InquiryRecord` output (ADR 0008/0010
|
|
21
|
+
consume-never-fork). It never writes to `kits/**`, `.datum/config.json`, or any gate/flow
|
|
22
|
+
config file.
|
|
23
|
+
|
|
24
|
+
## Invocation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
scripts/telemetry/learning-review-proposals.sh [economics.jsonl ...] # defaults to the local economics log
|
|
28
|
+
cat economics.jsonl | scripts/telemetry/learning-review-proposals.sh - # records on stdin
|
|
29
|
+
scripts/telemetry/learning-review-proposals.sh --since 2026-07-01 --until 2026-07-07 \
|
|
30
|
+
--sessions-root .kontourai/flow-agents --ledger .kontourai/telemetry/learning-review-proposals.jsonl
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Flag | Default | Meaning |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `--since VAL` | unbounded | date-only (`2026-07-01`), full ISO-8601 with `Z` (`2026-07-01T00:00:00Z`), full ISO-8601 with a `+00:00` UTC offset, or epoch-ms; lower bound on each record's `.at` |
|
|
36
|
+
| `--until VAL` | unbounded | same accepted formats; upper bound on each record's `.at` |
|
|
37
|
+
| `--sessions-root DIR` | `.kontourai/flow-agents` | where per-session `<task_slug>/trust.bundle` and `<task_slug>/gate-review.inquiries.json` are looked up for the kit/gate join |
|
|
38
|
+
| `--ledger PATH` | `.kontourai/telemetry/learning-review-proposals.jsonl` (override via `LEARNING_REVIEW_PROPOSALS_LEDGER` env, mirrors `TELEMETRY_ECONOMICS_LOG_FILE`) | the durable, idempotent proposal ledger |
|
|
39
|
+
| `-h`/`--help` | — | usage |
|
|
40
|
+
|
|
41
|
+
Positional args and stdin follow the exact `routing-efficiency.sh` convention: explicit file
|
|
42
|
+
args (a bare `-` means stdin), else stdin when piped, else the default log
|
|
43
|
+
(`${TELEMETRY_ECONOMICS_LOG_FILE:-${TELEMETRY_DATA_DIR:-<repo>}/.kontourai/telemetry/economics.jsonl}`).
|
|
44
|
+
|
|
45
|
+
**Accepted timestamp formats** (for `--since`/`--until` AND every record's own `.at`): an
|
|
46
|
+
epoch-ms integer (as a JSON number or a digit-only string); a date-only string (`YYYY-MM-DD`,
|
|
47
|
+
normalized to midnight UTC); a full ISO-8601 string with a literal `Z` UTC suffix; or a full
|
|
48
|
+
ISO-8601 string with an explicit `+00:00` UTC offset (converted internally to `Z` before
|
|
49
|
+
parsing — jq's `fromdateiso8601` only understands the `Z` form). Fractional seconds are
|
|
50
|
+
accepted and stripped before parsing (e.g. `2026-07-01T00:00:00.123Z`, the default shape of a
|
|
51
|
+
JS `Date.toISOString()` value) — precision finer than one second is not retained. Arbitrary
|
|
52
|
+
non-UTC offsets are not supported. A `--since`/`--until` value in none of these forms is a
|
|
53
|
+
CLI usage error: the analyzer exits non-zero with a stderr diagnostic and writes nothing,
|
|
54
|
+
never a fabricated `"insufficient-data"` result (an unrecognized CLI argument is operator-
|
|
55
|
+
correctable input, not a data gap). A record's own `.at` field, by contrast, degrades to an
|
|
56
|
+
unbounded (`null`) timestamp on an unrecognized value — the same honest-degradation treatment
|
|
57
|
+
as an absent `.at` — so one malformed record never aborts analysis of an otherwise-good window.
|
|
58
|
+
|
|
59
|
+
Tunables (env, all optional): `LR_MIN_WINDOW_SAMPLE` (5), `LR_MIN_KIT_SAMPLE` (6),
|
|
60
|
+
`LR_MIN_GATE_SAMPLE` (3), `LR_COST_RISE_PCT` (25), `LR_FLAT_FINDINGS_PCT` (10),
|
|
61
|
+
`LR_GATE_FALSE_BLOCK_RATE` (0.5), `LR_GATE_WELL_CALIBRATED_RATE` (0.9).
|
|
62
|
+
|
|
63
|
+
## Output document shape
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"schema": "kontour.learning-review-proposals",
|
|
68
|
+
"version": "0.1",
|
|
69
|
+
"window": { "since": "<epoch-ms|null>", "until": "<epoch-ms|null>" },
|
|
70
|
+
"records_considered": 0,
|
|
71
|
+
"outcome": "ok | insufficient-data",
|
|
72
|
+
"aggregates": {
|
|
73
|
+
"by_kit": [ { "kit_id": "...", "runs": 0, "...": "..." } ],
|
|
74
|
+
"by_gate": [ { "gate_id": "...", "fire_count": 0, "...": "..." } ],
|
|
75
|
+
"partial": false
|
|
76
|
+
},
|
|
77
|
+
"proposals": [
|
|
78
|
+
{
|
|
79
|
+
"proposal_id": "...",
|
|
80
|
+
"target": { "kind": "kit|gate", "id": "..." },
|
|
81
|
+
"pattern": "kit-review-cost-inflation | gate-false-block-review | gate-well-calibrated",
|
|
82
|
+
"proposed_change": "<human-readable recommendation>",
|
|
83
|
+
"severity": "advisory",
|
|
84
|
+
"evidence": { "cost": { "...": "..." }, "defect": { "...": "..." } },
|
|
85
|
+
"expected_effect": { "metric": "...", "direction": "decrease|increase|maintain", "description": "..." },
|
|
86
|
+
"decision": { "status": "proposed|ratified|rejected|deferred", "decided_by": null, "decided_at": null, "rationale": null },
|
|
87
|
+
"follow_on_ref": null,
|
|
88
|
+
"effect_observed": null,
|
|
89
|
+
"already_proposed": false
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"notes": [ "..." ]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`window.since`/`window.until` in the OUTPUT document are the run's **effective** bounds:
|
|
97
|
+
the `--since`/`--until` arg when given, else respectively the min/max `.at` (epoch-ms,
|
|
98
|
+
normalized) actually observed among the records considered (or `null` when no records at
|
|
99
|
+
all). This makes the effect-fill chronological comparison (below) well-defined even when a
|
|
100
|
+
run is invoked with unbounded `--since`/`--until`.
|
|
101
|
+
|
|
102
|
+
`already_proposed` is an OUTPUT-ONLY annotation (not stored on the ledger line itself): `true`
|
|
103
|
+
when a proposal with the same `proposal_id` already exists in the ledger from a prior run (no
|
|
104
|
+
duplicate line is appended); `false` when this run appended a brand-new ledger line for it.
|
|
105
|
+
|
|
106
|
+
## Kit/gate identity join (honest, reuses already-shipped shapes — no new field on fa#349's record)
|
|
107
|
+
|
|
108
|
+
For each economics record with a non-null `task_slug`, the analyzer looks for
|
|
109
|
+
`<sessions-root>/<task_slug>/trust.bundle` and `<sessions-root>/<task_slug>/gate-review.inquiries.json`
|
|
110
|
+
(both already-shipped, read-only inputs — never invoked, never modified):
|
|
111
|
+
|
|
112
|
+
- **`kit_id`** = the mode (most frequent) of `[.claims[].claimType | split(".")[0]]` from that
|
|
113
|
+
session's `trust.bundle` (the real Kit Catalog namespace, e.g. `builder`,
|
|
114
|
+
`veritas-governance` — `kits/<name>/` on disk). If the `trust.bundle` is absent, empty, or
|
|
115
|
+
unreadable, `kit_id` degrades to `"unattributed"` — the same honest-degradation idiom
|
|
116
|
+
`economics-record.sh` already uses for `phases[].phase`.
|
|
117
|
+
- **Per-gate rows** for that record = one row per `gate-review.inquiries.json` `InquiryRecord`:
|
|
118
|
+
`{ gate_id: .inquiry.target.fieldOrBehavior, outcome: .answer.value.calibration, fired: .answer.value.gateFired }`.
|
|
119
|
+
If the file is absent, empty, or unreadable, this record contributes zero gate rows (counted
|
|
120
|
+
separately in `notes` as "sessions without a gate-review join").
|
|
121
|
+
- A record with a **null** `task_slug` (or a `task_slug` with no matching session directory)
|
|
122
|
+
gets `kit_id = "unattributed"` directly, with zero gate rows contributed — no lookup is
|
|
123
|
+
attempted.
|
|
124
|
+
|
|
125
|
+
**Honest current-data gap (stated, not hidden):** real `economics.jsonl` records emitted today
|
|
126
|
+
all carry `"task_slug":null` (fa#349 slice-1; `task_slug` plumbing was never wired end-to-end
|
|
127
|
+
for the emitter's live invocation path). Until that separate, out-of-scope plumbing gap is
|
|
128
|
+
fixed, every real run's `kit_id` degrades to `"unattributed"` and it contributes zero gate
|
|
129
|
+
rows — per-kit/per-gate attribution is genuinely vacuous on live data today. This analyzer does
|
|
130
|
+
not fabricate an identity to paper over that gap; fixtures (not live data) are what exercise
|
|
131
|
+
the attributed path until `task_slug` is populated on real runs.
|
|
132
|
+
|
|
133
|
+
**Trusted-as-is inputs (consume-never-fork):** `cost.estimated_cost_usd` and every other numeric
|
|
134
|
+
field read from an `economics.jsonl` record is trusted exactly as fa#349 emitted it, including a
|
|
135
|
+
negative or zero value — this analyzer never clamps, floors, or re-derives it; a negative/zero
|
|
136
|
+
cost simply flows through the same average/percentage formulas as any other value (and can
|
|
137
|
+
legitimately produce a negative `cost_trend_pct`, which is not an error).
|
|
138
|
+
|
|
139
|
+
## Window and aggregates (exact formulas)
|
|
140
|
+
|
|
141
|
+
All derived floating-point values (rates, percentages, dollar/second averages) are rounded to
|
|
142
|
+
**4 decimal places** using `(<value> * 10000 | round) / 10000` — the same rounding idiom
|
|
143
|
+
`routing-efficiency.sh` uses for `trouble_rate` (scaled to 4 places here instead of 3, for
|
|
144
|
+
dollar-amount precision). Any ratio with a zero or otherwise undefined denominator is `null`,
|
|
145
|
+
never a fabricated `0` or `Infinity`.
|
|
146
|
+
|
|
147
|
+
### `by_kit[]`
|
|
148
|
+
|
|
149
|
+
Records are grouped by `kit_id`. Within a group, records are sorted ascending by `.at` and
|
|
150
|
+
split into a first half (oldest) and a second half (newest): `half_point = floor(runs / 2)`;
|
|
151
|
+
the first `half_point` records are the "first half," the remaining `runs - half_point` records
|
|
152
|
+
(the odd one out, if `runs` is odd, lands in the newer half) are the "second half."
|
|
153
|
+
|
|
154
|
+
| Field | Formula |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `kit_id` | group key |
|
|
157
|
+
| `runs` | count of records in the group |
|
|
158
|
+
| `first_half_avg_cost_usd` | mean of `.cost.estimated_cost_usd` over the first half (`null` if the first half is empty) |
|
|
159
|
+
| `second_half_avg_cost_usd` | mean of `.cost.estimated_cost_usd` over the second half |
|
|
160
|
+
| `cost_trend_pct` | `((second_half_avg_cost_usd - first_half_avg_cost_usd) / first_half_avg_cost_usd) * 100`; `null` if `first_half_avg_cost_usd` is `null` or `0` |
|
|
161
|
+
| `first_half_findings_total` | sum of `.defects.findings_by_severity.{critical,high,medium,low}` over the first half |
|
|
162
|
+
| `second_half_findings_total` | same, over the second half |
|
|
163
|
+
| `findings_delta_pct` | `((second_half_findings_total - first_half_findings_total) / first_half_findings_total) * 100`; if `first_half_findings_total == 0`: `0` when `second_half_findings_total == 0` too, else `null` (a rise from a zero base has no defined percentage) |
|
|
164
|
+
| `avg_wall_clock_s` | mean of `.time.wall_clock_s` over ALL records in the group (not half-split) |
|
|
165
|
+
| `avg_human_wait_s` | mean of `.time.human_wait_s` over ALL records in the group |
|
|
166
|
+
| `route_back_rate` | `sum(.iterations.route_backs) / sum(.iterations.count)` over ALL records in the group; `null` if the count sum is `0` |
|
|
167
|
+
| `caught_false_completions_total` | sum of `.defects.caught_false_completions` over ALL records in the group |
|
|
168
|
+
|
|
169
|
+
### `by_gate[]`
|
|
170
|
+
|
|
171
|
+
Gate rows from every considered record's join (above) are flattened and grouped by `gate_id`.
|
|
172
|
+
|
|
173
|
+
| Field | Formula |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `gate_id` | group key (`inquiry.target.fieldOrBehavior`) |
|
|
176
|
+
| `fire_count` | total gate rows for this `gate_id` (every calibration: `correct` + `false_block` + `missed_block`) |
|
|
177
|
+
| `correct_count` | rows with `outcome == "correct"` |
|
|
178
|
+
| `false_block_count` | rows with `outcome == "false_block"` |
|
|
179
|
+
| `missed_block_count` | rows with `outcome == "missed_block"` |
|
|
180
|
+
| `false_block_rate` | `false_block_count / fire_count` |
|
|
181
|
+
| `avg_wall_clock_s_when_fired` | mean of the PARENT economics record's `.time.wall_clock_s`, over the distinct records that contributed at least one row for this `gate_id` with `fired == true` |
|
|
182
|
+
| `avg_human_wait_s_when_fired` | same, for `.time.human_wait_s` |
|
|
183
|
+
|
|
184
|
+
(`InquiryRecord`s themselves carry no wall-clock time; "when fired" averages borrow the
|
|
185
|
+
parent record's own time fields for exactly the records whose gate-review join produced a
|
|
186
|
+
`fired:true` row for that `gate_id`.)
|
|
187
|
+
|
|
188
|
+
## Whole-window insufficient-data gate (AC4)
|
|
189
|
+
|
|
190
|
+
`records_considered` = the count of `kontour.console.economics` records whose `.at` falls in
|
|
191
|
+
`[since, until]` (both inclusive; unbounded when the flag is omitted). If
|
|
192
|
+
`records_considered < LR_MIN_WINDOW_SAMPLE` (default `5`): top-level `outcome` is
|
|
193
|
+
`"insufficient-data"`, `proposals` is `[]`, and `aggregates.partial` is `true` — but
|
|
194
|
+
`aggregates.by_kit`/`by_gate` are STILL computed and returned, for transparency (never hidden,
|
|
195
|
+
per `harness-capability-matrix.md`'s "degrade to an explicit unavailable, never fabricate").
|
|
196
|
+
This whole-window gate is distinct from, and evaluated in addition to, the per-target sample
|
|
197
|
+
gates (`LR_MIN_KIT_SAMPLE`/`LR_MIN_GATE_SAMPLE`) below, which just silently exclude a single
|
|
198
|
+
under-sampled target from proposal consideration without flipping the top-level `outcome`.
|
|
199
|
+
|
|
200
|
+
## Proposal rules (Goodhart-paired: `evidence.cost` + `evidence.defect` always co-required)
|
|
201
|
+
|
|
202
|
+
Only evaluated when `outcome == "ok"` (i.e. the whole-window gate passed).
|
|
203
|
+
|
|
204
|
+
1. **`kit-review-cost-inflation`** — fires when, for a `by_kit[]` entry: `runs >= LR_MIN_KIT_SAMPLE`
|
|
205
|
+
(default `6`) AND `cost_trend_pct >= LR_COST_RISE_PCT` (default `25`) AND
|
|
206
|
+
`findings_delta_pct <= LR_FLAT_FINDINGS_PCT` (default `10`) — cost is rising while defects
|
|
207
|
+
caught are flat or falling (the pattern a Goodhart-blind cost-only metric would miss).
|
|
208
|
+
- `evidence.cost = { first_half_avg_cost_usd, second_half_avg_cost_usd, cost_trend_pct }`
|
|
209
|
+
- `evidence.defect = { first_half_findings_total, second_half_findings_total, findings_delta_pct, caught_false_completions_total }`
|
|
210
|
+
- `expected_effect = { metric: "avg_cost_usd", direction: "decrease", description: "cost should come back down without a corresponding rise in escaped defects" }`
|
|
211
|
+
2. **`gate-false-block-review`** — fires when, for a `by_gate[]` entry:
|
|
212
|
+
`fire_count >= LR_MIN_GATE_SAMPLE` (default `3`) AND `false_block_rate >= LR_GATE_FALSE_BLOCK_RATE`
|
|
213
|
+
(default `0.5`) — the gate is blocking on already-passing claims more often than not.
|
|
214
|
+
- `evidence.cost = { avg_wall_clock_s_when_fired, avg_human_wait_s_when_fired }`
|
|
215
|
+
- `evidence.defect = { fire_count, correct_count, false_block_count, missed_block_count }`
|
|
216
|
+
- `expected_effect = { metric: "false_block_rate", direction: "decrease", description: "false_block_rate should fall toward correctly-calibrated blocking" }`
|
|
217
|
+
3. **`gate-well-calibrated`** (monitor-style, still `severity:"advisory"` — mirrors
|
|
218
|
+
`routing-efficiency.sh`'s `keep-tier`) — fires when `fire_count >= LR_MIN_GATE_SAMPLE` AND
|
|
219
|
+
`(correct_count / fire_count) >= LR_GATE_WELL_CALIBRATED_RATE` (default `0.9`) AND
|
|
220
|
+
`false_block_count == 0` AND `missed_block_count == 0`.
|
|
221
|
+
- `evidence.cost = { avg_wall_clock_s_when_fired, avg_human_wait_s_when_fired }`
|
|
222
|
+
- `evidence.defect = { fire_count, correct_count, false_block_count, missed_block_count }`
|
|
223
|
+
- `expected_effect = { metric: "false_block_rate", direction: "maintain", description: "gate is well-calibrated; no change expected or needed" }`
|
|
224
|
+
|
|
225
|
+
A `by_kit`/`by_gate` entry under its rule's sample-size gate contributes **no proposal at
|
|
226
|
+
all** for that rule (silently excluded from proposals, still visible in `aggregates`).
|
|
227
|
+
|
|
228
|
+
Every proposal's `evidence` object structurally REQUIRES both `cost` and `defect`
|
|
229
|
+
(`learning-review-proposals.schema.json`, mirroring fa#349's own `cost`+`defects`
|
|
230
|
+
co-requirement, R7) — a proposal citing cost alone (or defect alone) is schema-invalid. This
|
|
231
|
+
is the Goodhart guard: no proposal may claim "cheaper" without also showing "and here is what
|
|
232
|
+
it caught or missed."
|
|
233
|
+
|
|
234
|
+
## Idempotent ledger (`.kontourai/telemetry/learning-review-proposals.jsonl`)
|
|
235
|
+
|
|
236
|
+
Durable, append-mostly JSONL — one proposal per line, in the exact shape above (minus the
|
|
237
|
+
output-only `already_proposed` field). Default path
|
|
238
|
+
`.kontourai/telemetry/learning-review-proposals.jsonl`, override via `--ledger PATH` or
|
|
239
|
+
`LEARNING_REVIEW_PROPOSALS_LEDGER` (mirrors `TELEMETRY_ECONOMICS_LOG_FILE`'s local-first
|
|
240
|
+
convention: the per-run snapshot lives in the session artifact dir; this ledger is the durable,
|
|
241
|
+
cross-session trail).
|
|
242
|
+
|
|
243
|
+
- **`proposal_id`** = `slugify("<since>_<until>_<target_kind>-<target_id>_<pattern>")`, where
|
|
244
|
+
`<since>`/`<until>` are the raw `--since`/`--until` argument strings (or the literal `all`
|
|
245
|
+
when the flag was omitted) and `slugify()` is the SAME idiom already used at
|
|
246
|
+
`evals/ci/run-baseline.sh:139-140`:
|
|
247
|
+
`tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-//; s/-$//'` — no external hash
|
|
248
|
+
dependency.
|
|
249
|
+
- On every run: proposals are computed fresh (stateless — the analyzer never reads its own
|
|
250
|
+
ledger to decide WHAT to propose, only to decide whether a computed proposal is new). For
|
|
251
|
+
each computed proposal, the ledger is checked for an existing line with the same
|
|
252
|
+
`proposal_id`:
|
|
253
|
+
- **Found** → mark `already_proposed:true` in this run's OUTPUT; no duplicate line is
|
|
254
|
+
appended.
|
|
255
|
+
- **Not found** → append a new ledger line with
|
|
256
|
+
`decision:{status:"proposed",decided_by:null,decided_at:null,rationale:null}`,
|
|
257
|
+
`follow_on_ref:null`, `effect_observed:null`, and mark `already_proposed:false` in the
|
|
258
|
+
output.
|
|
259
|
+
- Running the analyzer twice over the identical window therefore produces zero duplicate
|
|
260
|
+
`proposal_id`s in the ledger (AC4) — the second run's output marks every proposal
|
|
261
|
+
`already_proposed:true`.
|
|
262
|
+
|
|
263
|
+
## Decision recording (`learning-review-decide.sh`)
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
scripts/telemetry/learning-review-decide.sh <ledger-path> <proposal-id> \
|
|
267
|
+
--ratify|--reject|--defer --decided-by NAME [--rationale TEXT] [--follow-on-ref REF]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Finds the ledger line with the matching `proposal_id` and rewrites its `decision` object
|
|
271
|
+
(`status`, `decided_by`, `decided_at: <now, ISO-8601>`, `rationale`) in place, atomically
|
|
272
|
+
(temp file + `mv`). `follow_on_ref` is set ONLY when `--ratify` and `--follow-on-ref` are both
|
|
273
|
+
present. Exits non-zero, writing nothing, when: the `proposal_id` is not found; more than one
|
|
274
|
+
of `--ratify`/`--reject`/`--defer` is passed; or `--follow-on-ref` is passed without `--ratify`
|
|
275
|
+
(the AC3 ratify-before-follow-on ordering guard — a follow-on work item can never be linked to
|
|
276
|
+
an unratified proposal). All other ledger lines are left byte-identical.
|
|
277
|
+
|
|
278
|
+
## Effect-fill pass (AC5)
|
|
279
|
+
|
|
280
|
+
On every `learning-review-proposals.sh` invocation, AFTER computing and ledgering the current
|
|
281
|
+
window's proposals: scan the ledger for entries where `decision.status == "ratified"` AND
|
|
282
|
+
`effect_observed == null` AND the entry's `window.until` (epoch-ms) is chronologically before
|
|
283
|
+
the CURRENT invocation's effective `window.since` (epoch-ms) — i.e. the ratified proposal's
|
|
284
|
+
own window has fully elapsed before this new window begins. For each match, recompute the
|
|
285
|
+
metric named in that entry's `expected_effect.metric` over the CURRENT window's aggregates,
|
|
286
|
+
for the exact same `target`:
|
|
287
|
+
|
|
288
|
+
- `target.kind == "kit"` → `after` = the current window's `by_kit[]` entry for that `kit_id`'s
|
|
289
|
+
overall average cost (`second_half_avg_cost_usd`, i.e. the newest-half average of the new
|
|
290
|
+
window — comparable apples-to-apples against the value that triggered the original
|
|
291
|
+
proposal); `before` = the ratified proposal's own `evidence.cost.second_half_avg_cost_usd`
|
|
292
|
+
(the elevated level that triggered it).
|
|
293
|
+
- `target.kind == "gate"` → `after` = the current window's `by_gate[]` entry's
|
|
294
|
+
`false_block_rate` for that `gate_id`; `before` = the ratified proposal's own
|
|
295
|
+
`evidence.defect.false_block_count / evidence.defect.fire_count`.
|
|
296
|
+
|
|
297
|
+
If no `by_kit`/`by_gate` entry exists for that target in the current window (e.g. the kit/gate
|
|
298
|
+
had zero runs this window), `effect_observed` is left `null` this pass — an honest gap, not a
|
|
299
|
+
fabricated measurement; it is retried on the next invocation that does have data.
|
|
300
|
+
|
|
301
|
+
Writes:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
"effect_observed": {
|
|
305
|
+
"measured_at": "<ISO-8601, now>",
|
|
306
|
+
"metric": "avg_cost_usd | false_block_rate",
|
|
307
|
+
"before": 0.0,
|
|
308
|
+
"after": 0.0,
|
|
309
|
+
"moved": "improved | worsened | unchanged"
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`moved` is derived from `expected_effect.direction`: `"improved"` = the metric moved in the
|
|
314
|
+
direction the proposal intended (`decrease` → `after < before`; `increase` → `after > before`);
|
|
315
|
+
`"unchanged"` = `after == before` exactly; `"worsened"` = moved, but the wrong way (or any
|
|
316
|
+
non-zero move when `direction == "maintain"`).
|
|
317
|
+
|
|
318
|
+
## Guardrails (ADR 0008/0010, ADR 0002/0003 call 5)
|
|
319
|
+
|
|
320
|
+
- **Advisory only.** Every proposal is `severity: "advisory"`. Neither
|
|
321
|
+
`learning-review-proposals.sh` nor `learning-review-decide.sh` EVER writes to `kits/**`,
|
|
322
|
+
`.datum/config.json`, or any gate/flow config file — they only read economics/session
|
|
323
|
+
artifacts and write to their own ledger.
|
|
324
|
+
- **Human-ratified, decision-before-follow-on.** A recorded human decision
|
|
325
|
+
(`learning-review-decide.sh`) is required before any follow-on work item may cite a
|
|
326
|
+
proposal; `--follow-on-ref` is refused without `--ratify` (AC3).
|
|
327
|
+
- **Consume-never-fork.** `economics-record.schema.json` (fa#349) and
|
|
328
|
+
`gate-review.inquiries.json` (#118-120) are read-only inputs, never modified, never
|
|
329
|
+
re-derived by inventing a parallel schema.
|
|
330
|
+
- **No fabrication.** Ratios with an undefined denominator are `null`, never `0`; an
|
|
331
|
+
under-threshold window yields an explicit `"insufficient-data"` outcome, never a confident
|
|
332
|
+
but thin call; real records lacking `task_slug` degrade to `"unattributed"`, never an
|
|
333
|
+
invented kit identity.
|
|
334
|
+
|
|
335
|
+
## Relationship to `routing-efficiency.sh` (#415)
|
|
336
|
+
|
|
337
|
+
Same architecture, same input stream (`economics.jsonl`), same guardrails — generalized from
|
|
338
|
+
per-`(role,model)` delegation efficiency to per-kit/per-gate review efficiency. Both are purely
|
|
339
|
+
manual/skill-invoked (no scheduler/cron primitive exists in this repo for either); both are
|
|
340
|
+
wired into `learning-review`'s closeout step (2a for routing, 2b for this contract).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Routing-efficiency review
|
|
2
|
+
|
|
3
|
+
**Turn recorded delegation outcomes into advisory proposals about which model each role should route
|
|
4
|
+
to — the #409 small-model value proof, pointed inward at our own agent fan-out (#415).**
|
|
5
|
+
|
|
6
|
+
`scripts/telemetry/routing-efficiency.sh` reads the per-run economics records (`delegations[]` +
|
|
7
|
+
`outcome`, from `economics-record.sh`) and, per `(role, model)`, asks: *is the model this role routes
|
|
8
|
+
to actually efficient, or is a cheaper tier being re-worked so often it should be escalated (or an
|
|
9
|
+
expensive tier so reliably accepted it is over-provisioned)?* `learning-review` runs it at closeout
|
|
10
|
+
(step 2a) and a human ratifies any change.
|
|
11
|
+
|
|
12
|
+
## Input / output
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
routing-efficiency.sh [economics.jsonl ...] # defaults to .kontourai/telemetry/economics.jsonl
|
|
16
|
+
cat economics.jsonl | routing-efficiency.sh - # or records on stdin
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Emits a `kontour.routing-efficiency` v0.1 document: `by_role_model[]` (per-group outcome counts +
|
|
20
|
+
`trouble_rate`) and `proposals[]` (one advisory proposal per group), plus `notes`.
|
|
21
|
+
|
|
22
|
+
## What it computes (honest by construction)
|
|
23
|
+
|
|
24
|
+
For each `(role, bare-model)` group across all delegations:
|
|
25
|
+
|
|
26
|
+
- `measurable = accepted + rework + diverged + failed` — **`unavailable` outcomes are excluded**. A
|
|
27
|
+
delegation whose outcome the harness could not expose is neither a success nor a failure, so it never
|
|
28
|
+
moves a rate (see `harness-capability-matrix.md`). It is counted separately as coverage.
|
|
29
|
+
- `trouble_rate = (rework + diverged + failed) / measurable` — the share of *measurable* delegations
|
|
30
|
+
that did not cleanly accept.
|
|
31
|
+
|
|
32
|
+
## Proposal kinds
|
|
33
|
+
|
|
34
|
+
| Kind | Fires when | Meaning |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `escalate-minimum-tier` | `measurable ≥ ROUTING_MIN_SAMPLE` and `trouble_rate ≥ ROUTING_HIGH_TROUBLE` (0.5) | the cheap tier is under-routed for this role — consider raising its minimum tier |
|
|
37
|
+
| `keep-tier` | `measurable ≥ min` and `trouble_rate ≤ ROUTING_LOW_TROUBLE` (0.1) | efficient — accepts cleanly; keep current routing (a candidate to try a *cheaper* tier lives in future work) |
|
|
38
|
+
| `monitor` | `measurable ≥ min`, trouble in between | within tolerance; watch |
|
|
39
|
+
| `insufficient-signal` | `measurable < min` | not enough measurable outcomes to judge — coverage reported, no confident call |
|
|
40
|
+
|
|
41
|
+
Thresholds are tunable via env: `ROUTING_MIN_SAMPLE` (default 3), `ROUTING_HIGH_TROUBLE` (0.5),
|
|
42
|
+
`ROUTING_LOW_TROUBLE` (0.1).
|
|
43
|
+
|
|
44
|
+
## Guardrails (ADR 0002 / ADR 0003 call 5)
|
|
45
|
+
|
|
46
|
+
- **Advisory only.** Every proposal is `severity: "advisory"`. The analyzer NEVER edits
|
|
47
|
+
`.datum/config.json` or any routing config — it only reads and reports.
|
|
48
|
+
- **Human-ratified.** A person decides whether to act on a proposal; the resulting role→model change
|
|
49
|
+
travels the normal deliver loop, never an auto-apply.
|
|
50
|
+
- **No fabrication.** `unavailable` outcomes are excluded from rates; a thin sample yields
|
|
51
|
+
`insufficient-signal`, not a confident verdict; empty input yields an empty `proposals[]`, never an
|
|
52
|
+
invented lesson.
|
|
53
|
+
|
|
54
|
+
## Relationship to the value plane
|
|
55
|
+
|
|
56
|
+
This is the same question the external value proof (`kontourai/evals`, #409) asks — "does routing the
|
|
57
|
+
right model to each task pay off?" — asked of our *internal* delegation. The console renders the
|
|
58
|
+
`(role, model)` cost + acceptance surface (#415 console panel); this analyzer produces the actionable
|
|
59
|
+
proposal that closes the loop back to the routing ladder.
|
package/docs/verifiable-trust.md
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
# Verifiable Trust —
|
|
1
|
+
# Verifiable Trust — tamper-evident done
|
|
2
2
|
|
|
3
3
|
> **The problem with autonomous coding agents: they grade their own homework.**
|
|
4
4
|
> An agent writes the code, runs the tests, and reports "all green, shipped." If it's
|
|
5
5
|
> wrong — or if it learns it can just *say* the tests passed — you find out in production.
|
|
6
|
-
> Flow Agents is built so
|
|
6
|
+
> Flow Agents is built so "done" is a claim with evidence, not a status the model gets
|
|
7
|
+
> to assert.
|
|
7
8
|
|
|
8
9
|
## The one-line pitch
|
|
9
10
|
|
|
10
11
|
Flow Agents treats "the work is done" as a **claim that must be proven**, not a status the
|
|
11
|
-
agent gets to assert. Completion is gated by
|
|
12
|
-
|
|
13
|
-
with **cryptographically signed provenance** of exactly what was verified.
|
|
12
|
+
agent gets to assert. Completion is gated by evidence the system re-derives, with two
|
|
13
|
+
different trust tiers:
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
- **L1 local runtime gate and capture** — advisory, best-effort protection inside the agent
|
|
16
|
+
runtime. It catches native command evidence, stop-short behavior, and obvious divergence
|
|
17
|
+
before the work leaves the session.
|
|
18
|
+
- **L2 controlled re-run anchor** — CI re-executes manifest commands and reconciles them
|
|
19
|
+
against git diff as ground truth. This is the authoritative boundary for CI-verified
|
|
20
|
+
status.
|
|
21
|
+
|
|
22
|
+
Most agent frameworks trust the model's self-report. Flow Agents treats that self-report as
|
|
23
|
+
input to reconcile, not as truth. The posture is **tamper-evident**, not tamper-proof.
|
|
17
24
|
|
|
18
25
|
## What that buys you
|
|
19
26
|
|
|
20
|
-
- **"Done" you can
|
|
21
|
-
review findings, captured command results
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
are rejected.
|
|
27
|
-
- **An external anchor that can't be switched off.** On
|
|
28
|
-
verification
|
|
27
|
+
- **"Done" you can inspect.** A finished task ends with evidence — tests, build, lint,
|
|
28
|
+
review findings, captured command results, CI output, or an explicit `NOT_VERIFIED` gap.
|
|
29
|
+
A claimed pass that contradicts captured or reconciled evidence is blocked or downgraded,
|
|
30
|
+
not laundered into verified status.
|
|
31
|
+
- **Anti-gaming by design.** Local capture raises the cost of direct native-command
|
|
32
|
+
self-tampering, and CI reconciles manifest commands against fresh results. Tricks like
|
|
33
|
+
`npm test || true` are rejected when they are represented as command evidence.
|
|
34
|
+
- **An external anchor that can't be switched off locally.** On pull requests wired to the
|
|
35
|
+
trust anchor, CI re-runs verification fresh in a clean environment and fails on divergence
|
|
29
36
|
between what the agent claimed and what CI actually observed. The agent can tamper with its
|
|
30
37
|
own machine all it likes; it can't reach into CI.
|
|
31
38
|
- **Signed provenance.** CI mints a Sigstore-signed attestation over its *own* results. The
|
|
@@ -46,7 +53,7 @@ its claims, *or* its environment.
|
|
|
46
53
|
reproducible record of *what was verified and how* — provenance you can hand to an auditor,
|
|
47
54
|
not a screenshot of a passing run.
|
|
48
55
|
- **Multi-agent and at-scale delivery.** Every agent's output is held to the same external,
|
|
49
|
-
|
|
56
|
+
evidence-backed bar — so you can fan out work without fanning out the risk that one agent
|
|
50
57
|
learns to game its gate.
|
|
51
58
|
- **Platform teams adopting agents.** Add the trust anchor as a required check in your repos
|
|
52
59
|
and get a consistent, enforced "agents must prove it" policy across every team.
|
|
@@ -55,20 +62,62 @@ its claims, *or* its environment.
|
|
|
55
62
|
|
|
56
63
|
| | Typical agent setup | Flow Agents |
|
|
57
64
|
|---|---|---|
|
|
58
|
-
| "Is it done?" | The agent says so | Re-derived from
|
|
59
|
-
| Failure hiding | Easy (claim pass, launder exit codes) |
|
|
60
|
-
| Where trust lives | In the agent's environment | **
|
|
65
|
+
| "Is it done?" | The agent says so | Re-derived from evidence; CI-verified only after the controlled anchor reconciles |
|
|
66
|
+
| Failure hiding | Easy (claim pass, launder exit codes) | Native command capture locally; manifest-command reconciliation in CI |
|
|
67
|
+
| Where trust lives | In the agent's environment | Local tier is advisory; authoritative tier is **external** CI + git ground truth |
|
|
61
68
|
| Provenance | A log line | **Sigstore-signed** attestation of CI's own results |
|
|
62
69
|
| Tampering with the gate | Possible | Required tests + code-owner review block it |
|
|
63
70
|
|
|
71
|
+
## Trust tiers
|
|
72
|
+
|
|
73
|
+
### L1: local runtime gate and capture
|
|
74
|
+
|
|
75
|
+
The local tier runs inside the agent's runtime. It can steer the workflow, capture native
|
|
76
|
+
command results, detect stop-short behavior, and warn or block depending on the host's hook
|
|
77
|
+
surface. It is useful because it catches problems early, while the agent is still in the
|
|
78
|
+
session.
|
|
79
|
+
|
|
80
|
+
It is not the authoritative trust boundary. The agent and its tools can still affect the
|
|
81
|
+
local machine. Local evidence is treated as session-local until it is either reconciled by
|
|
82
|
+
the controlled anchor or explicitly carried as a lower-trust attestation.
|
|
83
|
+
|
|
84
|
+
### L2: controlled CI re-run
|
|
85
|
+
|
|
86
|
+
The controlled anchor runs outside the agent session. CI re-executes the manifest commands,
|
|
87
|
+
checks the resulting status against the agent's claims, and uses git diff as the ground truth
|
|
88
|
+
for what changed. A write that bypassed local capture still appears in the diff. A command
|
|
89
|
+
claim that cannot be tied to a manifest command re-run does not become CI-verified just
|
|
90
|
+
because the agent said it passed.
|
|
91
|
+
|
|
92
|
+
The CI tier is where evidence can become independently reconciled. That is the line between
|
|
93
|
+
"the session reported this" and "the controlled environment re-ran or inspected this."
|
|
94
|
+
|
|
95
|
+
## Threat model note
|
|
96
|
+
|
|
97
|
+
The local tier keys on native command tools. MCP or other non-native tool effects can bypass
|
|
98
|
+
local capture, including file writes and test runs.
|
|
99
|
+
|
|
100
|
+
That blind spot is disclosed, not hidden:
|
|
101
|
+
|
|
102
|
+
- **File writes** still hit the CI/git boundary because the pull request diff is ground
|
|
103
|
+
truth for delivered changes.
|
|
104
|
+
- **Non-native test runs** do not become CI-verified evidence unless the same check is
|
|
105
|
+
represented by a manifest command that CI re-runs. Otherwise they remain session-local or
|
|
106
|
+
`NOT_VERIFIED`.
|
|
107
|
+
- **Bypassed local capture** should refuse, escalate, or degrade confidence. It must not be
|
|
108
|
+
laundered into a verified CI claim.
|
|
109
|
+
|
|
110
|
+
Every trust claim should be traceable to public artifacts: the recorded evidence, the
|
|
111
|
+
manifest command, the CI run, the git diff, or an explicit verification gap.
|
|
112
|
+
|
|
64
113
|
## The honest part
|
|
65
114
|
|
|
66
115
|
This is a **defense-in-depth bar-raiser, not a magic wall** — and the docs say so plainly.
|
|
67
|
-
The local gate raises the cost of casual or direct self-tampering; the
|
|
68
|
-
boundary is **external**: CI's fresh re-run,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
116
|
+
The local gate raises the cost of casual or direct self-tampering; the real authoritative
|
|
117
|
+
boundary is **external**: CI's fresh re-run, git diff reconciliation, CI-minted signatures,
|
|
118
|
+
and human (owner) review. Known residuals are documented openly rather than hidden, because
|
|
119
|
+
overstating security is its own risk. We'd rather you trust this *because* you can see where
|
|
120
|
+
the lines are.
|
|
72
121
|
|
|
73
122
|
> This posture wasn't designed on paper and declared safe — it was **earned by an adversarial
|
|
74
123
|
> loop**: independent reviewers repeatedly tried to defeat the gate (and found real holes we
|
|
@@ -201,6 +201,8 @@ Plans should preserve the work item's `R*` and `AC*` ids from `idea-to-backlog`.
|
|
|
201
201
|
|
|
202
202
|
Keep an isolated worktree alive through review, verification, provider change creation, and provider checks so the branch remains inspectable and fixable without disturbing the main checkout. Remove the worktree only after the change is merged or accepted, the branch is abandoned, or the user explicitly asks to collapse the isolation. Do not copy files back into the original checkout by hand; Git is the merge surface.
|
|
203
203
|
|
|
204
|
+
After creating or entering a fresh worktree, run `npm ci` there before pre-push checks or evals; each worktree needs its own `node_modules`.
|
|
205
|
+
|
|
204
206
|
When `pull-work` chooses a worktree, record `worktree_lifecycle`: path, branch, retain-until condition, cleanup owner, cleanup command, and cleanup blockers. Publish-change retains the worktree for review/CI fixes; final acceptance or explicit abandonment owns cleanup.
|
|
205
207
|
|
|
206
208
|
Example prompt:
|
|
@@ -581,6 +583,14 @@ Use learning-review. Capture facts, decisions, gaps, follow-ups, and durable kno
|
|
|
581
583
|
|
|
582
584
|
## Resumable sessions
|
|
583
585
|
|
|
586
|
+
Builder build sessions also project their canonical Flow run into the resume block.
|
|
587
|
+
Start a newly selected session with `flow-agents builder-run start --session-dir
|
|
588
|
+
.kontourai/flow-agents/<slug>`. Thereafter, follow `next_action.skills` or
|
|
589
|
+
`next_action.operations`, record the current gate evidence, and use the exact
|
|
590
|
+
`next_action.command` if automatic synchronization was interrupted. See
|
|
591
|
+
[`docs/spec/builder-flow-runtime.md`](spec/builder-flow-runtime.md) for the ownership,
|
|
592
|
+
trust-binding, route-back, and artifact-root contract.
|
|
593
|
+
|
|
584
594
|
When a session resumes (after context compaction, an agent restart, or a cross-session
|
|
585
595
|
handoff), the workflow-steering hook emits a `RESUME:` block on `SessionStart` that
|
|
586
596
|
gives the resuming agent immediate situational awareness without blocking or auto-deciding.
|