Capabilities · 3. Finding things without burning the context window

Recall fails in two directions, and this family is about both. It can miss the answer, or it can find the answer and spend the entire context window delivering it. A store that has been running for months will happily return more than the model can hold, and an agent that fills its context with recall has no room left to work.

This is the agent track: these are things your agent does. An operator reads this chapter to understand what their agents are doing and why an answer looked the way it did.

§CapabilityStatusFor
3.1Budget-aware fact recallSHIPPEDboth
3.2Corpus search with a coverage reportSHIPPED; graph expansion default offboth
3.3Two-pass scan, then addressSHIPPEDagent
3.4Reversible pointer budgetingSHIPPED, unconditionalagent
3.5Optional dense re-rank, and a pluggable embedderSHIPPED, conditional; the real model is a build optionboth
3.6Cold-start playbooks and self-observationSHIPPED; self-observation default offagent
3.7Reuse checkFLAG, default offagent
3.8Local prose ingestFLAG, default onboth
3.9Three-lane fusionDECLARED-NOT-WIREDnobody, today

Two lanes, and they share almost nothing. Facts are recalled by substring matching over the text you stored. Documents are searched by a real inverted index with proper scoring. Different tools, different guarantees, different failure modes. Every explainer below belongs to one lane or the other, and knowing which is the difference between a query that works and an hour of confusion.

One number governs the whole family. A token budget is a spending limit on the answer, not a filter on the question: the daemon ranks everything that matched, then emits down the list until the budget is spent, and tells you how many candidates there were. Passing one on every retrieval call is the single most effective defence against a session that fills its context with recall.

3.1 Budget-aware fact recall

Status · SHIPPED Reached through · MCP query_facts · HTTP GET /v1/facts?token_budget= Who it is for · both

What it does. Ask for facts under an explicit token budget and get back the highest-ranked set that fits, ranked by decay-adjusted confidence and then by recency. The response discloses the true number of candidates, so "nothing found" is always distinguishable from "your budget ran out". Matching is a case-insensitive substring test over the value, the attribute and the visible subject, with any-term semantics.

Why it works this way. The budget is enforced at emit time rather than at match time on purpose. Filtering the question would silently change the answer; capping the response changes only how much of a known-complete ranking you receive, and the count of what did not fit comes back with it. That is the design principle for the whole family: never let truncation look like absence.

Substring matching is the other half of the design, and it is a trade. There is no stemming, no relevance weighting by term rarity, and no phrase handling. In exchange it is deterministic, dependency-free, fast, and never surprises you with a ranking model that changed. Against a store whose subject names you control, it is a precise lookup.

What changes for you.

  • As an agent: pass a budget on every call. Then read the candidate count: if it far exceeds what you received, narrow the subject prefix rather than raising the budget.
  • As an operator: recall quality here is a naming problem, not a tuning problem. Facts written under disciplined subject prefixes are findable; facts written as prose are found only by the words they literally contain.

What it does not do.

  • It does not find synonyms. A query for "how do we deploy" will not match a value reading "release procedure", because no query word appears in the stored text.
  • It does not rank by how many of your terms matched. Any single matching term qualifies the fact.
  • It does not count tokens the way your model provider does. The budget is enforced against a rough characters-per-token estimate, so budgets are comparable to each other and not to a provider's usage report.

Where the detail lives. The five stages of the lane, in order, are daemon/11 §11.2; the tool arguments are api/12 §12.6; the match test is facts.rs:807.

3.2 Corpus search with a coverage report

Status · SHIPPED. Graph expansion behind CORECRUXD_QUERY_GRAPH_EXPAND, default off Reached through · MCP query · HTTP POST /v1/query/text-search, POST /v1/query/time-range, POST /v1/query/graph-expand · Console → Explorer Who it is for · both

What it does. Real Okapi BM25 over sealed document segments, with term rarity computed across every segment so multi-segment scores are comparable. The response carries a coverage report: how many of your query terms matched at least one document anywhere, and which ones matched nothing. Time-range and graph-shaped query forms sit on the same lane, the latter behind CORECRUXD_QUERY_GRAPH_EXPAND, default off.

Why it works this way. The coverage report is the part worth explaining, because no other retrieval system on your desk offers it. An empty result set from a search engine is undiagnosable: you cannot tell a misspelling from a vocabulary mismatch from an empty corpus. Returning the unmatched terms turns "no results" into a specific, actionable statement about which of your words the corpus has never seen. The cost accepted is a slightly larger response on every query, which was judged trivially worth it.

What changes for you.

  • As an agent: when a search returns nothing, read the unmatched terms before rewriting the query. They tell you whether the corpus lacks the topic or your phrasing missed.
  • As an operator: this lane only exists over sealed segments, which means it only exists if you have ingested documents (§3.8). On a daemon that stores only facts, query has nothing to search.

