refactor(context): transition IR to immutable Concrete/Logical split

This commit is contained in:
Your Name
2026-04-07 23:24:34 +00:00
parent 19c39885e7
commit cd14eb40ce
+116 -117
View File
@@ -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<string, Variant>;
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<string>;
}
/**
* 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<SemanticPart>;
}
export interface SystemEvent extends IrNode {
export interface SystemEvent extends BaseConcreteNode {
readonly type: 'SYSTEM_EVENT';
name: string;
payload: Record<string, unknown>;
readonly name: string;
readonly payload: Record<string, unknown>;
}
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<string, unknown>;
/** The result returned by the tool (The 'FunctionResponse') */
observation: string | Record<string, unknown>;
/** 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<string, unknown>;
observation?: string | Record<string, unknown>;
tokens: {
intent: number;
observation: number;
};
readonly toolName: string;
readonly intent: Record<string, unknown>;
readonly observation: string | Record<string, unknown>;
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<string, unknown>;
readonly observation?: string | Record<string, unknown>;
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<string, Variant>;
/** 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<string>;
}
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<string>;
}
export type LogicalNode = Task | Episode;