Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8780d6a
refactor(agent-core-v2): carry the context fold cursor in state and c…
7Sageer Aug 13, 2026
2d41945
test(agent-core-v2): move fold parity rationales into the test file h…
7Sageer Aug 13, 2026
e83ad3f
docs(agent-core-v2): move fold declaration comments into module headers
7Sageer Aug 13, 2026
2b47fe7
refactor(agent-core-v2): merge FoldFrame into generic ContextState
7Sageer Aug 13, 2026
e7fe16e
refactor(agent-core-v2): compile-enforce part/event handling decision…
7Sageer Aug 13, 2026
55b9e0b
refactor(agent-core-v2): converge undo-cut decision in conversationTime
7Sageer Aug 14, 2026
c5cf562
fix(agent-core-v2): drop removed prompts' injections on multi-turn tr…
7Sageer Aug 14, 2026
a8ee42c
refactor(agent-core-v2): accumulate request projection repairs as policy
7Sageer Aug 14, 2026
5ddab0c
refactor(agent-core-v2): derive the visible context window from an ap…
7Sageer Aug 14, 2026
8892e84
fix(agent-core-v2): rehydrate exactly the messages the visible window…
7Sageer Aug 14, 2026
5ba63a4
fix(agent-core-v2): pop the visible-tail swarm reminder behind a lega…
7Sageer Aug 14, 2026
a7a0b45
fix(agent-core-v2): settle open transcript frames when compaction lan…
7Sageer Aug 14, 2026
29c0a54
fix(agent-core-v2): settle open frames at the compaction marker
7Sageer Aug 17, 2026
51f31ae
Merge remote-tracking branch 'origin/main' into refactor-context
7Sageer Aug 17, 2026
641157e
refactor(agent-core-v2): tighten naming and comments in context memor…
7Sageer Aug 17, 2026
2c36afe
fix(agent-core-v2): preserve bounded context state
7Sageer Aug 17, 2026
a085428
Merge remote-tracking branch 'origin/main' into refactor-context
7Sageer Aug 17, 2026
450645f
docs(agent-core-v2): restore the domain identity line in the compacti…
7Sageer Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/agent-core-v2/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ One accepted exception: `features/tower/protocol` manages the `.tower/` director

## Conversation undo

`context.undo` is the only persisted undo fact. `contextMemory/conversationTime.ts` owns the conversation clock (`isUndoAnchor` — the single tick predicate used by `computeUndoCut`, the checkpoint reducers, and the transcript reducer) and the checkpoint protocol. A wire Model whose state must follow conversation undo (todo, plan, task-notification delivery, …) **MUST** be defined with `defineCheckpointedModel` — never hand-roll the push/clear/restore reducers — which also registers it into `CHECKPOINTED_MODELS` for the undo pipeline's pre-cut depth check. World-time state (turn counters, task registries, revision counters) must stay outside checkpointed Models.
`context.undo` is the only persisted undo fact. `contextMemory/conversationTime.ts` owns the conversation clock (`isUndoAnchor` — the single tick predicate — plus `computeUndoCut` / `computeUndoCutFrom`, the single anchor-walk decision: applied destructively by the `context.undo` Op and non-destructively by the transcript reducer, so a blocked undo reads identically on both sides) and the checkpoint protocol. A wire Model whose state must follow conversation undo (todo, plan, task-notification delivery, …) **MUST** be defined with `defineCheckpointedModel` — never hand-roll the push/clear/restore reducers — which also registers it into `CHECKPOINTED_MODELS` for the undo pipeline's pre-cut depth check. World-time state (turn counters, task registries, revision counters) must stay outside checkpointed Models.

## Model-facing reminders

Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core-v2/docs/wire-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ interface LlmRequestPayload {
messageCount: number;
turnStep?: string;
attempt?: string;
projection?: 'strict' | 'media-degraded' | 'media-stripped';
projection?: 'strict' | 'media-degraded' | 'media-stripped' | 'strict-media-degraded' | 'strict-media-stripped';
droppedCount?: number;
}

