From cd14eb40ce1e3d2c2ec0a5bf0901966dc9bbb1e0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 7 Apr 2026 23:24:34 +0000 Subject: [PATCH] refactor(context): transition IR to immutable Concrete/Logical split --- packages/core/src/context/ir/types.ts | 233 +++++++++++++------------- 1 file changed, 116 insertions(+), 117 deletions(-) diff --git a/packages/core/src/context/ir/types.ts b/packages/core/src/context/ir/types.ts index e46aa20ca6..f635c9b96d 100644 --- a/packages/core/src/context/ir/types.ts +++ b/packages/core/src/context/ir/types.ts @@ -13,109 +13,100 @@ import type { Part } from '@google/genai'; */ export interface IrMetadata { /** The estimated number of tokens this entity originally consumed. */ - originalTokens: number; + readonly originalTokens: number; /** The current estimated number of tokens this entity consumes in its degraded state. */ - currentTokens: number; + readonly currentTokens: number; /** An audit trail of all transformations applied by ContextProcessors. */ - transformations: Array<{ - processorName: string; - action: + readonly transformations: ReadonlyArray<{ + readonly processorName: string; + readonly action: | 'MASKED' | 'TRUNCATED' | 'DEGRADED' | 'SUMMARIZED' | 'EVICTED' | 'SYNTHESIZED'; - timestamp: number; + readonly timestamp: number; /** Pointer to where the original uncompressed payload was saved (if applicable) */ - diskPointer?: string; + readonly diskPointer?: string; }>; } export type IrNodeType = + // Organic Concrete Nodes | 'USER_PROMPT' | 'SYSTEM_EVENT' | 'AGENT_THOUGHT' | 'TOOL_EXECUTION' - | 'AGENT_YIELD'; + | 'AGENT_YIELD' + + // Synthetic Concrete Nodes + | 'SNAPSHOT' + | 'ROLLING_SUMMARY' + | 'MASKED_TOOL' -/** Base interface for all nodes in the Episodic IR */ -export type VariantStatus = 'computing' | 'ready' | 'failed'; - -export interface BaseVariant { - status: VariantStatus; - recoveredTokens?: number; - error?: string; -} - -export interface SummaryVariant extends BaseVariant { - type: 'summary'; - text: string; -} - -export interface MaskedVariant extends BaseVariant { - type: 'masked'; - text: string; -} - -export interface SnapshotVariant extends BaseVariant { - type: 'snapshot'; - episode: Episode; - replacedEpisodeIds: string[]; -} - -export type Variant = SummaryVariant | MaskedVariant | SnapshotVariant; + // Logical Nodes + | 'TASK' + | 'EPISODE'; /** Base interface for all nodes in the Episodic IR */ export interface IrNode { readonly id: string; readonly type: IrNodeType; - metadata: IrMetadata; - variants?: Record; + readonly metadata: IrMetadata; +} + +/** + * Concrete Nodes: The atomic, renderable pieces of data. + * These are the actual "planks" of the Ship of Theseus. + */ +export interface BaseConcreteNode extends IrNode { + /** The ID of the Logical Node (e.g., Episode) that structurally owns this node */ + readonly logicalParentId?: string; + + /** If this node replaced a single node 1:1 (e.g., masking), this points to the original */ + readonly replacesId?: string; + + /** If this node is a synthetic summary of N nodes, this points to the original IDs */ + readonly abstractsIds?: ReadonlyArray; } /** * Semantic Parts for User Prompts - * Ensures we can safely truncate text without deleting multi-modal parts (like images). */ export type SemanticPart = | { - type: 'text'; - text: string; - presentation?: { text: string; tokens: number }; + readonly type: 'text'; + readonly text: string; } | { - type: 'inline_data'; - mimeType: string; - data: string; - presentation?: { text: string; tokens: number }; + readonly type: 'inline_data'; + readonly mimeType: string; + readonly data: string; } | { - type: 'file_data'; - mimeType: string; - fileUri: string; - presentation?: { text: string; tokens: number }; + readonly type: 'file_data'; + readonly mimeType: string; + readonly fileUri: string; } | { - type: 'raw_part'; - part: Part; - presentation?: { text: string; tokens: number }; + readonly type: 'raw_part'; + readonly part: Part; }; /** * Trigger Nodes * Events that wake the agent up and initiate an Episode. */ -export interface UserPrompt extends IrNode { +export interface UserPrompt extends BaseConcreteNode { readonly type: 'USER_PROMPT'; - /** The semantic breakdown of the user's multi-modal input */ - semanticParts: SemanticPart[]; + readonly semanticParts: ReadonlyArray; } -export interface SystemEvent extends IrNode { +export interface SystemEvent extends BaseConcreteNode { readonly type: 'SYSTEM_EVENT'; - name: string; - payload: Record; + readonly name: string; + readonly payload: Record; } export type EpisodeTrigger = UserPrompt | SystemEvent; @@ -124,82 +115,90 @@ export type EpisodeTrigger = UserPrompt | SystemEvent; * Step Nodes * The internal autonomous actions taken by the agent during its loop. */ -export interface AgentThought extends IrNode { +export interface AgentThought extends BaseConcreteNode { readonly type: 'AGENT_THOUGHT'; - text: string; - /** Overrides the rendered output for this thought */ - presentation?: { - text: string; - tokens: number; - }; + readonly text: string; } -export interface ToolExecution extends IrNode { +export interface ToolExecution extends BaseConcreteNode { readonly type: 'TOOL_EXECUTION'; - /** The name of the tool invoked */ - toolName: string; - - /** The arguments passed to the tool (The 'FunctionCall') */ - intent: Record; - - /** The result returned by the tool (The 'FunctionResponse') */ - observation: string | Record; - - /** Granular token tracking for the different lifecycle phases of the tool */ - tokens: { - intent: number; - observation: number; - }; - - /** - * The presentation layer. If defined, the IrMapper uses this instead of the - * raw observation to build the functionResponse. - * This preserves the immutable raw data for semantic queries while modifying the rendered output. - */ - presentation?: { - intent?: Record; - observation?: string | Record; - tokens: { - intent: number; - observation: number; - }; + readonly toolName: string; + readonly intent: Record; + readonly observation: string | Record; + readonly tokens: { + readonly intent: number; + readonly observation: number; }; } -export type EpisodeStep = AgentThought | ToolExecution; +export interface MaskedTool extends BaseConcreteNode { + readonly type: 'MASKED_TOOL'; + readonly toolName: string; + readonly intent?: Record; + readonly observation?: string | Record; + readonly tokens: { + readonly intent: number; + readonly observation: number; + }; +} + +export type EpisodeStep = AgentThought | ToolExecution | MaskedTool; /** * Resolution Node * The final message where the agent yields control back to the user. */ -export interface AgentYield extends IrNode { +export interface AgentYield extends BaseConcreteNode { readonly type: 'AGENT_YIELD'; - text: string; - presentation?: { - text: string; - tokens: number; - }; + readonly text: string; } /** - * The Episode - * A discrete, continuous run of the agent. Represents the full cycle from - * taking control (Trigger) to returning control (Yield), encompassing all - * internal reasoning and observations (Steps). + * Synthetic Leaf Interfaces + * Processors that generate summaries emit explicit synthetic nodes. */ -export interface Episode { - readonly type: 'EPISODE'; - readonly id: string; - /** When the episode began */ +export interface Snapshot extends BaseConcreteNode { + readonly type: 'SNAPSHOT'; readonly timestamp: number; - variants?: Record; - - /** The event that initiated this run */ - trigger: EpisodeTrigger; - - /** The sequence of autonomous actions and observations */ - steps: EpisodeStep[]; - - /** The final handover back to the user (can be undefined if the episode was aborted/errored) */ - yield?: AgentYield; + readonly text: string; } + +export interface RollingSummary extends BaseConcreteNode { + readonly type: 'ROLLING_SUMMARY'; + readonly timestamp: number; + readonly text: string; +} + +export type SyntheticLeaf = Snapshot | RollingSummary; + +export type ConcreteNode = + | UserPrompt + | SystemEvent + | AgentThought + | ToolExecution + | MaskedTool + | AgentYield + | Snapshot + | RollingSummary; + +/** + * Logical Nodes + * These define hierarchy and grouping. They do not directly render to Gemini. + */ +export interface Episode extends IrNode { + readonly type: 'EPISODE'; + readonly timestamp: number; + /** References to the Concrete Node IDs that conceptually belong to this Episode. */ + readonly concreteNodeIds: ReadonlyArray; +} + +export interface Task extends IrNode { + readonly type: 'TASK'; + readonly timestamp: number; + readonly goal: string; + readonly status: 'active' | 'completed' | 'failed'; + /** References to the Episode IDs that belong to this task */ + readonly episodeIds: ReadonlyArray; +} + +export type LogicalNode = Task | Episode;