/** * Context Composer — token-budgeted, relevance-ranked context assembly. * * Agents have large context windows but a much smaller *effective reasoning* * window: irrelevant, stale, or noisy context degrades output quality long * before the hard token limit ("context rot"). This module assembles the * context block for an LLM call from candidate sources (blackboard entries, * project context, memory recall) under a **hard token budget**, ranked by: * * score = w.relevance × relevance + w.recency × recency + w.affinity × affinity * * - **relevance** — semantic similarity to the task via a pluggable * {@link SemanticRanker} (BYOE — e.g. wrap `SemanticMemory`), with a * deterministic lexical-overlap fallback when no ranker is supplied; * - **recency** — exponential half-life decay on the entry timestamp * (the same math `EpisodicMemory` uses); * - **affinity** — scope-tag match on the key (same substring semantics as * `ContextThrottler`). * * Pinned sources (task-critical instructions, Layer-3 project context) are * always included first. Assembly is position-aware by default: the * strongest items are placed at the start *and end* of the block, weakest in * the middle — mitigating "lost in the middle" attention decay. * * The result carries full observability metadata: what was included, * what was excluded and why, token usage, and budget utilization. * * @example * ```ts * const composer = new ContextComposer(); * const sources = ContextComposer.fromSnapshot(blackboard.getScopedSnapshot('analyst')); * const pack = await composer.compose(sources, { * task: 'Summarize Q3 revenue anomalies', * budgetTokens: 2000, * scopeTags: ['analytics', 'task'], * }); * llmPrompt = `${instructions}\n\n${pack.text}`; * ``` * * @module ContextComposer * @version 1.0.0 */ /** * Estimate the token count of a text without a tokenizer dependency. * * Uses the ~4-characters-per-token heuristic blended with a word count * (English prose averages ~0.75 tokens/word; code and JSON run denser). * Accurate to roughly ±15% across prose/code/JSON — sufficient for budget * enforcement, not for billing. */ export declare function estimateTokens(text: string): number; /** One candidate context item offered to the composer. */ export interface ContextSource { /** Unique identifier (blackboard key, memory id, …) */ key: string; /** Rendered text content of this item */ text: string; /** Agent that produced the item, when known */ sourceAgent?: string; /** ISO timestamp of the item, when known (drives recency decay) */ timestamp?: string; /** TTL in seconds (`null`/`undefined` = no expiry) — expired items are dropped */ ttl?: number | null; /** Pinned items bypass ranking and are always included first (budget permitting) */ pinned?: boolean; } /** A source annotated with its ranking scores. */ export interface RankedContextItem extends ContextSource { /** Semantic (or lexical-fallback) relevance to the task, 0–1 */ relevance: number; /** Recency after half-life decay, 0–1 */ recency: number; /** Scope-tag affinity, 0–1 */ affinity: number; /** Weighted total score, 0–1 */ score: number; /** Estimated token cost of this item's rendered block */ tokens: number; } /** Why an item was left out of the composed pack. */ export type ExclusionReason = 'budget' | 'score' | 'stale' | 'empty'; /** An excluded item and the reason it was dropped. */ export interface ExcludedItem { key: string; reason: ExclusionReason; score?: number; } /** * Pluggable semantic ranker: given the task text and candidate items, * return a map of key → similarity score (0–1). Missing keys fall back to * lexical scoring. BYOE — bring your own embeddings. */ export type SemanticRanker = (query: string, items: ReadonlyArray<{ key: string; text: string; }>) => Promise>; /** Scoring weights (normalized internally; defaults: 0.5 / 0.3 / 0.2). */ export interface ScoreWeights { relevance?: number; recency?: number; affinity?: number; } /** Options for a single {@link ContextComposer.compose} call. */ export interface ComposeOptions { /** The task/query driving relevance ranking (required) */ task: string; /** Hard token budget for the entire composed block (required, > 0) */ budgetTokens: number; /** Scope tags for affinity scoring (ContextThrottler semantics: substring match on key) */ scopeTags?: string[]; /** Score weights — merged over the composer defaults */ weights?: ScoreWeights; /** Recency half-life in ms (default: composer default, 30 min) */ halfLifeMs?: number; /** Items scoring below this are excluded outright (default 0.05) */ minScore?: number; /** Hard cap on the number of included items (0 = unlimited) */ maxItems?: number; /** Override the composer-level semantic ranker for this call */ ranker?: SemanticRanker; /** * Position-aware assembly (default true): strongest items at the start * AND end of the block, weakest in the middle. */ positionAware?: boolean; } /** The assembled, budget-enforced context pack. */ export interface ComposedContext { /** Final assembled context block, ready to inject into a prompt */ text: string; /** Items included, in ranked order (not layout order) */ included: RankedContextItem[]; /** Items excluded, with reasons */ excluded: ExcludedItem[]; /** The budget that was enforced */ budgetTokens: number; /** Estimated tokens used by `text` */ usedTokens: number; /** usedTokens / budgetTokens (0–1) */ utilization: number; } /** Constructor options for {@link ContextComposer}. */ export interface ContextComposerOptions { /** Default half-life for recency decay in ms (default: 1_800_000 = 30 min) */ halfLifeMs?: number; /** Default scoring weights */ weights?: ScoreWeights; /** Default semantic ranker (BYOE) */ ranker?: SemanticRanker; } /** Minimal `SemanticMemory`-compatible search surface. */ export interface SemanticSearchLike { search(query: string, topK?: number, threshold?: number): Promise>; } /** * Adapt a `SemanticMemory` instance (or anything with a compatible * `search()`) into a {@link SemanticRanker}. Items the memory does not know * about simply fall back to lexical scoring. */ export declare function createSemanticMemoryRanker(memory: SemanticSearchLike): SemanticRanker; /** * Assembles token-budgeted, relevance-ranked context packs from candidate * sources. See the module docs for the ranking model. */ export declare class ContextComposer { private readonly halfLifeMs; private readonly weights; private readonly ranker; constructor(options?: ContextComposerOptions); /** * Convert a blackboard snapshot (`getSnapshot()` / `getScopedSnapshot()` * shape) into {@link ContextSource} candidates. Values are rendered as * strings (JSON for objects) and truncated to `maxValueChars`. */ static fromSnapshot(snapshot: Record, options?: { maxValueChars?: number; }): ContextSource[]; /** * Rank the candidate sources against the task and assemble the largest * high-signal context block that fits the token budget. */ compose(sources: ContextSource[], options: ComposeOptions): Promise; } //# sourceMappingURL=context-composer.d.ts.map