Core Concepts¶
Architecture Overview¶
graph LR
Clock[Clock Tick] --> Sensor[PreExec Sensor]
Sensor --> SP[StateProvider]
SP --> Goals[Goals]
SP --> Context[Recent Context]
SP --> Mem[Memories]
Goals --> Emb[Embedder]
Context --> Emb
Mem --> Emb
Emb --> Metrics[MetricFn]
Metrics --> CI[Coherence Index]
CI --> Band[Band Classification]
Band --> Reading[CoherenceReading]
ScalarMetrics[ScalarMetricFn] --> CI
The sensor pipeline runs on every clock tick:
- StateProvider gathers the agent's current goals, context, and memories
- Embedder converts text into vectors
- MetricFn[] compute metrics from the embeddings (strategy pattern)
- ScalarMetricFn[] compute metrics directly from agent state (no embeddings)
- Coherence index is computed as a weighted sum of all metrics
- Band classification maps the index to green/yellow/orange/red
Interoception¶
Interoception means "internal sensing" — the ability to sense one's own internal state. In biology, it's how your body knows it's hungry, tired, or stressed.
For AI agents, interoception answers: "Am I thinking straight right now?"
The sensor measures four dimensions of coherence before the agent acts (pre-execution), giving you a chance to pause, re-plan, or alert when the agent is drifting.
Embedder¶
interface Embedder {
embed(text: string): Promise<number[]>;
embedBatch(texts: string[]): Promise<number[][]>;
readonly dimensions: number;
}
The embedder is pluggable — you bring your own implementation. It converts text into vectors that the metrics use to measure similarity, drift, and diffusion. The sensor deduplicates texts before calling embedBatch for efficiency.
StateProvider¶
interface StateProvider {
getGoals(): Promise<string[]>;
getRecentContext(): Promise<string[]>;
getGoalRelevantMemories(): Promise<string[]>;
getAllMemories(): Promise<string[]>;
}
The state provider is the bridge between your agent's internals and the sensor. All four methods return arrays of strings — the sensor handles embedding and metric computation from there.
MetricFn¶
interface MetricFn {
name: string;
inverted?: boolean;
compute(input: MetricInput): number;
}
Metrics follow the strategy pattern. Each has a name (used as the key in MetricSnapshot), an optional inverted flag for polarity, and a compute function that returns a value in [0, 1].
Polarity: The inverted flag declares whether higher values mean less coherent. Goal drift is inverted (1 = drifted = bad), while memory retention is not (1 = retained = good). The sensor reads these flags to build the inverted set automatically.
ScalarMetricFn¶
interface ScalarMetricFn {
name: string;
inverted?: boolean;
compute(): number | Promise<number>;
}
Scalar metrics don't need embeddings. They read directly from agent state — database lookups, API calls, counters, etc. They're async-safe and must return 0 if their data source is unavailable (noop-safe).
CoherenceReading¶
interface CoherenceReading {
ts: number;
tickSeq: number;
metrics: MetricSnapshot;
coherenceIndex: number;
band: Band;
}
The output of every sensor measurement. Contains all metric values, the weighted coherence index (0 = incoherent, 1 = coherent), and the band classification.
How They Fit Together¶
A clock ticks → the sensor calls your StateProvider to gather goals, context, and memories → passes everything through the Embedder → runs each MetricFn against the embeddings → runs each ScalarMetricFn independently → computes a weighted coherence index → classifies into a band → returns a CoherenceReading.
The sensor stores readings in a ring buffer (default 100), accessible via sensor.history(). The consumer decides what to do with the reading — pause, re-plan, log, alert, or continue.
Design Philosophy¶
- Pure measurement — the sensor has no side effects on the signal bus or agent behavior
- Pluggable everything — Embedder, StateProvider, MetricFn, and ScalarMetricFn are all consumer-provided
- Polarity as declaration — the
invertedflag is the single source of truth for metric polarity - Noop-safe — scalar metrics return 0 when data is unavailable; empty inputs produce 0, not errors
- Strategy pattern — swap, add, or remove metrics without changing the sensor