peaks-loop 4.0.25 → 4.0.27
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 +23 -0
- package/dist/cli/commands/code-runtime-commands.js +7 -1
- package/dist/cli/commands/core/memory-command.js +1 -3
- package/dist/cli/commands/dispatch-commands.js +14 -67
- package/dist/cli/commands/memory-commands.d.ts +0 -2
- package/dist/cli/commands/memory-commands.js +4 -7
- package/dist/cli/commands/preferences-commands.js +0 -1
- package/dist/cli/commands/qa-commands.js +5 -5
- package/dist/cli/commands/sub-agent-shared.d.ts +0 -4
- package/dist/cli/commands/sub-agent-shared.js +0 -14
- package/dist/cli/commands/workflow-commands.js +5 -5
- package/dist/services/code/auto-compact-orchestrator.js +3 -0
- package/dist/services/context/auto-compact-reader.d.ts +45 -49
- package/dist/services/context/auto-compact-reader.js +21 -148
- package/dist/services/context/auto-compact-types.d.ts +17 -2
- package/dist/services/context/build-dispatch-system-prompt.d.ts +4 -4
- package/dist/services/context/build-dispatch-system-prompt.js +6 -6
- package/dist/services/context/context-guard.d.ts +2 -4
- package/dist/services/context/context-guard.js +4 -6
- package/dist/services/context/{headroom-fetcher.d.ts → doc-cache-fetcher.d.ts} +2 -2
- package/dist/services/context/{headroom-fetcher.js → doc-cache-fetcher.js} +4 -3
- package/dist/services/context/memory-preflight-service.js +6 -17
- package/dist/services/context/threshold.d.ts +0 -3
- package/dist/services/context/threshold.js +0 -3
- package/dist/services/dispatch/batch-counter.js +1 -1
- package/dist/services/dispatch/leak-detector.js +1 -1
- package/dist/services/fuzzy-matching/fzf-pick-service.d.ts +1 -1
- package/dist/services/fuzzy-matching/fzf-pick-service.js +1 -1
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +12 -12
- package/dist/services/ide/adapters/claude-code-adapter.js +290 -1
- package/dist/services/ide/ide-types.d.ts +39 -0
- package/dist/services/memory/llm-reranker.d.ts +4 -5
- package/dist/services/memory/llm-reranker.js +6 -8
- package/dist/services/memory/memory-search-service.d.ts +0 -32
- package/dist/services/memory/memory-search-service.js +0 -37
- package/dist/services/preferences/preferences-service.d.ts +5 -8
- package/dist/services/preferences/preferences-service.js +0 -30
- package/dist/services/preferences/preferences-types.d.ts +0 -22
- package/dist/services/preferences/preferences-types.js +0 -12
- package/dist/services/retrospective/retrospective-search-service.d.ts +0 -18
- package/dist/services/retrospective/retrospective-search-service.js +0 -32
- package/dist/services/session/binding-status-service.d.ts +11 -0
- package/dist/services/session/binding-status-service.js +25 -4
- package/dist/services/skill/skill-search-service.d.ts +1 -1
- package/dist/services/slice/slice-benchmark-service.d.ts +1 -1
- package/dist/services/slice/slice-benchmark-service.js +1 -1
- package/dist/services/slice/slice-pick-service.d.ts +1 -1
- package/dist/services/slice/slice-pick-service.js +1 -1
- package/package.json +5 -6
- package/skills/bee/peaks-qa/references/qa-context-governance.md +1 -1
- package/skills/bee/peaks-rd/SKILL.md +2 -2
- package/skills/bee/peaks-rd/references/rd-context-governance.md +2 -2
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +1 -1
- package/skills/peaks-code/references/context-governance.md +7 -34
- package/skills/peaks-code/references/envelope-contract.md +2 -7
- package/skills/peaks-code/references/sub-agent-dispatch.md +2 -4
- package/dist/services/context/headroom-client.d.ts +0 -34
- package/dist/services/context/headroom-client.js +0 -117
- package/dist/services/context/headroom-prefs.d.ts +0 -46
- package/dist/services/context/headroom-prefs.js +0 -34
- package/skills/peaks-code/references/headroom-integration.md +0 -107
|
@@ -73,35 +73,3 @@ export function searchRetrospective(input) {
|
|
|
73
73
|
};
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
import { compressPrompt as compressPromptForRetro } from '../context/headroom-client.js';
|
|
77
|
-
import { loadPreferences as loadPreferencesForRetro } from '../preferences/preferences-service.js';
|
|
78
|
-
import { shouldCompressResults as shouldCompressResultsForRetro } from '../context/headroom-prefs.js';
|
|
79
|
-
export async function searchRetrospectiveWithResults(input, options = {}) {
|
|
80
|
-
const matches = searchRetrospective(input);
|
|
81
|
-
if (options.compressResults !== true) {
|
|
82
|
-
return { matches, compressedResults: null };
|
|
83
|
-
}
|
|
84
|
-
const projectRoot = input.projectRoot ?? process.cwd();
|
|
85
|
-
const prefs = loadPreferencesForRetro(projectRoot).headroom;
|
|
86
|
-
const joinedText = matches.map((m) => `${m.title}: ${m.summary}`).join('\n');
|
|
87
|
-
const joinedBytes = Buffer.byteLength(joinedText, 'utf8');
|
|
88
|
-
const decision = shouldCompressResultsForRetro(prefs, joinedBytes, 'retrospectiveSearch');
|
|
89
|
-
if (!decision.compress) {
|
|
90
|
-
return { matches, compressedResults: null };
|
|
91
|
-
}
|
|
92
|
-
const result = await compressPromptForRetro(joinedText, decision.mode);
|
|
93
|
-
if (result.warning !== null || result.compressedPrompt === null) {
|
|
94
|
-
return { matches, compressedResults: null };
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
matches,
|
|
98
|
-
compressedResults: {
|
|
99
|
-
mode: decision.mode,
|
|
100
|
-
originalSize: result.originalSize,
|
|
101
|
-
compressedSize: result.compressedSize,
|
|
102
|
-
compressionRatio: result.compressionRatio,
|
|
103
|
-
compressedText: result.compressedPrompt,
|
|
104
|
-
warning: result.warning
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
@@ -38,6 +38,17 @@ export type BindingStatusView = {
|
|
|
38
38
|
* helper that does no filesystem writes; safe to call from any test.
|
|
39
39
|
*/
|
|
40
40
|
export declare function loadBindingStatus(projectRoot: string): BindingStatusView;
|
|
41
|
+
export declare function readOuterSessionId(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the outer (harness / IDE) session id for the auto-compact
|
|
44
|
+
* context-percent probe. Order: env signal first (PEAKS_OUTER_SESSION_ID /
|
|
45
|
+
* CLAUDE_CODE_SESSION_ID), then the bound peaks session's recorded
|
|
46
|
+
* `outerSessionId` (`session.json` meta). Returns `undefined` when neither is
|
|
47
|
+
* available — the caller passes `undefined` through and the adapter's fallback
|
|
48
|
+
* returns null → conservative-fallback. Vendor-neutral: this helper has no
|
|
49
|
+
* IDE-specific naming; the env-var read lives alongside the binding layer.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveOuterSessionId(projectRoot: string, sessionId: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
41
52
|
/**
|
|
42
53
|
* Render the binding as a pipeable ASCII table. The columns are fixed
|
|
43
54
|
* (no truncation, no wrapping) so a downstream `awk` / `cut` pipeline
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { existsSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { readBinding } from './binding-store.js';
|
|
25
|
+
import { getSessionMeta } from './session-manager.js';
|
|
25
26
|
/**
|
|
26
27
|
* Read the binding from disk and assemble the read-only view. Pure
|
|
27
28
|
* helper that does no filesystem writes; safe to call from any test.
|
|
@@ -37,14 +38,34 @@ export function loadBindingStatus(projectRoot) {
|
|
|
37
38
|
: !Object.values(binding.instances).some((inst) => inst.callerId.startsWith(outerSessionId));
|
|
38
39
|
return { binding, source, projectRoot, stale, outerSessionId };
|
|
39
40
|
}
|
|
40
|
-
function readOuterSessionId() {
|
|
41
|
-
|
|
41
|
+
export function readOuterSessionId() {
|
|
42
|
+
return readOuterSessionIdFromEnv(process.env) ?? 'unknown';
|
|
43
|
+
}
|
|
44
|
+
/** Pure env read (no process.env dependency) so callers can inject an env. */
|
|
45
|
+
function readOuterSessionIdFromEnv(env) {
|
|
46
|
+
const peaks = env.PEAKS_OUTER_SESSION_ID;
|
|
42
47
|
if (typeof peaks === 'string' && peaks.length > 0)
|
|
43
48
|
return peaks;
|
|
44
|
-
const claude =
|
|
49
|
+
const claude = env.CLAUDE_CODE_SESSION_ID;
|
|
45
50
|
if (typeof claude === 'string' && claude.length > 0)
|
|
46
51
|
return claude;
|
|
47
|
-
return
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the outer (harness / IDE) session id for the auto-compact
|
|
56
|
+
* context-percent probe. Order: env signal first (PEAKS_OUTER_SESSION_ID /
|
|
57
|
+
* CLAUDE_CODE_SESSION_ID), then the bound peaks session's recorded
|
|
58
|
+
* `outerSessionId` (`session.json` meta). Returns `undefined` when neither is
|
|
59
|
+
* available — the caller passes `undefined` through and the adapter's fallback
|
|
60
|
+
* returns null → conservative-fallback. Vendor-neutral: this helper has no
|
|
61
|
+
* IDE-specific naming; the env-var read lives alongside the binding layer.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveOuterSessionId(projectRoot, sessionId, env = process.env) {
|
|
64
|
+
const envOuter = readOuterSessionIdFromEnv(env);
|
|
65
|
+
if (envOuter !== undefined)
|
|
66
|
+
return envOuter;
|
|
67
|
+
const recorded = getSessionMeta(projectRoot, sessionId)?.outerSessionId;
|
|
68
|
+
return typeof recorded === 'string' && recorded.length > 0 ? recorded : undefined;
|
|
48
69
|
}
|
|
49
70
|
/**
|
|
50
71
|
* Render the binding as a pipeable ASCII table. The columns are fixed
|
|
@@ -32,9 +32,9 @@ export declare const SkillSearchInputSchema: z.ZodObject<{
|
|
|
32
32
|
status: "status";
|
|
33
33
|
resume: "resume";
|
|
34
34
|
audit: "audit";
|
|
35
|
+
ide: "ide";
|
|
35
36
|
content: "content";
|
|
36
37
|
test: "test";
|
|
37
|
-
ide: "ide";
|
|
38
38
|
research: "research";
|
|
39
39
|
doctor: "doctor";
|
|
40
40
|
triage: "triage";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slice benchmark wrapper (slice 2026-06-14-fzf-
|
|
2
|
+
* Slice benchmark wrapper (slice 2026-06-14-fzf-rollout).
|
|
3
3
|
*
|
|
4
4
|
* Wraps `decomposeSlices` with a thin observability layer that records
|
|
5
5
|
* total wall time, codegraph call count, confidence distribution, and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slice benchmark wrapper (slice 2026-06-14-fzf-
|
|
2
|
+
* Slice benchmark wrapper (slice 2026-06-14-fzf-rollout).
|
|
3
3
|
*
|
|
4
4
|
* Wraps `decomposeSlices` with a thin observability layer that records
|
|
5
5
|
* total wall time, codegraph call count, confidence distribution, and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slice Pick Service — thin wrapper around the generic fzf picker.
|
|
3
3
|
*
|
|
4
|
-
* v2 (slice 2026-06-14-fzf-
|
|
4
|
+
* v2 (slice 2026-06-14-fzf-rollout): the actual fzf
|
|
5
5
|
* integration is now in `src/services/fuzzy-matching/fzf-pick-service.ts`.
|
|
6
6
|
* This file only knows how to format / parse SliceCandidate rows and
|
|
7
7
|
* pick the output path. The algorithm itself (slice-decompose-service)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slice Pick Service — thin wrapper around the generic fzf picker.
|
|
3
3
|
*
|
|
4
|
-
* v2 (slice 2026-06-14-fzf-
|
|
4
|
+
* v2 (slice 2026-06-14-fzf-rollout): the actual fzf
|
|
5
5
|
* integration is now in `src/services/fuzzy-matching/fzf-pick-service.ts`.
|
|
6
6
|
* This file only knows how to format / parse SliceCandidate rows and
|
|
7
7
|
* pick the output path. The algorithm itself (slice-decompose-service)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-loop",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.27",
|
|
4
4
|
"description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"keywords": [
|
|
@@ -99,13 +99,12 @@
|
|
|
99
99
|
"better-sqlite3": "^12.11.1",
|
|
100
100
|
"commander": "^12.1.0",
|
|
101
101
|
"fzf": "^0.5.2",
|
|
102
|
-
"headroom-ai": "0.22.4",
|
|
103
102
|
"yaml": "^2.9.0",
|
|
104
103
|
"zod": "^4.4.3",
|
|
105
|
-
"peaks-loop-
|
|
106
|
-
"peaks-loop-
|
|
107
|
-
"peaks-loop-shared
|
|
108
|
-
"peaks-loop-shared": "0.0.
|
|
104
|
+
"peaks-loop-mut": "0.1.25",
|
|
105
|
+
"peaks-loop-internal-runtime": "0.0.12",
|
|
106
|
+
"peaks-loop-shared": "0.0.61",
|
|
107
|
+
"peaks-loop-shared-channel": "0.0.29"
|
|
109
108
|
},
|
|
110
109
|
"devDependencies": {
|
|
111
110
|
"@changesets/cli": "2.31.1",
|
|
@@ -21,4 +21,4 @@ PROTOCOL (mandatory):
|
|
|
21
21
|
|
|
22
22
|
## G9 — QA prompt size self-check
|
|
23
23
|
|
|
24
|
-
Same as RD: 50% soft warn, 75% `CONTEXT_NEAR_LIMIT`, 80% hard reject unless `--force`. QA test plans can grow large;
|
|
24
|
+
Same as RD: 50% soft warn, 75% `CONTEXT_NEAR_LIMIT`, 80% hard reject unless `--force`. QA test plans can grow large; trim or split into multiple dispatches for plans > 75%.
|
|
@@ -235,9 +235,9 @@ The `karpathy-reviewer` sub-agent now reports its own runtime cost in the JSON e
|
|
|
235
235
|
|
|
236
236
|
The main RD loop MUST call `peaks job karpathy-cost-check` after every `peaks request transition --state qa-handoff` and before the next-slice work begins. If the decision kind is `downgraded`, the LLM MUST honor the downgraded `warn` and proceed to the next slice; the `'block'` was an artifact of the reviewer's own cost, not the slice's quality. If the decision kind is `reported` (costRatio > 50, gate not `'block'`), the LLM MAY continue; the sediment will be appended by `peaks memory extract` at handoff time. The full design lives in `.peaks/memory/2026-07-30-karpathy-evaluation-cost-self-review-design.md` (sediment locked 2026-07-30).
|
|
237
237
|
|
|
238
|
-
## Sub-agent context governance (G7 +
|
|
238
|
+
## Sub-agent context governance (G7 + G8 + G9 — slice #010)
|
|
239
239
|
|
|
240
|
-
RD sub-agent prompt template MUST include the G7 path convention + G8.6 share protocol. Detailed protocol: `skills/peaks-code/references/context-governance.md
|
|
240
|
+
RD sub-agent prompt template MUST include the G7 path convention + G8.6 share protocol. Detailed protocol: `skills/peaks-code/references/context-governance.md`.
|
|
241
241
|
|
|
242
242
|
→ see `references/rd-context-governance.md` for the full G7 / G8.6 / G9 protocol + RD sub-agent prompt template.
|
|
243
243
|
|
|
@@ -31,6 +31,6 @@ PROTOCOL (mandatory):
|
|
|
31
31
|
|
|
32
32
|
Before dispatching a sub-agent, RD self-checks prompt size:
|
|
33
33
|
- < 50%: pass through.
|
|
34
|
-
- 50-75%: soft warn (consider
|
|
35
|
-
- 75-80%: soft warn + `warnings: ["CONTEXT_NEAR_LIMIT"]` (
|
|
34
|
+
- 50-75%: soft warn (consider trimming the prompt).
|
|
35
|
+
- 75-80%: soft warn + `warnings: ["CONTEXT_NEAR_LIMIT"]` (trim or split into multiple dispatches).
|
|
36
36
|
- 80%+: reject (CLI 兜底 returns `code: "PROMPT_TOO_LARGE"`). Use `--force` at CLI only when overriding; hook layer will still reject (RL-30).
|
|
@@ -305,7 +305,7 @@ The TXT handoff summarizes the slice by listing the artifact metas + the share e
|
|
|
305
305
|
|
|
306
306
|
### G9 — TXT prompt size self-check
|
|
307
307
|
|
|
308
|
-
Same G9 threshold table. TXT handoff messages can grow large when the slice has many batches;
|
|
308
|
+
Same G9 threshold table. TXT handoff messages can grow large when the slice has many batches; trim or split the handoff prompt proactively for prompts > 50%.
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
## L2 surface reference (post rid-l2-extended)
|
|
@@ -302,7 +302,7 @@ After final validation, refresh project-local standards via `peaks standards ini
|
|
|
302
302
|
|
|
303
303
|
`peaks codegraph affected` is an optional project-analysis enhancement (untrusted supporting evidence) for role handoff. Code must not treat codegraph output as approval for scope, design, or QA verdict. Never mutate agent settings / hooks from codegraph; do not commit `.codegraph/` artifacts. RD writes `.peaks/_runtime/<sessionId>/rd/codegraph-context.md`; QA / TXT consume the same envelope. → `references/codegraph-orchestration.md`.
|
|
304
304
|
|
|
305
|
-
## Sub-agent context governance (G7 +
|
|
305
|
+
## Sub-agent context governance (G7 + G8 + G9 — slice #010)
|
|
306
306
|
|
|
307
307
|
Main LLM reducer sees metadata-only view (~200 chars/sub-agent); on-demand `Read` for full content. Threshold table: 50% soft warn, 75% `CONTEXT_NEAR_LIMIT`, 80% hard reject (CLI + hook double-guard). → `references/context-governance.md`.
|
|
308
308
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Context Governance — G7 +
|
|
1
|
+
# Context Governance — G7 + G8 + G9 protocol details
|
|
2
2
|
|
|
3
|
-
> Slice #010 (G7 +
|
|
4
|
-
> See: `.peaks/memory/sub-agent-context-minimal-occupation.md` + `sub-agent-shared-channel-cross-completion.md`
|
|
3
|
+
> Slice #010 (G7 + G8 + G9 context-governance push).
|
|
4
|
+
> See: `.peaks/memory/sub-agent-context-minimal-occupation.md` + `sub-agent-shared-channel-cross-completion.md` for the red lines.
|
|
5
5
|
|
|
6
6
|
## G7 — sub-agent context minimal-occupation (metadata-only + 按需 Read)
|
|
7
7
|
|
|
@@ -56,26 +56,6 @@ On completion:
|
|
|
56
56
|
|
|
57
57
|
3000-5000× improvement. Main LLM full-slice context net increase: < 10KB for 5 batches × 6 sub-agents.
|
|
58
58
|
|
|
59
|
-
## G7.7 — headroom-ai integration (opt-in)
|
|
60
|
-
|
|
61
|
-
### `--use-headroom` flag
|
|
62
|
-
|
|
63
|
-
Opt-in flag on `peaks sub-agent dispatch`. Default `false` (G7 metadata-only remains the default).
|
|
64
|
-
|
|
65
|
-
### Mode table
|
|
66
|
-
|
|
67
|
-
| Mode | tokenBudget | Use case |
|
|
68
|
-
|---|---|---|
|
|
69
|
-
| `balanced` (default) | promptSize * 0.40 / 4 | General sub-agent dispatch |
|
|
70
|
-
| `aggressive` | promptSize * 0.20 / 4 | Last-resort large prompt |
|
|
71
|
-
| `conservative` | promptSize * 0.70 / 4 | Sensitive code analysis |
|
|
72
|
-
|
|
73
|
-
### Failure mode (RL-22d / RL-32)
|
|
74
|
-
|
|
75
|
-
- headroom daemon dead / proxy unreachable / times out
|
|
76
|
-
- → `code: "HEADROOM_UNAVAILABLE"` warning + G7 metadata-only fallback
|
|
77
|
-
- → NOT blocking (warn, then continue dispatch)
|
|
78
|
-
|
|
79
59
|
## G8 — cross sub-agent shared channel
|
|
80
60
|
|
|
81
61
|
### Path convention
|
|
@@ -132,8 +112,8 @@ PROTOCOL (mandatory):
|
|
|
132
112
|
|
|
133
113
|
| Threshold | Prompt size | Behavior |
|
|
134
114
|
|---|---|---|
|
|
135
|
-
| 50% (early warn) | ≥ 128KB | Soft warning, suggest
|
|
136
|
-
| **75% (user red line)** | ≥ 192KB | Soft warn + mandatory
|
|
115
|
+
| 50% (early warn) | ≥ 128KB | Soft warning, suggest trimming the prompt |
|
|
116
|
+
| **75% (user red line)** | ≥ 192KB | Soft warn + mandatory trim/split suggestion; `warnings: ["CONTEXT_NEAR_LIMIT"]` |
|
|
137
117
|
| **80% (hard reject)** | ≥ 204KB | Hard reject `code: "PROMPT_TOO_LARGE"`; `--force` allowed at CLI |
|
|
138
118
|
| **90% (emergency)** | ≥ 230KB | Hard reject `code: "PROMPT_EMERGENCY"`; `--force` STILL rejects at 90% (no override) |
|
|
139
119
|
|
|
@@ -149,7 +129,7 @@ PROTOCOL (mandatory):
|
|
|
149
129
|
|
|
150
130
|
## AC mapping
|
|
151
131
|
|
|
152
|
-
- AC-38..AC-43 (G7) + AC-
|
|
132
|
+
- AC-38..AC-43 (G7) + AC-47..AC-49 (G8) + AC-50..AC-65 (G9)
|
|
153
133
|
- See PRD §Acceptance criteria.
|
|
154
134
|
|
|
155
135
|
---
|
|
@@ -171,13 +151,6 @@ Main LLM view format (G7.4.e):
|
|
|
171
151
|
- qa-perf → .../artifacts/003-qa-perf-001.md (5KB, sha256:ghi789) summary: "p95 latency target ≤ 200ms"
|
|
172
152
|
```
|
|
173
153
|
|
|
174
|
-
### G7.7 — headroom-ai integration (opt-in compression)
|
|
175
|
-
|
|
176
|
-
> Body of `### G7.7`. If a sub-agent prompt is too large even after G7 metadata-only (e.g. 1MB artifact description, 5MB mid-prompt analysis), use `--use-headroom`:
|
|
177
|
-
- Default `false` (G7 remains default).
|
|
178
|
-
- Modes: `balanced` (default) | `aggressive` | `conservative`.
|
|
179
|
-
- Failure: `HEADROOM_UNAVAILABLE` warning + G7 metadata-only fallback (NOT blocking).
|
|
180
|
-
|
|
181
154
|
### G8 — cross sub-agent shared channel (dispatcher-mediated indirect signal)
|
|
182
155
|
|
|
183
156
|
> Body of `### G8`. Sub-agent A's completion **immediately** writes a shared entry; sub-agent B (still in flight) can read shared entries from sibling sub-agents. **This is NOT peer-to-peer messaging.** The dispatcher stores, the sub-agents read/write; A and B never directly talk.
|
|
@@ -192,7 +165,7 @@ Main LLM view format (G7.4.e):
|
|
|
192
165
|
|
|
193
166
|
| Threshold | Prompt size | Behavior |
|
|
194
167
|
|---|---|---|
|
|
195
|
-
| 50% (early warn) | ≥ 128KB | Soft warning, suggest
|
|
168
|
+
| 50% (early warn) | ≥ 128KB | Soft warning, suggest trimming the prompt |
|
|
196
169
|
| **75% (user red line)** | ≥ 192KB | Soft warn + `warnings: ["CONTEXT_NEAR_LIMIT"]` |
|
|
197
170
|
| **80% (hard reject)** | ≥ 204KB | Hard reject `code: "PROMPT_TOO_LARGE"`; `--force` allowed at CLI |
|
|
198
171
|
| 90% (emergency) | ≥ 230KB | Hard reject + `contextWarning: 'high'` |
|
|
@@ -33,19 +33,14 @@ Top-level fields (post-2.1.0):
|
|
|
33
33
|
- `envelopeVersion: '2.1.0'`
|
|
34
34
|
- `role: string` — sub-agent role string
|
|
35
35
|
- `ide: string` — detected IDE label (`claude-code` etc.)
|
|
36
|
-
- `originalPromptSize: number` — bytes of the
|
|
37
|
-
- `promptSize: number` — bytes
|
|
38
|
-
equal to `originalPromptSize` when `--use-headroom` is off or
|
|
39
|
-
unavailable)
|
|
36
|
+
- `originalPromptSize: number` — bytes of the caller-supplied prompt
|
|
37
|
+
- `promptSize: number` — bytes of the composed dispatch prompt
|
|
40
38
|
- `toolCall: { name: string; args: Record<string, unknown> }` —
|
|
41
39
|
the per-IDE tool-call descriptor the LLM must execute
|
|
42
40
|
- `dispatchRecordPath: string` — absolute path to the dispatch
|
|
43
41
|
record on disk
|
|
44
42
|
- `batchId: string` — uuid-like opaque token grouping one batch
|
|
45
43
|
- `dispatchedInBatch: number` — current count after this dispatch
|
|
46
|
-
- `headroomCompressed: boolean` — true if headroom-ai actually
|
|
47
|
-
reduced the prompt
|
|
48
|
-
- `headroomResult: { mode, compressed, compressionRatio, tokensSaved, warning } | null`
|
|
49
44
|
- `forcedAt: string | null` — ISO8601 when `--force` overrode
|
|
50
45
|
the G9 hard-reject tier
|
|
51
46
|
- `contextImpact: { promptBytes, artifactBytes, totalBytes }`
|
|
@@ -51,8 +51,8 @@ peaks sub-agent dispatch <role> --prompt <text> [--request-id <rid>] [--session-
|
|
|
51
51
|
> scrollback. The dispatch record on disk (gitignored under
|
|
52
52
|
> `.peaks/_sub_agents/`) keeps the prompt for the sub-agent to read;
|
|
53
53
|
> CLI stdout stays metadata-only. Surface `originalPromptSize` +
|
|
54
|
-
> `promptSize` so the LLM-side runner can still reason about
|
|
55
|
-
> without seeing the content. The prompt content the LLM should pass
|
|
54
|
+
> `promptSize` so the LLM-side runner can still reason about the size
|
|
55
|
+
> delta without seeing the content. The prompt content the LLM should pass
|
|
56
56
|
> through lives in `data.toolCall.args.prompt` (the IDE-arg the
|
|
57
57
|
> consumer passes to the tool), NOT in the outer envelope.
|
|
58
58
|
|
|
@@ -77,8 +77,6 @@ peaks sub-agent dispatch <role> --prompt <text> [--request-id <rid>] [--session-
|
|
|
77
77
|
"dispatchRecordPath": ".peaks/_sub_agents/2026-06-06-session-5b1095/dispatch-002-2026-06-07-...-...json",
|
|
78
78
|
"batchId": "<uuid>",
|
|
79
79
|
"dispatchedInBatch": 3,
|
|
80
|
-
"headroomCompressed": false,
|
|
81
|
-
"headroomResult": null,
|
|
82
80
|
"forcedAt": null,
|
|
83
81
|
"contextImpact": {
|
|
84
82
|
"promptBytes": 4321,
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export type HeadroomMode = 'balanced' | 'aggressive' | 'conservative';
|
|
2
|
-
export interface HeadroomResult {
|
|
3
|
-
readonly compressed: boolean;
|
|
4
|
-
readonly originalSize: number;
|
|
5
|
-
readonly compressedSize: number;
|
|
6
|
-
readonly compressionRatio: number;
|
|
7
|
-
readonly mode: HeadroomMode;
|
|
8
|
-
/** `'HEADROOM_UNAVAILABLE'` on fallback; `null` on success. */
|
|
9
|
-
readonly warning: string | null;
|
|
10
|
-
/** Compressed prompt body. `null` if no compression happened. */
|
|
11
|
-
readonly compressedPrompt: string | null;
|
|
12
|
-
/** Tokens saved (from the SDK). 0 on fallback. */
|
|
13
|
-
readonly tokensSaved: number;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Compress a prompt via headroom-ai. The `fallback: true` option is
|
|
17
|
-
* non-negotiable: if the proxy daemon is unavailable, the SDK returns
|
|
18
|
-
* `result.compressed = false` and the original messages; we surface
|
|
19
|
-
* that as `HEADROOM_UNAVAILABLE` warning + G7 metadata-only fallback.
|
|
20
|
-
*/
|
|
21
|
-
export declare function compressPrompt(prompt: string, mode?: HeadroomMode): Promise<HeadroomResult>;
|
|
22
|
-
/**
|
|
23
|
-
* Bridge interface: when `--use-headroom` is set, share entries written
|
|
24
|
-
* via `peaks sub-agent share` MAY also flow through headroom's
|
|
25
|
-
* `SharedContext`. Slice #010 implements a peak-internal shared channel
|
|
26
|
-
* (see `shared-channel.ts`); the headroom-side `SharedContext` is a
|
|
27
|
-
* separate concept that future slices can layer on. For now this
|
|
28
|
-
* function is a stub that returns the peak-internal channel ID, which
|
|
29
|
-
* is enough to demonstrate the bridge contract.
|
|
30
|
-
*/
|
|
31
|
-
export declare function buildSharedContextBridge(batchId: string): {
|
|
32
|
-
peakChannelId: string;
|
|
33
|
-
headroomContextId: string;
|
|
34
|
-
};
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
2
|
-
const DEFAULT_MODEL = 'claude-sonnet-4-5-20250929';
|
|
3
|
-
// Approximate 1 token = 4 bytes for English text. This is a rough
|
|
4
|
-
// heuristic; the SDK does its own tokenization internally.
|
|
5
|
-
const BYTES_PER_TOKEN = 4;
|
|
6
|
-
/**
|
|
7
|
-
* Compress a prompt via headroom-ai. The `fallback: true` option is
|
|
8
|
-
* non-negotiable: if the proxy daemon is unavailable, the SDK returns
|
|
9
|
-
* `result.compressed = false` and the original messages; we surface
|
|
10
|
-
* that as `HEADROOM_UNAVAILABLE` warning + G7 metadata-only fallback.
|
|
11
|
-
*/
|
|
12
|
-
export async function compressPrompt(prompt, mode = 'balanced') {
|
|
13
|
-
const originalSize = Buffer.byteLength(prompt, 'utf8');
|
|
14
|
-
let compressFn = null;
|
|
15
|
-
try {
|
|
16
|
-
const mod = await import('headroom-ai');
|
|
17
|
-
if (typeof mod.compress === 'function') {
|
|
18
|
-
compressFn = mod.compress;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return fallback(originalSize, mode);
|
|
23
|
-
}
|
|
24
|
-
if (compressFn === null) {
|
|
25
|
-
return fallback(originalSize, mode);
|
|
26
|
-
}
|
|
27
|
-
const messages = [
|
|
28
|
-
{ role: 'user', content: prompt }
|
|
29
|
-
];
|
|
30
|
-
const opts = {
|
|
31
|
-
model: DEFAULT_MODEL,
|
|
32
|
-
timeout: DEFAULT_TIMEOUT_MS,
|
|
33
|
-
fallback: true, // CRITICAL: return original messages if proxy is down
|
|
34
|
-
retries: 1
|
|
35
|
-
};
|
|
36
|
-
if (mode === 'aggressive') {
|
|
37
|
-
opts.tokenBudget = Math.max(1, Math.floor(originalSize * 0.20 / BYTES_PER_TOKEN));
|
|
38
|
-
}
|
|
39
|
-
else if (mode === 'conservative') {
|
|
40
|
-
opts.tokenBudget = Math.max(1, Math.floor(originalSize * 0.70 / BYTES_PER_TOKEN));
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// balanced: target ~60% reduction
|
|
44
|
-
opts.tokenBudget = Math.max(1, Math.floor(originalSize * 0.40 / BYTES_PER_TOKEN));
|
|
45
|
-
}
|
|
46
|
-
let result;
|
|
47
|
-
try {
|
|
48
|
-
result = await compressFn(messages, opts);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return fallback(originalSize, mode);
|
|
52
|
-
}
|
|
53
|
-
if (result.compressed === false) {
|
|
54
|
-
return {
|
|
55
|
-
compressed: false,
|
|
56
|
-
originalSize,
|
|
57
|
-
compressedSize: originalSize,
|
|
58
|
-
compressionRatio: 1.0,
|
|
59
|
-
mode,
|
|
60
|
-
warning: 'HEADROOM_UNAVAILABLE',
|
|
61
|
-
compressedPrompt: null,
|
|
62
|
-
tokensSaved: 0
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const compressedContent = extractContent(result.messages);
|
|
66
|
-
if (compressedContent === null) {
|
|
67
|
-
return fallback(originalSize, mode);
|
|
68
|
-
}
|
|
69
|
-
const compressedSize = Buffer.byteLength(compressedContent, 'utf8');
|
|
70
|
-
return {
|
|
71
|
-
compressed: true,
|
|
72
|
-
originalSize,
|
|
73
|
-
compressedSize,
|
|
74
|
-
compressionRatio: compressedSize / originalSize,
|
|
75
|
-
mode,
|
|
76
|
-
warning: null,
|
|
77
|
-
compressedPrompt: compressedContent,
|
|
78
|
-
tokensSaved: result.tokensSaved ?? 0
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function fallback(originalSize, mode) {
|
|
82
|
-
return {
|
|
83
|
-
compressed: false,
|
|
84
|
-
originalSize,
|
|
85
|
-
compressedSize: originalSize,
|
|
86
|
-
compressionRatio: 1.0,
|
|
87
|
-
mode,
|
|
88
|
-
warning: 'HEADROOM_UNAVAILABLE',
|
|
89
|
-
compressedPrompt: null,
|
|
90
|
-
tokensSaved: 0
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function extractContent(messages) {
|
|
94
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
const last = messages[messages.length - 1];
|
|
98
|
-
if (typeof last.content === 'string') {
|
|
99
|
-
return last.content;
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Bridge interface: when `--use-headroom` is set, share entries written
|
|
105
|
-
* via `peaks sub-agent share` MAY also flow through headroom's
|
|
106
|
-
* `SharedContext`. Slice #010 implements a peak-internal shared channel
|
|
107
|
-
* (see `shared-channel.ts`); the headroom-side `SharedContext` is a
|
|
108
|
-
* separate concept that future slices can layer on. For now this
|
|
109
|
-
* function is a stub that returns the peak-internal channel ID, which
|
|
110
|
-
* is enough to demonstrate the bridge contract.
|
|
111
|
-
*/
|
|
112
|
-
export function buildSharedContextBridge(batchId) {
|
|
113
|
-
return {
|
|
114
|
-
peakChannelId: batchId,
|
|
115
|
-
headroomContextId: `headroom-ctx-${batchId}`
|
|
116
|
-
};
|
|
117
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Headroom preferences resolver (slice 2026-06-14-fzf-headroom-rollout).
|
|
3
|
-
*
|
|
4
|
-
* Pure functions that turn `ProjectPreferences.headroom.*` + CLI
|
|
5
|
-
* overrides into a single decision the dispatch path can act on.
|
|
6
|
-
* No IO. No side effects. Easy to test in isolation.
|
|
7
|
-
*
|
|
8
|
-
* Two distinct decisions:
|
|
9
|
-
* 1. `resolveHeadroomOptions` — should we compress the dispatch prompt?
|
|
10
|
-
* Used by `peaks sub-agent dispatch`. Returns one of:
|
|
11
|
-
* - `{ mode: <m>, blocked: null }` → compress with mode <m>
|
|
12
|
-
* - `{ mode: null, blocked: null }` → skip compression (no --use-headroom)
|
|
13
|
-
* - `{ mode: null, blocked: 'HEADROOM_DISABLED_BY_PREFERENCE' }` → hard fail
|
|
14
|
-
* 2. `shouldCompressResults` — should we compress search-result text?
|
|
15
|
-
* Used by `peaks memory search` / `peaks retrospective search`.
|
|
16
|
-
* Non-blocking: returns a `reason` instead of a `blocked` code.
|
|
17
|
-
*
|
|
18
|
-
* Precedence (dispatch):
|
|
19
|
-
* 1. `headroom.enabled = false` → hard block (regardless of --use-headroom)
|
|
20
|
-
* 2. `headroom.enabled = true` + `--headroom-mode <m>` → use <m>
|
|
21
|
-
* 3. `headroom.enabled = true` + `--use-headroom` (no --headroom-mode) →
|
|
22
|
-
* use `perTouchpoint.subAgentDispatch` (preferred) else `defaultMode`
|
|
23
|
-
* 4. no `--use-headroom` → mode = null (compression skipped)
|
|
24
|
-
*/
|
|
25
|
-
import type { HeadroomMode, ProjectPreferences } from '../preferences/preferences-types.js';
|
|
26
|
-
export type HeadroomTouchpoint = keyof ProjectPreferences['headroom']['perTouchpoint'];
|
|
27
|
-
export type HeadroomBlockCode = 'HEADROOM_DISABLED_BY_PREFERENCE';
|
|
28
|
-
export interface ResolvedHeadroomOptions {
|
|
29
|
-
/** The mode to use for compression, or null if not compressing. */
|
|
30
|
-
readonly mode: HeadroomMode | null;
|
|
31
|
-
/** Hard-block reason; null when compression is allowed (or simply not requested). */
|
|
32
|
-
readonly blocked: HeadroomBlockCode | null;
|
|
33
|
-
}
|
|
34
|
-
export interface ResolveCliOverrides {
|
|
35
|
-
readonly useHeadroom: boolean;
|
|
36
|
-
readonly headroomMode?: string;
|
|
37
|
-
}
|
|
38
|
-
export declare function isHeadroomMode(value: string | undefined): value is HeadroomMode;
|
|
39
|
-
export declare function resolveHeadroomOptions(prefs: ProjectPreferences['headroom'], cliOverrides: ResolveCliOverrides): ResolvedHeadroomOptions;
|
|
40
|
-
export interface ShouldCompressDecision {
|
|
41
|
-
readonly compress: boolean;
|
|
42
|
-
readonly mode: HeadroomMode;
|
|
43
|
-
/** Non-null reason if not compressing. */
|
|
44
|
-
readonly reason: 'DISABLED' | 'BELOW_THRESHOLD' | null;
|
|
45
|
-
}
|
|
46
|
-
export declare function shouldCompressResults(prefs: ProjectPreferences['headroom'], joinedBytes: number, touchpoint: HeadroomTouchpoint): ShouldCompressDecision;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const VALID_MODES = new Set([
|
|
2
|
-
'balanced',
|
|
3
|
-
'aggressive',
|
|
4
|
-
'conservative'
|
|
5
|
-
]);
|
|
6
|
-
export function isHeadroomMode(value) {
|
|
7
|
-
return typeof value === 'string' && VALID_MODES.has(value);
|
|
8
|
-
}
|
|
9
|
-
export function resolveHeadroomOptions(prefs, cliOverrides) {
|
|
10
|
-
// (1) Hard block when preference disables headroom entirely.
|
|
11
|
-
if (cliOverrides.useHeadroom === true && prefs.enabled === false) {
|
|
12
|
-
return { mode: null, blocked: 'HEADROOM_DISABLED_BY_PREFERENCE' };
|
|
13
|
-
}
|
|
14
|
-
// (4) No --use-headroom → no compression.
|
|
15
|
-
if (cliOverrides.useHeadroom !== true) {
|
|
16
|
-
return { mode: null, blocked: null };
|
|
17
|
-
}
|
|
18
|
-
// (2) CLI --headroom-mode wins (even if preference is set, CLI overrides).
|
|
19
|
-
if (isHeadroomMode(cliOverrides.headroomMode)) {
|
|
20
|
-
return { mode: cliOverrides.headroomMode, blocked: null };
|
|
21
|
-
}
|
|
22
|
-
// (3) Preference path: perTouchpoint > defaultMode > 'balanced'.
|
|
23
|
-
const touchpointMode = prefs.perTouchpoint.subAgentDispatch;
|
|
24
|
-
return { mode: touchpointMode ?? prefs.defaultMode, blocked: null };
|
|
25
|
-
}
|
|
26
|
-
export function shouldCompressResults(prefs, joinedBytes, touchpoint) {
|
|
27
|
-
if (prefs.enabled === false) {
|
|
28
|
-
return { compress: false, mode: prefs.defaultMode, reason: 'DISABLED' };
|
|
29
|
-
}
|
|
30
|
-
if (joinedBytes < prefs.compressMinBytes) {
|
|
31
|
-
return { compress: false, mode: prefs.perTouchpoint[touchpoint], reason: 'BELOW_THRESHOLD' };
|
|
32
|
-
}
|
|
33
|
-
return { compress: true, mode: prefs.perTouchpoint[touchpoint], reason: null };
|
|
34
|
-
}
|