Examples
Example: Pi Compactor
A structured, iterative context summary written with the agent's own model — no key, no permissions.
Example plugin · compaction.summarize · uses host.session.llm · no permissions
When a long mission's conversation is compacted, the default summary is prose. Pi Compactor (after the Pi coding-agent's compaction pattern) writes a structured summary instead — Goal, Constraints, Progress, Key Decisions, Next Steps, Critical Context, and cumulative lists of files read and modified — and refines its own previous summary each time instead of starting over. Long missions keep their file lists and decisions intact across many compactions.
The template
// infrastructure/plugins/pi-compactor/index.ts
const MAX_MESSAGE_CHARS = 2000; // Pi truncates bulky tool results before serializing.
const SUMMARY_MARKER = "# Conversation summary"; // first line of our output — lets the next compaction detect it.
const TEMPLATE =
"## Goal\n" +
"[What the user is trying to accomplish]\n\n" +
"## Constraints & Preferences\n" +
"- [Requirements/preferences the user stated]\n\n" +
"## Progress\n" +
"### Done\n" +
"- [x] [Completed work]\n" +
"### In Progress\n" +
"- [ ] [Current work]\n" +
"### Blocked\n" +
"- [Issues, if any]\n\n" +
"## Key Decisions\n" +
"- **[Decision]**: [Rationale]\n\n" +
"## Next Steps\n" +
"1. [What should happen next]\n\n" +
"## Critical Context\n" +
"- [Facts/identifiers/data needed to continue]\n\n" +
"<read-files>\n[one file path per line, cumulative across the whole session]\n</read-files>\n\n" +
"<modified-files>\n[one file path per line, cumulative across the whole session]\n</modified-files>";
The handler
// infrastructure/plugins/pi-compactor/index.ts
function serialize(messages: CompactionMessage[]): string {
return messages
.map((message) => {
let content = message.content || "";
if (content.length > MAX_MESSAGE_CHARS) {
content = content.slice(0, MAX_MESSAGE_CHARS) + "\n…[truncated " + (content.length - MAX_MESSAGE_CHARS) + " chars]";
}
return "[" + message.role + "]\n" + content;
})
.join("\n\n");
}
globalThis.compaction.summarize = async (context) => {
const messages = context.messages || [];
if (messages.length === 0) {
return "";
}
const first = messages[0];
const hasPriorSummary = first.content.indexOf(SUMMARY_MARKER) === 0;
const priorSummary = hasPriorSummary ? first.content : null;
const transcript = serialize(hasPriorSummary ? messages.slice(1) : messages);
const system =
"You compact a long agent session into a single structured summary that preserves everything needed to " +
"continue with NO loss of intent. Output EXACTLY the template below, filling every section from the " +
"conversation (drop a placeholder bullet only when nothing applies). Keep file paths, identifiers, commands, " +
"and decisions byte-exact. Accumulate <read-files>/<modified-files> across the entire history. Your FIRST " +
'line MUST be "' + SUMMARY_MARKER + '". Output only the summary — no preamble.\n\n' +
"TEMPLATE:\n" + SUMMARY_MARKER + "\n\n" + TEMPLATE +
(priorSummary
? "\n\nPREVIOUS SUMMARY — refine and extend this; carry its file lists forward:\n" + priorSummary
: "");
const result = await host.session!.llm!.complete({
messages: [
{ role: "system", content: system },
{ role: "user", content: "Conversation to summarize (oldest first):\n\n" + transcript },
],
params: { temperature: 0.2 },
});
return result.assistant.text || "";
};
Walkthrough
- What it's given.
context.messagesis the history since (and including) the last checkpoint, oldest first. The current request isn't there — the platform keeps it verbatim after the summary. - Recognizing its own previous summary. Every summary starts with
# Conversation summary. On the next compaction the platform hands that summary back asmessages[0]; the plugin spots the marker and feeds it to the model as "refine and extend this". That's what keeps the file lists cumulative. - Keeping the prompt bounded. Each message is cut to 2,000 characters — tool results are the usual bulk.
- Calling a model without a key.
host.session.llm.completeruns the chat's own agent model. No setting, no egress, no permission — it's granted by context. The!s are safe because a compaction always runs inside a chat. Temperature 0.2 keeps it faithful. - Returning. One string. An empty return (the
messages.length === 0case) or a summary more than half the compaction threshold makes the platform use its built-in summarizer and post a notice — the agent is never left uncompacted.
Using it
Install, then Settings → Engines → Context compaction → Pi Compactor. It applies to automatic compaction and
to /compact; /compact keep the deploy plan arrives as context.focus (this plugin doesn't use it yet — adding
context.focus to the system prompt is a one-line improvement).