Nick Duong · Architecture notes
← All projects

Agent memory architecture

ReliBot & MyClawBot

Three-layer auto-recall, hybrid search and fail-closed tool policy: one design running on an enterprise incident-response agent and a personal agent platform.

80+tools · 16 domains
12sub-agents
3-layerauto-recall
2,200+tests

Overview

ReliBot (The Hartford) is an incident-response assistant for SRE and development teams.

MyClawBot (personal) is the proving ground for the same patterns.


ReliBot system

Engineers
CLI · Teams · Web chat

Bridge
auth · rate limit · injection scan

Root agent (Google ADK)
Gemini Pro → Flash fallback

80+ tools / 16 domains
GitHub · Splunk · Dynatrace · Scanner

Memory
SQLite: notes · vectors · graph · conversations

Heartbeat (scheduled)
nightly maintenance + memory flush


Memory architecture

Design principle: cheap deterministic recall on every turn; expensive semantic search only when the model asks for it.

recalled on later turns

Recall: before the first model call · no LLM, pure SQL

L1 Core
MEMORY.md, always in prompt

L2 Recent
this chat, last 4 days

L3 Entity-triggered
service names / trigger words

User message

LLM + tools
search_notes when it needs more

Write back
save_memory · auto-saved incident fix · insights

Knowledge base
notes · vectors · graph

Recall layers

Layer What it holds ReliBot budget MyClawBot budget How it's selected
L1 Core MEMORY.md: preferences, standing decisions ≤ 8K chars ≤ 2K chars Always present
L2 Recent This chat's recent history last 4 days, ≤ 20K chars last 3 days of daily notes, ≤ 4K chars Recency only, scoped by chat_key
L3 Entity What we know about the services or people mentioned ≤ 6 entities, ≤ 9.6K chars ≤ 8 entities, ≤ 1.5K chars Substring / word-boundary match against a cached entity list. Microseconds, no LLM call.
Search Everything else on demand on demand Hybrid 0.7 vector + 0.3 BM25 (+ graph boost)

How memory is written and kept bounded


How the two projects relate

Lineage

re-implemented in Python

architecture patterns
bridge · memory · heartbeat · security

design idea
L2/L3 auto-recall

OpenClaw
open-source TypeScript agent
70/30 hybrid-search idea

MyClawBot (personal, Feb 2026)
Python · Claude Agent SDK
markdown vault + SQLite index

ReliBot (The Hartford)
Google ADK + Gemini
same layered architecture

ReliBot MyClawBot
Runtime Google ADK + Gemini (Vertex AI) Claude Agent SDK, Haiku / Sonnet / Opus router
Source of truth SQLite notes table Markdown vault. SQLite is only an index and can be rebuilt.
Session state ADK DatabaseSessionService + queryable conversations table SDK session resume + sessions.json (90-day TTL)
Where recall is injected system_instruction (ADK callback) Prepended to the user message, so the system-prompt prefix stays byte-stable for prompt caching
Embeddings fastembed bge-small 384d, sqlite-vec MiniLM 384d BLOB, cosine over a cached matrix
Ranking 0.7 vector + 0.3 BM25 same, × recency decay, then MMR re-rank and query expansion
Graph extraction regex (tier 1) + LLM queue (tier 2) rule-based
Idle flush Gemini Flash, 15 min idle + nightly Claude, 10 min idle
Tools plain ADK function tools in-process MCP @tool
Guardrails fail-closed before_tool_callback, output injection scan, 5-calls/tool cap fail-closed can_use_tool, untrusted-content fencing, path-traversal checks

Why the injection point differs.


Design decisions

Decision Why At larger scale
SQLite (WAL + FTS5 + sqlite-vec), not a vector DB Each instance is team-scoped: about 2.5K chunks and 500 notes. Millisecond queries, zero ops, one file to back up. Postgres + pgvector with tenant_id and row-level security, once you need multi-writer concurrency or multi-tenant isolation
Keep BM25 alongside vectors Incident language is full of exact tokens (ORA-01555, CVE IDs, service names) that embeddings blur. On rare-token queries, plain FTS5 got 38/40 recall@10 while the 0.7-weighted hybrid got 0–2/40. Reciprocal-rank fusion or an exact-match boost instead of fixed linear weights
Deterministic recall + on-demand search Injecting everything blows the context and the cost. Relying on the model to call a memory tool fails because models forget. Same pattern; tune the layer budgets per tenant
Model failover with a circuit breaker Fallback chain Pro → Flash → 3.1 Flash → Flash-Lite, retry backoff 1/3/8 s. The breaker opens after 2 failures, so a bad model is skipped instead of paying its timeout every request. Replies are tagged [fallback: model]. Per-provider health checks and budget-aware routing
Tool-call caps enforced in code Gemini sub-agents called search_notes 15+ times, and prompt instructions didn't stop it. Now max 5 calls per tool per agent per query, plus an event cap. Guardrails belong in code, not the prompt
Memory scoping chat_key = channel:chat_id scopes L2 and compression. Notes and L3 are shared per instance, as team knowledge. Hard isolation comes from separate instances and data directories, run by a manager process. tenant_id on every row, enforced in the database, never only in the prompt

Testing.


Lessons from token metering

Two real metering bugs from MyClawBot, both found by comparing claimed behavior against production logs:

  1. A cumulative counter treated as a delta.
    • When a session is resumed, the SDK's total_cost_usd is the session's running total, but it was summed per turn.
    • Recorded spend was $561.97 against $144.12 actual: a 3.9× overcount, and 7× on one bot.
    • Fix: know whether each provider field is a counter, a gauge or a delta; meter from raw token events and price them yourself.
  2. Cached tokens.
    • input_tokens excludes cache reads and writes, so session rotation keyed on it never fired. It saw 2–8 tokens while real context grew past 300K.
    • Fix: measure input + cache-read + cache-write, and price each token type separately.

Applying the lessons: a usage-metering pipeline.

Metering design

Accounts + entitlements

Billing

Usage store + limits

Ingest

webhooks: invoice.paid / payment_failed
idempotent · signature-verified

entitlement check

usage totals

billed totals

API gateway / SDK
usage event: tenant · model · token types · request_id

Kafka
usage.events

ClickHouse
raw: ReplacingMergeTree by event_id
rollups: AggregatingMergeTree

Real-time quota counter (Redis)
soft / hard limits

Billing job
usage × price book → line items

Stripe
metered billing · invoices

Webhook consumer

PostgreSQL
tenants · plans · subscriptions
entitlements · invoices

Entitlement service
cached, checked per request

Reconciliation
ClickHouse vs Stripe vs provider invoice

Acronyms

Underlined acronyms show these definitions on hover or tap. Full glossary →

ADK
Agent Development Kit Google's open-source framework for building agents and multi-agent systems. ReliBot and GitHubScanner run on it.
API
Application Programming Interface How programs call each other, such as a REST endpoint.
BM25
Best Match 25 The classic keyword-ranking formula behind SQLite FTS5. Strong on exact tokens such as error codes and CVE IDs.
CLI
Command-Line Interface A terminal tool.
CVE
Common Vulnerabilities and Exposures The ID of one specific, publicly known vulnerability, such as CVE-2021-44228 (Log4Shell).
FTS5
Full-Text Search, version 5 SQLite's built-in full-text index.
JWKS
JSON Web Key Set The published public keys used to verify JWT signatures, such as Microsoft's keys for Teams bots.
JWT
JSON Web Token A signed token that carries identity claims.
LLM
Large Language Model A model such as Gemini or Claude that reads and writes text. Every agent here is an LLM plus tools.
MCP
Model Context Protocol An open standard for exposing tools and data to LLM agents. MyClawBot's tools are in-process MCP tools.
MMR
Maximal Marginal Relevance A re-ranking step that trades relevance against diversity, so the top results aren't near-duplicates.
ONNX
Open Neural Network Exchange A portable model format. ReliBot runs its embedding model locally through ONNX, with no API call.
SDK
Software Development Kit A library for building on a platform, such as the Claude Agent SDK.
SHA-1
Secure Hash Algorithm 1 A 160-bit hash. Used here for deduplication and snippet-integrity checks, not for security.
SQL
Structured Query Language The standard language for querying relational databases.
SRE
Site Reliability Engineering The discipline (and teams) that keep production running. ReliBot's main users.
TTL
Time To Live How long a record or cache entry is kept before it expires.
WAL
Write-Ahead Logging A SQLite journal mode where readers don't block the writer.

More architecture notes