What it does not do.

  • The coverage field is named and shaped differently over HTTP and over MCP, and the mismatch is silent: a client written for one finds nothing under that name on the other.
  • The unmatched-term names are reconstructed positionally and are best-effort. The coverage score is reliable; do not build an alert on the exact token strings.
  • It does not search your facts. Facts are a different lane entirely, §3.1.

Where the detail lives. The scoring, the coverage fields and the naming mismatch are daemon/11 §11.4; the routes are api/03 §3.1; the search itself is bm25.rs:233.

3.3 Two-pass scan, then address

Status · SHIPPED Reached through · MCP query_scan, query_expand · HTTP POST /v1/query/text-search/expand Who it is for · agent

What it does. Survey the corpus cheaply under a hard budget, get back pointers plus the honest signal that the budget truncated the survey, choose which pointers matter, then expand only those. The scan charges the full document cost for what it returns, which makes it the bounded-cost variant: you know before you call what the worst case is.

Why it works this way. The single-pass alternative forces one decision at query time: how much am I willing to pay for an answer I have not seen. Splitting it lets the agent make a cheap decision first, on evidence, and spend the expensive budget on the two or three results that deserve it. This is the same instinct as the pointer budgeting in §3.4, applied at the level of a whole survey rather than a single response.

What changes for you.

  • As an agent: use query_scan when you need a ceiling on what a survey will cost, and query when you want maximum candidates for a fixed budget. They are not interchangeable, and the difference is which price the budget is charged at.
  • As an operator: nothing to configure. This is a calling pattern, not a feature to enable.

What it does not do.

  • Expansion returns metadata in this edition, not document text: segment, document identifier, offset and token count. If you were planning to pull document bodies into a prompt with it, it will not do that here. The two-pass path is scan then address, not scan then read.
  • Handles carry no expiry and no server-side cursor. They are positional addresses, and an expand against a segment that has since gone reports an eviction, which means re-query rather than retry.

Where the detail lives. What expansion returns and the three typed failures are daemon/11 §11.10; the scan pass is daemon/11 §11.11; the handler is query.rs:174.

3.4 Reversible pointer budgeting

Status · SHIPPED and unconditional. The former CRUX_BUDGET_REVERSIBLE flag has been removed and setting it does nothing Reached through · MCP query, query_facts Who it is for · agent

What it does. When results overflow the budget, they are not dropped. The leading results are returned in full, the next band is demoted to short pointers costing a fixed small number of tokens each, and only what is past that is dropped, with the true candidate total disclosed. Each pointer carries enough to re-address the thing it stands for, and on the fact lane it also carries a content hash so an agent can tell whether the value changed since it saw the pointer.

Why it works this way. The older behaviour was a cumulative cut: fill the budget with full results and drop the rest. That spends the entire budget on the top few candidates and leaves the agent unable to see that anything else existed, which is the worst possible failure for an agent trying to decide what to look at. Demotion inverts it. A budget that admitted a couple of full documents admits roughly a dozen pointers, so the agent sees the shape of the result set and chooses what to hydrate. The trade is explicit and worth stating: this can spend more tokens than the old drop behaviour, not fewer. Reversible overflow buys recall with tokens.

That trade is measured rather than asserted, and the honesty is the point. The daemon can run a fraction of live traffic unshaped as a control arm and report the difference, and the reported net figure goes negative exactly when recall is winning. There is a test asserting that outcome rather than papering over it. The measurement is off by default, so no holdout is running on your daemon unless you turned one on, and any savings figure is meaningless without naming which arm it came from.

The design rejected an alternative that looks attractive: server-side cursors, where the daemon keeps the unreturned results and hands out a continuation token. That would have meant per-query state, expiry, and a class of "your cursor expired" failures. Pointers are stateless addresses instead, and the cost is that a pointer can go stale, which the content hash makes detectable.

What changes for you.

  • As an agent: treat an answer as two tiers. Hydrate the pointers you actually need, re-addressing a fact by its subject and attribute; a content-hash mismatch means the value changed and no result at all means it was forgotten. Never treat the returned rows as the whole candidate set: the total is in the response.
  • As an operator: your token usage under a fixed budget may be higher than it was under the drop behaviour, and the return is that agents stop re-querying because they can see what they missed. If you want that trade measured on your own traffic rather than argued at you, the holdout is the mechanism, and it is default off.

What it does not do.

  • The pointer's content hash is a change-detection aid and is explicitly not security-sensitive. It is not a signature, it is not tamper-evidence, and it should never be quoted as either.
  • Pointers on the document lane do not carry text (§3.3), so demotion there gives you addresses, not summaries.
  • It does not reduce token spend on its own. Compaction of the response is what saves tokens; reversible overflow spends them to buy candidates. The reported net is the sum of both, which is why it can be negative.

Where the detail lives. The three enforcement shapes, including the exact demotion arithmetic, are daemon/11 §11.9; the holdout and why the net goes negative are daemon/11 §11.12; the budgeting function is budget.rs:79.

3.5 Optional dense re-rank, and a pluggable embedder

Status · SHIPPED, conditional. The default embedder is feature hashing. A real embedding model requires both CORECRUXD_DENSE_MODEL=fastembed and a build with the dense-embed-model feature, which the default build does not compile Reached through · MCP query · HTTP POST /v1/compute/embed · Console → System › Settings Who it is for · both

What it does. The daemon picks an embedder at start-up in a strict order: a delegated daemon, then an external HTTP embedder, then a real local model if the build has one, and otherwise a pure-Rust local hash embedder. When a document corpus has vector companions and a provider is wired, document results are re-ranked by combining lexical score with vector similarity, and the response declares which scoring space it actually used.

Why it works this way. The default has to work on a laptop with no network, no downloads and no model files, which rules out shipping a real embedding model as the default. What ships instead is feature hashing: it produces vectors, it is deterministic, it needs nothing, and lexically overlapping texts score high against each other. That makes it genuinely useful for near-duplicate detection and typo tolerance. It is not semantic search. It has no notion of synonymy: two documents saying the same thing in different words score near zero.

The declared scoring space on every response exists because configuration is a poor way to answer "am I actually getting dense re-ranking". Too many conditions have to hold at once, and any one of them missing degrades silently to lexical scoring. So the response tells you, per call, which algorithm ran.

What changes for you.

  • As an agent: read the declared scoring space rather than inferring it from configuration. If it says lexical, your query needs to share words with the target text.
  • As an operator: if you want meaning-based matching, you are choosing an embedder, either an external service or a build with the model feature compiled in. If a real model fails to initialise, the daemon falls back to the local hash embedder rather than refusing to start, so check which model identifier your vectors carry.

What it does not do.

  • It is not semantic search out of the box. The out-of-the-box path is lexical.
  • When the embedder is a delegated one, facts are not enriched with vectors at all, so fact recall stays lexical regardless.
  • Dense re-rank does not apply to facts. It is a document-lane feature, and it needs vector companions to exist alongside the corpus.

Turn it on. Point CORECRUXD_EMBEDDING_URL at an embedding service, or build with the model feature and set CORECRUXD_DENSE_MODEL=fastembed. The selection order and every fallback are in the startup chapter.

Where the detail lives. The two things that wear the word "dense", and what each actually computes, are daemon/11 §11.5; the selection order at start-up is daemon/04 §4.5; the default embedder is embeddings.rs:830.

3.6 Cold-start playbooks and self-observation

Status · SHIPPED. Self-observation behind CRUX_SELF_OBSERVE, default off Reached through · MCP get_bootstrap · HTTP POST /v1/bootstrap/pull, GET /v1/bootstrap/status, GET /v1/ops/facts, GET /v1/ops/errors, GET /v1/ops/health Who it is for · agent

What it does. On a cold session an agent can pull its own operating playbooks and error-resolution guides in one call, rather than rediscovering conventions turn by turn. Those playbooks are not something you have to author first: the daemon seeds a starting set into its own store at boot, idempotently, so a fresh install answers a bootstrap pull with something useful. The same lane serves operational facts, recent errors and health, and self-observation behind CRUX_SELF_OBSERVE, default off, adds the daemon's own telemetry to what it can tell you about itself.

Why it works this way. The problem is specific to agents rather than to people. A new session has no memory of how this deployment expects work to be done, and the conventions that matter are the ones nobody writes into the prompt. Seeding them into the store makes them recallable through the same mechanism as everything else, which means an operator can extend or replace them by writing facts, with no configuration format to learn. The seeding is idempotent so restarts do not accumulate duplicates.

What changes for you.

  • As an agent: call it once at session start with a topic, and treat the result as instructions rather than as data. It is the answer to "how does this deployment want me to behave".
  • As an operator: the playbook namespace is yours to curate. Adding a fact under it is how you change what every future session reads at boot.

What it does not do.

  • get_bootstrap accepts no token budget. It is not a parameter; passing one is accepted, silently ignored, and up to a hundred facts come back concatenated. If bootstrap output is filling your context, the levers are the topic filter and the size of the playbook namespace, not a budget argument.
  • It does not rank the way query_facts does. This path blends vector similarity with confidence, which given the default embedder means lexical-overlap similarity.
  • Self-observation is off by default, so a daemon that has not been configured for it reports nothing about its own behaviour here.

