Skip to content

build_context: let compact skip traversal work, not only output #1571

Description

@sammywachtel

The gap is already documented, in three places

compact reduces what the model sees. The docs are explicit that it stops there:

  • docs/COMPACT_DISCOVERY.md"These options reduce model-visible output, not database search or traversal work."
  • src/basic_memory/man/man3/build-context(3).md"This reduces MCP output, not API traversal work, and is not a fixed token limit."
  • src/basic_memory/mcp/tools/build_context.py"This reduces response size, not traversal work or a guaranteed token budget."

That is a deliberate and well-drawn boundary, and #686's closing note says why: the smaller delivered design was preferred over another loading abstraction. I think that was the right call. This issue is about the half that was consciously left outside it, not a re-proposal of tiered loading.

Why the server-side half matters separately

Running this as a shared HTTP service rather than on a laptop changes which half is expensive.

When a caller asks for graph shape — what is connected to what, so it can decide which one note to actually read — build_context(compact=True) still assembles every observation row and note body, serializes the full GraphContext, and discards the bodies at the formatting layer. The caller's context window is protected. The database work, the object construction and the serialization all happened anyway.

On a single-user local install that cost is invisible. On a small shared instance it is the difference between a discovery call being cheap and a discovery call being the most expensive thing an agent does — and discovery calls are the frequent ones, precisely because compact made them the recommended first step. The guidance in COMPACT_DISCOVERY.md ("use compact results to choose notes before reading their bodies") is good advice that increases how often the un-optimized path runs.

The shape I would suggest

Let the request say it does not want bodies, so the traversal can skip assembling them:

  • a parameter on the /memory API endpoints — include_content: bool = True — threaded into the context builder
  • when false, observation rows are not fetched or attached, and note content is left None
  • compact=True then becomes the presentation-layer expression of the same intent and can simply pass it through, so nothing changes for MCP callers and the existing flag keeps its meaning

That keeps one user-facing concept. compact already means "I want shape, not bodies" — this lets the server act on that rather than only the formatter.

What I have

I run a fork with roughly this implemented at the API layer. I would rather not carry it indefinitely: it is a parameter name on a public surface, which means reconciling it at every sync, and it is the kind of thing that would best live upstream.

Happy to open a PR against whatever shape you prefer — including a different parameter name, or folding it entirely into compact with no new public parameter, which may well be cleaner than what I have. Equally happy to be told the boundary is where you want it; in that case I will stop re-deciding it every sync, which is worth something on its own.

One note in case it affects the answer: _compact_observation and _format_entity_block currently do this work after GraphContext is built, so the change is additive at the API layer and would not disturb the existing formatting path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions