101 lines
5.0 KiB
TypeScript
101 lines
5.0 KiB
TypeScript
/**
|
|
* recall-context.ts — shared renderer for surfacing temporal information in
|
|
* recalled-memory blocks. Single source of truth imported by both the Waggle
|
|
* agent (production) and the benchmark harness, so the injected-memory format
|
|
* never drifts between the two.
|
|
*
|
|
* Scope (Temporal Substrate Fix, Phase 1 — "surface time", additive only):
|
|
* - Prefix each retrieved snippet with its own compact `[YYYY-MM-DD]` date.
|
|
* - Open the rendered memory block with one anchor line giving the most-recent
|
|
* memory date, so the model has a concrete "now" to resolve relative time
|
|
* expressions against.
|
|
* - Export `TEMPORAL_GUIDANCE`, the prompt fragment that tells the model to
|
|
* treat those timestamps as the anchor for relative-time arithmetic.
|
|
*
|
|
* Distilled "Memory Facts" (cross-session syntheses) are intentionally NOT
|
|
* dated here — they have no single reliable date. Phase 2 handles them.
|
|
*
|
|
* OSS-clean: pure string formatting, no vault/evolution/compliance deps.
|
|
*/
|
|
|
|
/**
|
|
* Prompt fragment wired into the memory-recall injection path (NOT the global
|
|
* system prompt). Teaches the model to use the surfaced `[YYYY-MM-DD]` stamps
|
|
* as the anchor for resolving relative time expressions and conflicting facts.
|
|
*
|
|
* W4.1 upgrade (W4-PRODUCTION-PORT-PLAN-2026-06-11.md §1): replaced the original
|
|
* "nearest timestamp as anchor" phrasing with the benchmark-proven W1 wording —
|
|
* concrete relative-date arithmetic with worked examples (Memori instruction-5
|
|
* lineage, incl. the verified conv-26 "yesterday" failure case) plus the
|
|
* granularity-calibration clause (failure mining: 22 temporal fails emitted a
|
|
* confident exact ISO day 1-7 days off where a coarse answer was correct).
|
|
* Temporal was the #1 LoCoMo lever (80.06 → 84.7 across W1-W3.1).
|
|
* Production-safe subset: no never-refuse clause (that was benchmark-cell
|
|
* policy only — conditional abstention stays).
|
|
*/
|
|
export const TEMPORAL_GUIDANCE =
|
|
"Memories and snippets are timestamped [YYYY-MM-DD]. Pay special attention to these " +
|
|
"timestamps to determine timing. If a question involves relative time references " +
|
|
"('last year', 'two months ago', 'yesterday', 'last week'), CALCULATE the actual date " +
|
|
"from the timestamp of the memory that mentions it. For example: a memory dated " +
|
|
"4 May 2022 that says 'went to India last year' means the trip was in 2021; a memory " +
|
|
"dated 8 May 2023 that says 'I went to the group yesterday' means the event was 7 May 2023. " +
|
|
"Always convert relative references to specific dates, months, or years using the " +
|
|
"memory's timestamp as the anchor, and ignore the relative phrase itself when answering. " +
|
|
"When the same fact appears at different times, the most recent version is correct. " +
|
|
"GRANULARITY: state an exact day ONLY when that exact date was explicitly stated or " +
|
|
"directly computed from an explicit relative reference; otherwise answer at the " +
|
|
"granularity you are confident in — 'early June 2023', 'the week before 9 August 2023', " +
|
|
"'August 2022'. A confidently wrong exact day is worse than a correct coarse answer. " +
|
|
"For 'how long / how many months' duration questions, give ONLY the final value " +
|
|
"(e.g. 'six months') — no intermediate dates, no reasoning steps.";
|
|
|
|
/** Anchor-line prefix for the most-recent rendered memory date. */
|
|
const REFERENCE_DATE_LABEL = 'Reference date (most recent memory):';
|
|
|
|
/**
|
|
* Slice an ISO-ish timestamp to its `YYYY-MM-DD` date prefix. Returns null when
|
|
* the value is missing or too short to carry a date.
|
|
*/
|
|
export function toDatePrefix(createdAt: string | null | undefined): string | null {
|
|
if (!createdAt || createdAt.length < 10) return null;
|
|
return createdAt.slice(0, 10);
|
|
}
|
|
|
|
/**
|
|
* Render a single retrieved snippet with its compact `[YYYY-MM-DD]` date prefix.
|
|
* `text` is the snippet text exactly as it would otherwise be rendered (the
|
|
* caller owns importance labels, truncation, etc.); this only prepends the date.
|
|
* Falls back to the bare text when the hit carries no usable timestamp.
|
|
*/
|
|
export function renderDatedSnippet(createdAt: string | null | undefined, text: string): string {
|
|
const date = toDatePrefix(createdAt);
|
|
return date ? `[${date}] ${text}` : text;
|
|
}
|
|
|
|
/**
|
|
* Compute the reference (anchor) date: the maximum `created_at` among the
|
|
* supplied timestamps, sliced to `YYYY-MM-DD`. Returns null when none carry a
|
|
* usable date (caller then omits the anchor line).
|
|
*/
|
|
export function referenceDate(createdAts: ReadonlyArray<string | null | undefined>): string | null {
|
|
let max: string | null = null;
|
|
for (const c of createdAts) {
|
|
const date = toDatePrefix(c);
|
|
if (date && (max === null || date > max)) max = date;
|
|
}
|
|
return max;
|
|
}
|
|
|
|
/**
|
|
* Build the single anchor line for a memory block, e.g.
|
|
* `Reference date (most recent memory): 2026-06-09`. Returns null when no date
|
|
* is available.
|
|
*/
|
|
export function renderReferenceDateLine(
|
|
createdAts: ReadonlyArray<string | null | undefined>,
|
|
): string | null {
|
|
const date = referenceDate(createdAts);
|
|
return date ? `${REFERENCE_DATE_LABEL} ${date}` : null;
|
|
}
|