Skip to content
← Documentation

docs/platform-architecture.md


Cognithamus — Platform Architecture Specification

Status: SPECIFICATION. Not implemented. Produced to prove the architecture before changing it. Nothing here is wired into the codebase.

The constraint this document must satisfy: Cognithamus knows only Worlds, Subjects, Facts, Conditions, Evidence, Time — and Actors and Places, argued for in §2. It must never know what GitHub, Jira, Kubernetes, Datadog or XI are.


0. Evidence — the core is not currently a core

Import graph of src/lib/world/, measured:

world/types.ts      → hq/roster
world/facts.ts      → hq/roster
world/structure.ts  → hq/roster, hq/estate, hq/telemetry
world/build.ts      → hq/roster, hq/state
world/answers.ts    → hq/roster, hq/state
substrate/runtime   → db, hq/roster, hq/findings, runtime/dispatch
substrate/telemetry → hq/state, hq/roster, hq/estate

Connector-specific knowledge inside the core:

Knowledge Located in
api.github.com, git subprocesses hq/telemetry.ts
Stripe price identifiers hq/estate.ts
JLV-000X, /Users/jlv hq/estate.ts, tenant.ts
repo-* condition keys hq/findings.ts
26 references to repo.* hq/state.ts

Finding: the canonical contract itself (types.ts, facts.ts) imports XI's roster. AgentId is a union of nine XI agents, so the type system that defines a World cannot be compiled without knowing XI exists. This is the root of every other coupling: it is not that the core has tenant data attached, it is that the core is partly tenant data.


1. The immutable core

The core is what remains when every connector, tenant and renderer is deleted. It must still compile, still be meaningful, and still enforce the constitution.

core/
  time.ts        Instant, Window. Everything is evaluable at an instant.
  subject.ts     Subject, SubjectKind, SubjectRef, URN construction + parsing.
  condition.ts   ConditionId (closed taxonomy), families, severity policy.
  evidence.ts    Evidence: provenance, citation, observer, observedAt.
  fact.ts        The seven fact families. Identity. Merge and dedupe rules.
  actor.ts       Actor identity, placement, ownership, history.
  place.ts       Plot allocation, hierarchy, stability guarantees.
  world.ts       World type, composition, projection, stats.
  legend.ts      Visual property ↔ source class mapping (Article II).
  governance.ts  Invariant checks, amendment ledger, probes.

The core imports nothing outside core/. That is the single mechanical test of whether this specification has been honoured, and it is enforceable by a lint rule (§6).

The core contains no notion of: repository, issue, pod, deployment, revenue, commit, product, department, agent-named-Z, GitHub, Stripe, or XI.


2. Constitutional concepts versus plugins

Constitutional — the core's vocabulary

Concept Definition Why it is constitutional
World The projection of one canonical operational state Article I
Subject Anything a condition can be about, identified by URN Facts must attach to something identifiable
Condition A state warranting attention, from a closed taxonomy Prevents semantic drift across connectors
Fact A timestamped, evidenced assertion The atom of the whole system
Evidence Provenance: who observed, when, citing what Article VI — no unciteable value
Time Every fact and placement is instant-evaluable Enables replay, determinism, interpolation
Actor A persistent operational entity with identity and history Article V — agents are not particles
Place A plot in the world with permanence guarantees Article III — spatial memory

Actor and Place are constitutional additions to the user's list, and I want to be explicit rather than smuggle them in. Article V requires identity, ownership, memory, movement and history "without redesign"; Article III requires plot permanence. Neither is expressible in terms of Subject/Fact/Condition alone: an Actor occupies Places and owns Subjects, which is a distinct relation. They are not new powers — they are already in the constitution — but they belong in the core vocabulary rather than being left implicit.

Plugin — everything else

Concept Why it is a plugin
Connector Observes one external system. Wholly replaceable.
Estate / Subject registry Which subjects exist is tenant data
Roster Which actors exist is tenant data; the Actor concept is not
Job / Runtime engine What an agent does is not constitutional
Renderer SVG, R3F, JSON, CLI — all equal consumers
Thresholds STALE_DAYS, budgets, ceilings: policy, declared
Severity policy Platform-owned but configurable; not core vocabulary

The sharpest line: Actor is constitutional; AGENTS is a plugin. Subject is constitutional; PRODUCTS is a plugin. The core defines the shape of identity; the tenant supplies the instances.


3. The smallest connector API

A connector emits Observations, never Facts.

This is the load-bearing decision of the whole design. A Fact carries identity (findingKey), severity, and normalisation — all of which must be platform-owned or connectors will drift. An Observation carries only what was seen. The platform mints the Fact.

