agentwrangler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +116 -0
- package/dist/apply/jobs.js +429 -0
- package/dist/apply/open-terminal-child.mjs +98 -0
- package/dist/apply/open-terminal.js +221 -0
- package/dist/apply/settings-gen.js +35 -0
- package/dist/cli/agentwrangler.js +18 -0
- package/dist/daemon/config.js +51 -0
- package/dist/daemon/http.js +258 -0
- package/dist/daemon/index.js +372 -0
- package/dist/daemon/outcomes-pass.js +82 -0
- package/dist/daemon/readiness.js +15 -0
- package/dist/daemon/router.js +756 -0
- package/dist/daemon/static.js +146 -0
- package/dist/db/migrate.js +72 -0
- package/dist/db/migrations/001_observe.sql +196 -0
- package/dist/db/migrations/002_indexes.sql +6 -0
- package/dist/db/migrations/003_context_inventory_history.sql +20 -0
- package/dist/db/migrations/004_apply_jobs.sql +17 -0
- package/dist/db/migrations/005_tool_event_metadata.sql +17 -0
- package/dist/db/migrations/006_d7_query_indexes.sql +9 -0
- package/dist/db/migrations/007_work_item_branch_keys.sql +11 -0
- package/dist/db/migrations/008_thinking_tokens.sql +1 -0
- package/dist/db/migrations/009_user_turn_count.sql +1 -0
- package/dist/db/migrations/010_workspace_cwd.sql +1 -0
- package/dist/db/migrations/011_reports.sql +1 -0
- package/dist/db/migrations/012_reconcile_indexes.sql +2 -0
- package/dist/db/migrations/013_friction_fields.sql +5 -0
- package/dist/db/migrations/014_session_churn.sql +11 -0
- package/dist/db/migrations/015_gap_aggregates.sql +6 -0
- package/dist/db/open.js +30 -0
- package/dist/detector/benchmark-anchors.js +36 -0
- package/dist/detector/calibration.js +302 -0
- package/dist/detector/context-history-retention.js +312 -0
- package/dist/detector/context-probe.js +574 -0
- package/dist/detector/d1-source-identity.js +25 -0
- package/dist/detector/detectors/d10_catalog_footprint.js +146 -0
- package/dist/detector/detectors/d1_ctx_always_loaded.js +203 -0
- package/dist/detector/detectors/d2_session_long_full_context.js +119 -0
- package/dist/detector/detectors/d4_model_mismatch.js +258 -0
- package/dist/detector/detectors/d5_limit_burn_forecast.js +138 -0
- package/dist/detector/detectors/d6_tool_result_bloat.js +301 -0
- package/dist/detector/detectors/d7_loop_retry_waste.js +345 -0
- package/dist/detector/detectors/d8_cache_write_churn.js +201 -0
- package/dist/detector/detectors/d9_idle_background_session.js +101 -0
- package/dist/detector/engine.js +88 -0
- package/dist/detector/index.js +17 -0
- package/dist/detector/measurement.js +426 -0
- package/dist/detector/practice-registry.js +259 -0
- package/dist/detector/registry.js +32 -0
- package/dist/detector/savings.js +249 -0
- package/dist/detector/types.js +14 -0
- package/dist/evidence/common/approved-input.js +632 -0
- package/dist/evidence/common/boundary.js +84 -0
- package/dist/evidence/common/canonical.js +55 -0
- package/dist/evidence/common/redaction.js +321 -0
- package/dist/evidence/common/sqlite.js +25 -0
- package/dist/evidence/common/state.js +29 -0
- package/dist/evidence/cond1/cli.js +289 -0
- package/dist/evidence/cond1/packet.js +407 -0
- package/dist/evidence/cond1/prepare.js +295 -0
- package/dist/evidence/cond1/score.js +349 -0
- package/dist/evidence/cond1/types.js +1 -0
- package/dist/evidence/create-approval.js +365 -0
- package/dist/evidence/create-scratch.js +542 -0
- package/dist/evidence/d7/cli.js +113 -0
- package/dist/evidence/d7/measure.js +193 -0
- package/dist/evidence/d7/types.js +1 -0
- package/dist/evidence/discover-approval.js +492 -0
- package/dist/evidence/g2/adjudicate.js +20 -0
- package/dist/evidence/g2/cli.js +207 -0
- package/dist/evidence/g2/kappa.js +39 -0
- package/dist/evidence/g2/pipeline.js +92 -0
- package/dist/evidence/g2/store.js +14 -0
- package/dist/evidence/github/client.js +1 -0
- package/dist/evidence/github/gh-cli-client.js +301 -0
- package/dist/evidence/r3/cli.js +209 -0
- package/dist/evidence/r3/evaluate.js +417 -0
- package/dist/evidence/r3/packet.js +162 -0
- package/dist/evidence/r3/prepare.js +405 -0
- package/dist/evidence/r3/score.js +341 -0
- package/dist/evidence/r3/transcript.js +155 -0
- package/dist/evidence/r3/types.js +4 -0
- package/dist/hook/context-budget-hook.mjs +138 -0
- package/dist/hook/danger-guard-denylist.json +27 -0
- package/dist/hook/danger-guard-hook.mjs +167 -0
- package/dist/hook/install.js +0 -0
- package/dist/hook/limit-burn-hook.mjs +127 -0
- package/dist/hook/loop-guard-hook.mjs +104 -0
- package/dist/hook/precompact-checkpoint-hook.mjs +123 -0
- package/dist/ingest/churn-collector.js +122 -0
- package/dist/ingest/detector-hook.js +52 -0
- package/dist/ingest/discovery.js +207 -0
- package/dist/ingest/health.js +43 -0
- package/dist/ingest/index.js +28 -0
- package/dist/ingest/ingestor.js +509 -0
- package/dist/ingest/parser.js +344 -0
- package/dist/ingest/pricing.js +153 -0
- package/dist/ingest/reconcile.js +52 -0
- package/dist/ingest/tail.js +152 -0
- package/dist/ingest/types.js +24 -0
- package/dist/ingest/workspace-mapping.js +114 -0
- package/dist/oauth/anthropic-api-key.js +88 -0
- package/dist/oauth/count-tokens.js +86 -0
- package/dist/oauth/credentials.js +171 -0
- package/dist/oauth/judge-g2-client.js +154 -0
- package/dist/oauth/usage.js +167 -0
- package/dist/outcomes/branch-key.js +49 -0
- package/dist/outcomes/conclusions.js +45 -0
- package/dist/outcomes/derive.js +94 -0
- package/dist/outcomes/finding-extractors.js +131 -0
- package/dist/outcomes/findings.js +237 -0
- package/dist/outcomes/github/client.js +367 -0
- package/dist/outcomes/github/credential.js +195 -0
- package/dist/outcomes/github/gh-cli-client.js +340 -0
- package/dist/outcomes/linker.js +486 -0
- package/dist/outcomes/pool.js +24 -0
- package/dist/outcomes/sync.js +276 -0
- package/dist/query/api/agents-liveness.js +182 -0
- package/dist/query/api/burn-status.js +50 -0
- package/dist/query/api/context-budget.js +114 -0
- package/dist/query/api/context-composition.js +67 -0
- package/dist/query/api/cost-per-success.js +104 -0
- package/dist/query/api/delivery.js +92 -0
- package/dist/query/api/effectiveness.js +254 -0
- package/dist/query/api/efficiency-headroom.js +74 -0
- package/dist/query/api/headroom-trend.js +105 -0
- package/dist/query/api/hook-config.js +75 -0
- package/dist/query/api/hook-install.js +8 -0
- package/dist/query/api/hot-sessions.js +17 -0
- package/dist/query/api/idle-sessions.js +52 -0
- package/dist/query/api/index.js +40 -0
- package/dist/query/api/loop-guard.js +90 -0
- package/dist/query/api/offload-share.js +41 -0
- package/dist/query/api/outcomes.js +218 -0
- package/dist/query/api/overview.js +535 -0
- package/dist/query/api/rec-prompt.js +138 -0
- package/dist/query/api/recommendations-ledger.js +111 -0
- package/dist/query/api/recommendations.js +514 -0
- package/dist/query/api/reports.js +78 -0
- package/dist/query/api/self-churn.js +77 -0
- package/dist/query/api/self-percentiles.js +109 -0
- package/dist/query/api/session-drivers.js +153 -0
- package/dist/query/api/settings.js +85 -0
- package/dist/query/api/spend-flavor.js +234 -0
- package/dist/query/api/trends.js +155 -0
- package/dist/query/cap-weighted.js +119 -0
- package/dist/query/db-context.js +42 -0
- package/dist/query/envelope.js +71 -0
- package/dist/query/forecast.js +191 -0
- package/dist/query/settings-store.js +441 -0
- package/dist/query/spend.js +171 -0
- package/dist/query/trends.js +194 -0
- package/dist/ui/assets/index-DnRKgc21.css +1 -0
- package/dist/ui/assets/index-h1Q1wWq5.js +168 -0
- package/dist/ui/index.html +39 -0
- package/package.json +59 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/detector/registry.ts — the registered Tier-1 detectors.
|
|
3
|
+
*
|
|
4
|
+
* Evaluated detectors fire recommendations; UNEVALUATED_DETECTORS carry a fixed
|
|
5
|
+
* status/note so the query layer can report them honestly in the detectors[]
|
|
6
|
+
* strip. Ids are reconciled to engine-spec §2.0 (runtime ids frozen for anything
|
|
7
|
+
* that has fired — D1/D2/D4/D5). Spec-D3 is RETIRED (its routing concept is
|
|
8
|
+
* realized at runtime D4); its id must not be reused.
|
|
9
|
+
*/
|
|
10
|
+
import { d1Detector } from "./detectors/d1_ctx_always_loaded.js";
|
|
11
|
+
import { d2Detector } from "./detectors/d2_session_long_full_context.js";
|
|
12
|
+
import { d4Detector } from "./detectors/d4_model_mismatch.js";
|
|
13
|
+
import { d5Detector } from "./detectors/d5_limit_burn_forecast.js";
|
|
14
|
+
import { d6Detector } from "./detectors/d6_tool_result_bloat.js";
|
|
15
|
+
import { d7Detector } from "./detectors/d7_loop_retry_waste.js";
|
|
16
|
+
import { d8Detector } from "./detectors/d8_cache_write_churn.js";
|
|
17
|
+
import { d9Detector } from "./detectors/d9_idle_background_session.js";
|
|
18
|
+
import { d10Detector } from "./detectors/d10_catalog_footprint.js";
|
|
19
|
+
/** The detectors this engine evaluates and can fire. */
|
|
20
|
+
export const DETECTORS = [
|
|
21
|
+
d1Detector,
|
|
22
|
+
d2Detector,
|
|
23
|
+
d4Detector,
|
|
24
|
+
d5Detector,
|
|
25
|
+
d6Detector,
|
|
26
|
+
d7Detector,
|
|
27
|
+
d8Detector,
|
|
28
|
+
d9Detector,
|
|
29
|
+
d10Detector,
|
|
30
|
+
];
|
|
31
|
+
/** Detectors catalogued but not evaluated, with their fixed live status. */
|
|
32
|
+
export const UNEVALUATED_DETECTORS = [];
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/detector/savings.ts — modeled_formula_json builders + µUSD helpers.
|
|
3
|
+
*
|
|
4
|
+
* Units: prices are $/MTok, which is numerically µUSD/token (matches
|
|
5
|
+
* ingest/pricing.ts). A token×price sum is therefore already in µUSD.
|
|
6
|
+
*/
|
|
7
|
+
/** Config knobs (defaults; §1). Unvalidated where noted. */
|
|
8
|
+
export const D1_TARGET_ALWAYS_LOADED_TOKENS = 40_000;
|
|
9
|
+
export const D2_REDUCTION_FRACTION = 0.33; // UNVALIDATED assumption-default (§1 D2 (c) / Review F1)
|
|
10
|
+
export const D2_MIN_QUALIFYING_SESSIONS = 3;
|
|
11
|
+
export const D2_TURN_COUNT_THRESHOLD = 150;
|
|
12
|
+
export const D2_AVG_CONTEXT_THRESHOLD = 180_000;
|
|
13
|
+
export const D1_ALWAYS_LOADED_OVERSIZE_TOKENS = 40_000;
|
|
14
|
+
export const D1_ALWAYS_LOADED_OVERSIZE_FRACTION = 0.25;
|
|
15
|
+
/**
|
|
16
|
+
* Per-source token targets (UNVALIDATED defaults — labeled as such in every rec).
|
|
17
|
+
* Surfaced as visible formula inputs (mirrors D2's reduction_fraction treatment).
|
|
18
|
+
*
|
|
19
|
+
* | component | target | rationale |
|
|
20
|
+
* |-------------|---------|--------------------------------------------------------|
|
|
21
|
+
* | CLAUDE_MD | 2,000 | keep current-state + pointers; move changelog/history |
|
|
22
|
+
* | MEMORY | 1,000 | keep active facts; prune stale/duplicate memories |
|
|
23
|
+
* | MCP_SCHEMAS | 3,000 | keep actively-used skills; disable rarely-used plugins |
|
|
24
|
+
*/
|
|
25
|
+
export const D1_SOURCE_TARGETS = {
|
|
26
|
+
CLAUDE_MD: 2_000,
|
|
27
|
+
MEMORY: 1_000,
|
|
28
|
+
MCP_SCHEMAS: 3_000,
|
|
29
|
+
};
|
|
30
|
+
/** Round a micro-USD figure to an integer (INTEGER column). */
|
|
31
|
+
export function roundU(u) {
|
|
32
|
+
return Math.round(u);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* D1 always-loaded-trim savings.
|
|
36
|
+
* Δcontext_tokens × turns_per_week / 1e6 × cache_read_price_usd_per_mtok (USD/wk),
|
|
37
|
+
* returned as integer µUSD/wk.
|
|
38
|
+
*/
|
|
39
|
+
export function d1Savings(deltaContextTokens, turnsPerWeek, cacheReadPriceUsdPerMtok) {
|
|
40
|
+
const savingsU = roundU(deltaContextTokens * turnsPerWeek * cacheReadPriceUsdPerMtok);
|
|
41
|
+
const formula = {
|
|
42
|
+
model: "D1_ALWAYS_LOADED_TRIM_V1",
|
|
43
|
+
inputs: {
|
|
44
|
+
delta_context_tokens: deltaContextTokens,
|
|
45
|
+
turns_per_week: turnsPerWeek,
|
|
46
|
+
cache_read_price_usd_per_mtok: cacheReadPriceUsdPerMtok,
|
|
47
|
+
},
|
|
48
|
+
expression: "delta_context_tokens * turns_per_week / 1e6 * cache_read_price_usd_per_mtok",
|
|
49
|
+
result_usd_per_wk: Number((savingsU / 1e6).toFixed(2)),
|
|
50
|
+
};
|
|
51
|
+
return { savingsU, formula };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* D2 long-context cache-read savings.
|
|
55
|
+
* cache_read_tokens_per_week / 1e6 × blended_cache_read_price × reduction_fraction (USD/wk),
|
|
56
|
+
* returned as integer µUSD/wk. The blended price is derived per-model from the
|
|
57
|
+
* turns' own pricing snapshots (§1 D2 (b)); reduction_fraction is surfaced as a
|
|
58
|
+
* visible, labeled input (§1 D2 (c) / Review F1).
|
|
59
|
+
*/
|
|
60
|
+
export function d2Savings(cacheReadTokensPerWeek, cacheReadSpendUPerWeek, reductionFraction) {
|
|
61
|
+
const blendedPrice = cacheReadTokensPerWeek > 0 ? cacheReadSpendUPerWeek / cacheReadTokensPerWeek : 0;
|
|
62
|
+
const savingsU = roundU(cacheReadSpendUPerWeek * reductionFraction);
|
|
63
|
+
const formula = {
|
|
64
|
+
model: "D2_LONG_CONTEXT_CACHE_READ_V1",
|
|
65
|
+
inputs: {
|
|
66
|
+
cache_read_tokens_per_week: cacheReadTokensPerWeek,
|
|
67
|
+
cache_read_price_usd_per_mtok: Number(blendedPrice.toFixed(6)),
|
|
68
|
+
reduction_fraction: reductionFraction,
|
|
69
|
+
},
|
|
70
|
+
expression: "cache_read_tokens_per_week / 1e6 * cache_read_price_usd_per_mtok * reduction_fraction",
|
|
71
|
+
result_usd_per_wk: Number((savingsU / 1e6).toFixed(2)),
|
|
72
|
+
};
|
|
73
|
+
return { savingsU, formula };
|
|
74
|
+
}
|
|
75
|
+
/** D5 warning-class formula (no savings model). */
|
|
76
|
+
export function d5Formula() {
|
|
77
|
+
return { model: "none", inputs: {}, kind: "WARNING" };
|
|
78
|
+
}
|
|
79
|
+
// ── D4 MODEL_MISMATCH config ──────────────────────────────────────────────────
|
|
80
|
+
/**
|
|
81
|
+
* Minimum Opus turns in the trailing window before D4 evaluates.
|
|
82
|
+
* Prevents false positives from sparse/cold data.
|
|
83
|
+
*/
|
|
84
|
+
export const D4_OPUS_MIN_TURNS = 5;
|
|
85
|
+
/**
|
|
86
|
+
* Fraction of Opus turns that must match the "high-context, low-output" pattern
|
|
87
|
+
* before a rec fires. 40% means the pattern is sustained, not incidental.
|
|
88
|
+
* CONSERVATIVE: some Opus use is legitimately complex even when output is brief.
|
|
89
|
+
*/
|
|
90
|
+
export const D4_MISMATCH_MIN_FRACTION = 0.4;
|
|
91
|
+
/**
|
|
92
|
+
* Minimum context_tokens (input + cache_read + cache_write) on a qualifying Opus
|
|
93
|
+
* turn. 50 K is well below Opus-4-8's 155 K observed average but excludes trivially
|
|
94
|
+
* small turns. UNVALIDATED default — labeled in every rec.
|
|
95
|
+
*/
|
|
96
|
+
export const D4_MISMATCH_MIN_CONTEXT_TOKENS = 50_000;
|
|
97
|
+
/**
|
|
98
|
+
* Maximum output_tokens on a qualifying Opus turn. ≤500 tokens is "lookup-style"
|
|
99
|
+
* output — suggests the Opus model produced a short answer from a large context.
|
|
100
|
+
* UNVALIDATED default — labeled in every rec.
|
|
101
|
+
*/
|
|
102
|
+
export const D4_MISMATCH_MAX_OUTPUT_TOKENS = 500;
|
|
103
|
+
/**
|
|
104
|
+
* Fraction of flagged mismatch turns assumed movable to Sonnet.
|
|
105
|
+
* 0.5 is deliberately conservative — the high-context-low-output filter already
|
|
106
|
+
* narrows to likely-transferable turns, but some may carry genuine reasoning needs
|
|
107
|
+
* not visible from token counts alone.
|
|
108
|
+
* UNVALIDATED — surfaced as a visible, labeled formula input.
|
|
109
|
+
*/
|
|
110
|
+
export const D4_REDUCTION_FRACTION = 0.5;
|
|
111
|
+
/**
|
|
112
|
+
* D4 model-mismatch savings.
|
|
113
|
+
*
|
|
114
|
+
* Models the per-turn cost differential between Opus and Sonnet for qualifying
|
|
115
|
+
* "high context, low output" turns, then scales by mismatch_turns_per_week and
|
|
116
|
+
* the conservative reduction_fraction.
|
|
117
|
+
*
|
|
118
|
+
* Formula:
|
|
119
|
+
* per_turn_savings_u =
|
|
120
|
+
* avg_input_tokens × (opus_input_price − sonnet_input_price)
|
|
121
|
+
* + avg_output_tokens × (opus_output_price − sonnet_output_price)
|
|
122
|
+
* savings_u_per_wk = per_turn_savings_u × mismatch_turns_per_week × reduction_fraction
|
|
123
|
+
*
|
|
124
|
+
* All prices are $/MTok = µUSD/token (matches pricing.ts conventions).
|
|
125
|
+
* reduction_fraction is UNVALIDATED; surfaced as a labeled input (mirrors D2 treatment).
|
|
126
|
+
*/
|
|
127
|
+
export function d4Savings(avgInputTokens, avgOutputTokens, mismatchTurnsPerWeek, opusInputPriceUsdPerMtok, opusOutputPriceUsdPerMtok, sonnetInputPriceUsdPerMtok, sonnetOutputPriceUsdPerMtok, reductionFraction) {
|
|
128
|
+
const perTurnSavingsU = avgInputTokens * (opusInputPriceUsdPerMtok - sonnetInputPriceUsdPerMtok) +
|
|
129
|
+
avgOutputTokens * (opusOutputPriceUsdPerMtok - sonnetOutputPriceUsdPerMtok);
|
|
130
|
+
const savingsU = roundU(perTurnSavingsU * mismatchTurnsPerWeek * reductionFraction);
|
|
131
|
+
const formula = {
|
|
132
|
+
model: "D4_MODEL_MISMATCH_V1",
|
|
133
|
+
inputs: {
|
|
134
|
+
avg_input_tokens: avgInputTokens,
|
|
135
|
+
avg_output_tokens: avgOutputTokens,
|
|
136
|
+
mismatch_turns_per_week: mismatchTurnsPerWeek,
|
|
137
|
+
opus_input_price_usd_per_mtok: opusInputPriceUsdPerMtok,
|
|
138
|
+
opus_output_price_usd_per_mtok: opusOutputPriceUsdPerMtok,
|
|
139
|
+
sonnet_input_price_usd_per_mtok: sonnetInputPriceUsdPerMtok,
|
|
140
|
+
sonnet_output_price_usd_per_mtok: sonnetOutputPriceUsdPerMtok,
|
|
141
|
+
reduction_fraction: reductionFraction,
|
|
142
|
+
},
|
|
143
|
+
expression: "(avg_input_tokens * (opus_input_price - sonnet_input_price) + avg_output_tokens * (opus_output_price - sonnet_output_price)) * mismatch_turns_per_week * reduction_fraction",
|
|
144
|
+
result_usd_per_wk: Number((savingsU / 1e6).toFixed(2)),
|
|
145
|
+
};
|
|
146
|
+
return { savingsU, formula };
|
|
147
|
+
}
|
|
148
|
+
// ── D8 CACHE_WRITE_CHURN config (flagship; all UNVALIDATED defaults) ───────────
|
|
149
|
+
/** Cache-creation spike floor on a resume turn (cw5m + cw1h + cw_other). */
|
|
150
|
+
export const D8_CREATION_SPIKE_TOKENS = 50_000;
|
|
151
|
+
/** Idle-gap threshold (minutes) when the 5m tier dominates the creation. */
|
|
152
|
+
export const D8_TTL_5M_GAP_MIN = 5;
|
|
153
|
+
/** Idle-gap threshold (minutes) when the 1h tier dominates the creation. */
|
|
154
|
+
export const D8_TTL_1H_GAP_MIN = 60;
|
|
155
|
+
/** A resume turn is a full re-write (not a warm read) when cache_read < ratio × creation. */
|
|
156
|
+
export const D8_LOW_READ_RATIO = 0.2;
|
|
157
|
+
/** Fire the scope rec at ≥ this many churn events per week. */
|
|
158
|
+
export const D8_MIN_EVENTS = 3;
|
|
159
|
+
/** …OR when churn-event creation tokens are ≥ this share of the scope's cap-weighted total. */
|
|
160
|
+
export const D8_CREATION_SHARE = 0.15;
|
|
161
|
+
/** Fraction of churn creation assumed avoidable (some resumes are unavoidable). */
|
|
162
|
+
export const D8_AVOIDANCE_FRACTION = 0.7;
|
|
163
|
+
/** Annotate regime=5m when this share (or more) of creation is 5m-tier. */
|
|
164
|
+
export const D8_REGIME_5M_SHARE = 0.8;
|
|
165
|
+
/**
|
|
166
|
+
* D8 cache-write-churn savings (cap-weighted; never a raw-token headline).
|
|
167
|
+
* avoidable_cap_tokens/wk = total_churn_creation_tokens × avoidance_fraction,
|
|
168
|
+
* translated to µUSD/wk via the (blended) cache-write price.
|
|
169
|
+
*/
|
|
170
|
+
export function d8Savings(totalChurnCreationTokens, blendedWritePriceUsdPerMtok, avoidanceFraction) {
|
|
171
|
+
const avoidableCapTokens = totalChurnCreationTokens * avoidanceFraction;
|
|
172
|
+
const savingsU = roundU(avoidableCapTokens * blendedWritePriceUsdPerMtok);
|
|
173
|
+
const formula = {
|
|
174
|
+
model: "D8_CACHE_WRITE_CHURN_V1",
|
|
175
|
+
inputs: {
|
|
176
|
+
total_churn_creation_tokens: totalChurnCreationTokens,
|
|
177
|
+
avoidance_fraction: avoidanceFraction,
|
|
178
|
+
cache_write_price_usd_per_mtok: Number(blendedWritePriceUsdPerMtok.toFixed(6)),
|
|
179
|
+
avoidable_cap_tokens_per_wk: Math.round(avoidableCapTokens),
|
|
180
|
+
},
|
|
181
|
+
expression: "total_churn_creation_tokens * avoidance_fraction / 1e6 * cache_write_price_usd_per_mtok",
|
|
182
|
+
result_usd_per_wk: Number((savingsU / 1e6).toFixed(2)),
|
|
183
|
+
};
|
|
184
|
+
return { savingsU, formula };
|
|
185
|
+
}
|
|
186
|
+
// ── D6 TOOL_RESULT_BLOAT config (turn/session grain; all UNVALIDATED defaults) ─
|
|
187
|
+
/** Fire only when ≥ this many sessions/wk meet the per-session bloat criteria. */
|
|
188
|
+
export const D6_MIN_SESSIONS = 3;
|
|
189
|
+
/** tool_result_bytes must be ≥ this share of the session's cap-weighted total. */
|
|
190
|
+
export const D6_BLOAT_SHARE = 0.3;
|
|
191
|
+
/** …AND exceed this absolute floor, so tiny sessions never fire (200 KB). */
|
|
192
|
+
export const D6_ABS_FLOOR_BYTES = 200 * 1024;
|
|
193
|
+
/**
|
|
194
|
+
* Fraction of carry exposure assumed avoidable (UNVALIDATED — some carry is
|
|
195
|
+
* unavoidable because the tool result must be in context at least once).
|
|
196
|
+
* Surfaced as a labeled input in every rec.
|
|
197
|
+
*/
|
|
198
|
+
export const D6_AVOIDANCE_FRACTION = 0.5;
|
|
199
|
+
/**
|
|
200
|
+
* D6 tool-result-bloat savings (gated on calibration — only emitted when a
|
|
201
|
+
* calibrated bytes_per_token is present; otherwise modeled_savings stays null).
|
|
202
|
+
*
|
|
203
|
+
* carry_exposure_tokens × avoidance_fraction = avoidable carry tokens/wk.
|
|
204
|
+
* avoidable × blended_cache_read_price = µUSD/wk saved by trimming bloat.
|
|
205
|
+
*
|
|
206
|
+
* The blended cache-read price is derived from the session's own pricing
|
|
207
|
+
* snapshots (same approach as D8's write price), never a global average.
|
|
208
|
+
*/
|
|
209
|
+
export function d6Savings(carryExposureTokensDirectional, avoidanceFraction, cacheReadPriceUsdPerMtok) {
|
|
210
|
+
const avoidableTokens = carryExposureTokensDirectional * avoidanceFraction;
|
|
211
|
+
const savingsU = roundU(avoidableTokens * cacheReadPriceUsdPerMtok);
|
|
212
|
+
const formula = {
|
|
213
|
+
model: "D6_TOOL_RESULT_BLOAT_V1",
|
|
214
|
+
inputs: {
|
|
215
|
+
carry_exposure_tokens_directional: Math.round(carryExposureTokensDirectional),
|
|
216
|
+
avoidance_fraction: avoidanceFraction,
|
|
217
|
+
cache_read_price_usd_per_mtok: Number(cacheReadPriceUsdPerMtok.toFixed(6)),
|
|
218
|
+
avoidable_carry_tokens: Math.round(avoidableTokens),
|
|
219
|
+
},
|
|
220
|
+
expression: "carry_exposure_tokens_directional * avoidance_fraction / 1e6 * cache_read_price_usd_per_mtok",
|
|
221
|
+
result_usd_per_wk: Number((savingsU / 1e6).toFixed(2)),
|
|
222
|
+
};
|
|
223
|
+
return { savingsU, formula };
|
|
224
|
+
}
|
|
225
|
+
// ── D9 IDLE_BACKGROUND_SESSION config (directional; all UNVALIDATED defaults) ──
|
|
226
|
+
/** Sidechain turns must contribute ≥ this share of the scope's cap-weighted tokens. */
|
|
227
|
+
export const D9_SIDECHAIN_SHARE = 0.25;
|
|
228
|
+
/** …AND absolute sidechain cap-weighted tokens must be ≥ this floor (per wk). */
|
|
229
|
+
export const D9_SIDECHAIN_ABS_CAP_TOKENS = 100_000;
|
|
230
|
+
/** Fraction of sidechain volume assumed unproductive (directional — fan-out is often justified). */
|
|
231
|
+
export const D9_UNPRODUCTIVE_FRACTION = 0.5;
|
|
232
|
+
/**
|
|
233
|
+
* D9 idle/background savings — DIRECTIONAL, cap-weighted, no crisp $ headline
|
|
234
|
+
* (research B: fan-out is a real quality/throughput tradeoff, not pure waste).
|
|
235
|
+
* The surfaced modeled_savings_u_per_wk is NULL; this formula records the
|
|
236
|
+
* directional cap-weighted estimate as a labeled input only.
|
|
237
|
+
*/
|
|
238
|
+
export function d9Formula(sidechainCapWeightedTokens, unproductiveFraction) {
|
|
239
|
+
return {
|
|
240
|
+
model: "D9_IDLE_BACKGROUND_SESSION_V1",
|
|
241
|
+
kind: "DIRECTIONAL",
|
|
242
|
+
inputs: {
|
|
243
|
+
sidechain_cap_weighted_tokens: Math.round(sidechainCapWeightedTokens),
|
|
244
|
+
unproductive_fraction: unproductiveFraction,
|
|
245
|
+
directional_avoidable_cap_tokens: Math.round(sidechainCapWeightedTokens * unproductiveFraction),
|
|
246
|
+
},
|
|
247
|
+
expression: "sidechain_cap_weighted_tokens * unproductive_fraction (directional; no $ headline)",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/detector/types.ts — DetectorEngine shared types (Tier-1 recs D1/D2/D5).
|
|
3
|
+
*
|
|
4
|
+
* A Detector evaluates the trailing-7d window (anchored at an injected `now`,
|
|
5
|
+
* never new Date()) and returns a DetectorOutcome: zero or more Fired rows to
|
|
6
|
+
* upsert into `recommendations`, plus one live status for the query layer's
|
|
7
|
+
* detectors[] strip. Non-firing statuses (INACTIVE/BLOCKED/NOT_EVALUATED) are
|
|
8
|
+
* NOT persisted (the DDL `state` CHECK forbids them) — only Fired rows persist
|
|
9
|
+
* as state='PROPOSED'.
|
|
10
|
+
*
|
|
11
|
+
* SEC-101: evidence/modeled-formula blobs carry ids + metric names + numbers
|
|
12
|
+
* only — never transcript text.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|