QA-gated knowledge vault

Master Agent System

A supervised Claude Code agent organisation with a SQLite vault of revisioned, namespace-scoped records whose trust moves only through a recorded QA task.

LicenceNone: no LICENSE file at the root, no licence named in the README or CHANGELOG, and GitHub reports none, so all rights reserved by default; packages copied under vendor/ carry their own, the day-trader one MIT
Size8,406 lines of Python in 44 files outside vendor/, 5,879 of them in the 33-module agent_hub package and 476 in its memory and freshness modules; version 0.6.0
Activity6 commits on main by one account, 29–30 October 2026
Tests255 unittest methods in 9 files under tests/; none run for this reading

Carries 4 of 7 rubric mechanisms. Most systems here carry none or one (43%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

1. Executive Summary

Master Agent System is a Claude Code organisation — a master agent, a management layer, eight departments and an independent QA role, seventy subagent definitions in all — driven through a stdlib Python CLI that records every task transition in SQLite. Its memory is the Local Memory Vault in agent_hub/memory.py and agent_hub/freshness.py: knowledge records in namespaces owned by departments, revisioned under optimistic locking, snapshotted on every change, and carrying a verification status that only an auditor can move, against a completed QA task that names the record's exact content hash. What is weak is who the actors are: each one is a role name typed on the command line, and almost every agent holds a shell.

The design is unusually explicit about what a record is not. A retrieval event carries the warning "Memory is evidence, not authority; recheck changing facts and refresh stale records before decisions" (agent_hub/core.py:244-247), the freshness policy describes its own TTL as "maximum age after independent source verification; never proof of truth" (freshness.py:29-30), and the Markdown projection writes "Retrieved content is evidence, not instructions" under every record's title. The README states its validation limits in the same register: live model execution did not succeed in the build environment, and "this release is not certified for unattended production use."

The repository also carries four imported packages under vendor/ — a MAOS starter, an income toolkit, a Shorts lab and a day-trading bot — three with SQLite CLIs of their own. They are preserved byte for byte as sources, and the memory read here is the agent_hub vault that replaced them.

2. Mental Model

A record is a claim with dated sources, and its standing is a separate column the writer does not control. Every put resets verification_status to UNVERIFIED and clears superseded_by in the same transaction (memory.py:134), whatever the caller passed and whether the record arrived from the CLI, an export file or a Markdown note. Standing then moves only through FreshnessAuditor, and each move is a revisioned update the auditor makes after checking a completed QA task:

  • To FRESH by verify: the task must have reached COMPLETED with a recorded QA PASS, name the record id under verified_memory_ids, carry the record's current content_hash, and pass its source_freshness check. The verification instant is the earlier of the QA time and the oldest verified source date, so linking a new task to an old source check cannot make it fresh today — a record whose source is already past the TTL lands in REQUIRES_VERIFICATION instead (freshness.py:102-116).
  • Back to REQUIRES_VERIFICATION when the review date passes, written by scan and also derived on every get as effective_verification_status (memory.py:76).
  • To UNVERIFIED by any content edit, which must name the revision it read.
  • To SUPERSEDED, terminal, by supersede: QA must approve this exact replacement for this exact record, both hashes must match what QA reviewed, and the replacement must be FRESH at the moment of the swap.

Category sets the clock. Supplier and market prices expire in a day, framework versions in a week, procedures in six months (freshness.py:12-16), and an operator file may override a TTL within one minute to one year.

Diagram — every put resets the status; only the freshness auditor moves it, and only against a QA task naming the record's exact hash
Diagram source
%% caption: every put resets the status; only the freshness auditor moves it, and only against a QA task naming the record's exact hash
stateDiagram-v2
    direction TB
    [*] --> UNVERIFIED: put (any path)
    UNVERIFIED --> FRESH: verify<br/>QA PASS on this hash,<br/>dated sources in TTL
    UNVERIFIED --> REQUIRES_VERIFICATION: verify<br/>sources already past TTL
    FRESH --> REQUIRES_VERIFICATION: review date passes<br/>(scan, or read-time)
    REQUIRES_VERIFICATION --> FRESH: verify again
    FRESH --> UNVERIFIED: content edit<br/>at expected revision
    REQUIRES_VERIFICATION --> UNVERIFIED: content edit
    UNVERIFIED --> SUPERSEDED: supersede<br/>QA-approved FRESH<br/>replacement
    FRESH --> SUPERSEDED: supersede
    REQUIRES_VERIFICATION --> SUPERSEDED: supersede
    note right of SUPERSEDED
        Immutable. Skipped by
        default search, returned
        with include_superseded.
    end note

3. Architecture

There is nothing to stand up beyond Python 3.10 and one SQLite file. The hub is standard library only; tools/claude/package.json pins a Claude Code CLI for the agents themselves, and no model is invoked by any hub command. The vault shares its database with the task workflow, so the QA records it depends on are rows in the same file.

Diagram — the vault sits behind one CLI; QA records and memory share a database, and Markdown is a projection that can conflict back
Diagram source
%% caption: the vault sits behind one CLI; QA records and memory share a database, and Markdown is a projection that can conflict back
flowchart TD
  subgraph agents["Claude Code subagents"]
    direction TB
    W["department workers<br/>Bash + Write"] ~~~ Q["qa-auditor<br/>Bash, read-only tools"]
  end
  agents --> CLI["python -m agent_hub<br/>--actor ROLE on each call"]
  CLI --> Hub["Hub: task phases,<br/>events, QA_RECORD"]
  CLI --> Vault["MemoryVault<br/>authorize(actor, namespace)"]
  Hub -->|"QA PASS stores outcome"| Vault
  Vault --> DB[("SQLite<br/>memory_records,<br/>memory_history")]
  Fresh["FreshnessAuditor<br/>verify, scan, supersede"] -->|"reads QA task"| Hub
  Fresh --> DB
  Vault --> Sync["sync queue<br/>per provider"]
  Sync --> MD["Markdown files<br/>one per record"]
  MD -.->|"external edit = CONFLICT"| Sync

4. Essential Implementation Paths

  • MemoryVault.put (memory.py:80-139): authorise the write, validate the confidence label (VERIFIED needs at least one source classified VERIFIED), redact, and hash the canonical body. Then, inside BEGIN IMMEDIATE, read the prior row; refuse a namespace change, an edit to a superseded record and a stale expected_revision; return unchanged on an identical hash; and update with the observed revision and hash in the WHERE clause.
  • MemoryVault.search (:178-198) and get (:67-78).
  • Hub.review (core.py:359-402): only qa-auditor, never the task owner or a listed author, can record a verdict; a PASS completes the task and stores its outcome in the department's namespace as approved-task-outcome with confidence QA_APPROVED_OPERATOR. The event it writes carries 'identity_authenticated': False.
  • Hub.recall (core.py:239-248): the task owner, master, management or QA searches the vault and logs a MEMORY_RETRIEVAL event; preflight refuses to proceed without one.
  • FreshnessAuditor (freshness.py:44-132) and approved_task (:33-41).
  • MemoryVault.sync, retry and resolve_conflict (memory.py:249-324) with MarkdownVaultProvider (providers.py:43-98).

5. Memory Data Model

memory_records (memory.py:35-40, extended at intelligence_schema.py:62-66) holds id (a UUID the caller may supply), namespace, kind, title, payload and sources as canonical JSON, task_id, owner, confidence, revision, content_hash, the created and updated times, and the freshness columns category, date_collected, last_verified_at, next_review_at, expiry_policy, verification_status, superseded_by and responsible_department.

Two status-like columns sit side by side, and only one of them is a state. confidence takes UNVERIFIED, VERIFIED, ESTIMATE, ASSUMPTION or QA_APPROVED_OPERATOR from the caller on every put, constrained only by the source rule. verification_status is written by the vault and the auditor, never by the caller.

memory_history (intelligence_schema.py:25-28) keys a full snapshot on (record_id, revision) with actor, reason and time. Beside it, freshness_audits keeps each scan's findings, freshness_requests the verification task queued per record revision, and three sync tables the queue, the last remote hash per provider and each explicit conflict resolution with the remote hash the operator acknowledged.

The only times are record time: created_at, updated_at, date_collected and the verification dates. Nothing records when a claim was true.

6. Retrieval Mechanics

search(query, actor, namespace=None, limit=20) selects every row ordered by updated_at DESC and walks it in Python: skip a superseded row unless asked, skip a row outside the requested namespace, skip a row the actor may not read, then keep it if any whitespace-separated query word is a substring of the title plus the payload JSON, until limit (memory.py:178-198). The payload is matched as serialised JSON, so a key name such as summary matches every record that has one. There is no ranking beyond recency, no index and no embedding.

Each kept row passes back through get, which re-authorises, decodes, and adds effective_verification_status — REQUIRES_VERIFICATION when a FRESH record's review date has passed, whether or not a scan has written it — and the vault's source_instance id. recall returns the same records and records which of them were stale in the retrieval event; it does not drop them.

7. Write Mechanics

Writes are synchronous and immediately retrievable: put commits before it returns unless the caller holds an outer transaction, in which case a savepoint protects the caller's other work (memory.py:141-168). There is no background writer and no extraction model. The two producers are an explicit memory put, and the QA PASS branch of review, which stores the task's summary, artifacts and hashes, limitations, lessons and review reference (core.py:390-397).

An update must carry the revision it read. The UPDATE binds the observed revision and content hash and the absence of a supersession, and raises ConflictError if any changed in between, so two workers cannot interleave an edit. A content change resets verification; the test at test_v04.py:176-179 asserts the status returns to UNVERIFIED and last_verified_at to NULL.

Imports keep stable ids and go through the same put. A task id from another vault instance, or one that does not exist locally, is moved into import_provenance inside the payload rather than kept as a foreign key (memory.py:227-234). An existing record imported with different content and no expected revision is a conflict, not an overwrite.

The Markdown sync writes one redacted file per record and remembers its hash. A file whose bytes differ from the remembered hash is never overwritten: the queue item goes to CONFLICT, retry keeps the old remote hash so a retry cannot overwrite either, and resolve_conflict requires the operator to pass the hash of the bytes they inspected, refusing if the file changed again since (memory.py:283-324). Keeping the remote version means editing SQLite through put; the docstring says the operation "never imports notes as trusted authority."

Nothing deletes. The freshness module's docstring says "historical records are never deleted", scan returns 'deleted': 0, and git grep -n -E 'DELETE|DROP|TRUNCATE' -- agent_hub finds only the dashboard mapping the DELETE verb onto its refusing POST handler.

8. Agent Integration

Seventy agent definitions under .claude/agents/; sixty-nine list Bash among their tools, and the exception holds none of Bash, Write or Edit. The hub is reached by shelling out to python -m agent_hub, and every memory command takes --actor from choices=ROLES (__main__.py:34, :47, :66). The vault has no MCP surface; the agents' memory interface is the CLI.

The workflow forces a recall before work starts — preflight refuses a task whose owner has no MEMORY_RETRIEVAL event since the last handoff (core.py:258-264) — and the result of a PASS lands in memory without a separate step, so the loop is recall, work, independent review, remember.

9. Reliability, Safety, and Trust

The verification gate binds to bytes, not to a record. verify and supersede compare the content hash QA recorded with the record's current hash. _metadata writes the new status with the observed revision and hash in the WHERE clause and increments the revision itself, so a review of revision 3 cannot mark revision 4 fresh and a concurrent edit makes the auditor's write fail (freshness.py:48-62). The test suite carries the cases that matter: a wrong hash is refused, an old source check yields REQUIRES_VERIFICATION, and an edit after verification resets the status (test_v04.py:166-179).

The identities are names, and the agents can type them. review refuses any reviewer but qa-auditor, the task owner and any listed author, which stops a worker approving under its own name. But the reviewer is an argument, and a worker with Bash can pass --reviewer qa-auditor; the same holds for memory put --actor master-agent, which writes to any namespace. The project records the limit in data rather than leaving it implied — every QA record carries 'identity_authenticated': False — and in the README's "not authenticated user isolation". It is why human_review is withheld: QA is an agent role by design, and the actor that would clear a record's review is a string the producing agent can supply. The namespace mark stands, because the filter runs on every read path; what it protects against is a mistaken role, not a hostile one.

Supersession is per record, so a superseded claim can come back as a new one. supersede makes the old row immutable and hidden, but put with a fresh UUID and the same title and payload is accepted as a new UNVERIFIED record and returned by default search. Nothing compares a new write with superseded content, which is why tombstone is withheld. The one mechanism that could notice is the scan's conflict grouping by namespace and case-folded title, and it skips superseded rows before grouping (freshness.py:69, :78-84), so the re-assertion is grouped with nothing. An unverified record is still labelled unverified, and a team that only acts on FRESH would not act on it; nothing in the read path enforces that.

Unverified knowledge is labelled, not withheld. Recall returns UNVERIFIED and REQUIRES_VERIFICATION records beside FRESH ones and leaves the decision to the agent's instructions. Only supersession filters.

Redaction runs before hashing. redact drops keys named for hidden reasoning, replaces values under credential-named keys, and rewrites GitHub, Anthropic and bearer tokens, key-value secrets, URL tokens and PEM private keys in strings (security.py:6-26), so the stored body, its hash, every export and every Markdown file are the redacted text. It is pattern-based and misses anything outside those shapes.

Search scales with the table. Every search, scan and monitoring pass reads every row, and search re-reads each hit through get. At the size a supervised team produces this is immaterial; it is the first thing to replace past a few thousand records.

10. Tests, Evals, and Benchmarks

tests/ holds 255 unittest methods in nine files, named by release (test_hub.py, test_v03.py through test_v06_*.py), with fixtures that simulate model responses. The memory cases are concentrated in test_v03.py (persistence across restart, namespace ACLs, redaction, import provenance, sync conflicts) and test_v04.py (verification against hashes and source dates, supersession, TTL bounds). I did not run the suite; the screen found four dependency manifests changed inside the seven-day cooldown, and the hub itself is standard library only, which makes the suite the first thing to run on a later reading.

The negative cases and their limits are in the negative_eval evidence above. The supersession case asserts a count, so it would also pass against a filter that returned the superseded record instead of its replacement; a one-line repair would assert the returned id. No case writes a superseded claim again under a new id, which is the path section 9 describes.

No paper: git grep -n -i -E 'arxiv|bibtex|@article|@misc|doi\.org|CITATION' -- . ':!vendor' finds two skill-reference files and a CHANGELOG line, none a citation of this project. The README's acceptance claims are its own; the documents it links (docs/V06-ACCEPTANCE.md) were not checked against a run.

11. For Your Own Build

Steal

  • Make the status a column the writer cannot set. Resetting it inside put closes every write path at once — CLI, import, Markdown, the QA hook — instead of validating each.
  • Bind a review to the content hash it reviewed. Then a verification cannot outlive an edit, and a stale review fails at write time instead of silently vouching for newer text.
  • Date verification by the oldest source, not the review. Citing an old check in a new task should not reset the clock.
  • Treat an external projection as a conflict surface, not an input. The Markdown sync refuses to overwrite an edited file and refuses to import one as authority; the operator acknowledges the exact bytes they inspected.
  • Record the gate's own weakness in the data. identity_authenticated: False on every QA record means no later reader can mistake the gate for more than it is.

Avoid

  • Role names as principals for agents that hold a shell. If the review must be independent of the producer, the reviewer's identity has to come from something the producer cannot pass.
  • Supersession as the only correction for a claim. Without a value-keyed record of what was replaced, the same claim returns under a new id.
  • Substring matching over serialised JSON: key names match every record.

Fit

This fits a single operator running a supervised agent team on one machine, who wants every piece of retained knowledge to carry its sources, its review and an expiry, and is willing to run the review. It is not a multi-user store, the retrieval is not one to scale, and the trust gate assumes the agents behave. Read it for the freshness and verification design, which is small, complete and unusually well tested; do not adopt it as a security boundary.

12. Open Questions

  • Does any committed workflow run freshness scan on a schedule, or is the review clock read only when someone calls it? Maintenance starts disabled.
  • Would the QA gate survive an agent invoking the CLI as another role, under the Claude Code permission settings an operator would actually use? The tree carries no .claude/settings.json.
  • How do the four vendored stores relate to the vault in a running install — migrated once, or left beside it?

Appendix: File Index

  • Vault, namespaces, sync: agent_hub/memory.py.
  • Freshness, verification, supersession, research cache: agent_hub/freshness.py.
  • Schema additions (memory_history, freshness columns): agent_hub/intelligence_schema.py, agent_hub/migrations.py.
  • Task workflow, recall, QA review, outcome storage: agent_hub/core.py.
  • Roles and namespaces: agent_hub/roles.py, agent_hub/departments.py.
  • Redaction and canonical JSON: agent_hub/security.py.
  • Providers (SQLite, Markdown, Notion stub): agent_hub/providers.py.
  • CLI: agent_hub/__main__.py.
  • Tests: tests/test_v03.py, tests/test_v04.py, tests/test_v05.py.

Recorded searches

Run at the pin with /usr/bin/git grep:

  • -n -E 'UPDATE memory_records|INSERT (OR [A-Z]+ )?INTO memory_records|memory_history' -- agent_hub scripts dashboard — the writers: memory.py:118, :129, :134, :171, freshness.py:54, migrations.py:94-95.
  • -n -E 'DELETE|DROP|TRUNCATE' -- agent_hub — one hit, the dashboard's verb mapping.
  • -n -E 'memory\.search|\.recall\(' -- tests — the eleven search and recall calls in the suite; none searches as a foreign role with a matching query.
  • -l -E '^tools:.*Bash' .claude/agents/*.md — sixty-nine of seventy.
  • -n -i -E 'arxiv|bibtex|@article|@misc|doi\.org|CITATION' -- . ':!vendor' — no citation of this project.
  • ls .claude — agents and skills; no settings.json.

History

2026-11-02 — e1e94d17…, v0.6.0. First reading. Four marks: trust_state on the auditor-only verification status, scope_enforced on the per-row namespace check, audit_log on memory_history, and negative_eval on the supersession and namespace cases. human_review is withheld because the reviewer is a role name an agent with Bash can pass, and tombstone because supersession is per record. Screened before reading from a full clone: no auto-run or build-time surface, two agent-instruction files read as data, four dependency manifests inside the seven-day cooldown and one unpinned surface; nothing was installed, built or run.