@iowarp/clio-coder 0.3.3 → 0.3.6
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/CHANGELOG.md +74 -0
- package/CONTRIBUTING.md +7 -7
- package/README.md +3 -3
- package/dist/{acp-P2AQILE2.js → acp-2BEHC4DL.js} +9 -8
- package/dist/{agents-72W3BI7I.js → agents-LNNFTM53.js} +29 -24
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-KXXFI2VS.js} +14 -10
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/chunk-3BPUFZDL.js +37 -0
- package/dist/chunk-43AOLP7E.js +375 -0
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/{chunk-6SGHMWE3.js → chunk-5JGRAMKL.js} +5 -5
- package/dist/{chunk-V6RTAOC2.js → chunk-6US73PDB.js} +572 -51
- package/dist/{chunk-5UFT4SUX.js → chunk-6XXKFVSN.js} +3 -3
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/chunk-CJUB2JJ2.js +1478 -0
- package/dist/{chunk-FNTMWMX5.js → chunk-CKXWIANG.js} +14 -12
- package/dist/{chunk-PIWWS5BL.js → chunk-CYQKWTG3.js} +63 -78
- package/dist/{chunk-LZSJBIVT.js → chunk-DJVECN66.js} +271 -762
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
- package/dist/{chunk-STBPMHSX.js → chunk-EKY57CSP.js} +51 -84
- package/dist/{chunk-DUYJ5IO6.js → chunk-EYPA3EGJ.js} +12 -4
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-OQ33BKR3.js → chunk-G7MUEIGA.js} +3 -60
- package/dist/chunk-GEYXPTRF.js +613 -0
- package/dist/chunk-GOXNB3AO.js +261 -0
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-4XUGQOHA.js → chunk-K7T3E2SR.js} +15 -8
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-UFIIWP2H.js → chunk-KHSFENX2.js} +8 -8
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-PAJK6MAQ.js → chunk-LYF7OHWH.js} +42 -15
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-ODFEOB4F.js +1082 -0
- package/dist/{chunk-AGYYIBLL.js → chunk-OH3TOQTB.js} +6 -2
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{chunk-DSELYM6W.js → chunk-PBTHKCPN.js} +30 -10
- package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-SRDMMSEP.js → chunk-QM3F2GKX.js} +1063 -1645
- package/dist/{chunk-X6IAEBZR.js → chunk-QNQHSOLF.js} +7 -7
- package/dist/{chunk-OC7FIQPC.js → chunk-R46L2BIR.js} +10 -7
- package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-6N5PTWMY.js → chunk-RY3LY4J5.js} +50 -13
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OOJYHWRB.js → chunk-TSHXZTOQ.js} +6 -5
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/{chunk-OAO4GE4M.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/chunk-WHJYKASB.js +677 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-TZTZS7QK.js → chunk-XE2VEJHX.js} +5 -3
- package/dist/{chunk-LM5TQCJZ.js → chunk-XF5N4U5A.js} +8 -7
- package/dist/{chunk-LWLEKMDQ.js → chunk-XXQNGV4M.js} +1073 -552
- package/dist/{chunk-KZWTDYJF.js → chunk-XYDYPRZI.js} +7 -7
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-LW6DSM3M.js → chunk-ZRGEBJ4T.js} +1192 -1119
- package/dist/{chunk-2DJ2KNFG.js → chunk-ZXF4XRKW.js} +202 -40
- package/dist/chunk-ZZMN5OM4.js +122 -0
- package/dist/cli/index.js +34 -30
- package/dist/{clio-JOU4FXVA.js → clio-M2KGYUFZ.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-GQNL7XA6.js} +8 -6
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
- package/dist/{config-XCDVKR23.js → config-XUUYQIWO.js} +47 -35
- package/dist/{configure-4GAP54ZW.js → configure-IHJ7YOMV.js} +18 -15
- package/dist/{context-77FM5DV5.js → context-74JLXAWD.js} +18 -10
- package/dist/{context-4UOGGLQ5.js → context-75MIWW3U.js} +41 -29
- package/dist/{context-5VKGUVJJ.js → context-ZQ7SIFJV.js} +85 -9
- package/dist/{context-clear-XXJRLCJJ.js → context-clear-GYKWNUML.js} +41 -29
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/context-working-set-UX5KEP4J.js +1553 -0
- package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-GIJBHNFL.js} +47 -32
- package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
- package/dist/{doctor-HR46URBJ.js → doctor-SVJ5BZCW.js} +12 -12
- package/dist/{eval-XSSNATB4.js → eval-CG6LLBLD.js} +54 -238
- package/dist/{evidence-6HG2PY2B.js → evidence-ZYFIEN42.js} +57 -28
- package/dist/{evolve-K7YU3NCY.js → evolve-QGEXEMDW.js} +36 -25
- package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
- package/dist/{fleet-VY3HHKN6.js → fleet-S5R4ZOQY.js} +73 -44
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-BHSNPBMH.js} +3 -3
- package/dist/{init-JYGXI3FK.js → init-5DRU55YR.js} +49 -37
- package/dist/memory-7YKKR6UC.js +467 -0
- package/dist/{models-I5QWSEOM.js → models-ZPOLRU2C.js} +24 -21
- package/dist/{monitor-GE4ID3IA.js → monitor-US5F5YGZ.js} +73 -46
- package/dist/{orchestrator-EM5MC3HM.js → orchestrator-E2AL4T5N.js} +1624 -1007
- package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
- package/dist/{reset-L2FQEE3E.js → reset-KZ652EK6.js} +6 -5
- package/dist/{run-ZU3QMZPZ.js → run-SRNBKDWD.js} +76 -54
- package/dist/{share-S5BZQC5I.js → share-CGZE33UP.js} +7 -6
- package/dist/{skills-X5VXCRNQ.js → skills-S2X4DLY5.js} +4 -4
- package/dist/{skills-eval-WKIHWTHR.js → skills-eval-W2GGIC4R.js} +40 -29
- package/dist/{targets-SNCPI2NR.js → targets-54SWINWB.js} +28 -23
- package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-SAIF2OGY.js} +6 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
- package/dist/{upgrade-JQHHPQ4K.js → upgrade-JKAR27XC.js} +20 -19
- package/dist/{usage-OR4O5SMZ.js → usage-MSAWCLX4.js} +79 -36
- package/dist/verifiers-NCBTHHN2.js +1220 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-GUSOQ6ZP.js} +50 -37
- package/dist/worker/entry.js +90 -70
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +8 -7
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +60 -26
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +26 -1
- package/docs/context-engine.md +67 -13
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +6 -6
- package/docs/documentation-guide.md +7 -6
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -32
- package/docs/evidence-and-memory.md +139 -7
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +49 -8
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +19 -2
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +7 -5
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +43 -40
- package/docs/safety-model.md +49 -8
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +38 -4
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +13 -13
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/fleet.ts +15 -0
- package/src/cli/index.ts +5 -1
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +55 -7
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +22 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/verification-scripts.ts +6 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +1 -1
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/agents/catalog.ts +5 -4
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +7 -4
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +20 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- package/src/domains/dispatch/contract.ts +2 -0
- package/src/domains/dispatch/extension.ts +81 -27
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +4 -0
- package/src/domains/dispatch/state.ts +1 -0
- package/src/domains/dispatch/types.ts +10 -3
- package/src/domains/dispatch/validation.ts +14 -0
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/memory-intervention.ts +38 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +208 -5
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/observability/contract.ts +6 -1
- package/src/domains/observability/cost.ts +20 -4
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +12 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +110 -1
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/domains/session/usage.ts +24 -7
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +32 -2
- package/src/engine/agent.ts +18 -1
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/session.ts +9 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +34 -5
- package/src/interactive/chat-loop-messages.ts +40 -6
- package/src/interactive/chat-loop.ts +13 -0
- package/src/interactive/chat-panel.ts +17 -1
- package/src/interactive/chat-renderer.ts +49 -24
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +120 -7
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/cost-overlay.ts +39 -8
- package/src/interactive/dispatch-board.ts +212 -35
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/interactive-application.ts +6 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/interactive-slash-runtime.ts +37 -1
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +46 -1
- package/src/interactive/overlay-key-routing.ts +41 -1
- package/src/interactive/overlay-lifecycle.ts +11 -4
- package/src/interactive/overlay-permission-lifecycle.ts +23 -8
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- package/src/interactive/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +37 -51
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/slash-commands.ts +31 -4
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/turn-context.ts +333 -110
- package/src/interactive/turn-middleware.ts +7 -6
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-stream.ts +51 -110
- package/src/tools/agent-tools.ts +39 -7
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +11 -5
- package/src/tools/context/index.ts +107 -5
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-arguments.ts +8 -0
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/dispatch.ts +24 -1
- package/src/tools/monitor.ts +43 -20
- package/src/tools/registry.ts +72 -10
- package/src/tools/result-disposition.ts +706 -0
- package/src/tools/result-shaping.ts +321 -20
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1120 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +54 -12
- package/src/worker/spec-contract.ts +43 -3
- package/dist/chunk-J7CWMCQD.js +0 -255
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
- package/dist/chunk-VPAYEGVX.js +0 -184
- package/dist/chunk-XBXAASKX.js +0 -18
- package/dist/memory-WFZMGYHX.js +0 -236
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { SessionEntry } from "../../../session/entries.js";
|
|
2
|
+
import type { EvictionCandidate, PolicyInput, WorkingSetPolicy, WorkingSetPolicyId } from "../contract.js";
|
|
3
|
+
import { tokensFreedByEviction } from "../engine.js";
|
|
4
|
+
import { protectionCutoffIndex } from "../horizon.js";
|
|
5
|
+
import { buildPathIndex, callPathsByToolCallId } from "../path-index.js";
|
|
6
|
+
import { isProtected } from "../protect.js";
|
|
7
|
+
import type { ReferenceGraph } from "./reference-graph.js";
|
|
8
|
+
import { countReplayTurns } from "./trace.js";
|
|
9
|
+
|
|
10
|
+
/** Replay-only diagnostic surface; it does not widen the live policy contract. */
|
|
11
|
+
export interface ReplayCandidatePoolPolicy extends WorkingSetPolicy {
|
|
12
|
+
/** Number of currently usable candidates before target-based truncation. */
|
|
13
|
+
replayCandidateCount(input: PolicyInput): number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function controlId(id: string): WorkingSetPolicyId {
|
|
17
|
+
return id as WorkingSetPolicyId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function eligibleToolResults(input: PolicyInput): SessionEntry[] {
|
|
21
|
+
const cutoff = protectionCutoffIndex(input.entries, input.settings.protectLastTurns);
|
|
22
|
+
const index = buildPathIndex(input.entries, { cwd: input.cwd });
|
|
23
|
+
const out: SessionEntry[] = [];
|
|
24
|
+
for (let entryIndex = cutoff - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
25
|
+
const entry = input.entries[entryIndex];
|
|
26
|
+
if (entry?.kind !== "message" || entry.role !== "tool_result") continue;
|
|
27
|
+
if (input.view.evicted.has(entry.turnId)) continue;
|
|
28
|
+
if (isProtected(entry, { entryIndex, cutoffIndex: cutoff, input, index })) continue;
|
|
29
|
+
out.push(entry);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function takeToTarget(input: PolicyInput, entries: ReadonlyArray<SessionEntry>): EvictionCandidate[] {
|
|
35
|
+
let tokensNeeded = Math.max(0, input.pressure.tokens - input.pressure.target * input.pressure.contextWindow);
|
|
36
|
+
if (tokensNeeded <= 0) return [];
|
|
37
|
+
const selected: EvictionCandidate[] = [];
|
|
38
|
+
const callPaths = callPathsByToolCallId(input.entries);
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const candidate: EvictionCandidate = { ref: { entry: entry.turnId }, reason: "age_horizon" };
|
|
41
|
+
selected.push(candidate);
|
|
42
|
+
tokensNeeded -= tokensFreedByEviction(input.estimateTokens, entry, candidate, callPaths);
|
|
43
|
+
if (tokensNeeded <= 0) break;
|
|
44
|
+
}
|
|
45
|
+
return selected;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function makeOraclePolicy(graph: ReferenceGraph): ReplayCandidatePoolPolicy {
|
|
49
|
+
let lastInput: PolicyInput | null = null;
|
|
50
|
+
let lastCandidateCount = 0;
|
|
51
|
+
const safeEntries = (input: PolicyInput): SessionEntry[] => {
|
|
52
|
+
const currentTurn = countReplayTurns(input.entries) + 1;
|
|
53
|
+
return eligibleToolResults(input).filter((entry) => {
|
|
54
|
+
const futureTurns = graph.futureTurnsOf.get(entry.turnId) ?? [];
|
|
55
|
+
return futureTurns.every((turn) => turn < currentTurn);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
id: controlId("oracle"),
|
|
60
|
+
select(input): ReadonlyArray<EvictionCandidate> {
|
|
61
|
+
// Replay calls before the next turn-start entry is appended. A reference
|
|
62
|
+
// in that next turn is therefore still future from the model's view.
|
|
63
|
+
const safe = safeEntries(input);
|
|
64
|
+
lastInput = input;
|
|
65
|
+
lastCandidateCount = safe.length;
|
|
66
|
+
return takeToTarget(input, safe);
|
|
67
|
+
},
|
|
68
|
+
replayCandidateCount(input): number {
|
|
69
|
+
return input === lastInput ? lastCandidateCount : safeEntries(input).length;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mulberry32(seed: number): () => number {
|
|
75
|
+
let value = seed >>> 0;
|
|
76
|
+
return () => {
|
|
77
|
+
value = (value + 0x6d2b79f5) >>> 0;
|
|
78
|
+
let next = value;
|
|
79
|
+
next = Math.imul(next ^ (next >>> 15), next | 1);
|
|
80
|
+
next ^= next + Math.imul(next ^ (next >>> 7), next | 61);
|
|
81
|
+
return ((next ^ (next >>> 14)) >>> 0) / 4_294_967_296;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function makeRandomPolicy(seed: number): ReplayCandidatePoolPolicy {
|
|
86
|
+
let lastInput: PolicyInput | null = null;
|
|
87
|
+
let lastCandidateCount = 0;
|
|
88
|
+
return {
|
|
89
|
+
id: controlId("random"),
|
|
90
|
+
select(input): ReadonlyArray<EvictionCandidate> {
|
|
91
|
+
const entries = [...eligibleToolResults(input)];
|
|
92
|
+
lastInput = input;
|
|
93
|
+
lastCandidateCount = entries.length;
|
|
94
|
+
const random = mulberry32(seed);
|
|
95
|
+
for (let index = entries.length - 1; index > 0; index -= 1) {
|
|
96
|
+
const swap = Math.floor(random() * (index + 1));
|
|
97
|
+
const value = entries[index];
|
|
98
|
+
entries[index] = entries[swap] as SessionEntry;
|
|
99
|
+
entries[swap] = value as SessionEntry;
|
|
100
|
+
}
|
|
101
|
+
return takeToTarget(input, entries);
|
|
102
|
+
},
|
|
103
|
+
replayCandidateCount(input): number {
|
|
104
|
+
return input === lastInput ? lastCandidateCount : eligibleToolResults(input).length;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const nonePolicy: WorkingSetPolicy = {
|
|
110
|
+
id: controlId("none"),
|
|
111
|
+
select: () => [],
|
|
112
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { parseSessionEntries } from "../../../session/archive-readers.js";
|
|
5
|
+
import { isSessionHeader, type SessionEntry } from "../../../session/entries.js";
|
|
6
|
+
import { filterEntriesToActivePath } from "../../../session/tree/active-path.js";
|
|
7
|
+
import { buildPathIndex } from "../path-index.js";
|
|
8
|
+
import { buildReferenceGraph } from "./reference-graph.js";
|
|
9
|
+
import { countReplayTurns, type Trace } from "./trace.js";
|
|
10
|
+
|
|
11
|
+
export interface ReplayFilterOptions {
|
|
12
|
+
minTurns?: number;
|
|
13
|
+
minToolResults?: number;
|
|
14
|
+
requireFileReread?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoadClioTraceOptions {
|
|
18
|
+
/** False is the CLI's --no-filter behavior. */
|
|
19
|
+
filter?: false | ReplayFilterOptions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ReplayLoadCascade {
|
|
23
|
+
found: number;
|
|
24
|
+
unreadable: number;
|
|
25
|
+
filtered: Record<string, number>;
|
|
26
|
+
kept: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_FILTER = {
|
|
30
|
+
minTurns: 8,
|
|
31
|
+
minToolResults: 8,
|
|
32
|
+
requireFileReread: true,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
async function collectLedgerFiles(input: string, out: Set<string>): Promise<void> {
|
|
36
|
+
const path = resolve(input);
|
|
37
|
+
let facts: Awaited<ReturnType<typeof stat>>;
|
|
38
|
+
try {
|
|
39
|
+
facts = await stat(path);
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (facts.isFile()) {
|
|
44
|
+
// Directory discovery is intentionally limited to current.jsonl below;
|
|
45
|
+
// an explicit JSONL path may also be a frozen synthetic fixture.
|
|
46
|
+
if (path.endsWith(".jsonl")) out.add(path);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!facts.isDirectory()) return;
|
|
50
|
+
|
|
51
|
+
const direct = join(path, "current.jsonl");
|
|
52
|
+
try {
|
|
53
|
+
if ((await stat(direct)).isFile()) {
|
|
54
|
+
out.add(direct);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// A sessions root or cwd-hash directory has no direct ledger.
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let children: Dirent[];
|
|
62
|
+
try {
|
|
63
|
+
children = await readdir(path, { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const child of children.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
68
|
+
if (!child.isDirectory()) continue;
|
|
69
|
+
await collectLedgerFiles(join(path, child.name), out);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sessionFacts(raw: string, source: string): { id: string; cwd: string | null } {
|
|
74
|
+
const fallback = { id: basename(dirname(source)), cwd: null };
|
|
75
|
+
for (const line of raw.split("\n")) {
|
|
76
|
+
if (line.trim().length === 0) continue;
|
|
77
|
+
try {
|
|
78
|
+
const value = JSON.parse(line) as unknown;
|
|
79
|
+
if (isSessionHeader(value)) return { id: value.id, cwd: value.cwd.length > 0 ? value.cwd : null };
|
|
80
|
+
} catch {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function pinnedLeafTurnId(source: string): Promise<string | undefined> {
|
|
88
|
+
try {
|
|
89
|
+
const raw = await readFile(join(dirname(source), "meta.json"), "utf8");
|
|
90
|
+
const value = JSON.parse(raw) as unknown;
|
|
91
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
92
|
+
const pinned = (value as Record<string, unknown>).pinnedLeafTurnId;
|
|
93
|
+
return typeof pinned === "string" && pinned.length > 0 ? pinned : undefined;
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function cleanActiveEntries(entries: ReadonlyArray<SessionEntry>, activeLeafTurnId?: string): SessionEntry[] {
|
|
100
|
+
return filterEntriesToActivePath(entries, activeLeafTurnId).filter(
|
|
101
|
+
(entry) => entry.kind !== "contextEviction" && entry.kind !== "contextRecall",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toolResultCount(entries: ReadonlyArray<SessionEntry>): number {
|
|
106
|
+
let count = 0;
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.kind === "message" && entry.role === "tool_result") count += 1;
|
|
109
|
+
}
|
|
110
|
+
return count;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Load only Clio ledgers. Inputs may be a current.jsonl file, one session
|
|
115
|
+
* directory, a cwd-hash directory, or the sessions root.
|
|
116
|
+
*/
|
|
117
|
+
export async function loadClioTraces(
|
|
118
|
+
paths: ReadonlyArray<string>,
|
|
119
|
+
options: LoadClioTraceOptions = {},
|
|
120
|
+
): Promise<{ traces: Trace[]; cascade: ReplayLoadCascade }> {
|
|
121
|
+
const discovered = new Set<string>();
|
|
122
|
+
let missingInputs = 0;
|
|
123
|
+
for (const path of paths) {
|
|
124
|
+
const before = discovered.size;
|
|
125
|
+
await collectLedgerFiles(path, discovered);
|
|
126
|
+
if (discovered.size === before) {
|
|
127
|
+
try {
|
|
128
|
+
await stat(resolve(path));
|
|
129
|
+
} catch {
|
|
130
|
+
missingInputs += 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const filtered: Record<string, number> = {
|
|
136
|
+
turns_lt_8: 0,
|
|
137
|
+
tool_results_lt_8: 0,
|
|
138
|
+
no_file_reread: 0,
|
|
139
|
+
};
|
|
140
|
+
const cascade: ReplayLoadCascade = {
|
|
141
|
+
found: discovered.size,
|
|
142
|
+
unreadable: missingInputs,
|
|
143
|
+
filtered,
|
|
144
|
+
kept: 0,
|
|
145
|
+
};
|
|
146
|
+
const traces: Trace[] = [];
|
|
147
|
+
const filter =
|
|
148
|
+
options.filter === false
|
|
149
|
+
? null
|
|
150
|
+
: {
|
|
151
|
+
minTurns: options.filter?.minTurns ?? DEFAULT_FILTER.minTurns,
|
|
152
|
+
minToolResults: options.filter?.minToolResults ?? DEFAULT_FILTER.minToolResults,
|
|
153
|
+
requireFileReread: options.filter?.requireFileReread ?? DEFAULT_FILTER.requireFileReread,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
for (const source of [...discovered].sort((a, b) => a.localeCompare(b))) {
|
|
157
|
+
let raw: string;
|
|
158
|
+
try {
|
|
159
|
+
raw = await readFile(source, "utf8");
|
|
160
|
+
} catch {
|
|
161
|
+
cascade.unreadable += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const parsed = parseSessionEntries(raw, source);
|
|
165
|
+
if (parsed.errors.length > 0) {
|
|
166
|
+
cascade.unreadable += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const entries = cleanActiveEntries(parsed.entries, await pinnedLeafTurnId(source));
|
|
170
|
+
const facts = sessionFacts(raw, source);
|
|
171
|
+
const trace: Trace = {
|
|
172
|
+
id: facts.id,
|
|
173
|
+
source,
|
|
174
|
+
cwd: facts.cwd,
|
|
175
|
+
entries,
|
|
176
|
+
turnCount: countReplayTurns(entries),
|
|
177
|
+
};
|
|
178
|
+
if (filter !== null) {
|
|
179
|
+
if (trace.turnCount < filter.minTurns) {
|
|
180
|
+
filtered.turns_lt_8 = (filtered.turns_lt_8 ?? 0) + 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (toolResultCount(entries) < filter.minToolResults) {
|
|
184
|
+
filtered.tool_results_lt_8 = (filtered.tool_results_lt_8 ?? 0) + 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (filter.requireFileReread) {
|
|
188
|
+
const graph = buildReferenceGraph(trace, buildPathIndex(entries, { cwd: trace.cwd }));
|
|
189
|
+
if (!graph.edges.some((edge) => edge.kind === "file_reread")) {
|
|
190
|
+
filtered.no_file_reread = (filtered.no_file_reread ?? 0) + 1;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
traces.push(trace);
|
|
196
|
+
}
|
|
197
|
+
cascade.kept = traces.length;
|
|
198
|
+
return { traces, cascade };
|
|
199
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { covers, type PathIndex, type PathObservation } from "../path-index.js";
|
|
2
|
+
import type { ReferenceGraph } from "./reference-graph.js";
|
|
3
|
+
import type { ReplayTraceResult } from "./runner.js";
|
|
4
|
+
import type { Trace } from "./trace.js";
|
|
5
|
+
|
|
6
|
+
export interface ReplayMetrics {
|
|
7
|
+
traces: number;
|
|
8
|
+
retention: number;
|
|
9
|
+
/** Retention after crediting a surviving newer read that covers the original range. */
|
|
10
|
+
retentionCovered: number;
|
|
11
|
+
retentionAt10: number;
|
|
12
|
+
evictionPrecision: number;
|
|
13
|
+
tokensEvicted: number;
|
|
14
|
+
/** Tokens freed by evicting items a later turn referenced again: the re-discovery bill a perfect recall would pay. */
|
|
15
|
+
recallTokens: number;
|
|
16
|
+
/** Sum over events of the projected tokens after the earliest evicted position: exact-prefix cache re-prefill cost. */
|
|
17
|
+
coldPrefixTokens: number;
|
|
18
|
+
evictionEvents: number;
|
|
19
|
+
/** Fraction of applied events that exhausted the policy's usable candidates. */
|
|
20
|
+
saturatedEvents: number;
|
|
21
|
+
turnsToFirstSummary: number | null;
|
|
22
|
+
/** Summary compactions the modeled summary stage applied; what a policy exists to make rare. */
|
|
23
|
+
summaries: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ReplayMeasurement {
|
|
27
|
+
trace: Trace;
|
|
28
|
+
index: PathIndex;
|
|
29
|
+
graph: ReferenceGraph;
|
|
30
|
+
replay: ReplayTraceResult;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReplayMetricAggregate {
|
|
34
|
+
/** Arithmetic mean of the per-trace metrics; `traces` is the sample size. */
|
|
35
|
+
mean: ReplayMetrics;
|
|
36
|
+
/** Number of traces contributing to the nullable `turnsToFirstSummary` mean. */
|
|
37
|
+
turnsToFirstSummaryCount: number;
|
|
38
|
+
/** Headline pair-level retention pooled across every critical future reference. */
|
|
39
|
+
pooledRetention: number;
|
|
40
|
+
pooledRetentionCovered: number;
|
|
41
|
+
pooledRetentionAt10: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface MeasuredTrace {
|
|
45
|
+
metrics: ReplayMetrics;
|
|
46
|
+
pairs: number;
|
|
47
|
+
retainedPairs: number;
|
|
48
|
+
coveredRetainedPairs: number;
|
|
49
|
+
pairsAt10: number;
|
|
50
|
+
retainedPairsAt10: number;
|
|
51
|
+
saturatedEventCount: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function safeFraction(numerator: number, denominator: number, empty: number): number {
|
|
55
|
+
return denominator === 0 ? empty : numerator / denominator;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function survivesThrough(
|
|
59
|
+
observation: PathObservation,
|
|
60
|
+
referenceTurn: number,
|
|
61
|
+
evictedAtTurn: ReadonlyMap<string, number>,
|
|
62
|
+
): boolean {
|
|
63
|
+
const evictedAt = evictedAtTurn.get(observation.ref.entry);
|
|
64
|
+
return evictedAt === undefined || evictedAt > referenceTurn;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasSurvivingCoveringRead(input: ReplayMeasurement, ref: string, referenceTurn: number): boolean {
|
|
68
|
+
const original = input.index.byRef.get(ref);
|
|
69
|
+
if (original?.op !== "read" || original.path.length === 0) return false;
|
|
70
|
+
return (input.index.byPath.get(original.path) ?? []).some(
|
|
71
|
+
(later) =>
|
|
72
|
+
later.op === "read" &&
|
|
73
|
+
!later.isError &&
|
|
74
|
+
later.entryIndex > original.entryIndex &&
|
|
75
|
+
later.turnIndex < referenceTurn &&
|
|
76
|
+
covers(later.range, original.range) &&
|
|
77
|
+
survivesThrough(later, referenceTurn, input.replay.evictedAtTurn),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function measure(input: ReplayMeasurement): MeasuredTrace {
|
|
82
|
+
let pairs = 0;
|
|
83
|
+
let retainedPairs = 0;
|
|
84
|
+
let coveredRetainedPairs = 0;
|
|
85
|
+
let pairsAt10 = 0;
|
|
86
|
+
let retainedPairsAt10 = 0;
|
|
87
|
+
for (const [ref, futureTurns] of input.graph.futureTurnsOf) {
|
|
88
|
+
const observationTurn = input.index.byRef.get(ref)?.turnIndex;
|
|
89
|
+
const evictedAt = input.replay.evictedAtTurn.get(ref);
|
|
90
|
+
for (const referenceTurn of futureTurns) {
|
|
91
|
+
pairs += 1;
|
|
92
|
+
const retained = evictedAt === undefined || evictedAt > referenceTurn;
|
|
93
|
+
if (retained) retainedPairs += 1;
|
|
94
|
+
if (retained || hasSurvivingCoveringRead(input, ref, referenceTurn)) coveredRetainedPairs += 1;
|
|
95
|
+
if (observationTurn !== undefined && referenceTurn - observationTurn <= 10) {
|
|
96
|
+
pairsAt10 += 1;
|
|
97
|
+
if (retained) retainedPairsAt10 += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Precision is the share of evicted items never referenced again; the
|
|
103
|
+
// complement (items the session came back to) is what live churn would
|
|
104
|
+
// count as recalls, so it is not reported as a second column.
|
|
105
|
+
let evictedItems = 0;
|
|
106
|
+
let safelyEvictedItems = 0;
|
|
107
|
+
let tokensEvicted = 0;
|
|
108
|
+
let recallTokens = 0;
|
|
109
|
+
let coldPrefixTokens = 0;
|
|
110
|
+
let saturatedEventCount = 0;
|
|
111
|
+
for (const event of input.replay.events) {
|
|
112
|
+
if (event.saturated) saturatedEventCount += 1;
|
|
113
|
+
coldPrefixTokens += event.coldPrefixTokens;
|
|
114
|
+
for (const item of event.items) {
|
|
115
|
+
evictedItems += 1;
|
|
116
|
+
tokensEvicted += item.tokensFreed;
|
|
117
|
+
const future = input.graph.futureTurnsOf.get(item.ref.entry) ?? [];
|
|
118
|
+
if (!future.some((turn) => turn > event.turnIndex)) safelyEvictedItems += 1;
|
|
119
|
+
else recallTokens += item.tokensFreed;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
metrics: {
|
|
125
|
+
traces: 1,
|
|
126
|
+
retention: safeFraction(retainedPairs, pairs, 1),
|
|
127
|
+
retentionCovered: safeFraction(coveredRetainedPairs, pairs, 1),
|
|
128
|
+
retentionAt10: safeFraction(retainedPairsAt10, pairsAt10, 1),
|
|
129
|
+
evictionPrecision: safeFraction(safelyEvictedItems, evictedItems, 1),
|
|
130
|
+
tokensEvicted,
|
|
131
|
+
recallTokens,
|
|
132
|
+
coldPrefixTokens,
|
|
133
|
+
evictionEvents: input.replay.events.length,
|
|
134
|
+
saturatedEvents: safeFraction(saturatedEventCount, input.replay.events.length, 0),
|
|
135
|
+
turnsToFirstSummary: input.replay.turnsToFirstSummary,
|
|
136
|
+
summaries: input.replay.summaries,
|
|
137
|
+
},
|
|
138
|
+
pairs,
|
|
139
|
+
retainedPairs,
|
|
140
|
+
coveredRetainedPairs,
|
|
141
|
+
pairsAt10,
|
|
142
|
+
retainedPairsAt10,
|
|
143
|
+
saturatedEventCount,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function measureReplayTrace(input: ReplayMeasurement): ReplayMetrics {
|
|
148
|
+
return measure(input).metrics;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function mean(values: ReadonlyArray<number>): number {
|
|
152
|
+
return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function aggregateReplayMetrics(inputs: ReadonlyArray<ReplayMeasurement>): ReplayMetricAggregate {
|
|
156
|
+
const measured = inputs.map(measure);
|
|
157
|
+
const firstSummaries = measured
|
|
158
|
+
.map((entry) => entry.metrics.turnsToFirstSummary)
|
|
159
|
+
.filter((value): value is number => value !== null);
|
|
160
|
+
const sum = (field: "pairs" | "retainedPairs" | "coveredRetainedPairs" | "pairsAt10" | "retainedPairsAt10"): number =>
|
|
161
|
+
measured.reduce((total, entry) => total + entry[field], 0);
|
|
162
|
+
const totalEvents = measured.reduce((total, entry) => total + entry.metrics.evictionEvents, 0);
|
|
163
|
+
const saturatedEvents = measured.reduce((total, entry) => total + entry.saturatedEventCount, 0);
|
|
164
|
+
return {
|
|
165
|
+
mean: {
|
|
166
|
+
traces: measured.length,
|
|
167
|
+
retention: measured.length === 0 ? 1 : mean(measured.map((entry) => entry.metrics.retention)),
|
|
168
|
+
retentionCovered: measured.length === 0 ? 1 : mean(measured.map((entry) => entry.metrics.retentionCovered)),
|
|
169
|
+
retentionAt10: measured.length === 0 ? 1 : mean(measured.map((entry) => entry.metrics.retentionAt10)),
|
|
170
|
+
evictionPrecision: measured.length === 0 ? 1 : mean(measured.map((entry) => entry.metrics.evictionPrecision)),
|
|
171
|
+
tokensEvicted: mean(measured.map((entry) => entry.metrics.tokensEvicted)),
|
|
172
|
+
recallTokens: mean(measured.map((entry) => entry.metrics.recallTokens)),
|
|
173
|
+
coldPrefixTokens: mean(measured.map((entry) => entry.metrics.coldPrefixTokens)),
|
|
174
|
+
evictionEvents: mean(measured.map((entry) => entry.metrics.evictionEvents)),
|
|
175
|
+
// Event-pooled: zero-event traces must not dilute the saturation rate.
|
|
176
|
+
saturatedEvents: safeFraction(saturatedEvents, totalEvents, 0),
|
|
177
|
+
turnsToFirstSummary: firstSummaries.length === 0 ? null : mean(firstSummaries),
|
|
178
|
+
summaries: mean(measured.map((entry) => entry.metrics.summaries)),
|
|
179
|
+
},
|
|
180
|
+
turnsToFirstSummaryCount: firstSummaries.length,
|
|
181
|
+
pooledRetention: safeFraction(sum("retainedPairs"), sum("pairs"), 1),
|
|
182
|
+
pooledRetentionCovered: safeFraction(sum("coveredRetainedPairs"), sum("pairs"), 1),
|
|
183
|
+
pooledRetentionAt10: safeFraction(sum("retainedPairsAt10"), sum("pairsAt10"), 1),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SessionEntry } from "../../../session/entries.js";
|
|
2
|
+
import type { PathIndex, PathObservation } from "../path-index.js";
|
|
3
|
+
import type { Trace } from "./trace.js";
|
|
4
|
+
|
|
5
|
+
export interface ReferenceEdge {
|
|
6
|
+
/** Ref key of the earlier tool_result. */
|
|
7
|
+
from: string;
|
|
8
|
+
toTurnIndex: number;
|
|
9
|
+
kind: "file_reread" | "file_discovery" | "file_rewrite";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ReferenceGraph {
|
|
13
|
+
edges: ReadonlyArray<ReferenceEdge>;
|
|
14
|
+
/** Critical future references only: file_reread and file_discovery. */
|
|
15
|
+
futureTurnsOf: ReadonlyMap<string, ReadonlyArray<number>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const READ_CLASS_OPS = new Set<PathObservation["op"]>(["read", "grep", "find", "ls", "code_nav"]);
|
|
19
|
+
const MUTATION_OPS = new Set<PathObservation["op"]>(["write", "edit"]);
|
|
20
|
+
|
|
21
|
+
function isToolResult(entry: SessionEntry | undefined): boolean {
|
|
22
|
+
return entry?.kind === "message" && entry.role === "tool_result";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isReadableObservation(observation: PathObservation): boolean {
|
|
26
|
+
return !observation.isError && observation.path.length > 0 && READ_CLASS_OPS.has(observation.op);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function edgeKey(edge: ReferenceEdge): string {
|
|
30
|
+
return `${edge.from}\u0000${edge.toTurnIndex}\u0000${edge.kind}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Label future path use without inspecting result prose. Rewrites are emitted
|
|
35
|
+
* for diagnosis but deliberately stay out of `futureTurnsOf`: a mutation makes
|
|
36
|
+
* the earlier read stale rather than critical to retain.
|
|
37
|
+
*/
|
|
38
|
+
export function buildReferenceGraph(trace: Trace, index: PathIndex): ReferenceGraph {
|
|
39
|
+
const entryById = new Map(trace.entries.map((entry) => [entry.turnId, entry]));
|
|
40
|
+
const edges: ReferenceEdge[] = [];
|
|
41
|
+
const seenEdges = new Set<string>();
|
|
42
|
+
const future = new Map<string, Set<number>>();
|
|
43
|
+
|
|
44
|
+
const add = (edge: ReferenceEdge): void => {
|
|
45
|
+
const key = edgeKey(edge);
|
|
46
|
+
if (seenEdges.has(key)) return;
|
|
47
|
+
seenEdges.add(key);
|
|
48
|
+
edges.push(edge);
|
|
49
|
+
if (edge.kind === "file_rewrite") return;
|
|
50
|
+
const turns = future.get(edge.from);
|
|
51
|
+
if (turns === undefined) future.set(edge.from, new Set([edge.toTurnIndex]));
|
|
52
|
+
else turns.add(edge.toTurnIndex);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (const earlier of index.observations) {
|
|
56
|
+
if (!isToolResult(entryById.get(earlier.ref.entry)) || !isReadableObservation(earlier)) continue;
|
|
57
|
+
const surfaced = new Set(earlier.surfaced);
|
|
58
|
+
for (const later of index.observations) {
|
|
59
|
+
if (later.entryIndex <= earlier.entryIndex) continue;
|
|
60
|
+
if (isReadableObservation(later) && later.path === earlier.path) {
|
|
61
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_reread" });
|
|
62
|
+
}
|
|
63
|
+
if (isReadableObservation(later) && surfaced.has(later.path)) {
|
|
64
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_discovery" });
|
|
65
|
+
}
|
|
66
|
+
if (later.path === earlier.path && MUTATION_OPS.has(later.op)) {
|
|
67
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_rewrite" });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const futureTurnsOf = new Map<string, ReadonlyArray<number>>();
|
|
73
|
+
for (const [ref, turns] of future)
|
|
74
|
+
futureTurnsOf.set(
|
|
75
|
+
ref,
|
|
76
|
+
[...turns].sort((a, b) => a - b),
|
|
77
|
+
);
|
|
78
|
+
return { edges, futureTurnsOf };
|
|
79
|
+
}
|