[{"data":1,"prerenderedAt":1493},["ShallowReactive",2],{"navigation_docs":3,"blog-\u002Fblog\u002Fcontext-assembly-budget":356},[4,8,34,64,90,139,157,171,193,227,241,266,312,334],{"title":5,"path":6,"stem":7},"Documentation","\u002Fdocs","docs\u002Findex",{"title":9,"path":10,"stem":11,"children":12},"Getting started","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started\u002Findex",[13,14,18,22,26,30],{"title":9,"path":10,"stem":11},{"title":15,"path":16,"stem":17},"Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F01.getting-started\u002F01.introduction",{"title":19,"path":20,"stem":21},"Concepts","\u002Fdocs\u002Fgetting-started\u002Fconcepts","docs\u002F01.getting-started\u002F02.concepts",{"title":23,"path":24,"stem":25},"Quickstart (5 minutes)","\u002Fdocs\u002Fgetting-started\u002Fquickstart","docs\u002F01.getting-started\u002F03.quickstart",{"title":27,"path":28,"stem":29},"Local development","\u002Fdocs\u002Fgetting-started\u002Flocal-development","docs\u002F01.getting-started\u002F04.local-development",{"title":31,"path":32,"stem":33},"FAQ","\u002Fdocs\u002Fgetting-started\u002Ffaq","docs\u002F01.getting-started\u002F05.faq",{"title":35,"path":36,"stem":37,"children":38},"Architecture","\u002Fdocs\u002Farchitecture","docs\u002F02.architecture\u002Findex",[39,40,44,48,52,56,60],{"title":35,"path":36,"stem":37},{"title":41,"path":42,"stem":43},"Architecture overview","\u002Fdocs\u002Farchitecture\u002Foverview","docs\u002F02.architecture\u002F01.overview",{"title":45,"path":46,"stem":47},"Services","\u002Fdocs\u002Farchitecture\u002Fservices","docs\u002F02.architecture\u002F02.services",{"title":49,"path":50,"stem":51},"Request lifecycle","\u002Fdocs\u002Farchitecture\u002Frequest-lifecycle","docs\u002F02.architecture\u002F03.request-lifecycle",{"title":53,"path":54,"stem":55},"Data model","\u002Fdocs\u002Farchitecture\u002Fdata-model","docs\u002F02.architecture\u002F04.data-model",{"title":57,"path":58,"stem":59},"Status and evidence","\u002Fdocs\u002Farchitecture\u002Fstatus-and-evidence","docs\u002F02.architecture\u002F05.status-and-evidence",{"title":61,"path":62,"stem":63},"Target architecture and authority","\u002Fdocs\u002Farchitecture\u002Ftarget-architecture","docs\u002F02.architecture\u002F06.target-architecture",{"title":65,"path":66,"stem":67,"children":68},"Auth","\u002Fdocs\u002Fauth","docs\u002F03.auth\u002Findex",[69,70,74,78,82,86],{"title":65,"path":66,"stem":67},{"title":71,"path":72,"stem":73},"Overview","\u002Fdocs\u002Fauth\u002Foverview","docs\u002F03.auth\u002F01.overview",{"title":75,"path":76,"stem":77},"Organization and agent model","\u002Fdocs\u002Fauth\u002Forg-project-model","docs\u002F03.auth\u002F02.org-project-model",{"title":79,"path":80,"stem":81},"Issuing API keys","\u002Fdocs\u002Fauth\u002Fissuing-api-keys","docs\u002F03.auth\u002F03.issuing-api-keys",{"title":83,"path":84,"stem":85},"Multi-tenant RLS","\u002Fdocs\u002Fauth\u002Fmulti-tenant-rls","docs\u002F03.auth\u002F04.multi-tenant-rls",{"title":87,"path":88,"stem":89},"Operator sessions and step-up","\u002Fdocs\u002Fauth\u002Foperator-sessions","docs\u002F03.auth\u002F05.operator-sessions",{"title":91,"path":92,"stem":93,"children":94},"Proxy","\u002Fdocs\u002Fproxy","docs\u002F04.proxy\u002Findex",[95,96,99,103,107,111,115,119,123,127,131,135],{"title":91,"path":92,"stem":93},{"title":71,"path":97,"stem":98},"\u002Fdocs\u002Fproxy\u002Foverview","docs\u002F04.proxy\u002F01.overview",{"title":100,"path":101,"stem":102},"Authentication","\u002Fdocs\u002Fproxy\u002Fauthentication","docs\u002F04.proxy\u002F02.authentication",{"title":104,"path":105,"stem":106},"Request routing","\u002Fdocs\u002Fproxy\u002Frequest-routing","docs\u002F04.proxy\u002F03.request-routing",{"title":108,"path":109,"stem":110},"Sessions","\u002Fdocs\u002Fproxy\u002Fsessions","docs\u002F04.proxy\u002F04.sessions",{"title":112,"path":113,"stem":114},"Directives","\u002Fdocs\u002Fproxy\u002Fdirectives","docs\u002F04.proxy\u002F05.directives",{"title":116,"path":117,"stem":118},"Idempotency and budgets","\u002Fdocs\u002Fproxy\u002Fidempotency-and-budgets","docs\u002F04.proxy\u002F06.idempotency-and-budgets",{"title":120,"path":121,"stem":122},"Rate limiting","\u002Fdocs\u002Fproxy\u002Frate-limiting","docs\u002F04.proxy\u002F07.rate-limiting",{"title":124,"path":125,"stem":126},"Provider adapters","\u002Fdocs\u002Fproxy\u002Fprovider-adapters","docs\u002F04.proxy\u002F08.provider-adapters",{"title":128,"path":129,"stem":130},"Auth caching","\u002Fdocs\u002Fproxy\u002Fauth-caching","docs\u002F04.proxy\u002F09.auth-caching",{"title":132,"path":133,"stem":134},"Configuration","\u002Fdocs\u002Fproxy\u002Fconfiguration","docs\u002F04.proxy\u002F10.configuration",{"title":136,"path":137,"stem":138},"Provider routing and resilience","\u002Fdocs\u002Fproxy\u002Fprovider-routing-and-resilience","docs\u002F04.proxy\u002F11.provider-routing-and-resilience",{"title":140,"path":141,"stem":142,"children":143},"Memory","\u002Fdocs\u002Fmemory","docs\u002F05.memory\u002Findex",[144,145,149,153],{"title":140,"path":141,"stem":142},{"title":146,"path":147,"stem":148},"Memory overview","\u002Fdocs\u002Fmemory\u002Foverview","docs\u002F05.memory\u002F01.overview",{"title":150,"path":151,"stem":152},"Write path","\u002Fdocs\u002Fmemory\u002Fwrite-path","docs\u002F05.memory\u002F02.write-path",{"title":154,"path":155,"stem":156},"Search and lifecycle","\u002Fdocs\u002Fmemory\u002Fsearch-and-lifecycle","docs\u002F05.memory\u002F03.search-and-lifecycle",{"title":158,"path":159,"stem":160,"children":161},"Context","\u002Fdocs\u002Fcontext","docs\u002F06.context\u002Findex",[162,163,167],{"title":158,"path":159,"stem":160},{"title":164,"path":165,"stem":166},"Assembly pipeline","\u002Fdocs\u002Fcontext\u002Foverview","docs\u002F06.context\u002F01.overview",{"title":168,"path":169,"stem":170},"Ranking and packing","\u002Fdocs\u002Fcontext\u002Franking-and-packing","docs\u002F06.context\u002F02.ranking-and-packing",{"title":172,"path":173,"stem":174,"children":175},"Intelligence","\u002Fdocs\u002Fintelligence","docs\u002F07.intelligence\u002Findex",[176,177,181,185,189],{"title":172,"path":173,"stem":174},{"title":178,"path":179,"stem":180},"Workers","\u002Fdocs\u002Fintelligence\u002Fworkers","docs\u002F07.intelligence\u002F01.workers",{"title":182,"path":183,"stem":184},"Extraction","\u002Fdocs\u002Fintelligence\u002Fextraction","docs\u002F07.intelligence\u002F02.extraction",{"title":186,"path":187,"stem":188},"Embedder","\u002Fdocs\u002Fintelligence\u002Fembedder","docs\u002F07.intelligence\u002F03.embedder",{"title":190,"path":191,"stem":192},"Extraction evaluation","\u002Fdocs\u002Fintelligence\u002Fevaluation","docs\u002F07.intelligence\u002F04.evaluation",{"title":194,"path":195,"stem":196,"children":197},"API reference","\u002Fdocs\u002Fapi-reference","docs\u002F08.api-reference\u002Findex",[198,199,203,207,211,215,219,223],{"title":194,"path":195,"stem":196},{"title":200,"path":201,"stem":202},"Chat completions","\u002Fdocs\u002Fapi-reference\u002Fchat-completions","docs\u002F08.api-reference\u002F01.chat-completions",{"title":204,"path":205,"stem":206},"Responses","\u002Fdocs\u002Fapi-reference\u002Fresponses","docs\u002F08.api-reference\u002F02.responses",{"title":208,"path":209,"stem":210},"Errors","\u002Fdocs\u002Fapi-reference\u002Ferrors","docs\u002F08.api-reference\u002F03.errors",{"title":212,"path":213,"stem":214},"Health and metrics","\u002Fdocs\u002Fapi-reference\u002Fproxy-health","docs\u002F08.api-reference\u002F04.proxy-health",{"title":216,"path":217,"stem":218},"HTTP route inventory","\u002Fdocs\u002Fapi-reference\u002Fhttp-routes","docs\u002F08.api-reference\u002F05.http-routes",{"title":220,"path":221,"stem":222},"Management API","\u002Fdocs\u002Fapi-reference\u002Fmanagement-api","docs\u002F08.api-reference\u002F06.management-api",{"title":224,"path":225,"stem":226},"Auth gRPC (ValidateToken, ValidateAgent)","\u002Fdocs\u002Fapi-reference\u002Fauth-grpc","docs\u002F08.api-reference\u002F07.auth-grpc",{"title":228,"path":229,"stem":230,"children":231},"MCP","\u002Fdocs\u002Fmcp","docs\u002F09.mcp\u002Findex",[232,233,237],{"title":228,"path":229,"stem":230},{"title":234,"path":235,"stem":236},"Memory server","\u002Fdocs\u002Fmcp\u002Fmemory-server","docs\u002F09.mcp\u002F01.memory-server",{"title":238,"path":239,"stem":240},"Operator gateway","\u002Fdocs\u002Fmcp\u002Foperator-gateway","docs\u002F09.mcp\u002F02.operator-gateway",{"title":242,"path":243,"stem":244,"children":245},"Security","\u002Fdocs\u002Fsecurity","docs\u002F10.security\u002Findex",[246,247,251,254,258,262],{"title":242,"path":243,"stem":244},{"title":248,"path":249,"stem":250},"Security overview","\u002Fdocs\u002Fsecurity\u002Foverview","docs\u002F10.security\u002F01.overview",{"title":100,"path":252,"stem":253},"\u002Fdocs\u002Fsecurity\u002Fauthentication","docs\u002F10.security\u002F02.authentication",{"title":255,"path":256,"stem":257},"Tenant isolation","\u002Fdocs\u002Fsecurity\u002Ftenant-isolation","docs\u002F10.security\u002F03.tenant-isolation",{"title":259,"path":260,"stem":261},"Secrets and keys","\u002Fdocs\u002Fsecurity\u002Fsecrets-and-keys","docs\u002F10.security\u002F04.secrets-and-keys",{"title":263,"path":264,"stem":265},"Prompt injection","\u002Fdocs\u002Fsecurity\u002Fprompt-injection","docs\u002F10.security\u002F05.prompt-injection",{"title":267,"path":268,"stem":269,"children":270},"Operations","\u002Fdocs\u002Foperations","docs\u002F11.operations\u002Findex",[271,272,276,280,284,288,292,296,300,304,308],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275},"Observability","\u002Fdocs\u002Foperations\u002Fobservability","docs\u002F11.operations\u002F01.observability",{"title":277,"path":278,"stem":279},"Traces and ClickHouse","\u002Fdocs\u002Foperations\u002Ftraces-and-clickhouse","docs\u002F11.operations\u002F02.traces-and-clickhouse",{"title":281,"path":282,"stem":283},"Health checks","\u002Fdocs\u002Foperations\u002Fhealth-checks","docs\u002F11.operations\u002F03.health-checks",{"title":285,"path":286,"stem":287},"Incident response","\u002Fdocs\u002Foperations\u002Fincident-response","docs\u002F11.operations\u002F04.incident-response",{"title":289,"path":290,"stem":291},"Troubleshooting","\u002Fdocs\u002Foperations\u002Ftroubleshooting","docs\u002F11.operations\u002F05.troubleshooting",{"title":293,"path":294,"stem":295},"Billing and budgets","\u002Fdocs\u002Foperations\u002Fbilling-and-budgets","docs\u002F11.operations\u002F06.billing-and-budgets",{"title":297,"path":298,"stem":299},"Evidence and relay","\u002Fdocs\u002Foperations\u002Fevidence-and-relay","docs\u002F11.operations\u002F07.evidence-and-relay",{"title":301,"path":302,"stem":303},"Console","\u002Fdocs\u002Foperations\u002Fconsole","docs\u002F11.operations\u002F08.console",{"title":305,"path":306,"stem":307},"Failure modes and recovery","\u002Fdocs\u002Foperations\u002Ffailure-and-recovery","docs\u002F11.operations\u002F09.failure-and-recovery",{"title":309,"path":310,"stem":311},"Data lifecycle and deletion","\u002Fdocs\u002Foperations\u002Fdata-lifecycle-and-deletion","docs\u002F11.operations\u002F10.data-lifecycle-and-deletion",{"title":313,"path":314,"stem":315,"children":316},"Deployment","\u002Fdocs\u002Fdeployment","docs\u002F12.deployment\u002Findex",[317,318,322,326,330],{"title":313,"path":314,"stem":315},{"title":319,"path":320,"stem":321},"Docker Compose (dev)","\u002Fdocs\u002Fdeployment\u002Fdocker-compose","docs\u002F12.deployment\u002F01.docker-compose",{"title":323,"path":324,"stem":325},"Environment variables","\u002Fdocs\u002Fdeployment\u002Fenvironment-variables","docs\u002F12.deployment\u002F02.environment-variables",{"title":327,"path":328,"stem":329},"Kubernetes","\u002Fdocs\u002Fdeployment\u002Fkubernetes","docs\u002F12.deployment\u002F03.kubernetes",{"title":331,"path":332,"stem":333},"Production readiness","\u002Fdocs\u002Fdeployment\u002Fproduction-readiness","docs\u002F12.deployment\u002F04.production-readiness",{"title":335,"path":336,"stem":337,"children":338},"Reference","\u002Fdocs\u002Freference","docs\u002F13.reference\u002Findex",[339,340,344,348,352],{"title":335,"path":336,"stem":337},{"title":341,"path":342,"stem":343},"Glossary","\u002Fdocs\u002Freference\u002Fglossary","docs\u002F13.reference\u002F01.glossary",{"title":345,"path":346,"stem":347},"Roadmap and gates","\u002Fdocs\u002Freference\u002Froadmap","docs\u002F13.reference\u002F02.roadmap",{"title":349,"path":350,"stem":351},"Architecture decisions","\u002Fdocs\u002Freference\u002Fadr","docs\u002F13.reference\u002F03.adr",{"title":353,"path":354,"stem":355},"Compatibility and versioning","\u002Fdocs\u002Freference\u002Fcompatibility","docs\u002F13.reference\u002F04.compatibility",{"all":357,"index":641},[358,667,1093],{"id":359,"title":360,"audience":361,"author":362,"authorUrl":363,"body":364,"coverIndex":651,"coverLabel":652,"coverTone":651,"date":653,"decisionStatus":361,"description":370,"excerpt":361,"extension":654,"featured":655,"historical":656,"meta":657,"navigation":655,"path":658,"readingTime":659,"scope":361,"seo":660,"sourceOfTruth":361,"status":361,"stem":661,"tags":662,"verifiedDate":361,"__hash__":666},"blog\u002Fblog\u002Fproxy-before-memory.md","Put the proxy before the memory",null,"Elshaday Mengesha","https:\u002F\u002Fgithub.com\u002FRick1330",{"type":365,"value":366,"toc":640},"minimark",[367,371,374,379,382,401,404,407,411,414,425,432,435,446,453,457,460,467,484,488,491,494,497,501,504,571,574,578,581,598,601,605,608,611,615],[368,369,370],"p",{},"Most agent-memory systems are designed from the retrieval side inward. A query arrives, vectors are compared, the best results are packed into a prompt, and the model answers. That design works in a notebook. It becomes dangerous the moment two organizations share the same database.",[368,372,373],{},"The interesting question is not “how do we retrieve relevant memories?” It is “what has to be true before retrieval is allowed to run?” At IBEX Harness, the answer is the request path: validate the credential, bind it to an agent and organization, apply policy and limits, then retrieve. Memory is a capability behind that boundary, not a side channel around it.",[375,376,378],"h2",{"id":377},"a-memory-is-not-a-document","A memory is not a document",[368,380,381],{},"It helps to stop thinking of a memory as text with a vector attached. In a multi-tenant system, a memory row is a compound assertion:",[383,384,385,389,392,395,398],"ul",{},[386,387,388],"li",{},"It belongs to exactly one organization and one agent.",[386,390,391],{},"It has provenance: where it came from and when it was observed.",[386,393,394],{},"It has a lifecycle: active, superseded, quarantined, forgotten, or tombstoned.",[386,396,397],{},"It has a confidence and a retrieval history.",[386,399,400],{},"It may contain untrusted instructions that must never be executed as policy.",[368,402,403],{},"Each of those properties is a security decision. If the organization comes from the request body instead of the authenticated principal, an attacker can choose whose memories to search. If the agent scope is inferred from a model name, one agent can read another agent’s context. If a superseded memory is still returned to users, “forget” becomes a suggestion rather than a guarantee.",[368,405,406],{},"That is why the proxy comes first.",[375,408,410],{"id":409},"the-order-that-matters","The order that matters",[368,412,413],{},"A protected chat request moves through the following gates before any provider sees it:",[415,416,422],"pre",{"className":417,"code":419,"language":420,"meta":421},[418],"language-text","body limits\n  → bearer token validation\n  → agent binding\n  → policy snapshot + budget admission\n  → rate limit\n  → request validation\n  → directive and session resolution\n  → bounded context assembly\n  → provider handoff\n","text","",[423,424,419],"code",{"__ignoreMap":421},[368,426,427,428],{},"The exact middleware names are less important than the ordering invariant: ",[429,430,431],"strong",{},"no tenant-scoped retrieval runs before tenant scope is established from the credential.",[368,433,434],{},"Two details make that invariant useful in practice.",[368,436,437,438,441,442,445],{},"First, the proxy does not trust a path parameter for tenancy. ",[423,439,440],{},"POST \u002Fv1\u002Fchat\u002Fcompletions"," derives the organization from the validated personal access token. Diagnostic routes that do carry an organization in the path must match the token’s organization, or the request fails with ",[423,443,444],{},"403",".",[368,447,448,449,452],{},"Second, agent binding is explicit. The caller sends ",[423,450,451],{},"X-IBEX-Agent-ID","; auth checks that the agent belongs to the token’s organization. A valid token for Agent A cannot be replayed against Agent B. This closes the “valid token, wrong principal” gap that a token-only design leaves open.",[375,454,456],{"id":455},"fail-closed-means-fail-before-work","Fail closed means fail before work",[368,458,459],{},"A fail-closed system does not merely return an error after doing the expensive or sensitive part. It refuses to start.",[368,461,462,463,466],{},"If auth is unavailable, the proxy returns ",[423,464,465],{},"503",". If the policy snapshot cannot be verified, the request does not proceed to a provider. If a durable budget hold cannot be claimed when one is required, the request fails rather than spending money it cannot account for. If a keyed memory write cannot reach its idempotency ledger, the write fails rather than risking a duplicate.",[368,468,469,470,473,474,477,478,480,481,483],{},"That last case is worth spelling out because “write it and deduplicate later” is a common shortcut. The memory write path requires an idempotency ledger scoped by ",[423,471,472],{},"(org, agent, key)",". The same key and body replays the stored response; the same key with a different body returns ",[423,475,476],{},"409","; an active claim returns ",[423,479,476],{}," with a retry hint; an unavailable ledger returns ",[423,482,465],{},". The system would rather be temporarily unavailable than silently create a second memory.",[375,485,487],{"id":486},"retrieval-is-bounded-not-best-effort","Retrieval is bounded, not best-effort",[368,489,490],{},"Context assembly is on the hot path, so it gets a deadline rather than an open-ended chance to be thorough. The service retrieves directive, hot memories, and cold memories in parallel, with per-branch timeouts under an outer retrieval wall. Slow branches are cancelled. Completed branches are kept.",[368,492,493],{},"If assembly fails or exceeds its deadline, the proxy can continue with directive-only or history-only context. That is a quality degradation, not an authorization change. A failed retrieval must never widen visibility, invent a memory, or fall back to an unscoped query.",[368,495,496],{},"The same principle applies to the data itself. Memory content is untrusted input. The formatter serializes it as data, escapes attribute values and bodies, and uses a per-assembly nonce so a memory cannot forge its own closing tag. A memory that says “ignore previous instructions” is still a memory, not an instruction.",[375,498,500],{"id":499},"where-isolation-is-enforced","Where isolation is enforced",[368,502,503],{},"Tenant isolation is deliberately redundant:",[505,506,507,520],"table",{},[508,509,510],"thead",{},[511,512,513,517],"tr",{},[514,515,516],"th",{},"Layer",[514,518,519],{},"Enforcement",[521,522,523,532,544,552,560],"tbody",{},[511,524,525,529],{},[526,527,528],"td",{},"HTTP",[526,530,531],{},"Organization comes from the validated token, not caller-supplied body fields.",[511,533,534,537],{},[526,535,536],{},"Application",[526,538,539,540,543],{},"Every query carries an explicit ",[423,541,542],{},"org_id"," predicate.",[511,545,546,549],{},[526,547,548],{},"PostgreSQL",[526,550,551],{},"Row-level security checks the transaction’s organization context.",[511,553,554,557],{},[526,555,556],{},"Redis",[526,558,559],{},"Tenant-bearing keys include the organization identifier.",[511,561,562,565],{},[526,563,564],{},"ClickHouse",[526,566,567,568,570],{},"Queries must carry an explicit ",[423,569,542],{},"; there is no row-level security to catch omissions.",[368,572,573],{},"RLS is not a substitute for application predicates, and application predicates are not a substitute for RLS. The redundancy is the point. A single missing check should not become a cross-tenant incident.",[375,575,577],{"id":576},"what-this-design-does-not-prove","What this design does not prove",[368,579,580],{},"The local implementation is substantial, but “implemented locally” is not a production-readiness claim. The current boundary still includes open work:",[383,582,583,586,589,592,595],{},[386,584,585],{},"There is no measured hosted p99 SLO for the full chat path.",[386,587,588],{},"Context assembly is provisional because caller authentication at its gRPC boundary and the deadline contract remain open.",[386,590,591],{},"Full end-to-end tenant-isolation proof across backups, archives, and every projection is not complete.",[386,593,594],{},"Typed memory lifecycle and a durable revision ledger remain open.",[386,596,597],{},"Hosted HA, restore, and multi-region behavior are not claimed.",[368,599,600],{},"Those limits belong in the same conversation as the design. A security architecture that only describes its happy path is marketing, not engineering.",[375,602,604],{"id":603},"the-rule-to-keep","The rule to keep",[368,606,607],{},"Memory makes an agent feel continuous. It also turns every request into a potential read of someone else’s history. The only durable way to reconcile those facts is to make identity, policy, and limits precede retrieval.",[368,609,610],{},"Put the proxy before the memory. Then make the memory prove, at every layer, that it knows whose context it is allowed to return.",[375,612,614],{"id":613},"read-next","Read next",[383,616,617,623,628,635],{},[386,618,619,622],{},[620,621,49],"a",{"href":50}," — the full protected request path.",[386,624,625,627],{},[620,626,255],{"href":256}," — RLS, Redis namespacing, and analytics rules.",[386,629,630,634],{},[620,631,633],{"href":632},"\u002Fblog\u002Fmemory-write-path","Memory write path"," — what happens after a memory is admitted.",[386,636,637,639],{},[620,638,57],{"href":58}," — the current capability boundary.",{"title":421,"searchDepth":641,"depth":641,"links":642},2,[643,644,645,646,647,648,649,650],{"id":377,"depth":641,"text":378},{"id":409,"depth":641,"text":410},{"id":455,"depth":641,"text":456},{"id":486,"depth":641,"text":487},{"id":499,"depth":641,"text":500},{"id":576,"depth":641,"text":577},{"id":603,"depth":641,"text":604},{"id":613,"depth":641,"text":614},"1","Request path","2026-10-22","md",true,false,{},"\u002Fblog\u002Fproxy-before-memory","9 min read",{"title":360,"description":370},"blog\u002Fproxy-before-memory",[663,664,665],"architecture","security","proxy","H_7c2sm5Hz1YTvTu3p94Bpx1ZxNhAxnyBXtCqJz4WYQ",{"id":668,"title":669,"audience":361,"author":362,"authorUrl":363,"body":670,"coverIndex":1082,"coverLabel":1083,"coverTone":1082,"date":1084,"decisionStatus":361,"description":674,"excerpt":361,"extension":654,"featured":656,"historical":656,"meta":1085,"navigation":655,"path":632,"readingTime":1086,"scope":361,"seo":1087,"sourceOfTruth":361,"status":361,"stem":1088,"tags":1089,"verifiedDate":361,"__hash__":1092},"blog\u002Fblog\u002Fmemory-write-path.md","The memory write path is a refinery, not an insert",{"type":365,"value":671,"toc":1068},[672,675,678,681,685,747,750,754,757,760,764,779,782,786,789,792,796,803,806,810,824,827,873,876,879,883,886,889,892,896,906,969,972,976,979,982,999,1002,1006,1009,1012,1026,1029,1033,1036,1039,1041,1064],[368,673,674],{},"A memory system that only inserts rows is a log with a search box. The hard part is deciding what should become memory, what should replace something older, what should be quarantined, and what should never be written twice.",[368,676,677],{},"That decision process is the write path. At IBEX Harness it is an ordered pipeline: validate, redact, deduplicate, embed, detect conflict, label, persist, then update caches and indexes after the transaction commits. Each stage has a narrow job and a deliberate failure mode.",[368,679,680],{},"The pipeline is implemented and locally evidenced. It is not a claim that every derived copy is deletion-certified or that the lifecycle contract is finished. Those boundaries matter, so they are stated alongside the design.",[375,682,684],{"id":683},"the-sequence","The sequence",[415,686,690],{"className":687,"code":688,"language":689,"meta":421,"style":421},"language-mermaid shiki shiki-themes material-theme-lighter vitesse-light vitesse-dark","flowchart LR\n  Validate[\"Validate\"] --> PII[\"PII detect \u002F redact\"]\n  PII --> Exact[\"Exact dedup\"]\n  Exact --> Embed[\"Embed\"]\n  Embed --> Near[\"Near dedup\"]\n  Near --> Conflict[\"Conflict \u002F supersession\"]\n  Conflict --> Labels[\"Labels\"]\n  Labels --> Persist[\"Transactional persist\"]\n  Persist --> Effects[\"After-commit cache + vector update\"]\n","mermaid",[423,691,692,700,705,711,717,723,729,735,741],{"__ignoreMap":421},[693,694,697],"span",{"class":695,"line":696},"line",1,[693,698,699],{},"flowchart LR\n",[693,701,702],{"class":695,"line":641},[693,703,704],{},"  Validate[\"Validate\"] --> PII[\"PII detect \u002F redact\"]\n",[693,706,708],{"class":695,"line":707},3,[693,709,710],{},"  PII --> Exact[\"Exact dedup\"]\n",[693,712,714],{"class":695,"line":713},4,[693,715,716],{},"  Exact --> Embed[\"Embed\"]\n",[693,718,720],{"class":695,"line":719},5,[693,721,722],{},"  Embed --> Near[\"Near dedup\"]\n",[693,724,726],{"class":695,"line":725},6,[693,727,728],{},"  Near --> Conflict[\"Conflict \u002F supersession\"]\n",[693,730,732],{"class":695,"line":731},7,[693,733,734],{},"  Conflict --> Labels[\"Labels\"]\n",[693,736,738],{"class":695,"line":737},8,[693,739,740],{},"  Labels --> Persist[\"Transactional persist\"]\n",[693,742,744],{"class":695,"line":743},9,[693,745,746],{},"  Persist --> Effects[\"After-commit cache + vector update\"]\n",[368,748,749],{},"The ordering is not arbitrary. Redaction happens before hashing because the same content with an email address should not produce two different dedup identities. Embedding happens after redaction because the vector should represent the content the system is allowed to store. Persistence happens after conflict resolution because a write may update relationships as well as insert a row.",[375,751,753],{"id":752},"validate-before-doing-anything-expensive","Validate before doing anything expensive",[368,755,756],{},"The request must carry an organization and agent scope, bounded content, a source, and valid optional temporal fields. Labels are validated as a set: an explicit empty array is invalid, duplicates are rejected, and the maximum is five. Invalid input fails before embedding or database work.",[368,758,759],{},"Tenant and agent scope come from the authenticated principal. They are not accepted as free-form body fields that a caller can point at another organization.",[375,761,763],{"id":762},"redact-first-then-hash","Redact first, then hash",[368,765,766,767,770,771,774,775,778],{},"Presidio runs a two-tier detect-and-redact pass. The default redaction confidence is ",[423,768,769],{},"0.70",". High-confidence findings become typed placeholders such as ",[423,772,773],{},"[EMAIL_ADDRESS]",". Low-confidence or disallowed findings can place the record in ",[423,776,777],{},"quarantined"," status.",[368,780,781],{},"Quarantined content is stored for review, but it is not ordinary active memory. It must not be injected as trusted context. “Stored” and “retrievable” are separate states, and the write path is where that distinction is created.",[375,783,785],{"id":784},"exact-deduplication-is-a-hash-not-a-model-call","Exact deduplication is a hash, not a model call",[368,787,788],{},"After redaction, the content is normalized and hashed with SHA-256. If an active row already has the same hash, the write is an exact duplicate. Retrieval metadata is bumped, embedding is skipped, and the duplicate is not persisted as a second active memory. A partial unique index protects the same invariant at the database level.",[368,790,791],{},"This is the cheapest and most reliable dedup stage. It also handles the most common case: a client retries the same request, or two sessions extract the same sentence verbatim.",[375,793,795],{"id":794},"near-deduplication-is-a-signal-not-a-verdict","Near deduplication is a signal, not a verdict",[368,797,798,799,802],{},"The service embeds the redacted content and searches for a sufficiently similar existing memory. The default threshold is ",[423,800,801],{},"0.92",", using a strict greater-than comparison. A match is reported as a near duplicate.",[368,804,805],{},"The wording matters. A near duplicate is not automatically “the same memory.” Two sentences can be close in embedding space while differing in a date, a scope, or a negation. The write path records the relationship and lets the conflict stage decide what it means rather than silently collapsing distinct knowledge.",[375,807,809],{"id":808},"conflict-handling-starts-with-intervals","Conflict handling starts with intervals",[368,811,812,813,816,817,820,821,823],{},"Conflict detection is interval-overlap-first. If the new memory has the same subject, a newer ",[423,814,815],{},"valid_from",", and a non-overlapping validity interval, it can produce a ",[423,818,819],{},"supersedes"," relationship without an LLM call. When intervals overlap or ",[423,822,815],{}," is missing, the service can call a pluggable classifier.",[368,825,826],{},"The relationship types are deliberately explicit:",[505,828,829,839],{},[508,830,831],{},[511,832,833,836],{},[514,834,835],{},"Relationship",[514,837,838],{},"Meaning",[521,840,841,849,857,865],{},[511,842,843,846],{},[526,844,845],{},"Contradiction",[526,847,848],{},"The new memory asserts the opposite of an existing active memory.",[511,850,851,854],{},[526,852,853],{},"Overlap",[526,855,856],{},"Both memories cover substantially the same claim or period.",[511,858,859,862],{},[526,860,861],{},"Supersedes",[526,863,864],{},"The new memory replaces an older claim going forward.",[511,866,867,870],{},[526,868,869],{},"Specializes",[526,871,872],{},"The new memory refines a broader existing claim.",[368,874,875],{},"Temporal validity is what keeps this from becoming a pile of contradictory facts. A memory can be true for a period and then stop being current without being deleted. User-facing reads are validity-fenced and return active, current memories by default. Internal conflict and dedup paths can request historical candidates through a capability that user reads do not get.",[368,877,878],{},"That separation is important. If normal search returned superseded memories, the agent would keep retrieving a fact that the system already knows is out of date. If conflict detection could only see current memories, it would lose the history it needs to decide whether a new claim supersedes an old one.",[375,880,882],{"id":881},"transaction-first-effects-after","Transaction first, effects after",[368,884,885],{},"The memory row, labels, and relationship changes are written in one transaction. After commit, the service updates the optional Redis hot cache and the vector index.",[368,887,888],{},"Cache and index updates are after-commit effects because they are derived state. A cache failure should not erase a durable memory; it should degrade read performance and emit telemetry. The database remains the source of truth.",[368,890,891],{},"The write path also records provenance and lifecycle columns so later stages can reason about where a memory came from and whether it has been superseded or forgotten.",[375,893,895],{"id":894},"idempotency-is-part-of-the-contract","Idempotency is part of the contract",[368,897,898,899,902,903,905],{},"Clients can send ",[423,900,901],{},"X-Idempotency-Key",". The ledger is scoped by ",[423,904,472],{},", not just the key alone.",[505,907,908,918],{},[508,909,910],{},[511,911,912,915],{},[514,913,914],{},"Case",[514,916,917],{},"Result",[521,919,920,928,938,949,959],{},[511,921,922,925],{},[526,923,924],{},"Same key, same body",[526,926,927],{},"Replay the stored successful response.",[511,929,930,933],{},[526,931,932],{},"Same key, different body",[526,934,935,445],{},[423,936,937],{},"409 IDEMPOTENCY_CONFLICT",[511,939,940,943],{},[526,941,942],{},"Claim still active",[526,944,945,948],{},[423,946,947],{},"409 IDEMPOTENCY_IN_PROGRESS"," with a retry hint.",[511,950,951,954],{},[526,952,953],{},"PostgreSQL unavailable for a keyed write",[526,955,956,445],{},[423,957,958],{},"503 IDEMPOTENCY_UNAVAILABLE",[511,960,961,964],{},[526,962,963],{},"Row committed but claim cannot finalize",[526,965,966,968],{},[423,967,465],{},"; do not report success over an unfinished claim.",[368,970,971],{},"This is stricter than a best-effort dedup check. A retry is safe because the system can recognize the original attempt, not because the embedding happens to be similar.",[375,973,975],{"id":974},"what-the-write-path-refuses-to-hide","What the write path refuses to hide",[368,977,978],{},"The pipeline logs identifiers, stage outcomes, and timings. It does not log raw content, embeddings, or credentials. Diagnosis uses the memory ID and request ID.",[368,980,981],{},"Failures are explicit:",[383,983,984,987,990,993,996],{},[386,985,986],{},"An embedding failure is an error, not a reason to persist a half-written active record.",[386,988,989],{},"A PII finding can quarantine the record rather than silently dropping it.",[386,991,992],{},"A conflict classifier failure does not grant permission to overwrite history.",[386,994,995],{},"A keyed write without a reachable ledger fails closed.",[386,997,998],{},"A cache outage degrades reads but does not corrupt the durable record.",[368,1000,1001],{},"These choices trade availability for integrity at the write boundary. That is usually the right trade for memory: a delayed write can be retried, but a wrongly merged or wrongly scoped memory is much harder to unwind.",[375,1003,1005],{"id":1004},"the-evidence-boundary","The evidence boundary",[368,1007,1008],{},"The substrate and write pipeline are implemented locally. The current evidence includes unit and integration coverage for dedup, conflict candidates, temporal reads, PII handling, tenant scope, idempotency, and lifecycle revision fencing.",[368,1010,1011],{},"It does not include:",[383,1013,1014,1017,1020,1023],{},[386,1015,1016],{},"A complete deletion guarantee across PostgreSQL, Redis, ClickHouse, and archives.",[386,1018,1019],{},"Exactly-once projection effects across stores.",[386,1021,1022],{},"A durable typed revision ledger with provenance checkpoints.",[386,1024,1025],{},"Hosted\u002FHA behavior or a measured write-latency SLO.",[368,1027,1028],{},"The forget route is the clearest example. It writes a revision-fenced tombstone, invalidates stale workers, removes the hot-cache entry after commit, and makes reads and dedup ignore the row. That is meaningful local behavior. It is not yet an accepted public deletion contract, because full-store deletion and restore semantics remain open.",[375,1030,1032],{"id":1031},"the-lesson","The lesson",[368,1034,1035],{},"Memory quality is not created at retrieval time. It is created when the system decides what to write, what to merge, what to supersede, and what to quarantine.",[368,1037,1038],{},"Treat the write path as a refinery. Make every stage observable, make every failure explicit, and never let “we stored it” stand in for “we know what it means.”",[375,1040,614],{"id":613},[383,1042,1043,1048,1054,1059],{},[386,1044,1045,1047],{},[620,1046,146],{"href":147}," — substrate, read path, and current limits.",[386,1049,1050,1053],{},[620,1051,1052],{"href":151},"Write path reference"," — validation, PII, dedup, conflict, and idempotency.",[386,1055,1056,1058],{},[620,1057,154],{"href":155}," — temporal reads, ranking, and tombstones.",[386,1060,1061,1063],{},[620,1062,360],{"href":658}," — the boundary that makes tenant-scoped retrieval possible.",[1065,1066,1067],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":421,"searchDepth":641,"depth":641,"links":1069},[1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081],{"id":683,"depth":641,"text":684},{"id":752,"depth":641,"text":753},{"id":762,"depth":641,"text":763},{"id":784,"depth":641,"text":785},{"id":794,"depth":641,"text":795},{"id":808,"depth":641,"text":809},{"id":881,"depth":641,"text":882},{"id":894,"depth":641,"text":895},{"id":974,"depth":641,"text":975},{"id":1004,"depth":641,"text":1005},{"id":1031,"depth":641,"text":1032},{"id":613,"depth":641,"text":614},"2","Memory writes","2026-10-18",{},"11 min read",{"title":669,"description":674},"blog\u002Fmemory-write-path",[1090,663,1091],"memory","data","mEo_jrk-zpz97z-LhnH2XknNax2Kvdv3ev6GlZXT87o",{"id":1094,"title":1095,"audience":361,"author":362,"authorUrl":363,"body":1096,"coverIndex":1481,"coverLabel":1482,"coverTone":1481,"date":1483,"decisionStatus":361,"description":1100,"excerpt":361,"extension":654,"featured":656,"historical":656,"meta":1484,"navigation":655,"path":1485,"readingTime":1486,"scope":361,"seo":1487,"sourceOfTruth":361,"status":361,"stem":1488,"tags":1489,"verifiedDate":361,"__hash__":1492},"blog\u002Fblog\u002Fcontext-assembly-budget.md","How to spend a token budget without losing the plot",{"type":365,"value":1097,"toc":1470},[1098,1101,1104,1107,1111,1114,1120,1123,1126,1129,1132,1136,1139,1195,1198,1217,1220,1224,1227,1230,1284,1298,1308,1322,1328,1335,1339,1342,1345,1369,1372,1375,1379,1382,1385,1388,1392,1395,1398,1401,1405,1408,1411,1428,1435,1439,1442,1445,1447],[368,1099,1100],{},"“Retrieve the top ten memories and concatenate them” is not context assembly. It is a way to discover your model’s context limit in production.",[368,1102,1103],{},"A real assembly pipeline has to divide a finite token budget among the response, the directive, recent conversation, tool schemas, formatting overhead, and retrieved memory. It has to do that under a deadline, while retrieval branches fail independently, and it has to leave a clear record of what was excluded and why.",[368,1105,1106],{},"At IBEX Harness, context assembly is a Python gRPC service between the proxy and the provider. The pipeline is implemented and locally tested. The service is provisional because caller authentication at its gRPC boundary and parts of the deadline contract remain open. That distinction is part of the design, not a footnote.",[375,1108,1110],{"id":1109},"the-budget-is-subtraction-not-guesswork","The budget is subtraction, not guesswork",[368,1112,1113],{},"The usable memory budget is what remains after every other obligation is paid:",[415,1115,1118],{"className":1116,"code":1117,"language":420,"meta":421},[418],"usable_budget =\n  context_window\n  − response_reserve\n  − safety_buffer\n  − directive_tokens\n  − messages_tokens\n  − tool_schema_tokens\n  − formatter_overhead_tokens\n",[423,1119,1117],{"__ignoreMap":421},[368,1121,1122],{},"Each term has a policy behind it.",[368,1124,1125],{},"The response reserve is bounded at a minimum of 500 tokens, a maximum of 4,096, and by default 15% of the context window, never exceeding the model’s maximum output. The safety buffer is a fraction of the context window from the tokenizer policy. Formatter overhead is estimated before packing, not after, so the packer’s guarantee still means something once tags and section joins are applied.",[368,1127,1128],{},"If the result falls below zero, it is clamped to zero. If the usable budget is below 256 tokens, the assembly is marked constrained. That flag matters: a technically valid request with almost no room for memory is not the same as a healthy one.",[368,1130,1131],{},"Token counts are conservative estimates, not exact tokenizer output. The capability catalog records the estimate kind and tokenizer family so incompatible assumptions fail instead of being silently averaged together.",[375,1133,1135],{"id":1134},"retrieval-branches-run-in-parallel-then-stop-waiting","Retrieval branches run in parallel, then stop waiting",[368,1137,1138],{},"The service retrieves three things concurrently:",[505,1140,1141,1154],{},[508,1142,1143],{},[511,1144,1145,1148,1151],{},[514,1146,1147],{},"Branch",[514,1149,1150],{},"Source",[514,1152,1153],{},"Default timeout",[521,1155,1156,1169,1182],{},[511,1157,1158,1161,1164],{},[526,1159,1160],{},"Directive",[526,1162,1163],{},"Redis-backed directive lookup",[526,1165,1166],{},[423,1167,1168],{},"5ms",[511,1170,1171,1174,1177],{},[526,1172,1173],{},"Hot memory",[526,1175,1176],{},"Per-agent recent\u002Fhigh-value memory cache",[526,1178,1179],{},[423,1180,1181],{},"15ms",[511,1183,1184,1187,1190],{},[526,1185,1186],{},"Cold memory",[526,1188,1189],{},"Semantic search in the memory service",[526,1191,1192],{},[423,1193,1194],{},"45ms",[368,1196,1197],{},"Recent messages are not a fourth branch. They arrive in the request and are counted directly. The session checkpoint table stores message hashes, not message text, so the context service cannot silently read conversation history from it.",[368,1199,1200,1201,1204,1205,1208,1209,1212,1213,1216],{},"The outer retrieval wall is the smaller of the configured client timeout and server deadline: by default ",[423,1202,1203],{},"min(45ms, 40ms)",". A slow branch is cancelled; a branch that has already completed is kept. Each branch records ",[423,1206,1207],{},"success",", ",[423,1210,1211],{},"timeout",", or ",[423,1214,1215],{},"error",", and can degrade independently.",[368,1218,1219],{},"This is the difference between a deadline and a timeout. A timeout says “this call must finish.” A deadline says “the request has this much total time, and every child operation shares it.” The second is the only useful contract when three retrievals and a packer are competing for the same milliseconds.",[375,1221,1223],{"id":1222},"there-are-two-scores-and-they-are-not-the-same","There are two scores, and they are not the same",[368,1225,1226],{},"A common source of bad benchmarking is comparing numbers that look similar but measure different stages.",[368,1228,1229],{},"The memory service ranks hydrated candidates with a five-part composite:",[505,1231,1232,1242],{},[508,1233,1234],{},[511,1235,1236,1239],{},[514,1237,1238],{},"Component",[514,1240,1241],{},"Weight",[521,1243,1244,1252,1260,1268,1276],{},[511,1245,1246,1249],{},[526,1247,1248],{},"Relevance",[526,1250,1251],{},"0.40",[511,1253,1254,1257],{},[526,1255,1256],{},"Recency",[526,1258,1259],{},"0.25",[511,1261,1262,1265],{},[526,1263,1264],{},"Usefulness",[526,1266,1267],{},"0.20",[511,1269,1270,1273],{},[526,1271,1272],{},"Confidence",[526,1274,1275],{},"0.10",[511,1277,1278,1281],{},[526,1279,1280],{},"Access frequency",[526,1282,1283],{},"0.05",[368,1285,1286,1287,1290,1291,1294,1295,1297],{},"Before that weighted sum, a scoring-time relevance floor drops candidates whose relevance component is too low. The default floor is ",[423,1288,1289],{},"0.15",". It must stay below ",[423,1292,1293],{},"0.5"," because full-text hits use a fixed relevance sentinel of ",[423,1296,1293],{}," when they have no cosine score.",[368,1299,1300,1301,1304,1305,1307],{},"That is separate from the retrieval-time ANN cutoff. The production vector path uses ",[423,1302,1303],{},"min_similarity"," around ",[423,1306,769],{}," before hydration. One threshold decides which candidates are worth loading; the other decides which loaded candidates are worth ranking. Conflating them produces nonsense conclusions about recall.",[368,1309,1310,1311,1208,1314,1317,1318,1321],{},"The context packer has a different problem. It consumes ",[423,1312,1313],{},"similarity",[423,1315,1316],{},"confidence",", and ",[423,1319,1320],{},"rank"," from the memory HTTP response. Until the memory service exposes its full numeric composite on that wire, the packer uses a clearly labeled interim score:",[415,1323,1326],{"className":1324,"code":1325,"language":420,"meta":421},[418],"interim = 0.85 × similarity + 0.15 × confidence\n",[423,1327,1325],{"__ignoreMap":421},[368,1329,1330,1331,1334],{},"The field is named ",[423,1332,1333],{},"composite_score"," internally for formatter and proto alignment, but it is not the memory-service composite. Per-request weight fields are a forward contract and do not override the formula yet. Naming that honestly is better than presenting an interim heuristic as the final ranking model.",[375,1336,1338],{"id":1337},"packing-is-a-knapsack-not-a-sort","Packing is a knapsack, not a sort",[368,1340,1341],{},"Once candidates are scored, the packer solves a 0\u002F1 knapsack under the usable token budget. Each candidate has a value and a token cost. The packer can take it or leave it.",[368,1343,1344],{},"The implementation is deliberately pragmatic:",[1346,1347,1348,1351,1354,1357,1360,1363,1366],"ol",{},[386,1349,1350],{},"Sort by descending interim score, then by memory ID for deterministic ties.",[386,1352,1353],{},"Discretize the budget into 16-token buckets.",[386,1355,1356],{},"Run a vectorized dynamic program while the table stays under a cell ceiling.",[386,1358,1359],{},"Fall back to score-descending greedy packing for very large candidate sets.",[386,1361,1362],{},"Repair the result so exact per-memory estimates never exceed the budget.",[386,1364,1365],{},"Refill and locally improve after repair instead of accepting floor-bucket waste.",[386,1367,1368],{},"Emit packed memories in deterministic order.",[368,1370,1371],{},"The result records whether the DP or greedy path ran, how many candidates were evaluated, how many were excluded for budget, and whether the budget was reached. An empty candidate set is not a truncation. A non-positive budget with candidates present is.",[368,1373,1374],{},"That distinction is operationally useful. “No memories matched” and “memories matched but there was no room” call for different fixes. A dashboard that collapses both into “0 results” hides the actual failure.",[375,1376,1378],{"id":1377},"formatting-is-a-security-boundary","Formatting is a security boundary",[368,1380,1381],{},"The formatter has to turn selected memories into text without letting a memory become markup or instructions.",[368,1383,1384],{},"It applies a fixed order: directive, history, memories, tools. Memory blocks use a per-assembly nonce in the opening tag and an explicit safety sentence telling the model to treat the block as data. Attribute values and bodies are escaped so a memory cannot forge a closing tag or inject an element boundary. The formatter serializes; it never parses memory markup or executes instructions found inside it.",[368,1386,1387],{},"This is why “just put the memories in the system prompt” is not sufficient. Prompt injection is not only a model-alignment problem. It is a serialization problem, and serialization boundaries belong in code.",[375,1389,1391],{"id":1390},"degradation-is-a-feature-with-a-budget","Degradation is a feature with a budget",[368,1393,1394],{},"The proxy may continue without assembled context when context is disabled, no client is present, memory is skipped, retrieval fails, or the deadline is exceeded. The fallback is directive-only or history-only context, with metadata recording that assembly was attempted and degraded.",[368,1396,1397],{},"That behavior is intentional. A context outage should not become an inference outage. But degradation is quality loss, not an authorization change. A failed retrieval must never widen visibility, invent a memory, or substitute an unscoped result.",[368,1399,1400],{},"The failure record is part of the product. If the system silently drops memory and returns a confident answer, nobody can tell whether the agent was forgetful or the retrieval branch timed out.",[375,1402,1404],{"id":1403},"what-the-evidence-supports","What the evidence supports",[368,1406,1407],{},"The local pipeline has tests for budget calculation, parallel retrieval, branch timeouts, scoring floors, packing determinism, formatter escaping, and degradation paths. The design is concrete enough to inspect and criticize.",[368,1409,1410],{},"It does not yet support:",[383,1412,1413,1416,1419,1422,1425],{},[386,1414,1415],{},"Caller authentication at the context gRPC boundary. Non-loopback use currently trusts caller-supplied organization and agent IDs.",[386,1417,1418],{},"Exact tokenizer parity with every model.",[386,1420,1421],{},"A hosted p99 or end-to-end assembly SLO.",[386,1423,1424],{},"The full five-component memory composite on the context wire.",[386,1426,1427],{},"A fully reconciled deadline contract between proxy and service.",[368,1429,1430,1431,1434],{},"Those are the reasons the public status is ",[429,1432,1433],{},"provisional",", not a reason to describe the pipeline as a sketch. The code exists; the acceptance boundary does not.",[375,1436,1438],{"id":1437},"the-takeaway","The takeaway",[368,1440,1441],{},"Context assembly is where retrieval quality, latency, security, and token economics meet. You cannot optimize it by retrieving more. You optimize it by making the budget explicit, the deadlines shared, the scores named honestly, the packing deterministic, and the failure modes visible.",[368,1443,1444],{},"The best context window is not the fullest one. It is the one where every token has a reason to be there.",[375,1446,614],{"id":613},[383,1448,1449,1454,1459,1465],{},[386,1450,1451,1453],{},[620,1452,164],{"href":165}," — service surface, deadlines, and degradation.",[386,1455,1456,1458],{},[620,1457,168],{"href":169}," — budget math, two scores, and the knapsack.",[386,1460,1461,1464],{},[620,1462,1463],{"href":632},"The memory write path"," — how the candidate memories are produced.",[386,1466,1467,1469],{},[620,1468,57],{"href":58}," — what is implemented, provisional, and open.",{"title":421,"searchDepth":641,"depth":641,"links":1471},[1472,1473,1474,1475,1476,1477,1478,1479,1480],{"id":1109,"depth":641,"text":1110},{"id":1134,"depth":641,"text":1135},{"id":1222,"depth":641,"text":1223},{"id":1337,"depth":641,"text":1338},{"id":1377,"depth":641,"text":1378},{"id":1390,"depth":641,"text":1391},{"id":1403,"depth":641,"text":1404},{"id":1437,"depth":641,"text":1438},{"id":613,"depth":641,"text":614},"3","Context budgets","2026-10-14",{},"\u002Fblog\u002Fcontext-assembly-budget","10 min read",{"title":1095,"description":1100},"blog\u002Fcontext-assembly-budget",[1490,1491,663],"context","retrieval","l4nEfFW05AoLujunMsuDBg02w9ZwUrVuAyaV5rphWbY",1792875080323]