@compaction/cli 0.1.4 → 0.3.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/README.md +51 -12
- package/dist/cli/commands/activity.d.ts +8 -0
- package/dist/cli/commands/activity.js +35 -0
- package/dist/cli/commands/apply-context.js +68 -0
- package/dist/cli/commands/billing-delta.js +12 -1
- package/dist/cli/commands/capture-claude-code.d.ts +44 -0
- package/dist/cli/commands/capture-claude-code.js +206 -0
- package/dist/cli/commands/capture.js +283 -1
- package/dist/cli/commands/compact.js +24 -11
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/dev.d.ts +17 -0
- package/dist/cli/commands/dev.js +131 -0
- package/dist/cli/commands/gateway.d.ts +31 -0
- package/dist/cli/commands/gateway.js +312 -0
- package/dist/cli/commands/hooks.d.ts +5 -0
- package/dist/cli/commands/hooks.js +181 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +422 -17
- package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
- package/dist/cli/commands/input-compaction-ab.js +125 -0
- package/dist/cli/commands/optimize-hosted.d.ts +54 -0
- package/dist/cli/commands/optimize-hosted.js +123 -0
- package/dist/cli/commands/optimize.js +8 -0
- package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
- package/dist/cli/commands/output-shaping-ab.js +132 -0
- package/dist/cli/commands/output-shaping.d.ts +7 -0
- package/dist/cli/commands/output-shaping.js +43 -0
- package/dist/cli/commands/policies.d.ts +2 -0
- package/dist/cli/commands/policies.js +80 -0
- package/dist/cli/commands/precall.d.ts +19 -0
- package/dist/cli/commands/precall.js +436 -0
- package/dist/cli/commands/recommend.js +1 -1
- package/dist/cli/commands/run.js +310 -18
- package/dist/cli/commands/upgrade-status.d.ts +42 -0
- package/dist/cli/commands/upgrade-status.js +152 -0
- package/dist/cli/cursor-export-read.d.ts +13 -0
- package/dist/cli/cursor-export-read.js +53 -0
- package/dist/cli/cursor-live-preflight.d.ts +19 -0
- package/dist/cli/cursor-live-preflight.js +46 -0
- package/dist/cli/index.js +39 -2
- package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
- package/dist/cli/onboarding/GatewayTui.js +113 -0
- package/dist/cli/onboarding/model.d.ts +32 -1
- package/dist/cli/onboarding/model.js +27 -2
- package/dist/cli/terminal-logo.d.ts +27 -0
- package/dist/cli/terminal-logo.js +49 -0
- package/dist/core/activity-event.d.ts +107 -0
- package/dist/core/activity-event.js +182 -0
- package/dist/core/activity-store.d.ts +63 -0
- package/dist/core/activity-store.js +254 -0
- package/dist/core/activity-view.d.ts +76 -0
- package/dist/core/activity-view.js +120 -0
- package/dist/core/api-client/index.d.ts +3 -1
- package/dist/core/api-client/index.js +4 -0
- package/dist/core/api-client/payload.d.ts +5 -1
- package/dist/core/api-client/payload.js +8 -6
- package/dist/core/api-client/persisted-config.d.ts +57 -0
- package/dist/core/api-client/persisted-config.js +156 -0
- package/dist/core/api-client/tool.d.ts +29 -0
- package/dist/core/api-client/tool.js +50 -0
- package/dist/core/api-client/types.d.ts +15 -4
- package/dist/core/auto-apply-ask.d.ts +58 -0
- package/dist/core/auto-apply-ask.js +105 -0
- package/dist/core/auto-apply-gates.d.ts +76 -0
- package/dist/core/auto-apply-gates.js +113 -0
- package/dist/core/before-call-activity.d.ts +35 -0
- package/dist/core/before-call-activity.js +103 -0
- package/dist/core/before-call-recovery.d.ts +19 -0
- package/dist/core/before-call-recovery.js +46 -0
- package/dist/core/before-call-stdin.d.ts +59 -0
- package/dist/core/before-call-stdin.js +78 -0
- package/dist/core/before-call.d.ts +149 -0
- package/dist/core/before-call.js +358 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
- package/dist/core/billing-delta/billing-delta-record.js +26 -0
- package/dist/core/capture-record.d.ts +40 -0
- package/dist/core/capture-record.js +55 -0
- package/dist/core/claude-code-before-call.d.ts +37 -0
- package/dist/core/claude-code-before-call.js +120 -0
- package/dist/core/claude-code-connect.d.ts +31 -0
- package/dist/core/claude-code-connect.js +87 -0
- package/dist/core/claude-code-hook-record.d.ts +71 -0
- package/dist/core/claude-code-hook-record.js +0 -0
- package/dist/core/claude-code-hooks.d.ts +77 -0
- package/dist/core/claude-code-hooks.js +141 -0
- package/dist/core/codex-capture.d.ts +45 -0
- package/dist/core/codex-capture.js +204 -0
- package/dist/core/command-runner.js +5 -1
- package/dist/core/compaction-artifacts.d.ts +51 -0
- package/dist/core/compaction-artifacts.js +242 -0
- package/dist/core/compactor.d.ts +9 -0
- package/dist/core/compactor.js +95 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/cross-surface-event.d.ts +306 -0
- package/dist/core/cross-surface-event.js +330 -0
- package/dist/core/cursor-capture.d.ts +54 -0
- package/dist/core/cursor-capture.js +215 -0
- package/dist/core/cursor-preflight-probe.d.ts +8 -0
- package/dist/core/cursor-preflight-probe.js +88 -0
- package/dist/core/cursor-preflight.d.ts +86 -0
- package/dist/core/cursor-preflight.js +126 -0
- package/dist/core/gateway/apply-activation.d.ts +39 -0
- package/dist/core/gateway/apply-activation.js +84 -0
- package/dist/core/gateway/apply-policy.d.ts +64 -0
- package/dist/core/gateway/apply-policy.js +221 -0
- package/dist/core/gateway/apply-receipt.d.ts +36 -0
- package/dist/core/gateway/apply-receipt.js +75 -0
- package/dist/core/gateway/cache-proof.d.ts +45 -0
- package/dist/core/gateway/cache-proof.js +65 -0
- package/dist/core/gateway/configure.d.ts +50 -0
- package/dist/core/gateway/configure.js +169 -0
- package/dist/core/gateway/openai-usage.d.ts +56 -0
- package/dist/core/gateway/openai-usage.js +128 -0
- package/dist/core/gateway/receipt.d.ts +138 -0
- package/dist/core/gateway/receipt.js +120 -0
- package/dist/core/gateway/recovery.d.ts +23 -0
- package/dist/core/gateway/recovery.js +68 -0
- package/dist/core/gateway/server.d.ts +51 -0
- package/dist/core/gateway/server.js +276 -0
- package/dist/core/gateway/status.d.ts +45 -0
- package/dist/core/gateway/status.js +109 -0
- package/dist/core/hook-usage-aggregate.d.ts +47 -0
- package/dist/core/hook-usage-aggregate.js +161 -0
- package/dist/core/input-compaction-ab.d.ts +111 -0
- package/dist/core/input-compaction-ab.js +158 -0
- package/dist/core/local-run-record.d.ts +109 -0
- package/dist/core/local-run-record.js +223 -0
- package/dist/core/output-shaping-ab.d.ts +140 -0
- package/dist/core/output-shaping-ab.js +146 -0
- package/dist/core/output-shaping-attach.d.ts +31 -0
- package/dist/core/output-shaping-attach.js +57 -0
- package/dist/core/output-shaping.d.ts +56 -0
- package/dist/core/output-shaping.js +89 -0
- package/dist/core/policy-middleware.d.ts +121 -0
- package/dist/core/policy-middleware.js +919 -0
- package/dist/core/policy-preferences.d.ts +99 -0
- package/dist/core/policy-preferences.js +232 -0
- package/dist/core/report-generator.d.ts +19 -1
- package/dist/core/report-generator.js +51 -0
- package/dist/core/run-aggregator.d.ts +56 -1
- package/dist/core/run-aggregator.js +93 -2
- package/dist/core/run-flow-report.d.ts +82 -0
- package/dist/core/run-flow-report.js +71 -0
- package/dist/core/safety-report.js +8 -1
- package/dist/core/shim-capture-bridge.d.ts +32 -0
- package/dist/core/shim-capture-bridge.js +88 -0
- package/dist/core/skill-injection-policy.d.ts +72 -0
- package/dist/core/skill-injection-policy.js +183 -0
- package/dist/core/spend-attribution.js +1 -1
- package/dist/core/token-accounting.d.ts +1 -1
- package/dist/core/tool-shim.d.ts +129 -0
- package/dist/core/tool-shim.js +447 -0
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/trace-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +45 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-shaping A/B experiment record + summary (PUBLIC CLI/SDK code — engine-free, ships in the npm CLI).
|
|
3
|
+
* Increment 4 of the output-shaping policy family (`docs/design/output-shaping-policy-family.md`): the
|
|
4
|
+
* smallest reliable OPERATOR-RUN path to produce + ingest a real provider-reported A/B comparison between
|
|
5
|
+
* a control arm (Codex live wrapper without output-shaping) and a treatment arm (with `--output-shaping`).
|
|
6
|
+
*
|
|
7
|
+
* Boundary / honesty (binding):
|
|
8
|
+
* - This PUBLIC module links the two arms and computes the **observed** measurement (means, delta,
|
|
9
|
+
* reduction %, N per arm) for operator visibility. It is **descriptive, NOT a savings claim.**
|
|
10
|
+
* - It NEVER produces a CONFIRMED output-savings number. `confirmedSavingsEligible` is the literal `false`
|
|
11
|
+
* here; a confirmed number can only ever be produced by the PRIVATE engine gate
|
|
12
|
+
* (`src/engine/output-shaping-verification.ts` — provider-reported A/B meeting the billing-delta
|
|
13
|
+
* criterion AND short-but-sufficient eval AND a reduction).
|
|
14
|
+
* - Output tokens that are not provider-reported (e.g. Cursor — local-estimate only) yield
|
|
15
|
+
* `confidence: "unavailable"` and can never be eligible. Missing output tokens stay unavailable, not 0.
|
|
16
|
+
* - The record is content-free: it stores token counts, the honest source, policy NAMES, operator eval
|
|
17
|
+
* outcome, and truncation/refusal flags — no prompt/response/trace content. Local artifact paths are
|
|
18
|
+
* operator-side references only.
|
|
19
|
+
*/
|
|
20
|
+
import type { TokenSource, ToolName } from "./api-client/index.js";
|
|
21
|
+
export declare const OUTPUT_SHAPING_AB_SCHEMA: "output-shaping.ab-experiment.v1";
|
|
22
|
+
export declare const CAPTURE_USAGE_SIDECAR_SCHEMA: "compaction.capture-usage.v1";
|
|
23
|
+
export declare const OUTPUT_SHAPING_POLICY_FAMILY: "output_shaping";
|
|
24
|
+
/**
|
|
25
|
+
* Written next to a capture artifact so a later A/B `add` can ingest the **provider-reported** tokens +
|
|
26
|
+
* (treatment) policy attribution without re-running anything. Content-free: counts + source + policy names.
|
|
27
|
+
*/
|
|
28
|
+
export interface CaptureUsageSidecar {
|
|
29
|
+
schema: typeof CAPTURE_USAGE_SIDECAR_SCHEMA;
|
|
30
|
+
tool: ToolName;
|
|
31
|
+
provider?: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
inputTokens: number | null;
|
|
34
|
+
outputTokens: number | null;
|
|
35
|
+
/** True ONLY when the counts are provider-reported (never local-estimate / unavailable). */
|
|
36
|
+
providerReported: boolean;
|
|
37
|
+
tokenSource: TokenSource;
|
|
38
|
+
/** "present" when the provider emitted a usage block; "missing" when usage was absent (never invented). */
|
|
39
|
+
tokenMetadataStatus: "present" | "missing";
|
|
40
|
+
/** Present on the treatment arm when an output-shaping policy was attached BEFORE generation. */
|
|
41
|
+
outputShaping?: {
|
|
42
|
+
policyFamily: typeof OUTPUT_SHAPING_POLICY_FAMILY;
|
|
43
|
+
policyNames: string[];
|
|
44
|
+
};
|
|
45
|
+
generatedAt: string;
|
|
46
|
+
}
|
|
47
|
+
export interface BuildCaptureUsageSidecarInput {
|
|
48
|
+
tool: ToolName;
|
|
49
|
+
provider?: string;
|
|
50
|
+
model?: string;
|
|
51
|
+
inputTokens?: number;
|
|
52
|
+
outputTokens?: number;
|
|
53
|
+
providerReported: boolean;
|
|
54
|
+
tokenSource: TokenSource;
|
|
55
|
+
tokenMetadataStatus: "present" | "missing";
|
|
56
|
+
policyNames?: string[];
|
|
57
|
+
generatedAt?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function buildCaptureUsageSidecar(input: BuildCaptureUsageSidecarInput): CaptureUsageSidecar;
|
|
60
|
+
export type OutputShapingAbArm = "control" | "treatment";
|
|
61
|
+
export interface OutputShapingAbRun {
|
|
62
|
+
arm: OutputShapingAbArm;
|
|
63
|
+
/** Provider-reported output tokens; null when unavailable (never invented as 0). */
|
|
64
|
+
outputTokens: number | null;
|
|
65
|
+
inputTokens: number | null;
|
|
66
|
+
/** True ONLY when the output tokens are provider-reported. */
|
|
67
|
+
providerReported: boolean;
|
|
68
|
+
tokenSource: TokenSource;
|
|
69
|
+
/** Treatment arm: the output-shaping policy family/names attached BEFORE generation. */
|
|
70
|
+
policyFamily?: typeof OUTPUT_SHAPING_POLICY_FAMILY;
|
|
71
|
+
policyNames?: string[];
|
|
72
|
+
/**
|
|
73
|
+
* Operator/eval-recorded short-but-sufficient outcome for a TREATMENT run: did the shaped (shorter)
|
|
74
|
+
* output preserve all required task-outcome markers? `null`/absent ⇒ not yet evaluated (review required).
|
|
75
|
+
* The rigorous marker check is the engine eval; this is the recorded outcome.
|
|
76
|
+
*/
|
|
77
|
+
evalMarkersPreserved?: boolean | null;
|
|
78
|
+
/** Observed truncation of the answer (an incomplete answer is not a saving). */
|
|
79
|
+
truncated?: boolean;
|
|
80
|
+
/** Observed refusal (a refusal is not a saving). */
|
|
81
|
+
refused?: boolean;
|
|
82
|
+
/** Local artifact path — operator-side evidence only, never content. */
|
|
83
|
+
reference?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface OutputShapingAbExperiment {
|
|
86
|
+
schema: typeof OUTPUT_SHAPING_AB_SCHEMA;
|
|
87
|
+
experimentId: string;
|
|
88
|
+
/** A short generic label of the shared task/prompt SHAPE (operator-supplied; not prompt content). */
|
|
89
|
+
taskShape: string;
|
|
90
|
+
createdAt: string;
|
|
91
|
+
runs: OutputShapingAbRun[];
|
|
92
|
+
}
|
|
93
|
+
export interface InitOutputShapingAbInput {
|
|
94
|
+
experimentId: string;
|
|
95
|
+
taskShape: string;
|
|
96
|
+
createdAt?: string;
|
|
97
|
+
}
|
|
98
|
+
export declare function initOutputShapingAbExperiment(input: InitOutputShapingAbInput): OutputShapingAbExperiment;
|
|
99
|
+
export declare function addOutputShapingAbRun(experiment: OutputShapingAbExperiment, run: OutputShapingAbRun): OutputShapingAbExperiment;
|
|
100
|
+
/**
|
|
101
|
+
* Public confidence ladder. NONE of these is a confirmed savings claim.
|
|
102
|
+
* - `unavailable` — an arm's output tokens are not provider-reported / are missing.
|
|
103
|
+
* - `review_required` — truncation/refusal observed, or treatment sufficiency not a clean pass.
|
|
104
|
+
* - `observed_not_confirmed` — provider-reported both arms, sufficiency passed, but N < 3 per arm.
|
|
105
|
+
* - `eligible_for_engine_confirmation` — all public preconditions met; the CONFIRMED verdict is an engine step.
|
|
106
|
+
*/
|
|
107
|
+
export type OutputShapingAbConfidence = "unavailable" | "review_required" | "observed_not_confirmed" | "eligible_for_engine_confirmation";
|
|
108
|
+
export type OutputShapingAbEvalStatus = "pass" | "fail" | "not_evaluated" | "mixed";
|
|
109
|
+
export interface OutputShapingAbSummary {
|
|
110
|
+
experimentId: string;
|
|
111
|
+
nControl: number;
|
|
112
|
+
nTreatment: number;
|
|
113
|
+
/** Mean control (no-shaping) output tokens — provider-reported; null when unavailable. */
|
|
114
|
+
outputTokensBefore: number | null;
|
|
115
|
+
/** Mean treatment (shaped) output tokens — provider-reported; null when unavailable. */
|
|
116
|
+
outputTokensAfter: number | null;
|
|
117
|
+
/** before − after: positive ⇒ treatment used FEWER output tokens. OBSERVED, not a confirmed saving. */
|
|
118
|
+
outputTokenDelta: number | null;
|
|
119
|
+
outputTokenReductionPct: number | null;
|
|
120
|
+
tokenSource: "provider-reported" | "mixed" | "local-estimate" | "unavailable";
|
|
121
|
+
policyFamily: string | null;
|
|
122
|
+
policyNames: string[];
|
|
123
|
+
evalStatus: OutputShapingAbEvalStatus;
|
|
124
|
+
confidence: OutputShapingAbConfidence;
|
|
125
|
+
/** ALWAYS false in the public CLI — a confirmed savings number is produced only by the engine gate. */
|
|
126
|
+
confirmedSavingsEligible: false;
|
|
127
|
+
reasons: string[];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compute the OBSERVED A/B measurement + a conservative confidence. Never confirms a saving (that is the
|
|
131
|
+
* engine gate). The decision order is fail-safe: unavailable → review_required → observed_not_confirmed →
|
|
132
|
+
* eligible_for_engine_confirmation. Means/delta/pct are computed only from provider-reported output tokens.
|
|
133
|
+
*/
|
|
134
|
+
export declare function summarizeOutputShapingAb(experiment: OutputShapingAbExperiment): OutputShapingAbSummary;
|
|
135
|
+
/** Provider-reported output-token arrays per arm — the only honest input to the engine confirmation gate. */
|
|
136
|
+
export declare function providerReportedOutputArms(experiment: OutputShapingAbExperiment): {
|
|
137
|
+
controlOutputTokens: number[];
|
|
138
|
+
treatmentOutputTokens: number[];
|
|
139
|
+
bothProviderReported: boolean;
|
|
140
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const OUTPUT_SHAPING_AB_SCHEMA = "output-shaping.ab-experiment.v1";
|
|
2
|
+
export const CAPTURE_USAGE_SIDECAR_SCHEMA = "compaction.capture-usage.v1";
|
|
3
|
+
export const OUTPUT_SHAPING_POLICY_FAMILY = "output_shaping";
|
|
4
|
+
export function buildCaptureUsageSidecar(input) {
|
|
5
|
+
return {
|
|
6
|
+
schema: CAPTURE_USAGE_SIDECAR_SCHEMA,
|
|
7
|
+
tool: input.tool,
|
|
8
|
+
...(input.provider ? { provider: input.provider } : {}),
|
|
9
|
+
...(input.model ? { model: input.model } : {}),
|
|
10
|
+
inputTokens: typeof input.inputTokens === "number" ? input.inputTokens : null,
|
|
11
|
+
outputTokens: typeof input.outputTokens === "number" ? input.outputTokens : null,
|
|
12
|
+
providerReported: input.providerReported,
|
|
13
|
+
tokenSource: input.tokenSource,
|
|
14
|
+
tokenMetadataStatus: input.tokenMetadataStatus,
|
|
15
|
+
...(input.policyNames && input.policyNames.length > 0
|
|
16
|
+
? { outputShaping: { policyFamily: OUTPUT_SHAPING_POLICY_FAMILY, policyNames: input.policyNames } }
|
|
17
|
+
: {}),
|
|
18
|
+
generatedAt: input.generatedAt ?? new Date().toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function initOutputShapingAbExperiment(input) {
|
|
22
|
+
return {
|
|
23
|
+
schema: OUTPUT_SHAPING_AB_SCHEMA,
|
|
24
|
+
experimentId: input.experimentId,
|
|
25
|
+
taskShape: input.taskShape,
|
|
26
|
+
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
27
|
+
runs: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function addOutputShapingAbRun(experiment, run) {
|
|
31
|
+
return { ...experiment, runs: [...experiment.runs, run] };
|
|
32
|
+
}
|
|
33
|
+
const MIN_RUNS_PER_ARM = 3;
|
|
34
|
+
function mean(xs) {
|
|
35
|
+
return xs.reduce((s, x) => s + x, 0) / xs.length;
|
|
36
|
+
}
|
|
37
|
+
function summarizeArmSource(runs) {
|
|
38
|
+
if (runs.length === 0)
|
|
39
|
+
return "unavailable";
|
|
40
|
+
const allProvider = runs.every((r) => r.providerReported && typeof r.outputTokens === "number");
|
|
41
|
+
if (allProvider)
|
|
42
|
+
return "provider-reported";
|
|
43
|
+
const anyProvider = runs.some((r) => r.providerReported && typeof r.outputTokens === "number");
|
|
44
|
+
if (anyProvider)
|
|
45
|
+
return "mixed";
|
|
46
|
+
const anyEstimate = runs.some((r) => r.tokenSource === "local-estimate");
|
|
47
|
+
return anyEstimate ? "local-estimate" : "unavailable";
|
|
48
|
+
}
|
|
49
|
+
function treatmentEvalStatus(treatment) {
|
|
50
|
+
if (treatment.length === 0)
|
|
51
|
+
return "not_evaluated";
|
|
52
|
+
const outcomes = treatment.map((r) => r.evalMarkersPreserved);
|
|
53
|
+
if (outcomes.some((o) => o === undefined || o === null)) {
|
|
54
|
+
return outcomes.some((o) => o === true || o === false) ? "mixed" : "not_evaluated";
|
|
55
|
+
}
|
|
56
|
+
if (outcomes.every((o) => o === true))
|
|
57
|
+
return "pass";
|
|
58
|
+
if (outcomes.every((o) => o === false))
|
|
59
|
+
return "fail";
|
|
60
|
+
return "mixed";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compute the OBSERVED A/B measurement + a conservative confidence. Never confirms a saving (that is the
|
|
64
|
+
* engine gate). The decision order is fail-safe: unavailable → review_required → observed_not_confirmed →
|
|
65
|
+
* eligible_for_engine_confirmation. Means/delta/pct are computed only from provider-reported output tokens.
|
|
66
|
+
*/
|
|
67
|
+
export function summarizeOutputShapingAb(experiment) {
|
|
68
|
+
const control = experiment.runs.filter((r) => r.arm === "control");
|
|
69
|
+
const treatment = experiment.runs.filter((r) => r.arm === "treatment");
|
|
70
|
+
const reasons = [];
|
|
71
|
+
const controlOut = control.filter((r) => r.providerReported && typeof r.outputTokens === "number").map((r) => r.outputTokens);
|
|
72
|
+
const treatmentOut = treatment.filter((r) => r.providerReported && typeof r.outputTokens === "number").map((r) => r.outputTokens);
|
|
73
|
+
const bothProviderReported = control.length > 0 &&
|
|
74
|
+
treatment.length > 0 &&
|
|
75
|
+
control.every((r) => r.providerReported && typeof r.outputTokens === "number") &&
|
|
76
|
+
treatment.every((r) => r.providerReported && typeof r.outputTokens === "number");
|
|
77
|
+
const outputTokensBefore = controlOut.length > 0 ? mean(controlOut) : null;
|
|
78
|
+
const outputTokensAfter = treatmentOut.length > 0 ? mean(treatmentOut) : null;
|
|
79
|
+
const outputTokenDelta = outputTokensBefore !== null && outputTokensAfter !== null ? outputTokensBefore - outputTokensAfter : null;
|
|
80
|
+
const outputTokenReductionPct = outputTokenDelta !== null && outputTokensBefore !== null && outputTokensBefore > 0
|
|
81
|
+
? (outputTokenDelta / outputTokensBefore) * 100
|
|
82
|
+
: null;
|
|
83
|
+
const controlSource = summarizeArmSource(control);
|
|
84
|
+
const treatmentSource = summarizeArmSource(treatment);
|
|
85
|
+
const tokenSource = bothProviderReported
|
|
86
|
+
? "provider-reported"
|
|
87
|
+
: controlSource === "local-estimate" || treatmentSource === "local-estimate"
|
|
88
|
+
? "local-estimate"
|
|
89
|
+
: controlSource === "mixed" || treatmentSource === "mixed"
|
|
90
|
+
? "mixed"
|
|
91
|
+
: "unavailable";
|
|
92
|
+
const policyNames = Array.from(new Set(treatment.flatMap((r) => r.policyNames ?? []))).sort();
|
|
93
|
+
const policyFamily = treatment.some((r) => r.policyFamily) ? OUTPUT_SHAPING_POLICY_FAMILY : null;
|
|
94
|
+
const evalStatus = treatmentEvalStatus(treatment);
|
|
95
|
+
const anyTruncatedOrRefused = treatment.some((r) => r.truncated || r.refused) || control.some((r) => r.truncated || r.refused);
|
|
96
|
+
let confidence;
|
|
97
|
+
if (!bothProviderReported) {
|
|
98
|
+
confidence = "unavailable";
|
|
99
|
+
reasons.push("output savings unavailable: both arms must have provider-reported output tokens (local-estimate / missing output can never produce an output-savings number).");
|
|
100
|
+
}
|
|
101
|
+
else if (anyTruncatedOrRefused) {
|
|
102
|
+
confidence = "review_required";
|
|
103
|
+
reasons.push("review required: a run was marked truncated or refused — an incomplete answer is not a saving.");
|
|
104
|
+
}
|
|
105
|
+
else if (evalStatus !== "pass") {
|
|
106
|
+
confidence = "review_required";
|
|
107
|
+
reasons.push(`review required: treatment short-but-sufficient eval status is "${evalStatus}" (a shorter-but-lossy output is not a saving; record --eval-pass/--eval-fail per treatment run).`);
|
|
108
|
+
}
|
|
109
|
+
else if (control.length < MIN_RUNS_PER_ARM || treatment.length < MIN_RUNS_PER_ARM) {
|
|
110
|
+
confidence = "observed_not_confirmed";
|
|
111
|
+
reasons.push(`observed, not confirmed: N ≥ ${MIN_RUNS_PER_ARM} per arm is required (have control=${control.length}, treatment=${treatment.length}). A single A/B pair is an anecdote.`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
confidence = "eligible_for_engine_confirmation";
|
|
115
|
+
reasons.push("eligible for engine confirmation: provider-reported both arms, N ≥ 3 per arm, sufficiency passed, no truncation/refusal. The CONFIRMED verdict is the engine gate (measured ±2·SE criterion + reduction).");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
experimentId: experiment.experimentId,
|
|
119
|
+
nControl: control.length,
|
|
120
|
+
nTreatment: treatment.length,
|
|
121
|
+
outputTokensBefore,
|
|
122
|
+
outputTokensAfter,
|
|
123
|
+
outputTokenDelta,
|
|
124
|
+
outputTokenReductionPct,
|
|
125
|
+
tokenSource,
|
|
126
|
+
policyFamily,
|
|
127
|
+
policyNames,
|
|
128
|
+
evalStatus,
|
|
129
|
+
confidence,
|
|
130
|
+
confirmedSavingsEligible: false,
|
|
131
|
+
reasons
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Provider-reported output-token arrays per arm — the only honest input to the engine confirmation gate. */
|
|
135
|
+
export function providerReportedOutputArms(experiment) {
|
|
136
|
+
const control = experiment.runs.filter((r) => r.arm === "control");
|
|
137
|
+
const treatment = experiment.runs.filter((r) => r.arm === "treatment");
|
|
138
|
+
const controlOutputTokens = control.filter((r) => r.providerReported && typeof r.outputTokens === "number").map((r) => r.outputTokens);
|
|
139
|
+
const treatmentOutputTokens = treatment.filter((r) => r.providerReported && typeof r.outputTokens === "number").map((r) => r.outputTokens);
|
|
140
|
+
const bothProviderReported = control.length > 0 &&
|
|
141
|
+
treatment.length > 0 &&
|
|
142
|
+
controlOutputTokens.length === control.length &&
|
|
143
|
+
treatmentOutputTokens.length === treatment.length;
|
|
144
|
+
return { controlOutputTokens, treatmentOutputTokens, bothProviderReported };
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=output-shaping-ab.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach the output-shaping instruction block to a wrapped CLI command's prompt BEFORE generation
|
|
3
|
+
* (PUBLIC CLI/SDK code, engine-free). Increment 3 of the output-shaping policy family — wiring the
|
|
4
|
+
* deterministic policies (`output-shaping.ts`) into the live wrappers (`capture codex`/`capture cursor`).
|
|
5
|
+
*
|
|
6
|
+
* Mechanism honesty: output-token reduction requires shaping the request BEFORE generation. This prepends
|
|
7
|
+
* the instruction block to the prompt argument the wrapper is about to run. It is OPT-IN, the original
|
|
8
|
+
* command is preserved by the caller, and it surfaces NO output-savings number (a savings figure requires
|
|
9
|
+
* the private measured-A/B + short-but-sufficient eval gate — `src/engine/output-shaping-verification.ts`).
|
|
10
|
+
*/
|
|
11
|
+
import { type BuildOutputShapingOptions, type OutputShapingAttribution } from "./output-shaping.js";
|
|
12
|
+
/**
|
|
13
|
+
* Index of the last positional (prompt) argument in a wrapped command — skipping the executable, flags,
|
|
14
|
+
* value-taking flags' values, and known subcommand keywords. Returns -1 when no prompt arg is found.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findPromptArgIndex(commandParts: string[]): number;
|
|
17
|
+
export interface AttachOutputShapingResult {
|
|
18
|
+
/** The command to run (a COPY; the original is untouched). When not attached, equals the input. */
|
|
19
|
+
commandParts: string[];
|
|
20
|
+
/** The output-shaping policies applied (content-free attribution); empty when not attached. */
|
|
21
|
+
applied: OutputShapingAttribution[];
|
|
22
|
+
attached: boolean;
|
|
23
|
+
/** Why attachment did not happen (e.g. no prompt arg, no matching policies). */
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Prepend the output-shaping instruction block to the prompt argument of `commandParts`. Returns a COPY
|
|
28
|
+
* (never mutates the input). When no prompt arg is found, or no policies match, returns `attached: false`
|
|
29
|
+
* with the original command + a reason — never silently shapes the wrong argument.
|
|
30
|
+
*/
|
|
31
|
+
export declare function attachOutputShapingToCommand(commandParts: string[], opts?: BuildOutputShapingOptions): AttachOutputShapingResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach the output-shaping instruction block to a wrapped CLI command's prompt BEFORE generation
|
|
3
|
+
* (PUBLIC CLI/SDK code, engine-free). Increment 3 of the output-shaping policy family — wiring the
|
|
4
|
+
* deterministic policies (`output-shaping.ts`) into the live wrappers (`capture codex`/`capture cursor`).
|
|
5
|
+
*
|
|
6
|
+
* Mechanism honesty: output-token reduction requires shaping the request BEFORE generation. This prepends
|
|
7
|
+
* the instruction block to the prompt argument the wrapper is about to run. It is OPT-IN, the original
|
|
8
|
+
* command is preserved by the caller, and it surfaces NO output-savings number (a savings figure requires
|
|
9
|
+
* the private measured-A/B + short-but-sufficient eval gate — `src/engine/output-shaping-verification.ts`).
|
|
10
|
+
*/
|
|
11
|
+
import { buildOutputShapingPolicy } from "./output-shaping.js";
|
|
12
|
+
// Boolean flags take no value, so the token after them is still a positional (the prompt).
|
|
13
|
+
const BOOLEAN_FLAGS = new Set(["-p", "--print", "--force", "--json", "--quiet", "-q", "--yes", "-y"]);
|
|
14
|
+
const SUBCOMMAND_KEYWORDS = new Set(["cursor", "cursor-agent", "codex", "agent", "chat", "exec", "run"]);
|
|
15
|
+
/**
|
|
16
|
+
* Index of the last positional (prompt) argument in a wrapped command — skipping the executable, flags,
|
|
17
|
+
* value-taking flags' values, and known subcommand keywords. Returns -1 when no prompt arg is found.
|
|
18
|
+
*/
|
|
19
|
+
export function findPromptArgIndex(commandParts) {
|
|
20
|
+
let last = -1;
|
|
21
|
+
let skipNext = false;
|
|
22
|
+
for (let i = 1; i < commandParts.length; i++) {
|
|
23
|
+
const part = commandParts[i];
|
|
24
|
+
if (skipNext) {
|
|
25
|
+
skipNext = false; // value of a preceding value-taking flag (e.g. `--output-format json`)
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (part.startsWith("-")) {
|
|
29
|
+
if (!BOOLEAN_FLAGS.has(part) && !part.includes("="))
|
|
30
|
+
skipNext = true;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (SUBCOMMAND_KEYWORDS.has(part))
|
|
34
|
+
continue;
|
|
35
|
+
last = i;
|
|
36
|
+
}
|
|
37
|
+
return last;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Prepend the output-shaping instruction block to the prompt argument of `commandParts`. Returns a COPY
|
|
41
|
+
* (never mutates the input). When no prompt arg is found, or no policies match, returns `attached: false`
|
|
42
|
+
* with the original command + a reason — never silently shapes the wrong argument.
|
|
43
|
+
*/
|
|
44
|
+
export function attachOutputShapingToCommand(commandParts, opts = {}) {
|
|
45
|
+
const { instructions, applied } = buildOutputShapingPolicy(opts);
|
|
46
|
+
if (instructions === "" || applied.length === 0) {
|
|
47
|
+
return { commandParts: [...commandParts], applied: [], attached: false, reason: "no matching output-shaping policies" };
|
|
48
|
+
}
|
|
49
|
+
const idx = findPromptArgIndex(commandParts);
|
|
50
|
+
if (idx === -1) {
|
|
51
|
+
return { commandParts: [...commandParts], applied: [], attached: false, reason: "no prompt argument found to attach the output-shaping policy to" };
|
|
52
|
+
}
|
|
53
|
+
const next = [...commandParts];
|
|
54
|
+
next[idx] = `${instructions}\n\n${next[idx]}`;
|
|
55
|
+
return { commandParts: next, applied, attached: true };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=output-shaping-attach.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-shaping policy family — deterministic, rule-based (PUBLIC CLI/SDK code, engine-free).
|
|
3
|
+
*
|
|
4
|
+
* Design: `docs/design/output-shaping-policy-family.md` (accepted; implementation authorized 2026-06-29).
|
|
5
|
+
* This is **increment 1**: the public/free deterministic policies that produce a CONTENT-FREE
|
|
6
|
+
* output-shaping **instruction block** to attach to a request **BEFORE generation** (the only mechanism
|
|
7
|
+
* that can reduce provider output tokens — post-generation processing does NOT).
|
|
8
|
+
*
|
|
9
|
+
* HONESTY (binding):
|
|
10
|
+
* - This module produces INSTRUCTIONS + attribution labels only. It computes **NO output-savings number**
|
|
11
|
+
* and makes **NO claim** that output was reduced. An output-savings number requires (separately, in the
|
|
12
|
+
* private/proprietary engine) a **measured A/B** (provider-reported output; N≥3/arm, ±2·SE excludes
|
|
13
|
+
* zero) **AND** the **eval-gated short-but-sufficient** check. Until both pass, savings are unavailable.
|
|
14
|
+
* - The short-but-sufficient **eval** (policy #6 in the design) is the **private eval-gated verification**
|
|
15
|
+
* — it is NOT in this public module; this module only emits the shaping instructions + their attribution.
|
|
16
|
+
* - Content-free: instructions are generic shaping text; no prompt/completion/trace content.
|
|
17
|
+
*/
|
|
18
|
+
export type RiskLevel = "low" | "medium" | "high";
|
|
19
|
+
/** Content-free attribution for a recorded run (matches the control-plane policy_* columns). */
|
|
20
|
+
export interface OutputShapingAttribution {
|
|
21
|
+
policy_name: string;
|
|
22
|
+
policy_family: "output_shaping";
|
|
23
|
+
risk_level: RiskLevel;
|
|
24
|
+
}
|
|
25
|
+
export interface OutputShapingPolicy extends OutputShapingAttribution {
|
|
26
|
+
/** Human-readable description (what the policy instructs — never content). */
|
|
27
|
+
description: string;
|
|
28
|
+
/** Whether the policy is part of the default rule-based set. */
|
|
29
|
+
defaultOn: boolean;
|
|
30
|
+
/** Builds the instruction line(s). `verbosityBudgetTokens` is used only by the verbosity-budget policy. */
|
|
31
|
+
instruction(verbosityBudgetTokens?: number): string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The deterministic rule-based output-shaping policies (the public/free tier). The learned tier and the
|
|
35
|
+
* eval-gated verification are private/proprietary (Track A) — not here.
|
|
36
|
+
*/
|
|
37
|
+
export declare const OUTPUT_SHAPING_POLICIES: readonly OutputShapingPolicy[];
|
|
38
|
+
/** Note attached to every emitted block: this is a request-shaping instruction, not a savings claim. */
|
|
39
|
+
export declare const OUTPUT_SHAPING_HONESTY_NOTE: string;
|
|
40
|
+
export interface OutputShapingResult {
|
|
41
|
+
/** The content-free instruction block to prepend to the request/system prompt BEFORE generation. */
|
|
42
|
+
instructions: string;
|
|
43
|
+
/** Attribution for the applied policies (content-free; for recording / reporting). */
|
|
44
|
+
applied: OutputShapingAttribution[];
|
|
45
|
+
}
|
|
46
|
+
export interface BuildOutputShapingOptions {
|
|
47
|
+
/** Policy names to apply; defaults to the `defaultOn` set. Unknown names are ignored (never invented). */
|
|
48
|
+
policies?: string[];
|
|
49
|
+
/** Soft verbosity budget in output tokens (used by the verbosity-budget policy). */
|
|
50
|
+
verbosityBudgetTokens?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build the content-free output-shaping instruction block + attribution from the deterministic family.
|
|
54
|
+
* Produces instructions only — NO savings number, NO claim of reduction.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildOutputShapingPolicy(opts?: BuildOutputShapingOptions): OutputShapingResult;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-shaping policy family — deterministic, rule-based (PUBLIC CLI/SDK code, engine-free).
|
|
3
|
+
*
|
|
4
|
+
* Design: `docs/design/output-shaping-policy-family.md` (accepted; implementation authorized 2026-06-29).
|
|
5
|
+
* This is **increment 1**: the public/free deterministic policies that produce a CONTENT-FREE
|
|
6
|
+
* output-shaping **instruction block** to attach to a request **BEFORE generation** (the only mechanism
|
|
7
|
+
* that can reduce provider output tokens — post-generation processing does NOT).
|
|
8
|
+
*
|
|
9
|
+
* HONESTY (binding):
|
|
10
|
+
* - This module produces INSTRUCTIONS + attribution labels only. It computes **NO output-savings number**
|
|
11
|
+
* and makes **NO claim** that output was reduced. An output-savings number requires (separately, in the
|
|
12
|
+
* private/proprietary engine) a **measured A/B** (provider-reported output; N≥3/arm, ±2·SE excludes
|
|
13
|
+
* zero) **AND** the **eval-gated short-but-sufficient** check. Until both pass, savings are unavailable.
|
|
14
|
+
* - The short-but-sufficient **eval** (policy #6 in the design) is the **private eval-gated verification**
|
|
15
|
+
* — it is NOT in this public module; this module only emits the shaping instructions + their attribution.
|
|
16
|
+
* - Content-free: instructions are generic shaping text; no prompt/completion/trace content.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The deterministic rule-based output-shaping policies (the public/free tier). The learned tier and the
|
|
20
|
+
* eval-gated verification are private/proprietary (Track A) — not here.
|
|
21
|
+
*/
|
|
22
|
+
export const OUTPUT_SHAPING_POLICIES = [
|
|
23
|
+
{
|
|
24
|
+
policy_name: "concise_response",
|
|
25
|
+
policy_family: "output_shaping",
|
|
26
|
+
risk_level: "low",
|
|
27
|
+
defaultOn: true,
|
|
28
|
+
description: "Ask for a concise answer where the task allows (no padding, no restating the prompt).",
|
|
29
|
+
instruction: () => "Answer concisely: omit preamble and restatement of the request; give only what the task needs."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
policy_name: "verbosity_budget",
|
|
33
|
+
policy_family: "output_shaping",
|
|
34
|
+
risk_level: "low",
|
|
35
|
+
defaultOn: true,
|
|
36
|
+
description: "Set a soft target on response length (a budget, not a hard cap).",
|
|
37
|
+
instruction: (budget) => typeof budget === "number" && budget > 0
|
|
38
|
+
? `Aim to stay within roughly ${budget} output tokens; prioritize the most task-relevant content if space is tight.`
|
|
39
|
+
: "Keep the response as short as is sufficient for the task; do not pad to fill space."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
policy_name: "structured_output_constraints",
|
|
43
|
+
policy_family: "output_shaping",
|
|
44
|
+
risk_level: "low",
|
|
45
|
+
defaultOn: true,
|
|
46
|
+
description: "Prefer a bounded, structured format (lists/sections) that reduces filler.",
|
|
47
|
+
instruction: () => "Prefer a tight structured format (short bullets or labeled sections) over long prose where it fits the task."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
policy_name: "redundant_chatter_suppression",
|
|
51
|
+
policy_family: "output_shaping",
|
|
52
|
+
risk_level: "low",
|
|
53
|
+
defaultOn: true,
|
|
54
|
+
description: "Suppress boilerplate, preamble, apologies, and repetition.",
|
|
55
|
+
instruction: () => "Skip boilerplate, apologies, and repetition; do not summarize what you just said."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
policy_name: "safe_tool_output_filtering",
|
|
59
|
+
policy_family: "output_shaping",
|
|
60
|
+
// Slightly higher risk: trimming tool output can drop detail, so it is OFF by default and only
|
|
61
|
+
// requests omission of clearly-redundant tool echo — recoverability of task-critical detail first.
|
|
62
|
+
risk_level: "medium",
|
|
63
|
+
defaultOn: false,
|
|
64
|
+
description: "Request omission of clearly-redundant tool-output echo (recoverability preserved first).",
|
|
65
|
+
instruction: () => "Do not echo large tool outputs verbatim; reference them and include only the task-relevant lines."
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
/** Note attached to every emitted block: this is a request-shaping instruction, not a savings claim. */
|
|
69
|
+
export const OUTPUT_SHAPING_HONESTY_NOTE = "Attach BEFORE generation (post-generation processing does not reduce provider output tokens). " +
|
|
70
|
+
"No output-token savings are claimed: a savings figure requires a measured before/after (provider-reported) " +
|
|
71
|
+
"and eval-confirmed sufficiency — neither is asserted here.";
|
|
72
|
+
/**
|
|
73
|
+
* Build the content-free output-shaping instruction block + attribution from the deterministic family.
|
|
74
|
+
* Produces instructions only — NO savings number, NO claim of reduction.
|
|
75
|
+
*/
|
|
76
|
+
export function buildOutputShapingPolicy(opts = {}) {
|
|
77
|
+
const selected = opts.policies && opts.policies.length > 0
|
|
78
|
+
? OUTPUT_SHAPING_POLICIES.filter((p) => opts.policies.includes(p.policy_name))
|
|
79
|
+
: OUTPUT_SHAPING_POLICIES.filter((p) => p.defaultOn);
|
|
80
|
+
const lines = selected.map((p) => `- ${p.instruction(opts.verbosityBudgetTokens)}`);
|
|
81
|
+
const instructions = selected.length === 0
|
|
82
|
+
? ""
|
|
83
|
+
: ["Output-shaping policy (apply to your response):", ...lines].join("\n");
|
|
84
|
+
return {
|
|
85
|
+
instructions,
|
|
86
|
+
applied: selected.map((p) => ({ policy_name: p.policy_name, policy_family: p.policy_family, risk_level: p.risk_level }))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=output-shaping.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { COMPACTION_POLICY_NAME, SKILL_INJECTION_POLICY_NAME, SUPPORTED_COMPACTION_POLICY_NAMES, isSupportedCompactionPolicyName } from "./policy-types.js";
|
|
2
|
+
import type { PolicyMiddlewareStateCapsule, ReplacedRange, ReplacementMode, SkillInjectionProvenance, SourcePointer, SupportedCompactionPolicyName } from "./policy-types.js";
|
|
3
|
+
import type { AgentTrace, CostEstimate, TokenEstimate, TraceMessage } from "./types.js";
|
|
4
|
+
export { COMPACTION_POLICY_NAME, SKILL_INJECTION_POLICY_NAME, SUPPORTED_COMPACTION_POLICY_NAMES, isSupportedCompactionPolicyName };
|
|
5
|
+
export type { PolicyMiddlewareStateCapsule, ReplacedRange, ReplacementMode, SkillInjectionProvenance, SourcePointer, SupportedCompactionPolicyName };
|
|
6
|
+
export interface CompactionPolicyThresholds {
|
|
7
|
+
minDuplicateTokens?: number;
|
|
8
|
+
minDuplicateCharacters?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface CompactionPolicyConfig {
|
|
11
|
+
policyName?: typeof COMPACTION_POLICY_NAME;
|
|
12
|
+
}
|
|
13
|
+
export interface PolicyMiddlewareInput {
|
|
14
|
+
trace?: AgentTrace;
|
|
15
|
+
messages?: TraceMessage[];
|
|
16
|
+
model?: string;
|
|
17
|
+
policy?: CompactionPolicyConfig;
|
|
18
|
+
thresholds?: CompactionPolicyThresholds;
|
|
19
|
+
/**
|
|
20
|
+
* APPROVAL-GATED (design §4). When true, ALSO apply the second policy
|
|
21
|
+
* `repeated_skill_injection_to_state_capsule`, compacting byte-identical same-skill later
|
|
22
|
+
* role:user skill injections into state capsules pointing at the first copy. DEFAULT false:
|
|
23
|
+
* with no approval this run compacts NO skill injections. This flag is set ONLY on explicit
|
|
24
|
+
* approval in optimization-approval.ts. There is NO auto-apply path that sets it unattended.
|
|
25
|
+
*/
|
|
26
|
+
compactSkillInjections?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface PolicyMiddlewareResult {
|
|
29
|
+
compactedTrace?: AgentTrace;
|
|
30
|
+
compactedMessages: TraceMessage[];
|
|
31
|
+
stateCapsules: PolicyMiddlewareStateCapsule[];
|
|
32
|
+
compactedMessageIds: string[];
|
|
33
|
+
tokenEstimateBefore: TokenEstimate;
|
|
34
|
+
tokenEstimateAfter: TokenEstimate;
|
|
35
|
+
tokensSaved: number;
|
|
36
|
+
costEstimateBefore: CostEstimate;
|
|
37
|
+
costEstimateAfter: CostEstimate;
|
|
38
|
+
savingsEstimate: number;
|
|
39
|
+
appliedPolicyName: typeof COMPACTION_POLICY_NAME;
|
|
40
|
+
/**
|
|
41
|
+
* Names of every policy that compacted at least one message in this run (design §Q1
|
|
42
|
+
* sequential composition). Always includes the tool-output policy name; additionally
|
|
43
|
+
* includes the skill-injection policy name when `compactSkillInjections` was enabled
|
|
44
|
+
* AND it compacted ≥1 byte-identical injection. Used by the safety report's
|
|
45
|
+
* supported-policy-set checks. Default (no approval) runs contain only the tool-output name.
|
|
46
|
+
*/
|
|
47
|
+
appliedPolicyNames: SupportedCompactionPolicyName[];
|
|
48
|
+
/** State capsules produced by the skill-injection policy only (disjoint from tool-output). */
|
|
49
|
+
skillInjectionCapsules: PolicyMiddlewareStateCapsule[];
|
|
50
|
+
safetyNotes: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error thrown when the provenance-completeness write-time invariant is violated.
|
|
54
|
+
* See `docs/ops/provenance-completeness-invariant.md`.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ProvenanceCompletenessError extends Error {
|
|
57
|
+
constructor(message: string);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Provenance-completeness write-boundary guard (design: docs/ops/provenance-completeness-invariant.md).
|
|
61
|
+
*
|
|
62
|
+
* Invariant: `compaction_changed_content ⟹ provenance_present`. The "content actually
|
|
63
|
+
* changed" trigger is derived by comparing each original message's payload against the final
|
|
64
|
+
* compacted payload (`diffMessagesByContent`) — NOT from `compactedMessageIds` collected
|
|
65
|
+
* from the capsules. Deriving the trigger from the capsules is tautological: if a regressed
|
|
66
|
+
* path drops/empties the capsule provenance while STILL replacing message content, the
|
|
67
|
+
* capsule-derived id set goes empty and the guard would no-op, missing exactly the
|
|
68
|
+
* changed-but-zero-provenance case it exists to reject. Comparing real content instead means a
|
|
69
|
+
* replaced message is "changed" regardless of whether any capsule exists for it.
|
|
70
|
+
*
|
|
71
|
+
* When content changed (by either real content change OR a capsule-claimed compacted id — the
|
|
72
|
+
* union, so no existing check is weakened), EVERY changed message id MUST have a covering state
|
|
73
|
+
* capsule, AND EVERY changed message MUST carry `metadata.compaction` with a resolvable
|
|
74
|
+
* `rawSourcePointer` + content hash (mirrors the required recoverability checks in
|
|
75
|
+
* safety-report.ts). On violation, FAIL CLOSED: throw, so a changed-but-zero/incomplete-provenance
|
|
76
|
+
* result can never be emitted as a silent no-op the eval harness cannot distinguish from a true no-op.
|
|
77
|
+
*
|
|
78
|
+
* The content-change comparison runs over `max(original.length, compacted.length)` positions
|
|
79
|
+
* (`diffMessagesByContent`) and enforces the COMPLETE honest-writer invariant: a 1:1 id-preserving
|
|
80
|
+
* positional map of original→compacted. A position with the SAME id whose content was replaced is a
|
|
81
|
+
* coverable change; ANYTHING that is not 1:1 id-preserving is a STRUCTURAL divergence that can carry
|
|
82
|
+
* no covering provenance and fails the guard closed — a DROPPED position (original present, compacted
|
|
83
|
+
* absent), an ADDED position (compacted present, original absent), AND a same-length position whose
|
|
84
|
+
* compacted id differs from the original id at that slot (a swap/substitution/duplication, i.e. one
|
|
85
|
+
* original dropped and a different id inserted at equal length). The last case is rejected BEFORE the
|
|
86
|
+
* id-based checks precisely because a capsule/metadata that exists for the DISPLACING id sources from
|
|
87
|
+
* a different original and can never make the displaced original recoverable. This closes the
|
|
88
|
+
* previously-missed cases where a regressed writer silently DROPS a message, or keeps the array length
|
|
89
|
+
* while substituting/duplicating an id, and the surviving capsules/ids happen to cover the wrong id.
|
|
90
|
+
*
|
|
91
|
+
* This is a NO-OP for every honest run: the shipped writer co-produces a covering capsule and
|
|
92
|
+
* `metadata.compaction` (with rawSourcePointer + contentSha256) for every compacted span, so
|
|
93
|
+
* every content-changed id is fully covered. The guard only fires on a regressed/malformed/
|
|
94
|
+
* hand-constructed path. `originalMessages` is optional for backward compatibility; when omitted,
|
|
95
|
+
* the guard falls back to the capsule-derived ids alone (the pre-hardening behavior).
|
|
96
|
+
*
|
|
97
|
+
* OCCURRENCE-AWARE COVERAGE — HASH BINDING (closes the shared-id residual, 2026-06-15). Message
|
|
98
|
+
* ids are NON-UNIQUE (a Claude turn's text + tool_use blocks share one id). A malformed
|
|
99
|
+
* same-length output can DROP one shared-id position and DUPLICATE the other into its slot: every
|
|
100
|
+
* position still has a matching id (the structural check passes, since both share the id), and the
|
|
101
|
+
* displaced position carries the duplicate's valid metadata, so an id-keyed coverage check is
|
|
102
|
+
* satisfied by a capsule belonging to a DIFFERENT occurrence while the displaced original stays
|
|
103
|
+
* unrecoverable. Merely COUNTING distinct covering capsules per id is ALSO insufficient (Codex P2,
|
|
104
|
+
* PR #280): a malformed output can keep BOTH shared-id capsules present (count satisfied) yet
|
|
105
|
+
* duplicate one occurrence's compacted message + metadata into the other's slot. To close this,
|
|
106
|
+
* when `originalMessages` is provided the coverage is matched per OCCURRENCE BY ORIGINAL-CONTENT
|
|
107
|
+
* HASH: each changed POSITION p (index + id, original payload P_orig) is bound to a DISTINCT,
|
|
108
|
+
* not-yet-consumed capsule whose `sourcePointer.contentSha256` hashes to the content that capsule
|
|
109
|
+
* replaced at p (whole P_orig for a whole-message capsule, a replaced sub-range of P_orig for an
|
|
110
|
+
* embedded one), AND p's OWN `metadata.compaction` pointer must hash to that same original. A
|
|
111
|
+
* shared-id duplication fails because the displaced slot's pointer hashes to a DIFFERENT
|
|
112
|
+
* occurrence's payload, not to its own original, so it FAILS CLOSED; an honest duplicate-id
|
|
113
|
+
* compaction (two messages sharing an id, each its own hash-correct capsule) still PASSES. The
|
|
114
|
+
* honest writer emits, per compacted target, exactly one capsule + metadata whose contentSha256 is
|
|
115
|
+
* that target's original payload hash, so every changed position binds 1:1 — a true no-op. (Binding
|
|
116
|
+
* is by original-content HASH, not by `sourcePointer.messageIndex`, which records the SOURCE payload
|
|
117
|
+
* index, not the changed/target position — an index match would break honest whole-message/embedded
|
|
118
|
+
* runs.)
|
|
119
|
+
*/
|
|
120
|
+
export declare function assertProvenanceCompleteness(compactedMessages: TraceMessage[], stateCapsules: PolicyMiddlewareStateCapsule[], compactedMessageIds: string[], originalMessages?: TraceMessage[]): void;
|
|
121
|
+
export declare function applyCompactionPolicy(input: PolicyMiddlewareInput): PolicyMiddlewareResult;
|