Turn it on. CRUX_SELF_OBSERVE, default off, for the self-observation lane. The bootstrap surface itself needs no flag.

Where the detail lives. The bootstrap surface and its arguments are api/11 §11.12; the operational lane is api/03 §3.4; the boot-time seeding is bootstrap.rs:109.

3.7 Reuse check

Status · FLAG CORECRUXD_FEATURE_REUSE_CHECK, default off Reached through · MCP reuse_check Who it is for · agent

What it does. Behind CORECRUXD_FEATURE_REUSE_CHECK, which is default off, an agent asks whether an equivalent result already exists in the corpus before it redoes a piece of work. It is a retrieval call with a specific question attached, not a new index.

Why it works this way. The expensive failure in a fleet is not a bad answer, it is the same answer computed four times by four agents who could not see each other's output. A dedicated check makes "has this already been done" a single call an agent can afford to make habitually, rather than a search it has to design each time. It is default off because it is only meaningful once you have a corpus with prior results in it, and a tool that returns nothing useful is worse than an absent one.

What changes for you.

  • As an agent: call it before expensive work when the flag is on, and treat a hit as a candidate to verify rather than as an answer to reuse blindly.
  • As an operator: it is only as good as what has been ingested. On an empty corpus it will find nothing, correctly.

What it does not do.

  • It does not detect semantic equivalence. It runs over the same lexical scoring as the rest of the document lane, so a differently worded prior result will not match.
  • It does not prevent duplicate work. It reports; the agent decides.

Turn it on. CORECRUXD_FEATURE_REUSE_CHECK, default off.

Where the detail lives. The tool contract is api/12 §12.3; the handler is reuse.rs:55.

3.8 Local prose ingest

Status · FLAG CORECRUXD_LOCAL_INGEST, default on. Index building is a separate setting Reached through · HTTP POST /v1/local/ingest, POST /v1/append · CLI corecruxctl ingest Who it is for · both

What it does. Behind CORECRUXD_LOCAL_INGEST, which is default on, your own documents become a searchable, sealed corpus on the machine you are already running: chunked, written into segments, and indexed for the lexical search lane. Nothing is uploaded and no external service is contacted. Ingest is serialised internally so two seals cannot race.

Why it works this way. This is the capability that makes §3.2 reachable at all, and it is deliberately local. A document search that requires shipping your documents to a third party is a different product with a different risk profile, and it is the one this daemon exists not to be. The trade accepted is that you get lexical search over your own corpus rather than a hosted semantic index, and §3.5 is honest about where that boundary sits.

What changes for you.

  • As an agent: the document lane has content only if someone ran ingest. If query returns nothing on every query, ask whether the corpus exists before debugging the query.
  • As an operator: this is the on-ramp to the search lane. Sealed segments are the unit, and index companions have to be built for the lexical lane to have anything to read.

What it does not do.

  • It does not watch a directory. Ingest is an act you run, not a daemon that follows your filesystem.
  • It does not extract facts from what it ingests. Documents and facts stay separate substrates, which is the split chapter 1 opens with.

Turn it on. CORECRUXD_LOCAL_INGEST, default on.

Where the detail lives. The ingest and append routes are api/01 §1.4; what the default configuration actually runs on this lane is daemon/11 §11.7; the ingest entry point is local_ingest.rs:685.

3.9 Three-lane fusion

Status · DECLARED-NOT-WIRED. The code exists and is tested; nothing in the running daemon calls it Reached through · nothing Who it is for · nobody, today

What it does. Nothing, in this build. A weighted fusion of lexical, graph and vector signals, with a cold-start rule that shifts weight away from the graph when the graph is too small to be informative, exists as library code with tested weights. Every reference to it inside the daemon is in a test module, and one of the four declared lanes is multiplied by zero in the formula itself. The production retrieval paths call the lexical search directly.

Why it works this way. It is listed here rather than omitted for one reason: fused multi-signal retrieval is the thing readers most often assume a memory product is doing, and an absence documented nowhere reads as a quiet yes. The library exists because the design was worked out and tested; it is unwired because no production path was built to it in this edition. Both halves of that are true and neither is embarrassing, provided the page says so.

What changes for you.

  • As an agent: nothing. No retrieval call you can make reaches it.
  • As an operator: treat any claim that this daemon fuses three retrieval lanes today as false, including in your own architecture diagrams. Graph expansion has its own separate flag on the query surface and it does not activate this code.

What it does not do.

  • It does not run. There is no flag that turns it on, because there is no call site to gate.

Where the detail lives. The weights, the cold-start rule and the evidence that there is no production caller are daemon/11 §11.6.

Sources