Back to atlas

Architecture as the memory

ARGO

A delivery harness whose memory is an ArchiMate architecture graph, canonical as JSON and rebuilt wholesale into Neo4j — with four times more test code than implementation, and its own credential gate red at HEAD.

Carries 2 of 7 rubric mechanisms. Most systems here carry none or one (52%), and a dash means the mechanism was not found at this commit — not that the system needed it.

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

1. Executive Summary

ARGO is a delivery harness — requirements through architecture, coding, testing and archival — whose distinguishing choice is what it remembers. Instead of storing conversation turns or extracted facts, it maintains an ArchiMate 3.2 enterprise-architecture model of the system under construction, and makes that model the thing agents query. The README's argument for the choice is that ArchiMate's constrained relationship types make architectural context "queryable, traceable, and usable by agents", which is a claim about the schema doing work that free-form extraction cannot.

The committed graph is real: design/KG/SystemArchitecture.json is 400 KB holding 96 elements, 140 relationships and 47 views. Around it sit 6,674 lines of GraphRAG JavaScript over Neo4j with a live embedding lifecycle, MCP servers, and platform bundles for Cursor, GitHub Copilot and OpenCode. MIT licensed, 861 commits.

Its test posture is inverted from almost everything in this atlas. 27,152 lines of tests across 100 files against 6,674 lines of graph-rag implementation — roughly four to one — and the tests are not unit checks but architecture fitness functions: entry-point boundaries, dependency direction, contract traceability, credential boundaries. node .argo/scripts/runArchitectureTests.js executes 61 named acceptance cases on a clean clone with no services.

It fails. 114 passed, 8 failed, exit 1, four unique failing cases. Two need a live provider or Neo4j. The other two do not, and one of them is worth reading closely because the project's own gate is red about its own code.

And the memory has no correction semantics of its own, which is the finding that matters most here. syncSystemArchitectureToNeo4j calls clearGraphMATCH (n {graphKey: $graphKey}) DETACH DELETE n — and then CREATEs every element again. The graph is a projection, not a system of record. Nothing in Neo4j supersedes, versions, tombstones or dates a belief; the JSON is canonical and git is its history. That is a defensible architecture and it means every question this atlas asks about correction has to be asked of a file and a review process rather than of the store.

2. Mental Model

A fact about the architecture is written to a schema-validated JSON file, projected into a graph that is rebuilt from scratch, and served only if the index is qualified.

flowchart TB
    LOOP["delivery loop<br/>business-partner → task-tidy → emit"] --> JSON[("design/KG/<br/>SystemArchitecture.json<br/><b>canonical</b>")]
    JSON --> VAL["validateSystemArchitecture<br/>+ ArchiMate 3.2 rules"]
    VAL --> SYNC["syncSystemArchitectureToNeo4j"]
    SYNC --> CLR["clearGraph:<br/>DETACH DELETE n"]
    CLR --> CRE["CREATE every element,<br/>relationship, view"]
    CRE --> NEO[("Neo4j projection<br/>+ vector index")]

    NEO --> GATE{"embedding qualification<br/>+ index gate"}
    GATE -->|"not qualified"| CLOSED["fail closed —<br/>no degraded answer"]
    GATE -->|"qualified"| Q["queryNodes(index, topK, vector)<br/>WHERE node.channel = $channel"]

    style JSON fill:#e7efe9,stroke:#3d6b59

The shaded node is the memory. Everything to its right is derived and disposable, which is why the delete path is a wipe rather than a correction: there is nothing in the graph worth preserving that is not already in the file.

The consequence for this atlas's questions is direct. Ask "can a wrong belief be retracted so it cannot return?" and the answer is not in the code — it is that someone edits the JSON and re-syncs, and whether the wrong value comes back depends on the review process around a file. The store cannot help, and does not pretend to.

3. Architecture

.argo/ is the payload: scripts/graph-rag/ (6,674 lines) holds productionGraphRagRuntime.js, mutationEmbeddingVectorLifecycle.js, defaultSemanticRetrieval.js, the three gates (embeddingQualificationGate, liveEmbeddingIndexGate, canonicalProjectionAuthority), semanticReadinessAttestationStore.js and a semantic-persistence/ subtree. Beside it, .argo/scripts/ carries the Neo4j store, three MCP servers, the ArchiMate rule engine, and validators for the architecture and for each stage handoff. .argo/schema/ holds four JSON schemas; .argo/rules/ holds three checklists; .argo/history/decision-tree/ holds dated decision records as Markdown.

ARCHITECTURE.md inside graph-rag/ is 54 KB, which is a larger design document than most systems here ship for their whole product.

Deployment and ergonomics

Copy a platform bundle plus .argo/ into a workspace and run /argo-init, which checks that the argo MCP tools and the vector graph are reachable. Neo4j is required for the graph, and an embedding provider for the vector arm — the README states that "automatic embedding and persistence run only when a vector-ranking model is configured", so the degraded mode is declared rather than discovered.

The real adoption cost is not the services. It is that ARGO expects you to model your system in ArchiMate and keep that model current, because the model is the memory. A team that will not maintain it gets a harness whose central asset is stale, and no amount of the surrounding machinery compensates.

4. Essential Implementation Paths

The graph is a projection, and the code says so

async function clearGraph(tx, graphKey) {
  await tx.run('MATCH (n {graphKey: $graphKey}) DETACH DELETE n', { graphKey });
}

then, per batch, CREATE (e:Element), SET e += row, MERGE (g)-[:OWNS_ELEMENT]->(e). Elements are created, never merged by identity; relationships and views the same. canonicalProjectionAuthority.js names the arrangement outright — the canonical artifact has authority, the projection does not.

A canonical store with a rebuildable projection is a shape many reports here describe without naming, and ARGO is the cleanest instance of it — the one where the trade is most visible: rebuildability buys you a graph that can never drift from the file, and it costs you every per-record property that would have made the graph itself correctable. There is no valid_from, no superseded_by, no status, no audit row, because a node's whole life is one sync.

Retrieval that fails closed

Recall is Neo4j native vector search:

CALL db.index.vector.queryNodes($indexName, $topK, $vector)
YIELD node, score
WHERE node.channel = $channel
RETURN properties(node) AS record, score
ORDER BY score DESC

Fully parameterised, with the channel applied as a filter in the query rather than after it — so the scope key reaches the read path, which is what the scope as a first-class key mark certifies.

In front of it sit embeddingQualificationGate and liveEmbeddingIndexGate, and the committed acceptance cases assert what happens when they are unhappy: SP-04-FailClosedReadiness and BP-AUTOALIGN-QUERY-FAILS-CLOSED both pass. A retrieval path that refuses to answer when its index is not qualified — rather than silently returning lexical or stale results — is the behaviour this atlas asks about under negative evidence, and here it is asserted rather than described.

Architecture tests as the product's own gate

tests/ is 100 files and 27,152 lines, and its centre of gravity is tests/architecture/ and tests/explicit/entries/: entry-point boundary, dependency direction, contract traceability, credential boundary, delivery gates. runArchitectureTests.js drives 61 named cases and prints a per-case verdict with its command line, which makes the suite legible as evidence rather than as a pass/fail.

Run at this commit, from a clean clone, with no Neo4j and no provider key:

61 acceptance cases — 114 passed, 8 failed — exit 1

The four unique failures: TS-06-Provider-E2E and SP-03-DefaultVectorRetrieval need a live provider and a live database respectively, which is expected. TS-07-Provider-Secret-Isolation and TS-07 do not.

The gate that is red about its own code

runExternalCredentialBoundary.js first asserts that each missing credential blocks delivery with EXTERNAL_CREDENTIALS_REQUIRED and no fallback — those assertions pass — and then audits every production source file for three things: hardcoded credential defaults, credential fallback expressions, and credential-bearing Cypher. At HEAD:

hardcodedDefaults    []
fallbackCredentials  []
cypherCredentialLeaks [".argo/scripts/graph-rag/mutationEmbeddingVectorLifecycle.js"]

TS07_CYPHER_CREDENTIAL_BOUNDARY_VIOLATION, in 30 ms, no services required.

The flagged Cypher carries no credential. It is the vector query above — indexName, topK, vector, channel. The detector taints identifiers by credential name across the whole file, and mutationEmbeddingVectorLifecycle.js contains embeddingCredential: configuration.qwenKey at line 158, so every session.run in that file is thereafter treated as a credential transport.

Two things worth separating, because they point opposite ways. The rule is file-scoped where it needed to be dataflow-scoped, so this specific failure is a false positive and not a leak. And the project ships at HEAD with its own security gate red, which means the signal it built is currently not being read — the failure mode where a well-designed check is present, correct in intent, and routinely overridden by whoever runs the suite and knows which reds to ignore. A test suite that is expected to be partly red teaches its readers to stop reading it.

Injection defence in the handoff validator

validateStageHandoff.js validates each stage handoff against a JSON schema and additionally refuses acceptance criteria that look like commands — DISALLOWED_ACCEPTANCE_CRITERIA_PATTERNS rejects anything starting with node, npm, python and friends, anything shaped like a path plus arguments, and anything containing backticks, quotes, pipes or semicolons.

Acceptance criteria are model-authored text that a later stage executes against, which makes them an injection surface most harnesses leave open. Refusing the shell-shaped ones at the schema boundary is a cheap, specific defence.

5. Memory Data Model

The unit is an ArchiMate 3.2 construct. SystemArchitecture.schema.json governs the canonical file; archimate32-rules.js enforces which relationship types may connect which element types, which is the constraint the README's whole argument rests on. In the graph, nodes carry graphKey for partitioning and channel for retrieval scope, plus an embedding.

Three further schemas cover the handoffs — intent to implementation, implementation to coding, and an implementation-to-intent trace proposal. The last is the interesting one: a proposal to write back from code to intent, schema-validated, so drift between what was designed and what was built has a typed channel rather than a conversation.

What is absent, measured against this atlas's rubric, follows from the projection model rather than from oversight. No discrete trust state on an element — the vocabulary of qualified, blocked, aligned, approved and rejected belongs to gates and delivery stages, and attaches to a run or an index, never to a belief as a field. No validity time apart from record time. No mutation audit in the store: semanticReadinessAttestationStore writes a readiness attestation to .argo/temp/ with an ACL check, which is a record of whether the index may be trusted, not of what changed. And no value-keyed rejection, so a wrong element removed from the JSON and re-added by a later modelling pass arrives as new.

6. Retrieval Mechanics

One embedding of the query, one Neo4j vector index lookup per channel, filtered and ordered by score. No lexical arm and no fusion, so the failure the hybrid retrieval fusion pattern describes applies — except that the corpus here is a controlled vocabulary of ArchiMate element names rather than free prose, which narrows the exposure considerably. Exact identifiers are what the graph structure is for; the vector arm is for finding the right region of it.

defaultSemanticRetrieval.js is 34 KB, and neo4jNativeRetrieval.js is a 1 KB adapter over the native index — the size difference is where the gating, channel routing and fallback policy live.

7. Write Mechanics

The delivery loop edits the canonical JSON; validateSystemArchitecture and the ArchiMate rules check it; syncSystemArchitectureToNeo4j projects it, with a --verify mode that checks the projection matches without writing. mutationEmbeddingVectorLifecycle.js maintains embeddings across mutations, and its acceptance cases assert the aligned-write and the failure-is-not-complete paths — BP-AUTOALIGN-WRITE-ALIGNED and BP-AUTOALIGN-WRITE-FAILURE-NOT-COMPLETE both pass, so a write that fails to index does not report success.

Operational cost

A Neo4j instance and an embedding provider, both required for the semantic path and both declared. The sync is O(graph) on every run because it rebuilds, which is cheap at 96 elements and is the number to watch if the model grows. No model sits in the read path; the models sit in the delivery loop that authors the model.

8. Agent Integration

Three MCP servers — architecture, validation, system metadata — plus prompt bundles for Cursor, GitHub Copilot and OpenCode, each exposing the same entrypoints. /business-partner converges a decision tree with a human; /task-tidy packages it; /task-emit-human-in-the-loop keeps a person on stage approvals while /task-emit-afk runs unattended and returns failing tasks until they pass.

The human-in-the-loop mode is a real workflow distinction and it is not a memory review surface. No schema carries an approver, a reviewer or an approval timestamp — acceptanceCriteria is the only related field — so the approval happens in the conversation and leaves nothing on the record it approved. That is the near-miss for human_review: the ceremony exists and the evidence of it does not.

9. Reliability, Safety, and Trust

Strengths:

  • Four times more test code than implementation, weighted toward architecture fitness functions rather than unit tests.
  • A runner that prints a verdict and a command per case, so a reader can re-execute any single one.
  • Retrieval that fails closed when the index is not qualified, asserted by two committed cases rather than described.
  • A write path that cannot report success on a failed index, also asserted.
  • Scope applied inside the query, not after it.
  • A constrained schema doing real work — ArchiMate's legal relationship types are enforced by a rule engine, so the graph cannot express a connection the language forbids.
  • Injection defence on acceptance criteria, refusing shell-shaped text at the schema boundary.
  • A declared degraded mode — no vector model configured means no automatic embedding, stated up front.

Gaps:

  • The store has no correction semantics. A wipe-and-rebuild projection has no supersession, no history, no tombstone and no per-element status; correction lives in a file and a review process.
  • The suite is red at HEAD, including one case whose subject is a credential boundary.
  • That case is a false positive from a file-scoped taint rule, which is its own problem: a check that cries wolf is a check that gets ignored.
  • No approver recorded on anything a human approved.
  • Single-arm retrieval, mitigated by the controlled vocabulary rather than by design.

10. Tests, Evals, and Benchmarks

Run at this commit: 61 acceptance cases, 114 passed, 8 failed, exit 1, on a clean clone with npm install and nothing else. Four unique failures, two environmental (TS-06-Provider-E2E, SP-03-DefaultVectorRetrieval) and two not (TS-07, TS-07-Provider-Secret-Isolation).

There is no retrieval-quality benchmark and nothing that measures whether the architecture graph improves an agent's output — which is the claim the whole design rests on. The tests establish that the machinery behaves, not that the machinery helps. For a project whose thesis is that ArchiMate structure makes context "queryable, traceable, and usable", the missing measurement is the one that would settle it, and its absence should be read as the honest state of an unproven thesis rather than as a gap in diligence.

The suite's real contribution to this atlas is the genre. Architecture fitness functions — dependency direction, entry-point boundary, contract traceability — are rare in the corpus, and they are the only committed tests here that would catch a structural regression rather than a behavioural one.

11. For Your Own Build

Steal

  • Make the schema carry the constraint. ArchiMate's legal relationship types mean a malformed architectural claim is rejected by a rule engine rather than by review. A controlled vocabulary is worth more than a validator over free text.
  • Fail retrieval closed when the index is not qualified. Returning degraded results silently is the failure a user cannot see; refusing is one they can. Assert both directions.
  • Make a failed index a failed write. BP-AUTOALIGN-WRITE-FAILURE-NOT-COMPLETE is the assertion that stops a store reporting success while being unsearchable.
  • Write architecture fitness functions, not only unit tests — dependency direction and entry-point boundaries catch the regressions that survive every behavioural test.
  • Refuse shell-shaped text at the schema boundary when model-authored fields are later executed against.
  • Give drift a typed channel. A schema-validated trace proposal from code back to intent beats noticing the divergence in a conversation.

Avoid

  • Shipping with your own gate red. A suite expected to be partly red trains its readers to skip the reds, and the case being ignored here is a credential boundary.
  • Taint analysis scoped to a file. One credential-named identifier anywhere in a module marks every query in it, and the resulting false positive is indistinguishable from the true one it was written to catch.
  • Treating a rebuildable projection as a memory. It is an index. If you need correction semantics, they belong in the canonical artifact, and you should say so rather than let a graph database imply otherwise.
  • Recording an approval only in the conversation. Human-in-the-loop that leaves no field is a mode, not a record.

Fit

Right for a team that already models its system and will keep doing so — the payoff is an agent that can query architecture rather than re-derive it, and the harness around that query is more carefully tested than most things in this atlas. The delivery-mode split and the stage handoffs are a coherent product.

Wrong as a memory component, and wrong for a team that will not maintain an ArchiMate model. There is no general-purpose store here: the memory is one project's architecture, its correction story is git, and adopting ARGO means adopting a modelling practice first and a retrieval stack second.

12. Open Questions

  • Does the architecture graph measurably improve delivery outcomes? The thesis is stated and nothing in the repository tests it.
  • Will the credential-boundary rule become dataflow-scoped, or will the suppression be an allowlist? Either fixes the red; only one keeps the check meaningful.
  • Is the wipe-and-rebuild sync intended to stay O(graph), and what is the model size at which it stops being free?
  • Should an approver be recorded on a stage acceptance, given the human-in-the-loop mode exists to produce one?
  • The trace proposal writes from implementation back to intent — what adjudicates a proposal that contradicts a decision already recorded in .argo/history/decision-tree/?

Appendix: File Index

  • Canonical memory: design/KG/SystemArchitecture.json (96 elements, 140 relationships, 47 views), .argo/schema/SystemArchitecture.schema.json, .argo/scripts/archimate32-rules.js.
  • Projection: .argo/scripts/neo4j-system-architecture-store.js (clearGraph, element/relationship/view writers), .argo/scripts/syncSystemArchitectureToNeo4j.js.
  • Retrieval and gates: .argo/scripts/graph-rag/defaultSemanticRetrieval.js, neo4jNativeRetrieval.js, embeddingQualificationGate.js, liveEmbeddingIndexGate.js, canonicalProjectionAuthority.js, productionGraphRagRuntime.js, mutationEmbeddingVectorLifecycle.js, semanticReadinessAttestationStore.js, and graph-rag/ARCHITECTURE.md.
  • Validation: .argo/scripts/validateSystemArchitecture.js, validateStageHandoff.js (DISALLOWED_ACCEPTANCE_CRITERIA_PATTERNS), validateTraceProposal.js.
  • MCP: .argo/scripts/argo-mcp-server.js, systemarchitecture-mcp-server.js, validator-mcp-server.js.
  • Tests: .argo/scripts/runArchitectureTests.js, tests/architecture/, tests/explicit/entries/ (notably runExternalCredentialBoundary.js, runProductionSemanticReadinessGate.js, runMutationIndexLifecycle.js), tests/harness/productionGraphRagHarness.js.
  • Decisions: .argo/history/decision-tree/, .argo/rules/.

History

2026-08-049cd3e70f… — first reading. node .argo/scripts/runArchitectureTests.js was executed on a clean clone: 61 acceptance cases, 114 passed, 8 failed, exit 1. The TS-07 credential-boundary failure was traced to cypherCredentialLeaks naming mutationEmbeddingVectorLifecycle.js, and the flagged Cypher was extracted and confirmed to carry no credential — a file-scoped taint rule, not a leak. Marks are withheld for trust state, bi-temporal validity, audit log, tombstone and human review because the Neo4j graph is rebuilt wholesale by clearGraph on every sync and carries no per-element lifecycle, and because no schema records an approver.