agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
75 lines (74 loc) • 3.77 kB
TypeScript
/**
* writeMessages — write-side stage that persists new turn messages as
* `MemoryEntry`s.
*
* Reads from scope: `identity`, `turnNumber`, `newMessages`
* Writes to store: one entry per message, id = `msg-{turnNumber}-{index}`
*
* Every written entry carries `source.turn` + `source.identity` so
* retrieval stages can later show "recalled from turn 5" with correct
* cross-session provenance. A content-hash signature is also registered
* via `store.recordSignature` so subsequent `seen()` calls recognize
* near-duplicate content without loading the full entries.
*
* Why a deterministic `id` format?
* `msg-{turn}-{index}` lets write-then-re-write be idempotent: a stage
* that re-runs in the same turn (retries, resumable turns) overwrites
* the same id instead of growing history. For non-turn-scoped writes,
* pass a custom `idFrom` that produces whatever shape your app needs.
*
* PII / redaction:
* Message content is stored VERBATIM — a memory is the agent's working
* state, what a later run recalls, and no redaction policy scrubs it
* (`../redactRefusal.ts` says why `defineMemory({ redact })` is refused).
* An agent's `redact` keeps named values out of the RECORD of this stage
* (its commit, its `memory.*` events, the narrative); it does not change
* what the store holds. If messages that contain PII must not be stored,
* do not write them (a read-only memory, or none), or scrub them before
* they reach the store — a `MemoryStore` wrapper whose `put` does it.
*
* Extended thinking blocks (Anthropic) / reasoning tokens (OpenAI):
* Persisting reasoning blocks is expensive — they can be 10-100× the
* size of the final message. Strip them from `scope.newMessages` before
* calling this stage UNLESS you plan to recall them (e.g. for debugging
* replay). A common pattern: write reasoning to `tier: 'cold'` with a
* short `ttlMs` so they age out quickly.
*/
import type { TypedScope } from 'footprintjs';
import type { MemoryStore } from '../store/index.js';
import type { LLMMessage as Message } from '../../provider-contract/types.js';
import type { MemoryState } from './types.js';
export interface WriteMessagesConfig {
/** The store to persist to. */
readonly store: MemoryStore;
/**
* Optional id producer — receives (turn, index, message) and returns
* the `MemoryEntry.id`. Defaults to `msg-{turn}-{index}` which makes
* re-runs of the same turn idempotent. Override for app-level ids
* (e.g. use a message's server-side id).
*/
readonly idFrom?: (turn: number, index: number, message: Message) => string;
/**
* Optional signature producer for the recognition set. When present,
* each message produces a signature that is registered via
* `store.recordSignature`; `seen()` later returns `true` for the same
* content. Default: skip (many apps don't need recognition).
*/
readonly signatureFrom?: (message: Message) => string;
/**
* Optional TTL in milliseconds from `Date.now()`. When set, written
* entries expire this long after they were stored. Useful for
* compliance retention windows ("delete chat history after 30 days").
*/
readonly ttlMs?: number;
/**
* Optional tier for the entries. Typical pattern:
* - `'hot'` for the last few turns
* - `'warm'` for older turns
* - `'cold'` for archived
* Stages in Layer 3+ can filter on tier. Omitting leaves entries
* untiered (read stages still see them; tier-filtered reads skip them).
*/
readonly tier?: 'hot' | 'warm' | 'cold';
}
export declare function writeMessages(config: WriteMessagesConfig): (scope: TypedScope<MemoryState>) => Promise<void>;