@matthewfl/pi-contemplator 0.0.4 → 0.0.5
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 +96 -18
- package/package.json +1 -1
- package/src/agents/contemplator/agent.ts +53 -5
- package/src/commands/status.ts +12 -0
- package/src/session-ledger/progress.ts +12 -0
- package/src/session-ledger/types.ts +2 -0
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# pi-contemplator
|
|
2
2
|
|
|
3
|
-
`pi-contemplator` is a [Pi](https://pi.dev/) plugin
|
|
3
|
+
`pi-contemplator` is a [Pi](https://pi.dev/) plugin for long-running, largely unsupervised agentic sessions. It does two things:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
1. **Keeps memory alive across compaction.** A background **observer**, **reflector**, and **dropper** distill the conversation into a durable, branch-local memory ledger. When Pi compacts the context window, the summary is rendered deterministically from that ledger — fast, model-free, and lossless enough that important facts survive.
|
|
6
|
+
2. **Gives the primary agent a second set of eyes.** A background **contemplator** reads the accumulated memories and watches for reasoning that is going wrong. When it finds a genuine problem, it can inject a focused, memory-cited question. For deeper, recurring structural issues, it can commission a short-lived **reviewer** that produces a durable, advisory design proposal.
|
|
7
|
+
|
|
8
|
+
The result: a long session is less likely to drift off course, get stuck in an unproductive loop, or silently compound a wrong conclusion — and when it does, it can often catch itself before the mistake poisons the rest of the work.
|
|
6
9
|
|
|
7
10
|
> [!WARNING]
|
|
8
11
|
> **Alpha-quality software:** `pi-contemplator` is under active development. Expect bugs, behavioral changes, and breaking configuration or memory-format changes.
|
|
@@ -15,7 +18,11 @@ It adds a background **contemplator** agent that examines accumulated session me
|
|
|
15
18
|
|
|
16
19
|
## Why use it?
|
|
17
20
|
|
|
18
|
-
A long-running agent
|
|
21
|
+
A long-running agent faces three compounding risks:
|
|
22
|
+
|
|
23
|
+
- **Compaction amnesia.** Pi compacts older messages to fit the context window. Details that weren't captured deliberately are simply gone.
|
|
24
|
+
- **Reasoning entrenchment.** An agent can draw the wrong conclusion from valid evidence and then repeatedly reinforce it. As the mistake accumulates in context, the agent becomes *less* likely to question it and *less* able to discover the real problem.
|
|
25
|
+
- **Unproductive loops.** The agent keeps bumping into the same obstacle through slightly different approaches, burning tokens without gaining information.
|
|
19
26
|
|
|
20
27
|
Without a contemplator, a session can look like this:
|
|
21
28
|
|
|
@@ -66,17 +73,86 @@ A or C is true
|
|
|
66
73
|
|
|
67
74
|
The contemplator does not take over the primary agent's work. It provides occasional, evidence-grounded challenges when reconsidering the current reasoning is more valuable than allowing the session to continue along the same path.
|
|
68
75
|
|
|
76
|
+
## What it does for you
|
|
77
|
+
|
|
78
|
+
### It catches reasoning problems before they settle
|
|
79
|
+
|
|
80
|
+
The contemplator is a "System 2" thinker: slower and more deliberative, focused on the larger shape of the problem while the primary agent does the hands-on work. It watches the memory ledger (not the live transcript) and looks for:
|
|
81
|
+
|
|
82
|
+
- unsupported assumptions — a conclusion that depends on something never established;
|
|
83
|
+
- contradictions — a recent claim that conflicts with earlier recorded evidence;
|
|
84
|
+
- drift from the user's intent, priorities, or constraints;
|
|
85
|
+
- overlooked alternatives — treating one explanation as settled when others are still plausible;
|
|
86
|
+
- unproductive loops — several memories showing the agent returning to the same obstacle;
|
|
87
|
+
- connections — relevant facts from separate memories that the primary agent never joined up.
|
|
88
|
+
|
|
89
|
+
When it finds something worth challenging, it sends a concise, memory-cited question as an asynchronous probe. Probes appear as purple cards in the chat and are delivered to the agent's next turn — they never interrupt mid-work or force a new turn:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
◆ CONTEMPLATOR PROBE
|
|
93
|
+
|
|
94
|
+
You concluded that fix Y resolved the failure, citing memory [a1b2c3d4e5f6].
|
|
95
|
+
Earlier, memory [d4e5f6a1b2c3] recorded that A, B, and C were also
|
|
96
|
+
possible explanations. What observation would distinguish Y from A, B, and C
|
|
97
|
+
before you build on that conclusion?
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### It commissions deeper structural reviews
|
|
101
|
+
|
|
102
|
+
Some patterns are bigger than one question. When several memories point at a *recurring* structural problem, the contemplator can request a review instead of a probe. The reviewer is a separate, short-lived, strictly-scoped agent that independently investigates the concern and, if the evidence supports it, records a durable conceptual proposal.
|
|
103
|
+
|
|
104
|
+
Two scopes are handled separately:
|
|
105
|
+
|
|
106
|
+
- **Workflow** — problems in *how* the work is performed: repeatedly reconstructing the same information, reasoning at length about something that could just be observed or computed, failing to preserve a reusable result, weak feedback loops.
|
|
107
|
+
- **Software** — problems in *the software being produced*: repeated special cases, several fixes touching the same missing invariant, duplicated concepts, unclear responsibility boundaries, local workarounds that hint at a missing abstraction.
|
|
108
|
+
|
|
109
|
+
The reviewer isn't just fed the concern and asked to design a fix. It independently searches the memory ledger *and* the primary agent's recorded chat history (including regex search over the transcript), looks for supporting and contrary evidence, and reaches exactly one terminal outcome: either a durable proposal with a memory id, or a conclusion that no proposal is currently justified. The primary agent receives only a compact notice pointing at the proposal:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
◆ CONTEMPLATOR REVIEW
|
|
113
|
+
|
|
114
|
+
BACKGROUND WORKFLOW REVIEW PROPOSAL [f7a8b9c0d1e2]
|
|
115
|
+
|
|
116
|
+
Several memories ([1111], [2222], [3333]) show the same multi-step lookup being
|
|
117
|
+
rebuilt from scratch each time. The proposal suggests a reusable, indexable
|
|
118
|
+
trace that would make the relationship reproducible in seconds instead of minutes.
|
|
119
|
+
|
|
120
|
+
Recall memory [f7a8b9c0d1e2] to read the full conceptual proposal when it is
|
|
121
|
+
relevant. This is advisory.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Proposals are deliberately conceptual — the reviewer cannot write code, specify exact APIs, name files, or hand over an implementation plan. The primary agent remains the only agent grounded in the actual codebase and environment, and it decides whether, when, and how to use a proposal.
|
|
125
|
+
|
|
126
|
+
### It keeps memory useful and accountable
|
|
127
|
+
|
|
128
|
+
The memory system is built to be trustworthy:
|
|
129
|
+
|
|
130
|
+
- **Every memory has provenance.** Observations cite the exact source entries that support them; reflections cite the observations they're based on. Everything gets a deterministic 12-character id computed in code, not guessed by a model.
|
|
131
|
+
- **Nothing important is silently lost.** The `recall` tool recovers the exact source evidence behind any memory id. The `search_memories` tool finds candidate memories by topic. Even observations the dropper later prunes remain recallable from ledger history.
|
|
132
|
+
- **Compaction is deterministic and model-free.** The summary the agent sees is folded from the ledger by code, not rewritten by a model. That makes compaction fast and cheap, and it means the same session state always produces the same summary.
|
|
133
|
+
|
|
134
|
+
## Why you can trust it in the background
|
|
135
|
+
|
|
136
|
+
Running extra agents in the background can be worrying — what if they take over, spam the user, or go off the rails? `pi-contemplator` is explicitly designed against that:
|
|
137
|
+
|
|
138
|
+
- **Advisory only.** The contemplator and reviewer never modify the primary agent's work, never implement anything, and never inject instructions into the user's session. They produce questions and proposals; the primary agent decides what to do.
|
|
139
|
+
- **One intervention per update.** The contemplator can send at most one probe *or* one review request per turn — never a barrage. Most turns it sends nothing at all.
|
|
140
|
+
- **Grounded in evidence, not noise.** Loops must be supported by a clear pattern across *multiple* memories. The contemplator is explicitly forbidden from inferring a stall from silence, elapsed time, or a missing recent result. It may consult activity signals (cumulative tokens, tool-call count, active time) as *advisory* context, but never as proof.
|
|
141
|
+
- **Sees memory, not secrets.** The contemplator reads only the memory ledger, not the live transcript, so it can't react to or interfere with in-flight work.
|
|
142
|
+
- **Epistemic humility.** The contemplator is instructed not to behave as though it already knows the answer, not to praise or criticize, and not to manage the primary agent step by step. It asks questions that help *both* agents discover what's missing.
|
|
143
|
+
- **Bounded and de-duplicated.** Reviews are serialized (one at a time), coalesced (no duplicate review for the same evidence and concern), and capped by a lifetime output-token budget — a review that runs out of budget records an honest "no proposal" rather than a half-baked design.
|
|
144
|
+
|
|
69
145
|
## Background agents
|
|
70
146
|
|
|
71
|
-
`pi-contemplator` uses five specialized background agents:
|
|
147
|
+
`pi-contemplator` uses five specialized background agents, all enabled by default:
|
|
72
148
|
|
|
73
|
-
| Agent |
|
|
149
|
+
| Agent | What it does | When it runs |
|
|
74
150
|
|---|---|---|
|
|
75
|
-
| **Observer** | Extracts concrete,
|
|
76
|
-
| **Reflector** |
|
|
77
|
-
| **Dropper** |
|
|
78
|
-
| **Contemplator** | Watches accumulated memories for reasoning gaps, contradictions, overlooked alternatives, and recurring structural concerns;
|
|
79
|
-
| **Reviewer** | Performs a
|
|
151
|
+
| **Observer** | Extracts concrete, timestamped observations from the primary session, citing source entries. | In the background after turns, once enough new source text accumulates. |
|
|
152
|
+
| **Reflector** | Distills durable conclusions (user intent, decisions, constraints) from observations, citing supporting observations. | Periodically, after the observer is up to date. |
|
|
153
|
+
| **Dropper** | Prunes observations that are obsolete, redundant, or safely represented elsewhere, keeping the active memory pool bounded. | Only after a successful reflection, when the memory pool is over target. |
|
|
154
|
+
| **Contemplator** | Watches accumulated memories for reasoning gaps, contradictions, overlooked alternatives, and recurring structural concerns; can send a focused probe or request a review. | Asynchronously after enough new memories accumulate. |
|
|
155
|
+
| **Reviewer** | Performs a deep, scoped (workflow or software) structural investigation and records a durable proposal or a no-proposal conclusion. | Only when the contemplator commissions a review, and only one at a time. |
|
|
80
156
|
|
|
81
157
|
The observer, reflector, and dropper provide the durable memory substrate. The contemplator reasons over that substrate, while the reviewer is launched only when a concern warrants a deeper structural investigation.
|
|
82
158
|
|
|
@@ -94,18 +170,20 @@ Or run it directly from a local checkout:
|
|
|
94
170
|
pi -e ./src/index.ts
|
|
95
171
|
```
|
|
96
172
|
|
|
97
|
-
##
|
|
173
|
+
## Commands and configuration
|
|
98
174
|
|
|
99
|
-
The plugin works with its defaults, including the contemplator and reviewer. Contemplator probes and review notices appear as purple cards in the chat by default; use `/om:settings messages off` to hide newly sent cards without stopping their delivery to the agent.
|
|
175
|
+
The plugin works with its defaults, including the contemplator and reviewer. Contemplator probes and review notices appear as purple cards in the chat by default; use `/om:settings messages off` to hide newly sent cards without stopping their delivery to the agent.
|
|
100
176
|
|
|
101
|
-
Useful commands
|
|
177
|
+
Useful commands:
|
|
102
178
|
|
|
103
|
-
- `/om:status` — show memory and background-agent status.
|
|
104
|
-
- `/om:view
|
|
179
|
+
- `/om:status` — show memory and background-agent status, including how much recorded memory is still visible vs. pending.
|
|
180
|
+
- `/om:view` — inspect visible memory (and attempt to copy it).
|
|
181
|
+
- `/om:view full` — inspect the full memory ledger, including everything not yet folded into a compaction.
|
|
182
|
+
- `/om:view contemplator` — inspect the contemplator's private transcript and probes.
|
|
105
183
|
- `/om:view reviewer` — inspect structural reviewer transcripts and outcomes.
|
|
106
|
-
- `/om:settings` — inspect or change session-level settings.
|
|
184
|
+
- `/om:settings` — inspect or change session-level settings (including `messages on|off`, `reviewer on|off`, `compaction on|off`).
|
|
107
185
|
|
|
108
|
-
See [docs/how-it-works.md](docs/how-it-works.md) for the memory lifecycle and
|
|
186
|
+
Model selection, trigger thresholds, passive mode (which stops all background work), compaction behavior, and other tuning are documented in [docs/configuration.md](docs/configuration.md). See [docs/how-it-works.md](docs/how-it-works.md) for the memory lifecycle and [docs/concepts.md](docs/concepts.md) for the mental model.
|
|
109
187
|
|
|
110
188
|
## Development
|
|
111
189
|
|
|
@@ -117,4 +195,4 @@ npm run typecheck
|
|
|
117
195
|
|
|
118
196
|
## License
|
|
119
197
|
|
|
120
|
-
MIT. See [LICENSE](LICENSE).
|
|
198
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthewfl/pi-contemplator",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A Pi extension that keeps long-running agentic sessions on track with background memory, contemplation, and structural review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -5,7 +5,7 @@ import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
|
5
5
|
import { generateSummaryWithUsage } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
8
|
-
import { assistantOutputTokens, assistantToolCallCount, fullProjection, isReviewRequestEntry, isReviewResultEntry, OM_REVIEWER_MESSAGE, OM_REVIEWER_NOTICE, OM_REVIEWER_STATE, OM_REVIEW_REQUEST, OM_REVIEW_RESULT, type Entry, type ReviewResult, type StructuralReviewRequest } from "../../session-ledger/index.js";
|
|
8
|
+
import { agentActiveTimeMs, assistantOutputTokens, assistantToolCallCount, fullProjection, isReviewRequestEntry, isReviewResultEntry, OM_AGENT_ACTIVITY, OM_REVIEWER_MESSAGE, OM_REVIEWER_NOTICE, OM_REVIEWER_STATE, OM_REVIEW_REQUEST, OM_REVIEW_RESULT, type Entry, type ReviewResult, type StructuralReviewRequest } from "../../session-ledger/index.js";
|
|
9
9
|
import { hashId } from "../../ids.js";
|
|
10
10
|
import { createSearchMemoriesAgentTool } from "../../tools/search-memories.js";
|
|
11
11
|
import { createRecallAgentTool } from "../../tools/recall-observation.js";
|
|
@@ -22,6 +22,7 @@ interface PendingUpdate {
|
|
|
22
22
|
reviews: string[];
|
|
23
23
|
mainAgentOutputTokens: number;
|
|
24
24
|
mainAgentToolCalls: number;
|
|
25
|
+
mainAgentActiveTimeMs: number;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
type Intervention =
|
|
@@ -90,6 +91,19 @@ function customMessageText(content: unknown): string {
|
|
|
90
91
|
.filter(Boolean)
|
|
91
92
|
.join("\n");
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
const AGENT_TIME_BUCKET_MINUTES = 5;
|
|
96
|
+
|
|
97
|
+
function coarseAgentTime(durationMs: number): string {
|
|
98
|
+
const totalMinutes = Math.floor(durationMs / 60_000);
|
|
99
|
+
const bucketMinutes = Math.floor(totalMinutes / AGENT_TIME_BUCKET_MINUTES) * AGENT_TIME_BUCKET_MINUTES;
|
|
100
|
+
if (bucketMinutes < AGENT_TIME_BUCKET_MINUTES) return `less than ${AGENT_TIME_BUCKET_MINUTES} minutes`;
|
|
101
|
+
const hours = Math.floor(bucketMinutes / 60);
|
|
102
|
+
const minutes = bucketMinutes % 60;
|
|
103
|
+
if (hours === 0) return `about ${minutes} minutes`;
|
|
104
|
+
const hourLabel = `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
105
|
+
return minutes === 0 ? `about ${hourLabel}` : `about ${hourLabel} ${minutes} minutes`;
|
|
106
|
+
}
|
|
93
107
|
const CONTEMPLATOR_MESSAGE = "om.contemplator.message";
|
|
94
108
|
const CONTEMPLATOR_STATE = "om.contemplator.state";
|
|
95
109
|
const CONTEMPLATOR_SUGGESTION = "om.contemplator.suggestion";
|
|
@@ -160,6 +174,8 @@ export class Contemplator {
|
|
|
160
174
|
private latestCtx: MemoryUpdateCtx | undefined;
|
|
161
175
|
private turnsSinceRun = 0;
|
|
162
176
|
private restoredTipId: string | undefined;
|
|
177
|
+
/** Start of the unpersisted portion of the current main-agent run. */
|
|
178
|
+
private agentActiveSince: number | undefined;
|
|
163
179
|
|
|
164
180
|
constructor(private readonly pi: ExtensionAPI, private readonly runtime: Runtime) {}
|
|
165
181
|
|
|
@@ -176,8 +192,16 @@ export class Contemplator {
|
|
|
176
192
|
return box;
|
|
177
193
|
});
|
|
178
194
|
this.runtime.setMemoryUpdateListener((ctx) => this.withDebugContext(ctx, () => this.observeTurn(ctx)));
|
|
195
|
+
this.pi.on("agent_start", () => {
|
|
196
|
+
this.agentActiveSince = Date.now();
|
|
197
|
+
});
|
|
198
|
+
this.pi.on("agent_end", (_event: unknown, ctx: ExtensionContext) => {
|
|
199
|
+
this.persistAgentActivity(ctx);
|
|
200
|
+
this.agentActiveSince = undefined;
|
|
201
|
+
});
|
|
179
202
|
this.pi.on("session_start", (event: any, ctx: ExtensionContext) => {
|
|
180
203
|
this.sessionGeneration++;
|
|
204
|
+
this.agentActiveSince = undefined;
|
|
181
205
|
// AgentSession preserves its steering queue across extension reloads. An
|
|
182
206
|
// undelivered tracking entry therefore still has a live queued message;
|
|
183
207
|
// restoring it here would enqueue the same probe a second time.
|
|
@@ -186,11 +210,13 @@ export class Contemplator {
|
|
|
186
210
|
});
|
|
187
211
|
this.pi.on("session_tree", (_event: any, ctx: ExtensionContext) => {
|
|
188
212
|
this.sessionGeneration++;
|
|
213
|
+
this.agentActiveSince = undefined;
|
|
189
214
|
// Pending steering messages remain queued while navigating the tree.
|
|
190
215
|
this.restore(ctx, true, true);
|
|
191
216
|
});
|
|
192
217
|
this.pi.on("session_shutdown", () => {
|
|
193
218
|
this.sessionGeneration++;
|
|
219
|
+
this.agentActiveSince = undefined;
|
|
194
220
|
this.history = [];
|
|
195
221
|
this.pending = undefined;
|
|
196
222
|
this.seenObservationIds.clear();
|
|
@@ -234,11 +260,23 @@ export class Contemplator {
|
|
|
234
260
|
}
|
|
235
261
|
});
|
|
236
262
|
this.pi.on("turn_end", (_event: any, ctx: ExtensionContext) => {
|
|
263
|
+
this.persistAgentActivity(ctx);
|
|
237
264
|
this.turnsSinceRun++;
|
|
238
265
|
this.withDebugContext(ctx, () => this.observeTurn(ctx));
|
|
239
266
|
});
|
|
240
267
|
}
|
|
241
268
|
|
|
269
|
+
private persistAgentActivity(ctx: MemoryUpdateCtx): void {
|
|
270
|
+
const startedAt = this.agentActiveSince;
|
|
271
|
+
if (startedAt === undefined) return;
|
|
272
|
+
const endedAt = Date.now();
|
|
273
|
+
this.agentActiveSince = endedAt;
|
|
274
|
+
const durationMs = Math.max(0, endedAt - startedAt);
|
|
275
|
+
if (durationMs === 0) return;
|
|
276
|
+
this.pi.appendEntry(OM_AGENT_ACTIVITY, { version: 1, durationMs, endedAt });
|
|
277
|
+
debugLog("agent.activity_recorded", { durationMs });
|
|
278
|
+
}
|
|
279
|
+
|
|
242
280
|
private withDebugContext<T>(ctx: MemoryUpdateCtx, fn: () => T): T {
|
|
243
281
|
this.runtime.ensureConfig(ctx.cwd);
|
|
244
282
|
const sessionManager = ctx.sessionManager as { getSessionId?: () => string; getSessionFile?: () => string };
|
|
@@ -359,7 +397,8 @@ export class Contemplator {
|
|
|
359
397
|
debugLog("contemplator.skipped", { reason: "passive" });
|
|
360
398
|
return;
|
|
361
399
|
}
|
|
362
|
-
const
|
|
400
|
+
const branchEntries = ctx.sessionManager.getBranch() as Entry[];
|
|
401
|
+
const projection = fullProjection(branchEntries);
|
|
363
402
|
const observations = projection.observations.map((item) => `[${item.id}] ${item.content}`);
|
|
364
403
|
const reflections = projection.reflections.map((item) => `[${item.id}] ${item.content}`);
|
|
365
404
|
const reviews = projection.reviews ?? [];
|
|
@@ -387,11 +426,18 @@ export class Contemplator {
|
|
|
387
426
|
observations: mergeMemoryLines(this.pending?.observations ?? [], newObservations),
|
|
388
427
|
reflections: mergeMemoryLines(this.pending?.reflections ?? [], newReflections),
|
|
389
428
|
reviews: mergeMemoryLines(this.pending?.reviews ?? [], newReviews),
|
|
390
|
-
mainAgentOutputTokens: assistantOutputTokens(
|
|
391
|
-
mainAgentToolCalls: assistantToolCallCount(
|
|
429
|
+
mainAgentOutputTokens: assistantOutputTokens(branchEntries),
|
|
430
|
+
mainAgentToolCalls: assistantToolCallCount(branchEntries),
|
|
431
|
+
mainAgentActiveTimeMs: agentActiveTimeMs(branchEntries),
|
|
392
432
|
};
|
|
393
433
|
}
|
|
394
434
|
if (!this.pending) return;
|
|
435
|
+
// Activity values are cumulative send-time snapshots, not values frozen when
|
|
436
|
+
// the first memory entered a pending batch. This includes work performed
|
|
437
|
+
// while that batch waits for its memory/turn thresholds.
|
|
438
|
+
this.pending.mainAgentOutputTokens = assistantOutputTokens(branchEntries);
|
|
439
|
+
this.pending.mainAgentToolCalls = assistantToolCallCount(branchEntries);
|
|
440
|
+
this.pending.mainAgentActiveTimeMs = agentActiveTimeMs(branchEntries);
|
|
395
441
|
const enoughMemories = this.pending.reviews.length > 0 || this.pending.observations.length >= this.runtime.config.contemplatorMinNewObservations || this.pending.reflections.length >= this.runtime.config.contemplatorMinNewReflections;
|
|
396
442
|
if (!enoughMemories || this.turnsSinceRun < this.runtime.config.contemplatorMinTurns) {
|
|
397
443
|
debugLog("contemplator.waiting", {
|
|
@@ -452,6 +498,7 @@ export class Contemplator {
|
|
|
452
498
|
reviews: mergeMemoryLines(pending?.reviews ?? [], update.reviews),
|
|
453
499
|
mainAgentOutputTokens: update.mainAgentOutputTokens,
|
|
454
500
|
mainAgentToolCalls: update.mainAgentToolCalls,
|
|
501
|
+
mainAgentActiveTimeMs: update.mainAgentActiveTimeMs,
|
|
455
502
|
};
|
|
456
503
|
this.turnsSinceRun = turnsBeforeRun;
|
|
457
504
|
}
|
|
@@ -476,7 +523,7 @@ export class Contemplator {
|
|
|
476
523
|
const interventionInstruction = reviewerEnabled
|
|
477
524
|
? "Use send_probe for one focused question, or request_review only when a deeper workflow or software review is justified. Use no more than one intervention."
|
|
478
525
|
: "Use send_probe only when one focused question is materially useful. Use no more than one intervention.";
|
|
479
|
-
const prompt: Message = { role: "user", content: [{ type: "text", text: `NEW MEMORY UPDATE\n\n${updateBody}\n\
|
|
526
|
+
const prompt: Message = { role: "user", content: [{ type: "text", text: `NEW MEMORY UPDATE\n\n${updateBody}\n\nCUMULATIVE ACTIVITY: ${update.mainAgentOutputTokens} generated tokens; ${update.mainAgentToolCalls} tool calls; ${coarseAgentTime(update.mainAgentActiveTimeMs)} active.\n\nConsider these updates in the context of the accumulated memories. Prioritize reasoning gaps, contradictions, user-intent alignment, relevant overlooked alternatives, well-supported loops, and recurring structural patterns. ${interventionInstruction}` }], timestamp: Date.now() };
|
|
480
527
|
promptMessage = prompt;
|
|
481
528
|
this.history.push(prompt);
|
|
482
529
|
let intervention: Intervention | undefined;
|
|
@@ -578,6 +625,7 @@ export class Contemplator {
|
|
|
578
625
|
reviews: mergeMemoryLines(pending?.reviews ?? [], update.reviews),
|
|
579
626
|
mainAgentOutputTokens: update.mainAgentOutputTokens,
|
|
580
627
|
mainAgentToolCalls: update.mainAgentToolCalls,
|
|
628
|
+
mainAgentActiveTimeMs: update.mainAgentActiveTimeMs,
|
|
581
629
|
};
|
|
582
630
|
this.turnsSinceRun = turnsBeforeRun;
|
|
583
631
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { observationPoolMetrics } from "../agents/dropper/pool.js";
|
|
|
3
3
|
import { resolveCompactAfterTokens } from "../config.js";
|
|
4
4
|
import type { Runtime } from "../runtime.js";
|
|
5
5
|
import {
|
|
6
|
+
agentActiveTimeMs,
|
|
6
7
|
diffProjection,
|
|
7
8
|
foldLedger,
|
|
8
9
|
fullProjection,
|
|
@@ -26,6 +27,16 @@ function formatTokens(n: number): string {
|
|
|
26
27
|
return String(n);
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function formatDuration(durationMs: number): string {
|
|
31
|
+
const totalSeconds = Math.floor(durationMs / 1_000);
|
|
32
|
+
const hours = Math.floor(totalSeconds / 3_600);
|
|
33
|
+
const minutes = Math.floor((totalSeconds % 3_600) / 60);
|
|
34
|
+
const seconds = totalSeconds % 60;
|
|
35
|
+
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
|
36
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
37
|
+
return `${seconds}s`;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
function truncateStatusText(value: string, limit = 1_000): string {
|
|
30
41
|
return value.length <= limit ? value : `${value.slice(0, limit - 1)}…`;
|
|
31
42
|
}
|
|
@@ -99,6 +110,7 @@ export function registerStatusCommand(pi: ExtensionAPI, runtime: Runtime): void
|
|
|
99
110
|
`Visible observation pool: ~${visibleObservationTokens.toLocaleString()} / ${runtime.config.observationsPoolMaxTokens.toLocaleString()} tokens (${pct(visibleObservationTokens, runtime.config.observationsPoolMaxTokens)}%)`,
|
|
100
111
|
`Active observation pool: ~${activeObservationPool.observationTokens.toLocaleString()} / ${runtime.config.observationsPoolTargetTokens.toLocaleString()} target tokens (${pct(activeObservationPool.observationTokens, runtime.config.observationsPoolTargetTokens)}%)`,
|
|
101
112
|
`Reflection pool: ~${visibleReflectionTokens.toLocaleString()} tokens`,
|
|
113
|
+
`Cumulative agent time: ${formatDuration(agentActiveTimeMs(entries))}`,
|
|
102
114
|
`Compaction observer: ${runtime.config.compactionObserverEnabled === false ? "disabled" : "enabled"}`,
|
|
103
115
|
`Contemplator: ${runtime.config.contemplatorEnabled ? "enabled" : "disabled"}`,
|
|
104
116
|
`Contemplator model: ${runtime.config.contemplatorModel ? `${runtime.config.contemplatorModel.provider}/${runtime.config.contemplatorModel.id}` : "current session model"}`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { estimateEntryTokens } from "../tokens.js";
|
|
2
2
|
import {
|
|
3
|
+
OM_AGENT_ACTIVITY,
|
|
3
4
|
OM_OBSERVATIONS_DROPPED,
|
|
4
5
|
OM_OBSERVATIONS_RECORDED,
|
|
5
6
|
OM_REFLECTIONS_RECORDED,
|
|
@@ -52,6 +53,17 @@ export function assistantToolCallCount(entries: Entry[]): number {
|
|
|
52
53
|
return total;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/** Sum persisted main-agent active wall-clock time on the current branch. */
|
|
57
|
+
export function agentActiveTimeMs(entries: Entry[]): number {
|
|
58
|
+
let total = 0;
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (entry.type !== "custom" || entry.customType !== OM_AGENT_ACTIVITY || !isObject(entry.data)) continue;
|
|
61
|
+
const durationMs = entry.data.durationMs;
|
|
62
|
+
if (typeof durationMs === "number" && Number.isFinite(durationMs) && durationMs >= 0) total += durationMs;
|
|
63
|
+
}
|
|
64
|
+
return total;
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
56
68
|
return typeof value === "object" && value !== null;
|
|
57
69
|
}
|
|
@@ -9,6 +9,8 @@ export const OM_REVIEWER_MESSAGE = "om.reviewer.message";
|
|
|
9
9
|
export const OM_REVIEWER_STATE = "om.reviewer.state";
|
|
10
10
|
/** Compact proposal notice queued for the primary agent. */
|
|
11
11
|
export const OM_REVIEWER_NOTICE = "om.reviewer.notice";
|
|
12
|
+
/** Main-agent active wall-clock time, excluding idle waits for user input. */
|
|
13
|
+
export const OM_AGENT_ACTIVITY = "om.agent.activity";
|
|
12
14
|
export const OM_FOLDED = "om.folded";
|
|
13
15
|
|
|
14
16
|
export const RELEVANCE_VALUES = ["low", "medium", "high", "critical"] as const;
|