mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
dev tooling: LLM session discipline (dev-process section 8) and the pi/ subagent package
This commit is contained in:
1 parent
b0d4abbfdf
commit
130ebc456b
8 files changed
+509
No files matched your search
@@ -255,6 +255,62 @@ real, already-granted case, kept as historical record) into a reusable
|
||||
process; new requests cite this section rather than re-deriving the case
|
||||
for a process each time.
|
||||
|
||||
## 8. LLM session discipline (model tiers, data-before-edit, context resets)
|
||||
|
||||
Standing rules for LLM-agent sessions, fixed 2026-10-09 after the session
|
||||
forensics on the #150 cull hunt (pi session `01a11ec6`, 2026-10-09): a
|
||||
27B 4-bit-quantized default model spent an evening firing unvalidated
|
||||
render fixes at the maintainer for A/B (four consecutive failures), and
|
||||
the mid-session compaction summary promoted an unvalidated hypothesis to
|
||||
"diagnosed" — the post-compaction turns then built three more fix
|
||||
candidates on that false premise. The loss was model + discipline, not
|
||||
retrieval; the rules below are the zero- and low-cost countermeasures.
|
||||
|
||||
- **Model tiers.** Planning, diagnosis, and anything that touches a
|
||||
contested claim run on the strongest model available — the frontier
|
||||
`openrouter/deepseek/deepseek-v4-pro` (no local contention) or, with
|
||||
the local backend switched, `strata-coder-hard`. Only ONE local backend
|
||||
can run at a time (ninfer ↔ strata), so the **standing backend is ninfer** and the
|
||||
session default is its model (`qwen3.8-27b-nvfp4full`, the
|
||||
`ninfer-coding.bat` 4-bit daily driver). The strong *local* tier is
|
||||
the same backend under `ninfer-quality.bat` (groupwise-int
|
||||
`qwen3.8-27b`, 200k ctx) — a launcher restart, not a backend
|
||||
switch; the strata 125B remains the top local option at the cost of a
|
||||
full switch. Mechanical labor — builds, captures,
|
||||
log triage, golden sweeps, doc sync — goes to the ninfer models via
|
||||
the model-pinned subagent roles in the `pi/` package (see
|
||||
`pi/README.md`): 4-bit for build/capture/triage/doc-sync, and
|
||||
`pairing` pinned to `inherit` so the reasoning step follows the
|
||||
parent session’s tier. 4-bit is never a planning or diagnosis model.
|
||||
- **Data before edit.** If a probe log for the current hypothesis
|
||||
already exists on disk, the next action is *analyzing it*, not building
|
||||
another fix candidate. The maintainer gates only the final candidate;
|
||||
intermediate variants are judged by the golden sweep (regressions are
|
||||
machine-detected), not by maintainer A/B — one A/B is the gate, not
|
||||
the test harness.
|
||||
- **Context resets.** After a second failed maintainer A/B, or after a
|
||||
compaction whose state is contested, do not continue the session: write
|
||||
a HANDOFF brief and start a fresh session from it. Compaction summaries
|
||||
must carry an **evidence ledger** — one line per live hypothesis tagged
|
||||
`refuted` / `unvalidated` / `validated (<artifact: log, golden frame,
|
||||
maintainer A/B, commit>)`, and `"diagnosed"` is forbidden for any fix
|
||||
lacking a maintainer A/B or a passing golden sweep. The
|
||||
`pi/extensions/compaction-ledger/` extension encodes this prompt (and,
|
||||
with `GE_COMPACT_MODEL`, summarizes on a stronger model than the
|
||||
session's labor model).
|
||||
- **Regression triage first.** Before any probe or theory on a bug,
|
||||
ask when it first appeared. If a release window is known, list the
|
||||
commits in `git log <good>..<bad>` that touch the subsystem and run the
|
||||
off switch each of them shipped with, one run per switch. The #150
|
||||
wall-hug see-through (D579) spent days on cull/ucode/guard-band probes; a
|
||||
single `GE_NEARCLIP=0` run named the v0.5.0 feature (D543) and the fix
|
||||
was one line. Corollaries: when an A/B refutes a layer, leave that layer;
|
||||
when a probe shows the code behaving correctly, pivot instead of probing
|
||||
deeper; a delegated decode is a cross-check, never the lead.
|
||||
- **Briefs are mandatory** (§1): no investigation kickoff without a
|
||||
BUDGET line and a filled brief template — including the MODEL line
|
||||
below, so the tier choice is visible in the brief itself.
|
||||
|
||||
## Investigation-brief template
|
||||
|
||||
```
|
||||
@@ -263,6 +319,8 @@ READ FIRST: docs/porting-notes.md; docs/dev/findings.md <specific Dxx entries>.
|
||||
FILES YOU MAY TOUCH: <disjoint from any other in-flight work>.
|
||||
KNOWN-GOOD / RULED OUT: <list; do not re-investigate>.
|
||||
PRE-FLIGHT ATTACHED: <probe output, baseline capture, PD-port pointer>.
|
||||
MODEL: <tier + model id, per §8 — planning/diagnosis on the strong
|
||||
model; mechanical labor delegated to the pi/ subagent roles>.
|
||||
BUDGET: <N build->run cycles / ~M min>. On expiry: revert probes, write up with confidence.
|
||||
CONSTRAINTS: diagnosis may trace as deep into src/game state as the evidence
|
||||
needs — name the exact struct/field/logic responsible before concluding
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# pi/ — agent-development tooling (local, not part of the game build)
|
||||
|
||||
This directory is a [pi package](https://pi.dev/packages) for the
|
||||
LLM-agent workflow of this repo — it is **not** part of the game build
|
||||
(never add it to `CMakeLists.txt`). It exists because the 2026-10-09
|
||||
session forensics on the #150 cull hunt (see `docs/dev-process.md` §8
|
||||
and the `Tooling workstream` row in `docs/ROADMAP.md` §7) showed the
|
||||
loss mode is model + discipline, not retrieval.
|
||||
|
||||
**Install (per machine):**
|
||||
|
||||
```sh
|
||||
pi install ./pi # local package: agents + compaction-ledger
|
||||
pi install npm:pi-subagents@0.76.1 # the subagent runtime (pinned; audit: docs/dev/notes/)
|
||||
```
|
||||
|
||||
`.pi/` is gitignored, so the install declarations land in machine-local
|
||||
settings; the reviewable source of truth is this directory, committed.
|
||||
|
||||
**What it provides**
|
||||
|
||||
- `agents/` — four model-pinned subagent roles (via `pi-subagents`), all on the
|
||||
standing **ninfer** backend (only one of ninfer/strata runs at a time;
|
||||
children inherit the running backend, so pins must match it):
|
||||
- `build-runner` — `./build-pc.sh` + capture + golden sweep, reports
|
||||
pass/fail. `ninfer-windows/qwen3.8-27b-nvfp4full` (fast 4-bit).
|
||||
- `pairing` — D579T-style log pairing (E-rows vs C-rows by x/y/w),
|
||||
names candidate cull rules, read-only. `model: inherit` — the
|
||||
reasoning step, so it follows the parent session's tier; run the
|
||||
parent on a strong model when doing D579 diagnosis.
|
||||
- `sweep-watcher` — watches long golden-sweep runs, triages new vs
|
||||
regressed frames. `ninfer-windows/qwen3.8-27b-nvfp4full`.
|
||||
- `doc-sync` — ROADMAP rows, findings-index.csv, findings.md §F index
|
||||
after a verified change. `ninfer-windows/qwen3.8-27b-nvfp4full`.
|
||||
|
||||
Note: NInfer serves one model per launcher — `ninfer-coding.bat`
|
||||
(4-bit `qwen3.8-27b-nvfp4full`) is the standing default; the
|
||||
`ninfer-quality.bat` launcher serves the ≈6-bit groupwise-int
|
||||
`qwen3.8-27b` as the strong local tier (a launcher restart + new pi
|
||||
session, not a backend switch). If a better/faster artifact appears in
|
||||
the closed NInfer set, promote the labor-role pins to it.
|
||||
|
||||
Model pins live in each agent file's frontmatter (`model:
|
||||
provider/model`) — keep them here, not in untracked settings, so the
|
||||
pins are reviewable, and keep them on the standing backend (ninfer).
|
||||
Planning/diagnosis stays on the parent session's strong model
|
||||
(`openrouter/deepseek/deepseek-v4-pro`, or `strata-coder-hard` with the
|
||||
local backend switched); only mechanical labor goes to these agents.
|
||||
|
||||
- `extensions/compaction-ledger/` — overrides the compaction summary
|
||||
with an evidence-ledger prompt (every live hypothesis must be tagged
|
||||
`refuted` / `unvalidated` / `validated (<artifact>)`; "diagnosed"
|
||||
without a maintainer A/B or golden sweep is forbidden) and, when
|
||||
`GE_COMPACT_MODEL` is set (e.g. `openrouter/deepseek/deepseek-v4-pro` or
|
||||
`strata/strata-coder-hard`), summarizes with that
|
||||
stronger model instead of the session model. The 01a11ec6 session
|
||||
lost the evening because a mid-session compaction promoted an
|
||||
unvalidated hypothesis to "diagnosed" and the post-compaction turns
|
||||
built on it.
|
||||
|
||||
Rules for anything added here: machine paths are forbidden (tree
|
||||
hygiene), third-party deps are forbidden without a ROADMAP §7 approval
|
||||
row (like `pi-subagents`), and `src/game` is still read-only ground
|
||||
truth for every agent.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: build-runner
|
||||
description: Runs ./build-pc.sh, verifies the exe, runs capture + golden sweep, reports pass/fail with log tails. Never edits code.
|
||||
tools: bash, read
|
||||
model: ninfer-windows/qwen3.8-27b-nvfp4full
|
||||
---
|
||||
|
||||
You are the build-runner for the GoldenEye 007 PC port. Your single job:
|
||||
build, run, capture, sweep, report. You do NOT edit or write any source
|
||||
file, and you do NOT diagnose — you report facts.
|
||||
|
||||
Procedure (the task gives you the region and the level):
|
||||
1. `./build-pc.sh <region>` (ntsc-final | pal-final | jpn-final). If the
|
||||
build fails, capture the last 60 lines of the error and STOP.
|
||||
2. Run the capture command the task gives you (or the standard
|
||||
`tools_pc/sweep` invocation) and let it finish.
|
||||
3. Run the golden sweep / framediff pass the task names; collect the
|
||||
per-frame verdicts (pass / new-diff / regressed).
|
||||
4. Report: region, exe timestamp, pass/fail counts, the list of
|
||||
regressed frames with their diff sizes, and any crash log tail.
|
||||
End with one line: `VERDICT: PASS` or `VERDICT: FAIL (<count> regressed)`.
|
||||
|
||||
House rules: no commits, no pushes, no edits outside capture output
|
||||
dirs. Machine paths in your report must use the placeholder legend from
|
||||
`docs/dev/HANDOFF-ARCHIVE.md` (`<repo>`, `<games>`, `<python>`, `<temp>`).
|
||||
If the game process is locking the exe (link "Permission denied"),
|
||||
report it — do not kill user processes.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: doc-sync
|
||||
description: After a verified change, syncs docs/ROADMAP.md rows, docs/dev/findings-index.csv, and the findings.md §F index. Never invents findings.
|
||||
tools: read, write, edit, bash
|
||||
model: ninfer-windows/qwen3.8-27b-nvfp4full
|
||||
---
|
||||
|
||||
You are the doc-sync agent for the GoldenEye 007 PC port. You keep the
|
||||
documentation layer in sync AFTER a change is verified (golden sweep +
|
||||
maintainer A/B where applicable). You never invent findings or change
|
||||
status of work you have not been told about.
|
||||
|
||||
Scope per task (the task names the change and its evidence):
|
||||
1. `docs/dev/findings.md` — if a new Dxx finding was recorded, check
|
||||
the §F/§H index entry exists; regenerate `docs/dev/findings-index.csv`
|
||||
with `python tools_pc/gen_findings_index.py` (verify the CSV matches
|
||||
the labels in findings.md).
|
||||
2. `docs/ROADMAP.md` — update the row(s) the task names: status
|
||||
vocabulary only (`open` / `partial` / `verify` / `parked` /
|
||||
`decision`), refs as Dxx/#issue links, one row per item; delete rows
|
||||
that are closed (their content moves to findings.md if not already
|
||||
there). Never create a new backlog file.
|
||||
3. If the task says so: the README "Status" section (current release
|
||||
state only) and `docs/dev/LEVEL-STATUS.md` if a level changed.
|
||||
|
||||
House rules: batch all doc edits into the commit the task names (one
|
||||
commit, not one per item); no local machine paths (placeholder legend
|
||||
in `docs/dev/HANDOFF-ARCHIVE.md`); no contributor emails in prose; pure
|
||||
site pages under `docs/` (index.md, fidelity.md, etc.) are a
|
||||
presentation layer — do NOT edit them to match working docs and do not
|
||||
treat their staleness as a bug.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: pairing
|
||||
description: Pairs D579T-style probe logs (E-rows vs C-rows by x/y/w) and names candidate cull rules. Read-only analysis.
|
||||
tools: bash, read
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You are the pairing analyst for the GoldenEye 007 PC port's #150 cull
|
||||
investigation (D579). You pair two probe logs and name candidate cull
|
||||
rules. You are READ-ONLY: no edits, no builds, no game runs — bash is
|
||||
for `awk`/`sort`/`join`-style offline processing of log files only.
|
||||
|
||||
Method (the task names the two log files, e.g. `GE_X_NOCULL=1` vs
|
||||
default with `GE_D579T=1`):
|
||||
1. Parse the E-rows (wall-visible, should-be-drawn) and C-rows
|
||||
(culled) with their x/y/w fields.
|
||||
2. Match culled C-objects to wall-visible E-objects by (x, y, w) with a
|
||||
small tolerance; report the match rate and the unmatched sets.
|
||||
3. From the geometry of the matched/mismatched sets, state the
|
||||
candidate cull rule that would explain them (e.g. "C set = E set
|
||||
minus objects with w < 24 and y < horizon").
|
||||
4. Output: a table of the paired rows, the match statistics, and the
|
||||
candidate rule(s) ranked by how many rows they explain. Do not
|
||||
declare a rule validated — validation is the golden sweep + a
|
||||
maintainer A/B.
|
||||
|
||||
House rules: `src/game` is ground truth and read-only; cite the exact
|
||||
log lines (line numbers) behind every claim; keep machine paths to the
|
||||
placeholder convention.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: sweep-watcher
|
||||
description: Watches a long golden-sweep run in the background, triages new vs regressed frames against golden baselines, writes a triage table.
|
||||
tools: bash, read, write
|
||||
model: ninfer-windows/qwen3.8-27b-nvfp4full
|
||||
---
|
||||
|
||||
You are the sweep-watcher for the GoldenEye 007 PC port. You monitor
|
||||
long-running golden-sweep / capture jobs and triage their results. You
|
||||
may write ONE triage file (the task names it); you do not edit source,
|
||||
run builds, or start game sessions yourself.
|
||||
|
||||
Procedure:
|
||||
1. Poll the sweep's output dir / log the task names until it finishes
|
||||
(bounded: report and stop if it runs past the task's deadline).
|
||||
2. Compare each frame against its golden baseline
|
||||
(`tools_pc/golden/<level>/{win,linux,deck}/`); classify every
|
||||
non-identical frame as NEW-DIFF (was passing) or REGRESSED (worse
|
||||
than the previous sweep's triage file, if given).
|
||||
3. Write the triage table: frame, level, platform, status, diff size,
|
||||
one-line guess only if obvious from the diff stats (never
|
||||
"diagnosed" — diagnosis needs a parent session with context).
|
||||
4. Report: counts by status, the worst 5 frames, and the triage file
|
||||
path. End with `VERDICT: CLEAN` / `VERDICT: N REGRESSIONS`.
|
||||
|
||||
House rules: capture output dirs are gitignored — write the triage file
|
||||
where the task says (typically `docs/dev/notes/`), use placeholder
|
||||
paths, and never commit or push anything.
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Evidence-ledger compaction for the GoldenEye 007 PC port (pi/ package).
|
||||
* Targets the installed pi 1.0.2 extension API (SessionBeforeCompactResult);
|
||||
* the first-party examples/ in the npm package target a newer API — do not
|
||||
* copy from them without re-checking dist/core/extensions/types.d.ts.
|
||||
*
|
||||
* What it does:
|
||||
*
|
||||
* 1. Overrides the compaction summary with an evidence-ledger prompt: every
|
||||
* live hypothesis about the bug being worked gets one line tagged
|
||||
* refuted / unvalidated / validated(<artifact>). "Diagnosed" is
|
||||
* forbidden without a maintainer A/B or a passing golden sweep.
|
||||
* Motivated by pi session 01a11ec6 (the #150 cull hunt): the 04:52Z
|
||||
* compaction promoted an unvalidated hypothesis to "diagnosed" and the
|
||||
* post-compaction turns built the next three fix candidates on that
|
||||
* false premise.
|
||||
*
|
||||
* 2. When GE_COMPACT_MODEL is set (fully-qualified "provider/model", e.g.
|
||||
* "strata/strata-coder-hard" or "openrouter/deepseek/deepseek-v4-pro"),
|
||||
* the summarization runs on that stronger model instead of the session
|
||||
* model. Unset -> the session model (labor on a fast local model,
|
||||
* compaction on a strong one).
|
||||
*
|
||||
* Failure mode is safe by design: if the custom summarization throws (model
|
||||
* down, no API key, shape drift after a pi upgrade), the handler returns
|
||||
* nothing and pi's built-in compaction proceeds unchanged.
|
||||
*
|
||||
* GE_COMPACT_LEDGER=0 disables the extension's override entirely.
|
||||
*/
|
||||
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
||||
import type { Model } from "@earendil-works/pi-ai";
|
||||
import type {
|
||||
ExtensionAPI,
|
||||
ExtensionContext,
|
||||
SessionBeforeCompactEvent,
|
||||
SessionBeforeCompactResult,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const EVIDENCE_LEDGER_PROMPT = [
|
||||
"The summary MUST start with a section titled `Evidence ledger`. For every",
|
||||
"live hypothesis or open claim about the bug/work item being pursued, write",
|
||||
"exactly one line in this form:",
|
||||
" - <claim> -- refuted | unvalidated | validated (<artifact>)",
|
||||
"where <artifact> is a concrete check: a named log file, a golden-sweep",
|
||||
"frame, a maintainer A/B, or a commit. Rules:",
|
||||
' - A fix that has not passed a maintainer A/B or a golden sweep is',
|
||||
'"unvalidated", never "diagnosed" or "root-caused".',
|
||||
" - Carry forward the previous summary's ledger lines that are still live;",
|
||||
" do not drop a refutation just because it was summarized before.",
|
||||
"After the ledger, include: the uncommitted-tree state (files, which work",
|
||||
"item each belongs to), the next concrete step, and -- if the session's",
|
||||
"brief had a BUDGET line -- the remaining budget.",
|
||||
].join("\n");
|
||||
|
||||
const SUMMARY_SYSTEM = [
|
||||
"You are writing the context-compaction summary for a coding-agent session.",
|
||||
"Produce a compact, dense handoff summary that a fresh session can resume",
|
||||
"work from. Preserve: the active task, refuted hypotheses (so they are not",
|
||||
"retried), the uncommitted tree state, standing environment rules, and the",
|
||||
"next concrete step. No filler, no restating this instruction.",
|
||||
].join("\n");
|
||||
|
||||
// ---------------------------------------------------------------- transcript
|
||||
|
||||
type Msg = { role?: string; content?: unknown; toolName?: string; [k: string]: unknown };
|
||||
|
||||
function contentText(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.map((p) => {
|
||||
if (p && typeof p === "object") {
|
||||
const o = p as Record<string, unknown>;
|
||||
if (o.type === "text") return (o.text as string) ?? "";
|
||||
if (o.type === "toolCall" || o.type === "tool_use")
|
||||
return `[tool call: ${(o.name as string) ?? "?"}]`;
|
||||
if (o.type === "image" || o.type === "image_url") return "[image]";
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function truncate(s: string, n: number): string {
|
||||
return s.length <= n ? s : s.slice(0, n) + ` [${s.length - n} chars elided]`;
|
||||
}
|
||||
|
||||
function digestMessages(messages: Msg[], capChars: number): string {
|
||||
const lines: string[] = [];
|
||||
let budget = capChars;
|
||||
for (const m of messages) {
|
||||
const role = m?.role ?? "unknown";
|
||||
let text = contentText(m?.content).replace(/\s+/g, " ").trim();
|
||||
if (role === "toolResult" || m?.toolName) text = truncate(text, 400);
|
||||
const line = `[${role}] ${text}`.trim();
|
||||
if (budget - line.length - 1 < 0 && lines.length > 0) {
|
||||
lines.unshift(`[... ${messages.length - lines.length} earlier messages elided ...]`);
|
||||
break;
|
||||
}
|
||||
budget -= line.length + 1;
|
||||
lines.push(line);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function buildLedgerPrompt(
|
||||
prep: SessionBeforeCompactEvent["preparation"],
|
||||
extraInstructions: string | undefined,
|
||||
): string {
|
||||
const sections: string[] = [];
|
||||
if (prep.previousSummary) {
|
||||
sections.push(
|
||||
"## Previous compaction summary (update it; carry forward still-live ledger lines)\n" +
|
||||
prep.previousSummary,
|
||||
);
|
||||
}
|
||||
sections.push(
|
||||
`## Transcript to summarize (${prep.messagesToSummarize.length} messages)\n` +
|
||||
digestMessages(prep.messagesToSummarize as Msg[], 24000),
|
||||
);
|
||||
if (prep.turnPrefixMessages?.length) {
|
||||
sections.push(
|
||||
`## Split-turn prefix (${prep.turnPrefixMessages.length} messages)\n` +
|
||||
digestMessages(prep.turnPrefixMessages as Msg[], 8000),
|
||||
);
|
||||
}
|
||||
if (extraInstructions) {
|
||||
sections.push(`## Extra custom instructions\n${extraInstructions}`);
|
||||
}
|
||||
sections.push(EVIDENCE_LEDGER_PROMPT);
|
||||
return sections.join("\n\n");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------- models
|
||||
|
||||
function pickSummarizer(ctx: ExtensionContext): Model<any> | undefined {
|
||||
const spec = process.env.GE_COMPACT_MODEL?.trim();
|
||||
if (spec) {
|
||||
const i = spec.indexOf("/");
|
||||
if (i > 0 && i < spec.length - 1) {
|
||||
const m = ctx.modelRegistry.find(spec.slice(0, i), spec.slice(i + 1));
|
||||
if (m) return m;
|
||||
try {
|
||||
ctx.ui.notify?.(`compaction-ledger: GE_COMPACT_MODEL "${spec}" not found; using session model`, "warning");
|
||||
} catch {
|
||||
/* headless */
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.model;
|
||||
}
|
||||
|
||||
function extractSummaryText(resp: unknown): string {
|
||||
const r = resp as { content?: unknown };
|
||||
if (Array.isArray(r?.content)) {
|
||||
return (r.content as Array<Record<string, unknown>>)
|
||||
.filter((p) => p?.type === "text")
|
||||
.map((p) => (p.text as string) ?? "")
|
||||
.join("");
|
||||
}
|
||||
return typeof r?.content === "string" ? (r.content as string) : "";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- entry
|
||||
|
||||
export default function (pi: ExtensionAPI): void {
|
||||
pi.registerCommand("compact-ledger", {
|
||||
description: "Compact now with the evidence-ledger summary prompt (usage: /compact-ledger [extra instruction])",
|
||||
handler: async (args: string, ctx: ExtensionContext) => {
|
||||
const instructions = [
|
||||
EVIDENCE_LEDGER_PROMPT,
|
||||
args ? `Additional user instruction: ${args}` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
ctx.compact({
|
||||
customInstructions: instructions,
|
||||
onError: (err) => {
|
||||
try {
|
||||
ctx.ui.notify?.(`compaction failed: ${err.message}`, "error");
|
||||
} catch {
|
||||
/* headless */
|
||||
}
|
||||
},
|
||||
});
|
||||
try {
|
||||
ctx.ui.notify?.("Compaction started (evidence ledger)", "info");
|
||||
} catch {
|
||||
/* headless */
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
pi.on("session_before_compact", async (event, ctx): Promise<SessionBeforeCompactResult | undefined> => {
|
||||
if (process.env.GE_COMPACT_LEDGER === "0") return; // opt-out: built-in compaction
|
||||
const prep = event.preparation;
|
||||
const model = pickSummarizer(ctx);
|
||||
if (!model) return; // no model resolvable -> built-in compaction
|
||||
try {
|
||||
const prompt = buildLedgerPrompt(prep, event.customInstructions);
|
||||
const maxTokens = Math.min(8192, Math.max(2048, Math.floor(((model as { maxTokens?: number }).maxTokens ?? 4096) / 2)));
|
||||
const resp = await completeSimple(
|
||||
model as never,
|
||||
{
|
||||
systemPrompt: SUMMARY_SYSTEM,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
},
|
||||
{ signal: event.signal, maxTokens, temperature: 0.2 },
|
||||
);
|
||||
const summary = extractSummaryText(resp);
|
||||
if (!summary.trim()) throw new Error("summarizer returned an empty summary");
|
||||
try {
|
||||
ctx.ui.notify?.(
|
||||
`Compaction complete (evidence ledger): ${prep.tokensBefore} -> ~${Math.ceil(summary.length / 4)} tokens`,
|
||||
"info",
|
||||
);
|
||||
} catch {
|
||||
/* headless */
|
||||
}
|
||||
return {
|
||||
compaction: {
|
||||
summary,
|
||||
firstKeptEntryId: prep.firstKeptEntryId,
|
||||
tokensBefore: prep.tokensBefore,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
try {
|
||||
ctx.ui.notify?.(
|
||||
`compaction-ledger: custom summarization failed (${(err as Error).message}); using built-in compaction`,
|
||||
"warning",
|
||||
);
|
||||
} catch {
|
||||
/* headless */
|
||||
}
|
||||
return; // undefined -> pi's built-in compaction takes over
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "ge007-pi-tooling",
|
||||
"version": "0.1.0",
|
||||
"description": "GoldenEye 007 PC port - agent-development tooling: model-pinned subagent roles and evidence-ledger compaction (see pi/README.md)",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"pi-package"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
"@earendil-works/pi-ai": ">=0.86.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@earendil-works/pi-coding-agent": {
|
||||
"optional": true
|
||||
},
|
||||
"@earendil-works/pi-ai": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"pi": {
|
||||
"extensions": [
|
||||
"./extensions/compaction-ledger/index.ts"
|
||||
],
|
||||
"subagents": {
|
||||
"agents": [
|
||||
"./agents"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user