Skip to content

memory: tolerate optional YAML frontmatter on entries (typed memory MVP) - #21945

Closed
alexzhu0 wants to merge 1 commit into
NousResearch:mainfrom
alexzhu0:alex/memory-frontmatter-tolerance
Closed

alexzhu0 wants to merge 1 commit into
NousResearch:mainfrom
alexzhu0:alex/memory-frontmatter-tolerance

Conversation

@alexzhu0

@alexzhu0 alexzhu0 commented May 8, 2026

Copy link
Copy Markdown
Contributor

Why

Memory entries today are flat strings. There's no built-in way for the agent to mark an entry as time-bound (a travel date, a sprint-scoped preference, a one-off project setting), so stale facts keep getting injected into the system prompt long after they stopped being true. The discussion in #10771 has been pushing toward typed memory entries with frontmatter (type / created_at / last_used_at / source / valid_until / supersedes / evidence); related work in #17380 is tightening the boundary between compaction summaries and durable memory along the same direction.

This PR is the smallest useful slice in that direction.

What

Reader-side only — no schema changes, no writer-side changes, no migration.

An entry may now optionally start with a YAML frontmatter block:

---
type: preference
valid_until: 2026-12-31
---
<body of entry>

When present, it's stripped at render time so the model sees a clean body. When valid_until is in the past, the rendered entry gets a [STALE — expired YYYY-MM-DD] prefix so the model has a visible signal to deprioritise the entry without the writer having to scrub it first.

Recognised keys today: type, created_at, last_used_at, source, valid_until, supersedes, evidence. Unknown keys are preserved on disk and ignored at render time — that leaves room to layer additional behaviour (decay weighting, supersession resolution, provenance display) in follow-up PRs without re-litigating the format.

Why this shape

  • Zero migration: existing entries stay valid as-is. There is no on-disk format change, no version bump, no migration script.
  • Zero tool-schema changes: the memory tool's OpenAI function-calling schema is untouched. An agent that wants typed entries simply writes the frontmatter block in the existing content argument; an agent that doesn't, never sees it.
  • Char-budget invariant preserved: usage bookkeeping continues to measure the full on-disk entry (frontmatter included), so writers can't silently free budget by adding metadata.
  • Fail-soft: malformed YAML, scalar frontmatter, or non-mapping payloads all degrade to plain-text rendering. Memory must keep working when an agent writes a slightly broken header — otherwise the feature becomes a liability.
  • Reader-side first: holding off on writer-side changes until the schema settles avoids painting ourselves into a corner. Once a typed-write API exists (e.g. a metadata parameter on add / replace), it can encode into the same on-disk format this reader already understands.

Test coverage

22 new tests in tests/tools/test_memory_tool.py:

  • frontmatter parsing: no header, empty entry, basic case, blank-line-after-fence, malformed YAML, scalar payload (---\nfoo\n---), --- appearing later in body, unknown-key preservation
  • date coercion: date passthrough, ISO date string, ISO datetime string with Z, garbage/None/int/empty
  • staleness: empty metadata, no valid_until, future date, past date, today (fresh), yesterday (stale)
  • system-prompt rendering: frontmatter stripped from prompt, STALE marker on expired entries, legacy entries unchanged, usage counter reflects on-disk size

Existing tests (33 in test_memory_tool.py + sibling memory tests in tests/agent/ and tests/tools/) continue to pass — 147 / 147 across the relevant suite locally.

Follow-ups (out of scope here)

  • Writer-side metadata parameter on add / replace (separate PR — easier to review when the read path is in)
  • Optional prune_stale(target, before=...) administrative API
  • Decay / last_used_at updates on memory-read paths
  • Surfacing recognised metadata in _success_response so tool callers see the full structure, not just the body

Happy to split, reshape, or rebase — whatever works best with the typed-memory direction in #10771 and the compaction-isolation work in #17380.

Lets memory entries optionally start with a YAML frontmatter block:

  ---
  type: preference
  valid_until: 2026-12-31
  ---
  <body>

Reader-side only — no schema or writer-side changes:

- Backward-compatible: entries without frontmatter render identically,
  on-disk format is unchanged, char-budget bookkeeping still measures the
  full on-disk entry (so writers can't silently free budget by adding
  metadata).
- The block is stripped from what gets injected into the system prompt,
  so the model sees a clean body.
- Entries with 'valid_until' in the past are prefixed with a
  '[STALE — expired YYYY-MM-DD]' marker at render time, giving the model
  a visible signal to deprioritise time-bound facts (e.g. travel dates,
  expired credentials, one-off project state) without the writer having
  to scrub them first.
- Malformed YAML / scalar frontmatter / non-mapping payloads degrade to
  plain-text rendering — memory must keep working even when an agent
  writes a slightly broken header.

Recognised keys today: type, created_at, last_used_at, source,
valid_until, supersedes, evidence. Unknown keys are preserved on disk
and ignored at render time, leaving room to layer additional behaviour
(decay, supersession, provenance display) in follow-ups.

Tests cover frontmatter parsing, date coercion, staleness logic,
prompt-render stripping, the STALE marker, legacy-entry preservation,
and the usage-counter invariant.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have tool/memory Memory tool and memory providers labels May 11, 2026
@alexzhu0

Copy link
Copy Markdown
Contributor Author

Status / gentle ping — opened 2026-05-08, sitting without review.

This is a typed-memory MVP that addresses a recurring class of bug: stale facts (travel dates, sprint preferences, one-off project settings) keep getting injected into the system prompt long after they stopped being true. The PR adds optional YAML frontmatter so an entry can carry expires, scope, source etc. without breaking any existing flat-string entries (frontmatter is opt-in and ignored by old readers).

Re: failing CI — none of the failures are caused by this diff:

  • check-attribution and ruff + ty diff fail with HttpError 403: Resource not accessible by integration — workflows trying to post issue comments from a fork (permissions limitation, not my code)
  • test failures are all pre-existing on main: _CONTEXT_1M_BETA in _COMMON_BETAS, max_completion_tokens KeyError, matrix _auto_create_thread await assertion, cron MCP discovery scaffolding — none of these touch memory/ paths

Happy to rebase on current main if a fresh CI pass would help. Would value @teknium1 / @alt-glitch eyes on whether the frontmatter shape and scope semantics match what you'd want from a typed-memory first pass.

@alexzhu0

Copy link
Copy Markdown
Contributor Author

Status ping (15 days, no review yet) — branch still MERGEABLE against current main (HEAD 52a368fa7). +329 is on the larger side; happy to slice into a smaller "frontmatter parser only" PR (drop the typed-memory MVP additions) if that's easier to land. Just say the word.

@alexzhu0 alexzhu0 closed this by deleting the head repository May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have tool/memory Memory tool and memory providers type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants