Research Dossier

The Cognitive Memory Architecture Research Programme

Author: ZenTrader Core Team  ·  Status: Draft research protocol — proposed, not yet peer-reviewed or preregistered

Executive Summary

The whitepaper introduces ZenTrader's Cognitive Memory Architecture (CMA) as the platform's structured, AAS-inspired long-term memory for trading agents. This dossier is the research-grade companion to that narrative: it states plainly what is implemented today, what is a working foundation rather than a finished capability, and what remains a proposed design. It exists so that reviewers — technical, scientific, or funding — can evaluate the architecture on verified evidence rather than marketing language.

ZenTrader already implements an AAS-inspired relational hierarchy (assets → shells → submodels → typed elements), a genuinely insert-only policy-decision ledger, a versioned, golden-set-evaluated scoring pipeline (GeoScore), and a dedicated, database-enforced append-only memory-event ledger (cma_memory_events, stage 1 of the roadmap below). Cryptographic tamper evidence (a SHA-256 hash chain and Ed25519 signatures) and a narrowly-scoped temporal retrieval API with hybrid lexical/dense search are also built and tested — neither is yet enabled in production. What remains genuinely open is retrieval scoring with explanations at a tenant-facing endpoint and a preregistered evaluation programme. This document specifies the research questions, hypotheses, methodology, and roadmap for closing the rest of that gap.


1. Purpose and Status of This Document

Every claim below is labelled so a reader never has to guess whether something exists in production:

LabelMeaning
ImplementedVerified directly against the current source code and the live database schema.
PartialA working foundation exists, but the complete behaviour described in the research programme is not yet present.
ProposedA design recommended for implementation and validation; not yet built.

This dossier was produced from an internal architecture review combined with a direct inspection of the relevant source files and the production database schema. Where the two disagreed, the verified code/database state is what is reported here.


2. The CMA Research Problem

Large language models have finite working context and no inherently persistent, trustworthy memory of earlier interactions, market states, or decisions. Retrieval-augmented generation addresses part of this limitation, but provenance, temporal validity, and updating remain open problems in the general literature. Financial agents raise the bar further: a retrieved statement may have been correct when generated but invalid after a regime change; an analysis may be an inference rather than an observation; a policy may have changed after a trade was placed; and a plausible narrative must never be confused with verified execution evidence.

Research problem. How can heterogeneous observations, probabilistic agent inferences, deterministic policies, execution states, and eventual outcomes be represented as temporally valid, semantically typed, retrievable, and independently verifiable memories — without allowing mutable projections or probabilistic agents to rewrite historical evidence?


3. Current Implementation, Verified Against Code and Database

core/aas_research.py builds AAS-inspired symbol shells and currently emits five submodels — Provenance, MacroRegimeContext, FactorEngineState, SignalDecision, and LiveExecution — normalised into four relational tables (aas_assets → aas_shells → aas_submodels → aas_submodel_elements).

SubmodelStatusPurpose
ProvenanceImplementedSource, task, profile, observed time
MacroRegimeContextImplementedMarket regime and readiness gate state
FactorEngineStateImplementedDeterministic factor values (price, volume, scores)
SignalDecisionImplementedProposed or rejected trading signal, with rationale
LiveExecutionImplementedSelected broker route and readiness

A direct schema inspection confirms that aas_shells and aas_submodels write via INSERT ... ON CONFLICT DO UPDATE SET raw_json = EXCLUDED.raw_json, so the SQL permits overwriting a shell or submodel row. In practice this almost never happens: both tables' identifiers embed the run's task_id, which is unique per market-watch cycle, so each cycle inserts fresh rows rather than colliding with an earlier one — a direct count found only 2 of 907,164 aas_shells rows have ever actually been updated via that conflict path, and both were from an unrelated, stable-ID closed-loop tracking shell, not a per-symbol research snapshot. The real risk these tables carry today is therefore not silent history loss but unbounded, uncompressed growth — 907K shell rows, 4.6M submodel rows, 16.5M element rows, ~14GB combined and rising with every cycle, the same growth pattern GeoScore had before its own hypertable migration (see the GeoScore research page). A dedicated, database-enforced event ledger closes the narrower "could theoretically be silently overwritten" gap and adds explicit temporal/authority semantics these tables don't have; it does not, by itself, address the growth problem, which needs its own compression treatment.