Expand Down
29 changes: 10 additions & 19 deletions packages/agent-core-v2/src/agent/contextMemory/compactionHandoff.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
/**
* `contextMemory` domain helper — derives the v1-compatible full-compaction
* handoff shape for live rewrites, wire replay, and snapshot reducers.
*
* Token budgeting runs through an injectable {@link TokenEstimate}: the live
* path (`AgentContextMemoryService.applyCompaction`) passes the estimator
* from `IAgentTokenCountingService` (the raw heuristics — the
* `[token_counting]` strategy never gates internal estimates); the pure
* wire-replay / reducer paths keep the same heuristics — their estimate
* fallback only fires when a record lacks `tokensAfter`, so the measured
* chain is unaffected.
* Builds the bounded context window produced by compaction and exposes the
* shared user-message selection rules used by live execution and replay.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the contextMemory identity in the header

This rewritten header no longer begins with the required `contextMemory` domain — ... identity line and omits the helper’s cross-domain roles, including token estimation through kosong and reminder rendering through systemReminder. Restore those ownership details in the top-of-file block so the module remains discoverable under the package’s mandatory header convention.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L38-L40

Useful? React with 👍 / 👎.

*/

import { estimateTokens, estimateTokensForMessage, estimateTokensForMessages } from '#/kosong/contract/tokens';
Expand All @@ -24,7 +16,6 @@ export const COMPACTION_ELISION_VARIANT = 'compaction_elision';

type MessageLike = ContextMessage;