/* ── everything a connector author must implement ────────────────── */

export interface Connector {
  /** Provenance only. Never appears in any identity. */
  readonly id: string;
  readonly manifest: ConnectorManifest;
  observe(window: Window): Promise<Observation[]>;
}

export type ConnectorManifest = {
  /** Every (condition, subjectKind) pair this connector may emit. */
  emits: ReadonlyArray<{ condition: ConditionId; subject: SubjectKind }>;
  /** Metrics it may report, and their unit. */
  measures: ReadonlyArray<{ metric: MetricId; unit: Unit }>;
  /** Credentials it requires, by name. Never values. */
  requires: readonly string[];
  /** Requests per window it promises not to exceed. */
  budget: { requests: number; perSeconds: number };
};

export type Observation =
  | { kind: "condition"; subject: SubjectRef; condition: ConditionId;
      magnitude?: number; observedAt: Instant; evidence: Evidence }
  | { kind: "measure"; subject: SubjectRef; metric: MetricId;
      value: number; observedAt: Instant; evidence: Evidence }
  | { kind: "exchange"; from: SubjectRef; to: SubjectRef;
      observedAt: Instant; evidence: Evidence }
  | { kind: "presence"; actor: ActorRef; at: SubjectRef;
      since: Instant; evidence: Evidence };

export type SubjectRef = { kind: SubjectKind; authority: string; path: string };
export type Evidence = { citation: string; observedAt: Instant };

That is the entire surface. Four observation shapes, one manifest, one method.

What a connector may not do

Prohibited Enforced by
Mint a findingKey Platform derives it from condition:subject-urn
Choose severity Platform derives from condition + magnitude
Emit an undeclared condition Manifest validation at registration
Invent a condition or subject kind Both are closed unions in the core
Emit a fact family directly The API has no such shape
Exceed its budget Platform meters and trips a breaker
Fail the world Isolation already implemented (F8, 63e37ed)

What the platform guarantees in return

Identity minting, deduplication, severity, normalisation, plot allocation, quantisation with hysteresis, failure isolation, and provenance retention. A connector author writes an adapter and gets constitutional behaviour for free — which is the only way "no connector may invent semantics" survives contact with a third-party author.


4. Three connectors, one World

Deliberately unlike each other: a version-control system, a work tracker, and a container orchestrator.

4.1 What each observes

GitHub Jira Kubernetes
Native vocabulary repo, commit, PR, issue project, issue, sprint, status namespace, deployment, pod, probe
Native "problem" no commits in 14 days issue flagged Blocked pod in CrashLoopBackOff
Native severity none P1–P4 none
Native identity owner/name ENG-1234 ns/deploy/pod

4.2 Each connector's mapping

GitHub      observe() → { kind: "condition",
                          subject: { kind: "repo", authority: "github",
                                     path: "JLV-000X/Xcortex" },
                          condition: "stalled", magnitude: 14,
                          evidence: { citation: "GET /repos/... pushed_at=2026-07-16" } }

Jira        observe() → { kind: "condition",
                          subject: { kind: "workitem", authority: "jira",
                                     path: "ENG-1234" },
                          condition: "blocked",
                          evidence: { citation: "issue ENG-1234 status=Blocked" } }

Kubernetes  observe() → { kind: "condition",
                          subject: { kind: "service", authority: "k8s",
                                     path: "prod/checkout-api" },
                          condition: "degraded", magnitude: 7,
                          evidence: { citation: "pod checkout-api-7f9 restarts=7" } }

4.3 What the platform produces — identical in shape