Phase 1 — event ledger and first producer. cma_memory_events now exists: a dedicated table with the four-clock temporal model above, enum-checked memory/authority/lifecycle columns, and — going one step further than gate_decisions — a BEFORE UPDATE OR DELETE trigger that blocks mutation unconditionally, independent of database role grants. This was verified live in production (a manual insert, then a blocked UPDATE and a blocked DELETE, both rolled back) in addition to 16 passing tests. A first producer is now wired: core/aas_research.py mirrors every written AAS submodel into the ledger as an observation-class event, additively and best-effort (a mirroring failure is logged, never breaks the primary AAS write). It is gated behind CMA_LEDGER_MIRROR, default-OFF — matching this codebase's established rollout convention for new observability hooks. A supervised observation run (one market-watch cycle with the flag temporarily enabled) confirmed it works end-to-end in production: 1,280 rows written, 0 mirroring failures, correct grouping and content — then the flag was turned back off. The table is now also a TimescaleDB hypertable on event_time with a compression policy (chunks older than 30 days compress automatically, data stays fully queryable) — but deliberately no retention/drop policy, unlike the GeoScore hypertables precedent: this table is CMA evidence, and the design principles above ("forgetting must not destroy auditability") explicitly require it to remain permanent, not decay-and-drop like GeoScore's narrower signal system. There is still no projection bridge reading the ledger back into aas_shells/aas_submodels, and no other producer (gate_decisions, GeoScore) is wired yet — those remain open roadmap stages (both were wired in later updates below).

Phase 2 — AAS hypertable compression. The growth problem flagged above is now partially solved: aas_shells is a TimescaleDB hypertable on created_at, with the same compression-only, no-retention policy as cma_memory_events — deliberately, because this data backs the whitepaper's trade-explainability claim rather than being disposable signal data. Its child tables (aas_submodels, aas_data_statements) gained a denormalised shell_created_at column and composite foreign keys pointing at the new hypertable, auto-populated by a BEFORE INSERT trigger so no existing writer code needed to change; one line in core/aas_research.py's ON CONFLICT target was updated to match the now-composite unique constraint. Verified live: the hypertable, compression policy, and both triggers are active in production, with the trigger proven correct by production writes that landed during the migration window itself (zero rows with a NULL shell_created_at). Stage 2: aas_submodels — the largest of the four AAS tables (4.58M rows, 13GB) — is now a hypertable too, with the identical compression-only treatment and the same denormalised-timestamp pattern extended to its own children (aas_submodel_elements, aas_data_statements). This stage surfaced a genuine Timescale limitation the first migration didn't: a hypertable cannot itself hold an outbound foreign key to another hypertable, which broke on the first attempt (rolled back cleanly, no damage) because aas_submodels already FKs into the now-hypertable aas_shells. Fixed by dropping that foreign key — aas_submodels.shell_id is now a soft, application-enforced reference rather than a database-checked one, the same trust model already accepted for cma_memory_events.supersedes_event_id. Stage 3: aas_submodel_elements — the largest AAS table by row count (16.56M rows, ~8GB) — is now a hypertable too. Applying the previous stage's lesson up front, its two outbound foreign keys (to the now-hypertable aas_submodels, and a self-reference via parent_element_id) were dropped before calling create_hypertable(), which then succeeded on the first attempt. Two things measured directly rather than assumed: aas_qualifiers, the only other table besides aas_data_statements that references elements, turned out to have zero rows and no writer anywhere in the codebase — dead schema; and parent_element_id itself is NULL on all 16.56M rows — the element-hierarchy feature this dropped foreign key protected has never actually been used in production. Stage 4 — plan complete. aas_data_statements (10.97M rows, 13GB) is now a hypertable too, closing out all four AAS tables. It turned out structurally simpler than the earlier stages expected: nothing foreign-keys into it, so no denormalised columns or triggers were needed at all — only dropping its three outbound composite foreign keys to the other, now-hypertable AAS tables (all becoming soft references, same as before) and partitioning on observed_at, its only timestamp column. No application code changed for this stage. All four AAS tables — aas_shells, aas_submodels, aas_submodel_elements, aas_data_statements — are now compressed TimescaleDB hypertables with no retention policy; only aas_assets stays a regular table, correctly, since it is genuinely upserted (~650 rows). The unbounded-growth limitation this whole update thread tracked is resolved.

Phase 3 — projection bridge (roadmap item 4). A read-only module (trading/cma/projection.py) now reconstructs the latest AAS submodel state purely from cma_memory_events and compares it against the live aas_submodels rows — the first direct test of the design principle above ("current state is a projection... always derived from immutable events") and of RQ-B (evidence reconstruction) against real data rather than an assumption. Run against every shell the ledger has seen (the Phase 1 mirroring proof run): 256 of 256 shells and 1,280 of 1,280 submodels reconstruct exactly — a measured 100% match rate. This is a narrow result, stated precisely rather than oversold: one producer (AAS submodel mirroring), one supervised run, observation-class events only, and content that is a direct verbatim copy by construction (the mirroring code passes the submodel dict unchanged into content_json), so exact equality is the expected outcome being verified, not yet a claim about reconstruction from more distantly related or partially-derived evidence. It is, however, a genuine live measurement with a real failure mode (a mismatch, or a row present on one side only) that did not occur — not a synthetic benchmark. 9 new rollback-isolated tests cover both directions of drift (ledger has it, AAS doesn't; and vice versa) plus the match/mismatch cases directly. Building this also surfaced and fixed a real bug unrelated to CMA specifically: a session-lifecycle helper that unconditionally closed any injected database session on exit, which breaks for any caller supplying a longer-lived session (not just tests) — fixed by mirroring the safer pattern already used by trading/db/order_store.py.

The gate_decisions table is a separate evidence structure. Its writer (trading/policy/decision_store.py) contains no update or delete path in code — every call either raises before writing (fail-closed validation) or inserts exactly one row. A live schema check found the distinction that matters for an honest claim: at that point the append-only guarantee was enforced by application code discipline only, not by the database — no trigger blocked mutation, and the application's own database role retained ordinary UPDATE/DELETE privileges on the table.

Phase 4 — gate_decisions database-enforced. That gap is closed: gate_decisions now carries the identical BEFORE UPDATE OR DELETE trigger already proven on cma_memory_events, unconditionally rejecting mutation independent of role grants. The database roles' own UPDATE/DELETE grants are deliberately left in place — revoking them is a separate, larger change touching every role with legitimate other privileges on this table — but the trigger alone already closes the actual gap: a manual UPDATE and DELETE via raw SQL were both tested live and blocked. gate_decisions and cma_memory_events are now both database-enforced append-only. Neither is yet cryptographically tamper-evident — no content hash, previous-hash chain, or signature column exists on any evidence table today.

Phase 5 — gate_decisions as second producer. gate_decisions is now wired as the ledger's second producer: every written decision is additionally mirrored into cma_memory_events as a memory_type='decision', authority='deterministic_derived' event (trading/policy/decision_store.py), best-effort and never blocking the primary write, gated behind its own default-OFF flag (CMA_LEDGER_MIRROR_GATE_DECISIONS) kept deliberately separate from the AAS producer's flag since this writer sits on the live order-execution path. Unlike the AAS producer, this one has not yet been proven live: two supervised overnight observation windows found no organic gate_decisions row to mirror — the platform runs globally in dry_run mode and neither attempt's paper-mode cycles produced an order intent to route. Both attempts happened on a Friday night/weekend, when the equity-hours-gated strategies don't fire at all; a third attempt is planned for a weekday with both crypto and equity timers active.

Phase 6 — GeoScore as third producer. GeoScore's scoring pipeline (trading/geoscore/scoring.py:score_pending) is now wired as the ledger's third producer: every assembled geoscore_event_scores row is additionally mirrored into cma_memory_events, one event per asset × horizon, as a memory_type='assertion', authority='model_inferred' event — the first producer to actually exercise model_inferred's cross-field validation rule (both model_version and prompt_version required), carrying GeoScore's own resolved model id and its geoscore-prompt-v2 contract version. Best-effort, never blocking the primary score write, gated behind its own default-OFF flag (CMA_LEDGER_MIRROR_GEOSCORE). Covered by mocked unit tests and a rollback-isolated real-database test asserting the mirrored rows' content and count against a seeded event — not yet observed live in production, the same open step as the second producer above.

Incident — a "rollback-isolated" real-DB test wasn't. While building the retrieval API below, an unrelated check surfaced that GeoscoreLedgerMirrorRealDbTest (the real-database test for the producer above) had been silently committing real rows to production cma_memory_events on every run since it was written — 27 rows, IDs 1480-1800, all with the unmistakable fake ticker stream_id='geoscore:ZZZTESTBTC:*'. Root cause: _mirror_geoscore_score_to_ledger() calls record_memory_event() without a session argument by design (the mirror deliberately runs in its own transaction, separate from the primary write's — the same convention every producer in this section uses); that fallback path resolves its session through trading.db.order_store._SESSION_FACTORY, and the test never redirected that hook to its own rollback-isolated session, unlike the equivalent gate_decisions test, which does. The primary GeoScore tables were correctly isolated throughout (verified: zero leaked rows in geoscore_events/geoscore_event_scores) — only the separate-transaction ledger mirror escaped. Fixed by adding the same session-factory redirect the gate_decisions test already used; verified clean on rerun (row count unchanged across two subsequent runs). The 27 leaked rows themselves were left in place rather than forced out through the append-only trigger that would otherwise need disabling to remove them — a deliberate call consistent with this ledger's own principle that mistakes get documented, not erased, and they are harmless: inert, unambiguously fake, and excluded from the reconstruction-rate and audit measurements elsewhere in this dossier by their fake ticker alone.

Phase 7 — hash chain (roadmap item 9, part 1 of 2). cma_memory_events' two reserved integrity columns (content_hash, previous_event_hash) are now populated when trading/cma/chain.py's hash chain is enabled: every row's content_hash is a SHA-256 of its canonicalized content_json, and previous_event_hash links it to the prior row's hash, forming a linked list any of the three producers can append to transparently — none of them had to change. A Postgres advisory transaction lock serializes the read-last-hash-then-insert step so concurrent producers can never race for the same link. A companion verify_chain() walks the ledger and checks both a content re-hash and the link continuity; its detection logic is unit-tested directly against fabricated tampered/deleted/spliced-in rows (constructing a genuinely tampered row in the live table is not possible — the append-only trigger above already blocks it, which is itself a meaningful result: this hash chain is defense-in-depth for a scenario where that trigger is bypassed, e.g. a restore from an externally tampered dump, not the primary defense). Practical canonical JSON (sorted keys, UTF-8, no whitespace), explicitly not a certified RFC 8785 (JCS) implementation — number formatting and non-BMP key ordering are not JCS-exact, a real gap against the roadmap item's original wording. Gated behind CMA_LEDGER_HASH_CHAIN, default OFF, not yet enabled in production.

Phase 8 — Ed25519 signatures (roadmap item 9, part 2 of 2). A migration added two more nullable columns (signature, signing_key_id) and trading/cma/signing.py now signs each row's hash-chain link (content_hash, previous_event_hash, plus stream_id/event_type/event_time so a signature cannot be replayed onto an unrelated row) with an Ed25519 private key, when enabled. signing_key_id is a short, non-secret fingerprint of the public key — deliberately not one hardcoded key baked into the verifier, so a future key rotation doesn't retroactively invalidate old rows' verifiability. A companion verify_signatures() checks every signed row against a caller-supplied key map and reports an unknown-key row separately from an actually-invalid signature, since those mean different things (verifier coverage gap vs. real tamper). The private key itself (CMA_LEDGER_SIGNING_KEY) is read from the environment only, never written to the database or this repository — and today it is unset: no signing key has been generated for production, CMA_LEDGER_SIGN_CHAIN is unset/OFF, and signing depends on the hash chain above also being enabled. Roadmap item 9 (hash chain and signatures) is now built end to end; item 10 (external Merkle checkpoints) remains open.