/** Injectable token-count estimates; see the file header for who passes what. */
export interface TokenEstimate {
readonly text: (text: string) => number;
readonly message: (message: MessageLike) => number;
Expand All @@ -51,15 +42,7 @@ export interface ContextCompactionShapeInput {
readonly compactedCount: number;
readonly tokensBefore: number;
readonly tokensAfter?: number;
/** Measured output tokens of the compaction LLM exchange — the REAL size of
* the generated summary. Preferred over the summary-text estimate in the
* `tokensAfter` fallback when present. */
readonly summaryOutputTokens?: number;
/** Estimated fixed request overhead (system prompt + non-deferred tool
* schemas) surviving the compaction; counted into the `tokensAfter`
* fallback so the result stays on the same full-request basis as the
* measured exchange anchors. Live path only — replay reads the persisted
* `tokensAfter` verbatim. */
readonly requestOverheadTokens?: number;
readonly keptUserMessageCount?: number;
readonly keptHeadUserMessageCount?: number;
Expand Down Expand Up @@ -139,6 +122,14 @@ export function buildContextCompactionShape(
};
}

export function compactedWindowMessageCount(
keptUserMessageCount: number | undefined,
keptHeadUserMessageCount: number | undefined,
): number | undefined {
if (keptUserMessageCount === undefined) return undefined;
return keptUserMessageCount + (keptHeadUserMessageCount === undefined ? 1 : 2);
}

export function buildCompactionSummaryText(summary: string): string {
const suffix = summary.trim();
return `${COMPACTION_SUMMARY_PREFIX}\n${suffix.length > 0 ? suffix : '(no summary available)'}`;
Expand Down
16 changes: 8 additions & 8 deletions packages/agent-core-v2/src/agent/contextMemory/contextMemory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/**
* Declares the Agent-scoped context-memory contract and compaction handoff
* data shared by request, token-counting, and persistence consumers.
*/

import { createDecorator } from "#/_base/di/instantiation";

import type { UndoCut } from './contextOps';
import type { UndoCut } from './conversationTime';
import type { LoopRecordedEvent } from './loopEventFold';
import type { ContextMessage } from './types';

Expand All @@ -10,14 +15,7 @@ export interface ContextCompactionInput {
readonly compactedCount: number;
readonly tokensBefore: number;
readonly tokensAfter?: number;
/** Measured output tokens of the compaction LLM exchange (the REAL summary
* size); preferred over the summary-text estimate in the `tokensAfter`
* fallback when present. */
readonly summaryOutputTokens?: number;
/** Estimated fixed request overhead (system prompt + non-deferred tool
* schemas) that every post-compaction exchange still carries. Counted into
* the `tokensAfter` fallback so the result stays on the same full-request
* basis as the measured exchange anchors. */
readonly requestOverheadTokens?: number;
readonly keptUserMessageCount?: number;
readonly keptHeadUserMessageCount?: number;
Expand All @@ -40,6 +38,8 @@ export interface IAgentContextMemoryService {

get(): readonly ContextMessage[];

getMessageLog(): readonly ContextMessage[];

append(...messages: readonly ContextMessage[]): void;

appendLoopEvent(event: LoopRecordedEvent): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
/**
* `contextMemory` domain — `IAgentContextMemoryService` implementation.
*
* Owns per-agent conversation history through `wire`, maintains measurements
* with `tokenCounting`, and broadcasts live mutations through `event`. Every
* splice-shaped mutation (`clear` / `applyCompaction` / `undo`, plus verified
* cross-model trailing removal) publishes `context.spliced` from the live path
* only — replay rebuilds silently — and truncates the measured-anchor ledger
* when a cut crosses an anchor, letting `tokenCounting` restore the surviving
* prefix's REAL size from the remaining anchors. Bound at Agent scope.
* Owns the Agent-scoped bounded context window, persists its mutations, keeps
* token anchors aligned, and publishes live splice events to consumers.
*/
Comment on lines 1 to 11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the required domain implementation header

The shortened header no longer starts with the required `contextMemory` domain — ... identity line and omits the implementation’s cross-domain collaborators (wire, tokenCounting, and event) and Agent binding scope. Restore those boundary details here, and in the other source headers shortened by this change, so the module documentation follows the package convention.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L38-L40

Useful? React with 👍 / 👎.


import { Disposable } from '#/_base/di/lifecycle';
Expand All @@ -30,16 +23,18 @@ import {
} from './contextMemory';
import { buildContextCompactionShape, type TokenEstimate } from './compactionHandoff';
import {
computeUndoCut,
ContextModel,
contextAppendLoopEvent,
contextAppendMessage,
contextApplyCompaction,
contextClear,
contextUndo,
} from './contextOps';
import {
computeUndoCut,
isFullyUndoable,
type UndoCut,
} from './contextOps';
} from './conversationTime';
import type { LoopRecordedEvent } from './loopEventFold';
import type { ContextMessage } from './types';

Expand All @@ -54,7 +49,6 @@ declare module '#/app/event/eventBus' {
}
}

// NOTE: stays Disposable — its own 'get' collides with the Fiber
export class AgentContextMemoryService extends Disposable implements IAgentContextMemoryService {
declare readonly _serviceBrand: undefined;

Expand All @@ -75,7 +69,11 @@ export class AgentContextMemoryService extends Disposable implements IAgentConte
}

get(): readonly ContextMessage[] {
return this.wire.getModel(ContextModel) as readonly ContextMessage[];
return this.getMessageLog();
}

getMessageLog(): readonly ContextMessage[] {
return this.wire.getModel(ContextModel).messages as readonly ContextMessage[];
}

append(...messages: readonly ContextMessage[]): void {
Expand Down Expand Up @@ -172,9 +170,6 @@ export class AgentContextMemoryService extends Disposable implements IAgentConte
private sizeOpsForCut(cutIndex: number): Op[] {
const model = this.wire.getModel(TokenCountingModel);
if (!model.anchors.some((anchor) => anchor.length > cutIndex)) return [];
// The display tokens are the post-cut size computed from the CURRENT
// ledger — anchors at or below the cut are identical before and after
// the truncation, so the pre-dispatch read is exact.
return [
tokenCountingTruncated({
length: cutIndex,
Expand Down
Loading
Loading