This commit is contained in:
100
packages/hive-mind-core/src/mind/recall-context.ts
Normal file
100
packages/hive-mind-core/src/mind/recall-context.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user