Phase 9 — retrieval API, part 1 (roadmap item 5). trading/cma/retrieval.py turns RQ-A into a queryable function rather than only a hypothesis: retrieve(as_of=T, ...) answers "what did the ledger know, and consider still valid, at time T" by requiring both ingested_at <= T (never let a point-in-time query see information the system only learned about later) and valid_from <= T < valid_to (half-open, NULL valid_to meaning still valid) — the two-clock distinction the CMA design principles describe, now enforced in a WHERE clause instead of only in prose. A companion retrieve_latest_per_stream() uses Postgres DISTINCT ON (not a naive top-N-then-group, which a rollback-isolated test proved would silently drop a real result: a stream with few events can get crowded out of a simple truncated result set by a stream with many). Both functions also accept ordinary scope filters (tenant, profile, strategy, asset, memory/event type, authority, correlation id) and default to excluding non-active lifecycle rows. Explicitly scoped narrow at the time this phase shipped: no ranking, no lexical or dense search, no Golden Set evaluation — this was the plumbing those later phases build on, not a finished retrieval system (roadmap items 7 and 8, both now shipped separately below). Its docstring carries an explicit warning against exposing it to a tenant-facing endpoint without passing tenant_id, given this codebase's own prior evidence-API cross-tenant leak history. 7 new rollback-isolated tests.