GitHub Jira Kubernetes
Fact family WorkItemFact WorkItemFact IncidentFact
findingKey stalled:repo:github/JLV-000X/Xcortex blocked:workitem:jira/ENG-1234 degraded:service:k8s/prod/checkout-api
Severity platform-derived platform-derived (Jira's P1 discarded) platform-derived
Evidence retained verbatim retained verbatim retained verbatim
Source github jira k8s

4.4 The proof

  1. The World's type signature is unchanged by which connectors exist. No if (source === "github") is reachable anywhere in the core.
  2. Identity is connector-independent. GitHub and GitLab observing the same repository produce the same findingKey and collapse to one finding (Article I.4). The observer appears only in evidence.
  3. Severity is uniform. Jira's P1 is discarded at the boundary — a connector that could set severity would let Jira's scale govern Datadog's incidents.
  4. Removing a connector removes facts, never capability. Delete the Kubernetes connector and the world is smaller, not broken (F8).
  5. Adding a connector requires no core change. The manifest is validated against the closed taxonomy; if it maps, it works; if it does not map, it is rejected at registration rather than drifting at runtime.

Where this breaks, honestly: a condition genuinely outside the taxonomy — say Datadog's "anomaly detected", which is a statistical claim rather than a state. The connector must drop it and report a gap. If such gaps become common, that is evidence for a taxonomy amendment under Article X, and the gap report is exactly the runtime evidence such a proposal requires.


5. XI-specific assumption audit

Assumption Location Classification
AgentId closed union imported by types.ts/facts.ts hq/roster.ts Platform bug
Feed union ("repos" | "content" | "finance" …) hq/roster.ts Platform bug
deriveStructure(repos: RepoStat[]) — geometry typed on a connector's shape world/structure.ts Platform bug
repo-* condition keys hq/findings.ts Platform bug (F6, approved)
substrate/telemetry imports hq/state world/substrate/ Platform bug
GitHub API client, git subprocesses hq/telemetry.ts Connector implementation
Stripe price env, revenue streams hq/estate.ts Connector implementation
PRODUCTS, REPOS, OWNER, SIBLING hq/estate.ts Connector implementation
AGENTS — nine named agents hq/roster.ts Connector implementation (tenant data)
engineeringItems, financeItems, contentItems hq/state.ts Connector implementation
repo-triage job runtime/jobs/ Connector implementation
STALE_DAYS = 7 hq/state.ts Connector implementation (policy)
TENANT constant tenant.ts Connector implementation (tenant data)
accent, glyph per agent hq/roster.ts Visualisation concern
domain, charter strings hq/roster.ts Visualisation concern
The seven standing questions world/answers.ts Visualisation concern
IconMark (XI logo) components/ Visualisation concern
hq_* table prefix db/schema.sql Technical debt
HQ_* env, xi_hq_* cookies auth, config Technical debt
MAX_CONCURRENT_RUNS module constant runtime/dispatch.ts Technical debt
stats.simulationLive naming world/types.ts Technical debt
Repo name Xcortex vs product Cognithamus repository Technical debt

Five platform bugs, twelve connector implementations, four visualisation concerns, five technical debts. Only the five platform bugs block the platform-first architecture. Everything classified as connector implementation is correct code in the wrong place — it moves, it does not get rewritten.

The one that cannot simply move

world/answers.ts asks "Which project is behind?" and "Which projects make money?" — questions presupposing projects and revenue. Classified as a visualisation concern, but it sits in world/. It must become either a tenant-supplied question set over core vocabulary, or a renderer concern. It cannot stay in the core as written.


6. Migration plan

Ordered so that each step is independently verifiable and none requires the next.

# Step Unblocks Difficulty
1 Extract core/ with a lint rule forbidding imports outside it. Move types, facts, slots, constitution. The mechanical test Medium
2 Actor identity becomes data (F2). AgentId → opaque branded string; roster → registry. Removes roster from types.ts/facts.ts. 1, 3, 5 High
3 Subject registry replaces estate. deriveStructure takes Subject[], not RepoStat[]. Any non-Git connector High
4 Condition taxonomy (F6, approved). findingKeycondition:subject-urn. 5, 6 Medium
5 Observation → Fact boundary. Platform mints identity and severity; substrates become connectors. 6 High
6 Extract the XI connector. hq/telemetry, hq/state, hq/estate, repo-triage move out of the core wholesale. Platform-first Medium
7 Renderers consume World only (F3, F4). Article I.3 Medium
8 Debt: prefixes, naming, MAX_CONCURRENT_RUNS as policy. Low

F3 and F4 are independent of all of this and can proceed immediately, as already agreed.

Enforcement — the mechanical test

Step 1 delivers a lint rule and a regression test (Article XI):

core/ may import only from core/ and node: builtins.

If that rule passes, the platform constraint holds by construction rather than by vigilance. If it fails, the build fails. That single rule is what makes this specification enforceable rather than aspirational — and it is the reason step 1 comes first despite delivering no user-visible change.


7. Residual risk

  1. Subject aliasing (taxonomy §8) — the same real thing observed by two connectors under different authorities. Needs a declared alias table; without one, Article I.4 holds only within an authority.
  2. Taxonomy completeness is unproven against real Jira/Datadog/K8s data. The gap-reporting requirement converts this from a risk into a measurement, but it will produce amendment proposals.
  3. Step 2 is the highest-risk change in the system. AgentId appears in every module and in findingKey identity. It must land before step 4 or identity churns twice.