Project Info
Inspiration
Agents have long term memory now, and they trust whatever they pull out of it. That turns out to be a real hole. If a bad fact gets written into that memory, the agent retrieves it later and acts on it without a second thought. OWASP gave this its own category in 2026 (ASI06, memory and context poisoning), and it has already been shown working against ChatGPT, Gemini, and Bedrock. What got us going was a smaller observation. Most defenses ask a second model whether a memory is trustworthy, and if your judge is a language model, the same poisoning that fools the agent can fool the judge. We wanted to point at the exact memory that caused a wrong answer and prove it, with no model involved in that call.
What it does
IMMUNE sits between an agent and its memory and watches both directions. On the way in, every memory gets tagged with where it came from, and that provenance sets its trust. Something a user typed cannot quietly outrank the official policy. On the way out, recall runs through Redis vector search, and anything that has been quarantined is filtered out by the index itself, so the agent never even sees it. The interesting part happens after a wrong answer. When the agent says something that conflicts with a memory we trust more, IMMUNE re runs that same question with different memories pulled out, and watches for the moment the answer flips back to correct. The memory whose removal fixes things is the culprit. We quarantine it, log an incident to Sentry, and let the agent answer again, now correctly. No language model is involved in deciding which memory was at fault..
How we built it
We chose to use Claude Haiku 4.5 as our model choice through the Anthropic API. Its memory lives in Redis with RediSearch, using a FLAT index and cosine distance. Each memory is stored as a hash with a float32 embedding, and recall is a k-nearest-neighbor query. Quarantine is part of the query itself through a status filter, which is why a jailed memory simply cannot come back. The attribution engine is the piece we spent the most time on. It removes memories in an adaptive order, suspicious ones first, then runs a delta debugging pass to shrink the result down to the smallest set of memories that actually caused the failure. Detection works without any ground truth, because it only fires when an answer disagrees with a higher trust memory, which is the situation you actually have in production. Trust comes entirely from provenance and from a memory's own history (official documents start at 0.9, user writes at 0.5, trust collapses on quarantine and recovers on parole). We kept it completely separate from similarity on purpose. Around that core we wired Sentry for incident reporting, Arize Phoenix and OpenTelemetry for tracing, a LangGraph store adapter, an MCP server for Claude Code, and a Streamlit dashboard. There are 44 tests, and the whole thing also runs fully offline and deterministic as a backup. Sentry is used as an alerting layer for the memory firewall: when the quarantine() function detects a poisoned or malicious memory record, it fires a sentry_sdk.capture_message event (via _sentry_quarantine in immune/store.py) tagged with the threat type and offending content. It's initialized lazily via SENTRY_DSN/USE_SENTRY env vars and is a no-op when unconfigured, so the core firewall logic runs without it. Link to our Sentry org: https://berkeley-hackathon2026.sentry.io/.
Challenges we ran into
Throughout our build, IMMUNE surfaced challenges at every layer. The problem space is open-ended and unsolved: memory integrity spans poisoning, rot, and pollution, and scoping what to demo first was difficult in itself. From there, even defining “untrustworthy” required multiple design passes before settling on a numeric trust score per memory, rather than simpler heuristics like recency or source tags. Once we had that model, deciding where to enforce it became its own debate: we ultimately accept all writes and wait for a contradiction signal before attributing and quarantining, but arriving at that layering took significant iteration. Attribution itself introduced a further constraint, since production lacks labeled ground truth, prompting us to invent contradiction-with-a-trusted-anchor as our detection signal. That detection logic then had to be balanced against false positives, since an overly aggressive quarantine harms legitimate memories and required us to distinguish soft decay from hard quarantine. Validating any of this was its own problem, as no standard benchmark exists for memory-level attacks, so we designed our own adversarial scenarios and metrics from scratch. All of this was compounded by a mid-project migration from mocked agents and in-memory stubs to a live system, which exposed integration gaps that mocks had quietly hidden.
Accomplishments we're proud of
The same replay engine generalizes cleanly across single-poison, redundant-poison, and staleness-rot without any special-casing, which we didn't fully anticipate when we designed it. At the core of that engine is a deterministic blame path: counterfactual group testing finds the minimal guilty memory set without another model in the loop, making attribution auditable and reproducible in a way that an LLM judge never could be. We also pushed quarantine down into the Redis layer itself, so poison isn't just flagged in application code but physically removed from the KNN index and impossible to retrieve. Rounding it out, the parole system ensures memories aren't permanently blacklisted: offline re-trial against logged failures gives stale but non-malicious memories a path back, which felt important for building something we'd actually trust in production. More broadly, we’re glad to have spent our time on this problem. Silent memory corruption is one of the least-understood attack surfaces in agentic AI, and we think the approach here, whatever its current limitations, points toward something worth building on.
What we learned
“ Prove, don't guess" wasn't just a tagline — it shaped the whole design. By refusing to let any AI model decide who's to blame, we were forced into a cleaner approach that demonstrates the guilty memory by experiment. The payoff: every decision is reproducible and auditable, not a model's opinion you have to take on faith. The hard part isn't locking up a bad memory — it's knowing one went bad. In the real world you don't have an answer key, so we detect trouble by catching when an answer contradicts a fact we already trust (the official record). That's also our honest limit: we protect facts you've registered as the source of truth — not brand-new things the agent has never been told. On our fake memory everything passed. On real Redis we discovered the poison was winning purely because it was the newest memory — something the stub never showed us. Going live didn't just polish the demo; it revealed how the attack actually works. Attacking our own system changed how we built it. When we tried planting the same lie several times, our first "remove-one-memory-at-a-time" approach missed it completely — so we redesigned attribution to catch the whole group of culprits at once.
What's next
for Immune Right now heal fires on an explicit check and we want to wrap that so an agent heals on every response without anyone calling it by hand. The write and read protections are already automatic, so this is mostly packaging. We also want a larger benchmark, since our current scenarios are small, with redundant and cascading poison to really stress the attribution. Longer term there is a version that scales recall with an approximate index while keeping an exact set aside for replay, and there is the harder question of defending more open ended memory, since today we lean on having an authoritative source to contradict the poison in the first place.
🧬 IMMUNE — a self-healing immune system for agent memory
Agents poison their own memory and compound the error. IMMUNE detects the culprit by counterfactual replay (not a fallible LLM judge), quarantines it, and paroles it only when offline re-trial proves it's safe — so the agent improves instead of locking in false beliefs.
"We don't ask a model who's to blame — we replay the failure with each memory removed, quarantine the one that empirically caused it, and release it only when offline re-trial against logged failures proves it's safe."
The problem
Long-term agent memory goes bad three ways, and the agent can't tell good from bad — so it retrieves the bad memory, trusts it, and repeats the mistake:
- Poisoning — an attacker plants a false fact ("internal endpoints don't need auth").
- Rot — a once-true memory goes stale ("the user works at Company A").
- Pollution — temporary/noisy context gets stored as permanent fact ("use this debug token").
Write-time filters and retrieval gates help but miss the feedback loop: when the agent fails, find the memory that caused it and lower its trust. That's IMMUNE.
Docs
- docs/FIREWALL.md — the headline demo: the immune system live on a real Claude + Redis agent, run modes, sponsor map, the pitch script.
- docs/DEMO.md — the side-by-side naive-vs-IMMUNE demo: run modes, annotated output.
- docs/SETUP.md — set up the whole stack (uv, env, LLM, Redis, Arize, Sentry).
- docs/CONCEPT.md — the full idea, problem, demo story, scope, and track/sponsor fit.
- docs/ARCHITECTURE.md — components, data flow, the ablation engine, and the v2 swap seams.
- docs/PLAN.md — the 4-person parallel build plan: ownership, dependencies, timeline.
- docs/tasks/ — per-person task board: tracer-bullet slices to grab and tick off (P1–P4).
- docs/ARIZE.md — Arize integration runbook (P3): prize criteria, setup, evals, the meta-eval talking point.
- docs/MCP.md — plug IMMUNE into Claude Code as an MCP server (
claude mcp add+ scripted demo). - docs/REDIS.md — Redis integration runbook (P2): setup paths, vector search, Sentry on quarantine.
- TEAM.md — onboarding and per-lane ownership.
The headline demo — the immune system on a real agent (Claude + Redis)
Same self-healing immune system, shown live: a real Claude agent whose long-term memory is Redis vector search gets poisoned, and IMMUNE finds the culprit by replay, quarantines it, and heals the agent — in front of you.
make demo-firewall # live: real Claude + Redis; needs ANTHROPIC_API_KEY + Redis (make up)
make demo-firewall-offline # deterministic, zero network — the can't-fail backup
You watch the Redis index reject the attack: 3 active → 4 (poison RETRIEVABLE) → 3 (poison not served) the instant the culprit is quarantined; a Sentry incident fires on quarantine. Full runbook + pitch: docs/FIREWALL.md.
Run (zero deps, zero API keys)
make setup # uv sync (one-time)
make demo # side-by-side: naive vs IMMUNE on the same self-poison
make test # invariants lock the moat
Demo output: naive agent scores 1/4 (injected poison wins via recency), IMMUNE
heals to 4/4, the poison ends quarantined, then paroled once truth
changes. Runs identically with real Claude (IMMUNE_LIVE=1).
make dashboard # visual web dashboard → http://localhost:8501
Dark, interactive view: trust-score timeline, side-by-side, live memory table, parole — with a quarantine-threshold slider. See docs/DEMO.md.
The moat (immune/replay.py + immune/attribution.py)
The blame path is deterministic counterfactual replay — no LLM, no judge:
- Attribution by group testing — find the minimal set of memories whose removal flips the failure to correct: adaptive peel (most-suspect-first) + a delta-debug shrink to a 1-minimal set. Beats fixed singles/pairs against k-redundant poison, at ~O(D·log N) replays.
- Detection without an oracle (
detectors.py) — a bad answer is caught by contradiction with a higher-trust memory, so no ground truth is needed in production. - Quarantine + provenance cascade — jail the culprit and everything derived
from it (
provenance.py); ambiguous cases soft-decay only (anti-autoimmune). - Parole — re-admit offline, replay logged failures, release only if safe.
Architecture
write → store.add(mem, parents) record + provenance edge store.py
retrieve→ store.search(topic) admission gate (trust+status) store.py / embed.py
answer → Agent.answer(q) mock (default) | live Claude agent.py
detect → ContradictionDetector.check() answer vs trusted anchor detectors.py
attribute→ ShadowReplay.handle_failure() group-testing replay replay.py / attribution.py ★
quarantine→ store.quarantine_cascade() culprit + derived subtree store.py / provenance.py
parole → ShadowReplay.parole() offline re-trial → release replay.py ★
Full detail: docs/ARCHITECTURE.md.
Integrations (wired)
Redis (real vector search — RediSearch KNN drives recall; quarantine enforced
at the index via @status:{active}; make up) · Anthropic/Claude (live agent
behind IMMUNE_LIVE) · Arize/Phoenix (traces + naive-vs-immune evals) ·
Sentry (quarantine → triaged incident; set SENTRY_DSN) · MCP
(immune/mcp_server.py → Claude Code/Desktop) · LangGraph (secondary —
ImmuneStore is a drop-in BaseStore, immune/langgraph_store.py). The
replay/attribution moat stays in-memory and deterministic — no integration sits
in the blame path.
Honest limits (say these before judges ask)
- Detection defends known facts — contradiction needs an authoritative anchor; it catches poison vs the system-of-record, not novel hallucinations.
- Attribution reasons over the deterministic stand-in (the mock), even when the live answer came from Claude — blame stays on the reproducible path by design.
- The benchmark is small (3 topics);
redteam.pywidens it (k-redundant, cascade).
Status
End-to-end loop runs offline + deterministic; 44 tests pass (incl. the
red-team battery, the LangGraph BaseStore drop-in, and Redis-gated vector
search). The headline demo (make demo-firewall) runs a real Claude agent on
Redis vector memory and self-heals live. Redis vector search, Arize/Phoenix,
Claude (live), MCP, and Sentry are wired; Sentry needs a DSN. Browser dashboard +
terminal chat both live.
Analysis
View
Metric
- 56
- 34
- 16
- 15
- 14
- 8
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- AnthropicIn code
- PythonIn code
- RedisIn code
- StreamlitIn code
- DockerClaimed
4 of 5 appear in the indexed code. 1 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeCommits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
322 KB
Source files
56
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
Sixxhi/AI-Hackathon-20206
68 files · 2.8 MB · @ d000797
Structure
Interface
1 file · 1%Screens, components and styles rendered to the user.
Application logic
28 files · 41%Domain rules, services and shared utilities.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Python71%
- Markdown29%
- YAML0%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
pyproject.toml
pypi · 1111 development-only dependencies.
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.