Phase 10 — prompt/model registry (roadmap item 6). trading/cma/model_registry.py is a small, code-level registry of known (component, prompt_version) pairs, each entry citing the real git commit and date that introduced it — not a database table, deliberately: GeoScore's own version discipline already lives in git history (a versioned PROMPT_VERSION constant, evaluated against a Golden Set before each change ships), and a separate mutable registry table could itself drift from the code it describes, the opposite of what a registry is for. Building it against real history surfaced a genuine, previously undocumented gap: an earlier calibration round that added 5 VIP/influencer golden-set events (commits 880e3e1a/99b35ad8, 7/10 → 14/15) never bumped PROMPT_VERSION — still geoscore-prompt-v2 as of this writing. Recorded honestly in the registry entry rather than retroactively inventing a "v2.1" this codebase never used. A read-only audit_ledger_versions() scans the live ledger for any model_inferred row using a (component, prompt_version) pair this registry doesn't recognise; run against production it found zero unregistered versions (the ledger's only model_inferred rows are the 27 leaked test rows noted above, which happen to carry a genuinely registered version). Not wired into record_memory_event as an enforcement gate — that would be a separate, later decision on an already HIGH-risk shared writer, not bundled into introducing the registry itself. 12 new tests, including one that pins the real current registry state so a future geoscore-prompt-v3 ship without a matching entry fails loudly.

Phase 11 — Golden Set + CI non-regression (roadmap item 7). trading/cma/golden/cma_retrieval_golden_set.json freezes 5 retrieve()/retrieve_latest_per_stream() queries against real, immutable rows from the Phase 1 AAS mirroring proof run (the TXN research shell's 5 submodels, ids 1276-1280), each with an exact expected event-ID set — not ranges, unlike GeoScore's LLM-graded golden set, because retrieval over an append-only ledger given fixed evidence has exactly one correct answer. tests/test_cma_golden_set_unittest.py replays every case against the live database as an ordinary part of the pytest suite: true CI non-regression, no separate eval script needed. Building the "latest state per stream" case caught a real bug before it was ever pinned: all 5 of the TXN shell's rows share one event_time (they were mirrored in a single market-watch cycle), and retrieve_latest_per_stream()'s tie-break — nonexistent until this phase — silently returned Postgres's scan order, observed picking the first-inserted row of the tied group, the wrong direction for "current state". Fixed by adding id DESC as an explicit tiebreaker (the row that actually entered the ledger last now wins); the golden set's tiebreak case pins the corrected behaviour against the same real data that exposed the bug, so a regression would be caught immediately, not rediscovered.

Phase 12 — hybrid retrieval (roadmap item 8). This codebase had zero vector-search infrastructure before this phase: no pgvector, no embedding-generation code anywhere, no embeddings surface on the AION MCP integration (checked directly rather than assumed). What it did have, already running, was nomic-embed-text (768-dim, a dedicated embedding model, confirmed present) on the same GPU Ollama instance GeoScore's LLM calls already depend on (100.98.188.22:11434) — so building this needed a new Postgres extension and a new Python client, not a new model or a new server. Installed pgvector 0.8.1 (confirmed installable by the existing zentrader_researcher role without superuser — it's a "trusted" extension) and added a nullable vector(768) column plus an HNSW cosine-distance index to cma_memory_events. Because the table is append-only, embeddings cannot be backfilled onto an existing row via UPDATE the way a normal "reserved column" would be — trading/cma/embeddings.py's apply_embedding() runs at insert time only, alongside the hash-chain and signing steps, computing a vector from search_text when present. Unlike those two, a failed embedding call is caught and logged, never raised: it is a network call to a remote GPU that can fail transiently even when correctly configured, and the ledger's evidence-recording contract must not depend on that GPU being reachable. lexical_search() (Postgres ts_rank over the GIN index this table has carried since Phase 1), dense_search() (pgvector cosine distance), and hybrid_search() (min-max-normalized weighted fusion of the two) are three independently swappable pieces — the "replaceable interface" the roadmap item asks for. Verified against the real GPU once, end to end, outside the permanent test suite (mocked in all committed tests, matching how GeoScore's own tests never call the real Ollama instance either): a query for "central bank monetary policy rate decision" correctly ranked a row about "Federal Reserve signals interest rate cuts" top via the dense signal alone (lexical score 0 -- no shared words), demonstrating genuine semantic match, not just keyword overlap. Both new flags (CMA_LEDGER_EMBED, gating insert-time computation) default OFF; no embedding has been written to a real production row. 22 new tests across three files; full cma/geoscore/gate_decision/aas suite green (263 passed), plus a full 1,826-test whole-suite run confirming no regressions elsewhere (2 pre-existing, unrelated failures in an unrelated API area, untouched by this work).

Phase 13 — the model registry catches its own kind of drift a second time. Phase 10 recorded, honestly, that GeoScore's prompt constant was still geoscore-prompt-v2 despite an undocumented calibration change. That has since shipped as geoscore-prompt-v3: a new golden-set-evaluated change (adding a TRACKED_ASSETS asset-naming preference list, golden set 13/15 with n=3 self-consistency runs) correctly bumped the version this time — geoscore-prompt-v2 is now marked deprecated in trading/cma/model_registry.py, geoscore-prompt-v3 is the new active entry, citing its real introducing commits. The two golden-set misses that remain under v3 (a memecoin-post score range, exchange-hack factor signs) are pre-existing calibration gaps unrelated to this change, not a new regression. This is a genuine, small proof point for the registry's own stated purpose: a real version change happened, and it landed in the registry rather than drifting silently a second time.

GeoScore, the platform's qualitative-narrative scoring pipeline, is the most mature in-repository template for CMA's evaluation discipline: every prompt or model change is evaluated against a versioned, range-graded Golden Set before it ships, scoring itself is a deterministic, unit-tested function of the LLM's structured (never numeric) output, and re-scoring an event never overwrites a row — it inserts a new one chained via superseded_by. This append-and-chain pattern is exactly what the proposed CMA event ledger should generalise across all memory classes, not only narrative scores.


4. Research Questions and Hypotheses

QuestionHypothesis
RQ-A — Temporal retrieval. Does CMA retrieve information valid at the requested decision time more reliably than recency, lexical, or dense retrieval alone?Hybrid CMA retrieval improves temporal-validity accuracy and nDCG@k over recency-only, BM25-only, and dense-only baselines.
RQ-B — Evidence reconstruction. Can a past decision be reconstructed from its exact data, memory, model, prompt, strategy, and policy state?CMA achieves a higher complete-reconstruction rate and lower unsupported-claim rate than free-text journals or unversioned RAG.
RQ-C — Contradiction handling. Does explicit validity, supersession, and source authority reduce retrieval of stale or contradicted memories?Validity-aware retrieval reduces stale-memory inclusion and contradiction errors versus timestamp-only retrieval.
RQ-D — Controlled consolidation. Can derived reflections improve multi-hop reasoning without being mistaken for primary evidence?Provenance-bound reflections improve multi-hop retrieval while preserving evidence precision.
RQ-E — Memory economy. Can ranked, compressed memory reduce context size and latency without materially reducing decision quality?CMA uses fewer prompt tokens than full-context baselines while remaining non-inferior on task accuracy.
RQ-F — Tamper evidence. Can canonicalisation, hashing, and signatures detect unauthorised alteration or omission of stored evidence?Injected record mutations are detected, and omission attacks are detectable against independently retained checkpoints.

Provisional engineering targets — to be fixed after a labelled pilot and before any holdout is opened — include a five-percentage-point absolute improvement in temporal-validity accuracy, at least 95% complete decision reconstruction, zero undetected mutation in an adversarial test suite, and at least 30% fewer prompt tokens than full-context retrieval at non-inferior accuracy. These are validation thresholds to be tested, not current product claims.


5. Design Principles

The formal CMA is governed by six invariants:

  1. Evidence is immutable. Observations, decisions, executions, and outcomes are appended, never overwritten.
  2. Current state is a projection. The "latest known state" may be rebuilt, but it is always derived from immutable events.
  3. Time has more than one meaning. Observation time, event time, ingestion time, and validity interval are stored separately.
  4. Authority is explicit. Broker facts, deterministic calculations, human assertions, and LLM inferences are distinct memory classes with distinct trust levels.
  5. Agents propose; deterministic services commit. An agent may request a memory write, but schema, tenancy, policy, provenance, and authority checks decide whether it is accepted.
  6. Forgetting must not destroy auditability. Decay, summarisation, and archiving affect retrieval and cost, never the underlying evidence ledger.

6. Memory Taxonomy (excerpt)

ClassDefinitionMutability
ObservationData received directly from a defined sourceAppend-only
AssertionA claim derived from observationsSuperseded, never overwritten
DecisionDeterministic or human gate outcomeAppend-only
ExecutionBroker-facing action and reported stateAppend-only event stream
ReconciliationComparison between internal and broker statesAppend-only
OutcomeLater evidence evaluating an earlier assertion or decisionAppend-only
ProjectionMaterialised current view (e.g. today's aas_shells)Rebuildable and mutable

7. Relation to Prior Work

The reviewed literature supplies strong solutions for long-term retrieval, reflection, graph memory, financial memory, and provenance individually. ZenTrader's design is unusual in combining these with AAS-inspired semantic twins, deterministic policy evidence, and multi-broker reconciliation — but this is a research inference, not an established uniqueness claim; a systematic literature review is still required before publication.

FieldImplication for CMA
AAS & digital twins (IDTA metamodel)Use AAS identity/shell/submodel principles; maintain a documented ZenTrader compatibility profile rather than claiming conformance.
Retrieval-augmented generationRetain external, queryable evidence rather than treating model weights as system memory.
Generative Agents (Park et al.)Adopt scored retrieval and reflection, but bind reflections to evidence so they cannot silently become authoritative facts.
MemGPTExpose explicit retrieval interfaces; never let an LLM bypass deterministic write policy.
MemoryBankApply decay to retrieval visibility only, never to immutable financial evidence.
HippoRAGConsider graph/PageRank expansion only after the relational-temporal baseline is stable.
FinMemCompare CMA against a layered financial-memory baseline; differentiate on evidence governance and temporal provenance, not layering alone.
LoCoMoReuse its task taxonomy as a general memory sanity check; build a finance-specific temporal benchmark separately.
W3C PROVMap datasets, prompts, models, policies, agents, and decisions to an explicit provenance vocabulary.

8. Validation Programme

Five properties are tested independently rather than collapsed into trading profitability, which is affected by strategy quality, regime, and cost and cannot alone establish that the memory architecture is better:

Baselines span no-memory and full-context extremes, recency/BM25/dense-RAG, Generative-Agents-style scoring, a FinMem-style layered baseline, and three CMA configurations (relational, hybrid, graph+reflection). The final confirmatory evaluation is intended to be preregistered — in the OSF sense of a timestamped, read-only plan frozen before the holdout is accessed — before any headline result is published.

Concrete validation milestones

MilestoneExit criterion
Memory-event schemaJSON Schema validation and database constraints agree on valid/invalid fixtures
Temporal queriesAll synthetic bitemporal test cases pass, including delayed corrections
Evidence reconstructionAt least 95% of labelled decisions reconstruct with all mandatory evidence
Golden SetVersioned query set, evaluator, and non-regression CI are operational
Hashing & signaturesMutation, reorder, and wrong-predecessor tests fail verification as designed
Preregistered evaluationLocked holdout analysed once under the registered plan

9. Limitations

  1. Standards scope. ZenTrader borrows AAS concepts but uses a domain-specific JSON shape and relational normalisation. Full AAS conformance cannot be claimed until serialisation and interfaces are tested against the official IDTA specifications.
  2. Current event immutability; unbounded growth resolved. Gate decisions are append-only in application code and, in a later phase, database-enforced by a trigger too, matching cma_memory_events; AAS shell and submodel writes are technically upserts, though a direct count found the conflict path fires almost never in practice (2 of 907,164 rows) because each cycle's task_id makes IDs unique — the real historical risk was unbounded, uncompressed growth (~14GB and rising across four AAS tables), not silent overwrite, and that growth problem is now resolved: all four AAS tables (aas_shells, aas_submodels, aas_submodel_elements, aas_data_statements) are now compressed TimescaleDB hypertables with no retention/drop policy, completed across four incremental migrations in one phase of this programme — and it is holding: measured again since, combined row counts have grown 20-30% across three of the four tables (now 1.15M / 5.80M / 21.0M / 10.97M rows respectively), yet the combined compressed footprint is ~8.3GB, down from the ~14GB pre-compression baseline. A dedicated, database-enforced event ledger (cma_memory_events) also now exists, with three producers wired (AAS submodel mirroring, gate_decisions, GeoScore) each gated behind its own default-OFF flag — a separate, narrower gap (overwrite-proofing and temporal/authority semantics) from the growth problem, and still open pending production enablement.
  3. Source truth. A future hash chain would prove content has not changed relative to a checkpoint — it would not prove that a broker, news source, model, or human assertion was correct.
  4. Non-stationarity. Financial regimes, broker behaviour, and models change over time; results from a fixed historical window may not generalise, so temporal cross-validation is mandatory.
  5. Private reproducibility. Proprietary data and code limit full external replication; a synthetic benchmark, public schemas, and frozen evaluation artefacts are planned to mitigate this without publishing trading IP.

10. Roadmap

The roadmap prioritises schema stability and temporal/evidence correctness before embeddings, graph databases, or autonomous reflection:

  1. Terminology and ADR freeze (memory taxonomy, authority classes, temporal semantics)
  2. JSON Schema package with valid/invalid fixtures
  3. Done Immutable event ledger (cma_memory_events; insert-only, DB-trigger-enforced — three producers wired: AAS submodels, gate_decisions, GeoScore, each behind its own default-OFF flag; AAS producer proven live in production, the other two verified in tests only)
  4. Done Projection bridge from events into existing AAS tables (read-only reconstruction + verification; 100% match on the one dataset measured so far)
  5. Partial Read-only, temporally filtered retrieval API — point-in-time (as_of) + scope filtering, a per-stream "latest state" query, and hybrid lexical/dense ranking now built (trading/cma/retrieval.py, item 8 below); still no tenant-facing endpoint and no preregistered evaluation harness (item 11)
  6. Done Prompt/model registry, generalising GeoScore's version discipline — code-level registry (trading/cma/model_registry.py), not a DB table, deliberately matching how GeoScore itself already tracks versions (git history, not a mutable registry that could itself drift)
  7. Done Golden Set with expected evidence IDs and CI non-regression — 5 cases frozen against real, immutable production rows (trading/cma/golden/cma_retrieval_golden_set.json), run as an ordinary part of the pytest suite; caught a real ordering bug while being built
  8. Done Hybrid (lexical + dense) retrieval behind a replaceable interface — pgvector installed, real GPU embedding calls verified end-to-end; not enabled by default
  9. Done Hash chain and signatures (JCS canonicalisation, SHA-256, Ed25519) — both built (practical canonical JSON, not certified JCS); neither enabled in production
  10. External checkpoints (Merkle batches, independently retained roots)
  11. Preregistered holdout evaluation and public research package

The first implementable release is scoped as CMA v1: immutable events, temporal retrieval, and evidence reconstruction. Graph memory, autonomous reflections, and external transparency logs are later increments — sequenced this way so sophisticated retrieval never hides weak temporal or provenance semantics underneath it.


References