Ashnode – Bounded Memory Layer for Temporally Consistent RAG (GitHub)
hackernews
|
|
📰 뉴스
#ai agent
#ashnode
#memory layer
#open source
#openai
#rag
#머신러닝/연구
원문 출처: hackernews · Genesis Park에서 요약 및 분석
요약
Ashnode는 장기 구동 AI 에이전트를 위한 오픈소스(MIT) 인식 메모리 레이어로, 사실의 노화, 모순, 대체를 관리하여 기존 RAG의 시간적 일관성 문제를 해결합니다. 이 시스템은 모순을 자동 탐지하고 신선도 기반 순위를 매기며 동일한 쿼리에 항상 동일한 결과를 반환하는 구조화된 컨텍스트 패킷을 제공합니다. 특히 법률, 의료, 금융 등 4개 도메인 벤치마크에서 기존 RAG의 시간적 정확도 11.2%와 비교해 오래된 정보를 완벽히 걸러내며 100%의 정확도를 달성했습니다.
본문
Open source (MIT). Epistemic memory layer for long-lived AI agents — bounded recall, supersession, contradiction surfacing, deterministic context packets. See docs/ashnode.tex for the system paper (build with LaTeX). Ashnode is a memory layer for agents that need to run seriously — over hours, days, or longer — where the facts they hold can conflict, age, or be superseded. Standard vector search was built for single-session retrieval. It returns the nearest neighbours and stops there. It has no concept of whether two retrieved facts contradict each other, whether one is three days stale, or whether you've already been told something newer. For short-lived chat that's fine. For agents making decisions over time, it silently produces wrong context. Ashnode replaces that retrieval step with a contract: - Bounded recall — k items, ranked by relevance and freshness, under a versioned policy - Contradiction surfacing — tensions between retrieved items are detected and attached to the packet - Freshness and supersession — every fact carries its age and decay score; newer facts replace older ones automatically - Provenance — source, timestamp, and ingest metadata on every item - Determinism — same query, same store state, same policy → identical packet, every time - Honest completeness — explicit flags when any cap was hit; no silent gaps The result is that every recall returns a structured, inspectable snapshot of exactly what the agent is about to reason over. The packet is replayable. If something goes wrong, you can pull the exact context that drove the decision — no custom logging needed. That observability is a consequence of how Ashnode is built, not a separate tool. Who uses it: Any team building agents or copilots where facts evolve over time and a wrong or contradictory memory causes a real cost — ops incidents, compliance exposure, broken user trust. If your agent runs longer than one session and acts on what it remembers, this is the memory layer for it. pip install git+https://github.com/itachi-hue/ashnode.git Local models (~90 MB) download automatically on first use. No API key required for the default setup. Persistence — Ashnode stores everything in SQLite: memory = Ashnode() # creates ashnode.db in current directory memory = Ashnode(db_path="agent.db") # explicit path — survives between runs memory = Ashnode(db_path=":memory:") # in-memory only — lost on exit, useful for tests Claim — An atomic, source-tagged fact your agent knows. The input unit. "The payment service latency spiked to 800ms." source="datadog" Keep claims atomic. One fact per call. Pass them as strings. Context packet — What recall returns: bounded, inspectable, deterministic. Contains evidence items, contradiction flags, freshness scores, and completeness bits — all under a versioned policy. Policy — Versioned configuration that controls how many items to return (k ), decay half-life (tau ), contradiction sensitivity, and more. Same query + same policy = identical packet, always. Supersession — only the current truth comes back: from ashnode import Ashnode mem = Ashnode() # Two facts about the same thing, ingested at different times mem.ingest("Jake is on metformin 500mg.", source="chart-jan") mem.ingest("Jake switched to sitagliptin 100mg. Metformin discontinued.", source="chart-mar", claim_key="jake.medication") packet = mem.recall("what medication is Jake on?") print(packet.items[0].content) # Jake switched to sitagliptin 100mg. Metformin discontinued. The first fact is archived — not deleted, just excluded from recall by default. Plain RAG would have returned both, leaving the LLM to guess which is current. Ashnode returns only what is true now. Contradiction detection — implicit conflicts surfaced automatically: import time mem.ingest("The payment service is healthy and responding normally.", source="datadog") mem.ingest("The payment service is down — all requests failing.", source="pagerduty") time.sleep(2) # background NLI brain detects contradictions asynchronously packet = mem.recall("payment service status") for item_id, contras in packet.contradictions.items(): print(f"Contradiction on {item_id[:8]}…: {len(contras)} record(s)") # Contradiction on a3f72b1c…: 1 record(s) Expected output: [datadog] The payment service is healthy and responding normally. age=2s decay=1.00 [pagerduty] The payment service is down — all requests failing. age=0s decay=1.00 Contradiction on a3f72b1c…: 1 record(s) Truncated? False First run note: On the very first run, the NLI model (~66MB) downloads and initialises. Subsequent runs are instant. If contradictions don't appear, increase time.sleep() to 5 — model load time varies by machine. Claim keys — auto-supersede stale facts without tracking IDs. Use the same claim_key every time you update a fact: memory.ingest("MSA contract value: $2.5M.", source="legal/v1", claim_key="acme.contract.value") memory.ingest("MSA contract value revised to $3.2M per Amendment 1.", source="legal/v2", claim_key="acme.contract.value") memory.ingest("MSA contract value revised to $4.1M per Amendment 2.", source="legal/v3", claim_key="acme.contract.value") packet = memory.recall("what is the current contract value for Acme?") print(packet.items[0].content) # MSA contract value revised to $4.1M per Amendment 2. # Previous versions are archived and excluded. No stale figures in context. Decay — tau is the half-life in seconds. Items re-ranked by cosine_similarity × e^(−age/tau) at recall time: memory.ingest("Deploy succeeded.", source="ci", claim_key="deploy.status", tau=3600) Contradiction detection — runs in the background, never blocks ingest. NLI semantic detection is on by default (cross-encoder/nli-MiniLM2-L6-H768 , 66MB, downloaded once and cached). Catches vocabulary-variant contradictions — "approved" ↔ "rejected", "healthy" ↔ "down" — not just explicit negations. To opt out to the faster heuristic: from ashnode import Ashnode from ashnode.models import Policy pol = Policy(policy_version="fast-v1", nli_model=None) # heuristic only, lower recall memory = Ashnode(policy=pol) Policy caps — hard bounds on what recall returns: from ashnode import Ashnode, register_policy from ashnode.models import Policy pol = Policy(policy_version="tight", k=5, d=3, c=2, b=5, default_tau=86400) register_policy(pol) memory = Ashnode() packet = memory.recall("system health", policy_version="tight") Large corpus tip: At corpus sizes above ~5k items, increase k to 50 for better recall precision. The latency cost is ~2–3ms. The defaultk=10 is intentionally conservative to keep prompt context bounded.pol = Policy(policy_version="large-corpus", k=50) Embedding model — swap to any HuggingFace model or an API provider for better recall precision: from ashnode import Ashnode from ashnode.models import Policy # Local — no API key, downloaded once memory = Ashnode(policy=Policy( policy_version="v1", embedding_model="all-mpnet-base-v2", # higher quality local model )) # API providers — best-in-class quality, bring your own key memory = Ashnode(policy=Policy( policy_version="v1", embedding_model="openai:text-embedding-3-small", # set OPENAI_API_KEY # embedding_model="cohere:embed-english-v3.0", # set COHERE_API_KEY # embedding_model="voyage:voyage-3-lite", # set VOYAGE_API_KEY )) API keys are read from environment variables. Copy .env.example to .env and fill in your key: cp .env.example .env pip install python-dotenv from dotenv import load_dotenv load_dotenv() # reads .env — call before creating Ashnode Do not switch models mid-corpus — vectors from different models are incompatible. Set once at project start. | Field | Description | |---|---| items | Top-k claims, re-ranked by freshness | contradictions | Detected tensions, keyed by item_id | freshness | Per-item age, decay factor, supersession status | beliefs | Derived beliefs activated by retrieved items | completeness | Flags if any cap (k, d, c, b) was hit | store_revision | Monotonic write counter — enables replay | policy_version | Pins exactly which policy produced this packet | projections_seq | How far background processing has caught up | Sample packet — the full structure returned by recall() for the quick start example above, shown as JSON for readability: { "query": "payment service status", "store_revision": 2, "policy_version": "default-v1", "served_at": "2026-04-04T10:23:41", "items": [ { "item_id": "a3f72b1c-...", "content": "The payment service is healthy and responding normally.", "source": "datadog", "ingest_at": "2026-04-04T10:23:39", "claim_key": null }, { "item_id": "b9e14d22-...", "content": "The payment service is down — all requests failing.", "source": "pagerduty", "ingest_at": "2026-04-04T10:23:39", "claim_key": null } ], "freshness": { "a3f72b1c-...": { "age_seconds": 2.1, "decay_factor": 1.0, "is_superseded": false, "superseded_by": null }, "b9e14d22-...": { "age_seconds": 0.4, "decay_factor": 1.0, "is_superseded": false, "superseded_by": null } }, "contradictions": { "a3f72b1c-...": [ { "item_id_a": "a3f72b1c-...", "item_id_b": "b9e14d22-...", "similarity_score": 0.97, "detected_at": "2026-04-04T10:23:40" } ] }, "completeness": { "items_truncated": false, "contradictions_truncated": false, "edges_truncated": false, "beliefs_truncated": false }, "edges": {}, "beliefs": [], "projections_seq": 2 } Item IDs are UUIDs truncated for readability. decay_factor: 1.0 means no decay policy is set — items never age out by default. - O(log N) recall latency — HNSW ANN search, bounded by policy caps independent of corpus size - Deterministic — same (store_revision, query, policy_version) → identical packet - No silent staleness — every item carries age, decay factor, and supersession status - Honest completeness — if a cap was hit, the flag is set; no silent gaps Controlled benchmark across 4 domains (legal, clinical, finance, DevOps), 80 claim keys, 4 versions per key (3 superseded + 1 current), k=5. Same embedding model and HNSW library for both. Embedding cost charged equally to both systems. | Metric | Plain RAG | Ashnode | |---|---|---| | Temporal Accuracy (top-1 is current) | 11.2% | 100.0% | | Staleness Rate (stale fact in top-k) | 78.8% | 0.0% | | Determinism Rate | — | 100.0% | RAG reaches ~11% accuracy because the embeddings for all 4 versions of a fact are semantically similar — the retriever has no signal for currency. Ashnode uses claim_key -based supersession to exclude stale versions from the candidate set structurally; accuracy is 100% across all domains. Run it yourself: python tests/temporal_benchmark.py --domains legal clinical finance devops --versions 4 See MANUAL.md for: policy reference, contradiction strategies, background brain, eval harness, supersession chains, edges, beliefs, scale test results, and limitations. Issues and pull requests are welcome. Please run pytest and ruff check ashnode tests before submitting. For design questions, open a discussion or refer to MANUAL.md and the paper in docs/ashnode.tex . The MIT license requires that you keep the copyright line and this permission notice in any copy or substantial portion of the Software (including forks and redistributions). That is how downstream users know who to credit for the code. For papers, talks, or products that build on Ashnode, please cite the work (BibTeX below or the compiled paper from docs/ashnode.tex ) in addition to preserving LICENSE . The PDF lists the author as Vivek Srinivas to match prior patent filings; repository LICENSE may use a different legal name for copyright. If you use Ashnode in research, cite the technical report / preprint (PDF from docs/ashnode.tex once compiled) and this repository. Example BibTeX (adjust venue when published): @misc{ashnode2026, title={{Ashnode}: Bounded, Inspectable, and Reproducible Memory Retrieval for Long-Lived {AI} Agents}, author={Srinivas, Vivek}, year={2026}, howpublished={\url{https://github.com/itachi-hue/ashnode}}, note={Open-source implementation; see repository for paper source} } A provisional patent application related to this work was filed March 25, 2026. The MIT license grants copyright permissions on this repository’s code; it does not grant a license under any patent. If you need patent clearance for commercial use, consult qualified counsel. Released under the MIT License.
Genesis Park 편집팀이 AI를 활용하여 작성한 분석입니다. 원문은 출처 링크를 통해 확인할 수 있습니다.
공유