Context graph architecture
Overview
Section titled “Overview”Status: reflects the canonical Context Runtime boundary, last reviewed 2026-08-24.
This is the implementation map for the Context Graph: the layered code, the
composition roots that wire it, the daemon that hosts it, the swappable
GraphBackend, and the shared storage engine room underneath. It orients you to
the moving parts and points at the docs that own each flow in full — the read
path lives in querying.md, the write path in
writing.md, the vocabulary in ontology.md, and
ingestion/nudge in ingestion-nudge.md.
The single most important framing fact: the potpie graph … workbench is
shipped today as the V1.5 surface. The string v2 survives only as the
workbench envelope version (GRAPH_WORKBENCH_CONTRACT_VERSION="v2"); the data
plane is GRAPH_CONTRACT_VERSION="v1.5" / ONTOLOGY_VERSION="2026-06-graph".
Both the legacy resolve/search/record wrappers and the full workbench ship
now. The canonical write door is graph propose → graph commit --verify;
graph mutate is a legacy wrapper over it (see Writing the graph, high
level).
Hexagonal layers
Section titled “Hexagonal layers”The engine is ports-and-adapters. Pure model in the middle, I/O at the edges, composition roots at the top.
flowchart TB cg_inbound["inbound adapters<br/>typed clients · http ingestion · daemon protocol · webhooks"] cg_app["application<br/>services · readers · use_cases"] cg_domain["domain<br/>ontology · contract · ports · DTOs · ranking · coherence · identity"] cg_outbound["outbound adapters<br/>graph backends + engine room · connectors · ledger clients · postgres event store · local embedder"] cg_boot["composition roots<br/>potpie/runtime/composition.py · ingestion_server.py"] cg_host["runtime boundary<br/>ContextEngine · Resource Manager · EngineClient · daemon"]
cg_boot --> cg_host cg_boot --> cg_inbound cg_inbound --> cg_app cg_app --> cg_domain cg_app --> cg_outbound cg_outbound --> cg_domain| Layer | Path | Responsibility |
|---|---|---|
| domain/ | domain/ |
Pure model and contracts: the three ontology catalogs, contract constants, ports (Protocols), DTOs, ranking, coherence invariants, identity. No I/O. Import-time coherence guards fail startup fast if vocabularies drift. |
| application/ | application/ |
Services and use cases that orchestrate domain over ports: services/ (graph_service, graph_workbench, agent_context, read_orchestrator, envelope_builder, nudge_service, skill_manager, semantic_mutation_validator/lowering, reconciliation_validation, ingestion_submission_service), readers/ (the 9 readers), use_cases/ (the ingestion pipeline). |
| adapters/ | adapters/inbound, adapters/outbound |
Concrete I/O. Inbound: HTTP ingestion server and webhooks. Outbound: graph backends + the shared engine room, connectors, ledger clients, the Postgres event store, intelligence/local_embedder, the session injection ledger. Root CLI, daemon, and capability adapters live under potpie/. |
| bootstrap/ | bootstrap/ |
Context Engine standalone HTTP composition. The local product composition lives under potpie/runtime/. |
| runtime + daemon | potpie/runtime/; potpie/daemon/ |
Context-bound clients, authorization/resource management, typed daemon protocol, and root capability lifecycle. |
The local spine is CLI → EngineClient → ContextResourceManager → ContextEngine
for engine-owned operations. Root capability commands use finite root-owned
services. Agents reach both through the CLI.
Two composition roots / two HTTP roots
Section titled “Two composition roots / two HTTP roots”There are two separate composition roots, and conflating them is the most common architecture error.
flowchart TB subgraph cg_spine["Local agent spine — potpie/runtime/composition.py"] direction TB cg_cli["potpie CLI · LocalEngineClient / DaemonEngineClient"] cg_shell["ContextResourceManager<br/>authorized context lease"] cg_services["ContextEngine + root capability services"] cg_lite[("default backend: falkordb_lite<br/>default host mode: daemon")] cg_cli --> cg_shell --> cg_services --> cg_lite end
subgraph cg_ingest["Ingestion HTTP server — bootstrap/ingestion_server.py"] direction TB cg_fastapi["FastAPI app (adapters/inbound/http/)"] cg_pipeline["async reconciliation pipeline · connectors · optional reconciliation agent"] cg_neo[("default backend: neo4j")] cg_fastapi --> cg_pipeline --> cg_neo end- The local agent spine —
potpie/runtime/composition.py build_local_runtime()composes separate root capability and engine service groups. Engine-owned CLI operations useLocalEngineClientin-process orDaemonEngineClientover the versioned authenticated daemon protocol. The daemon listens privately over UDS with loopback TCP fallback. Root capability commands use finite root-owned services. Default backend:falkordb_lite. Default host mode:daemon. - The ingestion HTTP server —
bootstrap/ingestion_server.py IngestionServerContainercomposes the FastAPI app underadapters/inbound/http/(the async reconciliation pipeline, connectors, the optional reconciliation agent). That public FastAPI app is the second HTTP root. Default backend here:neo4j(viasettings.graph_db_backend()). It is a distinct Context Engine delivery root.
Both roots run the same domain/application code over a swappable GraphBackend;
they differ only in wiring and default storage.
Roadmap (not yet wired): a managed Potpie backend that hosts the same service modules on hosted stores.
potpie use --managed,pot list --managed, and the entirecloudcommand group raiseCapabilityNotImplementedtoday. Local and managed are designed to share one graph model; runtime composition, authorization, and storage adapters provide the deployment-specific seams.
The daemon model
Section titled “The daemon model”potpie/daemon/lifecycle.py Daemon is the local background-process lifecycle
service; potpie/runtime/controller.py owns direct subprocess observation and
the typed protocol provides IPC. The daemon is explicitly not the business
layer. Two modes:
- in_process —
LocalEngineClientinvokes the same typed operation handlers and Resource Manager in the CLI process. - daemon — a separate
python -m potpie.daemonprocess serves the typed, authenticated protocol over UDS or loopback TCP. The CLI usesDaemonEngineClientafter a successful handshake.
The handshake compares the recursive operation and wire-schema catalog and returns a unique, opaque, boot-scoped compatibility ticket. The daemon derives the ticket from its per-boot secret and requires it on every later non-handshake request. The ticket proves that one client negotiated this protocol catalog; it is not caller identity or authorization. Bearer authentication is checked separately and remains the current transitional daemon-auth mechanism, not the final identity model for local IPC, Potpie login, hosted clients, integrations, or browser access.
Context-free typed metadata such as graph describe still crosses the
authenticated daemon boundary but bypasses context selection and engine
leasing. Destructive graph reset takes the opposite path: the CLI binds
confirmation to one resolved context and the selected runtime’s Context Engine
executes the reset against its own backend. Pot metadata services never reset a
graph or compose a second backend.
Mode is chosen by CONTEXT_ENGINE_HOST_MODE (daemon | in_process);
default_host_mode() returns daemon, so the shipped CLI default is a
background daemon. get_engine_client() (commands/_common.py) selects the
daemon client by default and the local client when
CONTEXT_ENGINE_HOST_MODE=in_process.
Liveness ≠ readiness. The daemon can be live while a backend or semantic
index is not yet ready; the two are reported separately. potpie doctor
composes backend.capabilities() + backend.mutation.readiness() +
daemon.status() + ledger.status() so a half-ready system is debuggable.
potpie ui (the read-only graph explorer) is served by this daemon — it ensures
the daemon, discovers base_url, and opens <base>/ui.
CLI surfaces onto one data plane
Section titled “CLI surfaces onto one data plane”Reads and writes both reach DefaultGraphService
(application/services/graph_service.py) over the backend, but the CLI exposes
two shapes:
- Compatibility commands —
potpie resolve/search/record/status, bound byAgentContextService. - Graph Surface Lite (V1.5) — the richer
potpie graph …workbench (catalog/read/search-entities/neighborhood/propose/commit/bulk/history/inbox/quality…).
Both are shipped. The read shapes and the 9-reader trunk are owned by
querying.md; the write stack is owned by
writing.md. No read returns a server-synthesized answer —
reads return ranked evidence (AgentEnvelope) and the agent reasons over it.
The GraphBackend port
Section titled “The GraphBackend port”domain/ports/graph/backend.py defines GraphBackend as a @runtime_checkable
Protocol that bundles six capability ports in two tiers, plus three bundle
members.
flowchart TB cg_be["GraphBackend"] subgraph cg_canon["canonical source of truth"] cg_mut["mutation (GraphMutationPort)"] cg_cq["claim_query (ClaimQueryPort)"] end subgraph cg_proj["rebuildable projections"] cg_sem["semantic (SemanticSearchPort)"] cg_insp["inspection (GraphInspectionPort)"] cg_an["analytics (GraphAnalyticsPort)"] cg_snap["snapshot (GraphSnapshotPort)"] end cg_be --> cg_mut cg_be --> cg_cq cg_be --> cg_sem cg_be --> cg_insp cg_be --> cg_an cg_be --> cg_snap cg_be -. "profile · capabilities() · provision(plan)" .-> cg_meta["bundle members"]- Canonical:
mutation(apply/apply_async,invalidate,reset_pot,readiness) andclaim_query(find_claims,entity_labels,entity_properties). NoteClaimQueryPortlives atdomain/ports/claim_query.py, not undergraph/. - Projections (rebuildable):
semantic(vector NN over claim embeddings, stampssemantic_similarity),inspection(neighborhood/path/labels/slice→ backsgraph neighborhood/inspect+ the explorer UI),analytics(counts/freshness/quality/repair),snapshot(export/import_). - Bundle members:
profile(string),capabilities() -> BackendCapabilities(a frozen dataclass declaring which of the six are really implemented vs fail-closed — read bybackend status/doctor), andprovision(plan: SetupPlan) -> StepResult(the setup seam where a backend stands up its own store idempotently).
Two workbench stores also live under domain/ports/graph/ but are not part
of the six-cap bundle: inbox_store.py and plan_store.py.
The registry (adapters/outbound/graph/backends/__init__.py) is
build_backend(profile, *, settings, embedder). KNOWN_PROFILES = (in_memory, embedded, neo4j, falkordb, falkordb_lite, postgres, chroma, hosted). It
normalizes names (falkordblite→falkordb_lite, dashes→underscores) and, when
embedder is None for a real profile, default-builds the bundled local embedder
(disable via CONTEXT_ENGINE_EMBEDDER=none).
Backend coverage — real capabilities per profile
Section titled “Backend coverage — real capabilities per profile”This table is the corrected source of truth (the older doc overstated coverage).
A “real cap” is one whose port actually executes; everything else is fail-closed
and raises CapabilityNotImplemented.
| Profile | Backend class | Real caps | Notes |
|---|---|---|---|
in_memory |
InMemoryGraphBackend |
6/6 | Conformance/reference; genuinely real (validates, MERGEs by identity, bitemporal invalidation, embeds on write); dump_store/load_store. |
embedded |
EmbeddedGraphBackend |
6/6 (delegated) | OSS JSON-persisted fallback wrapping in_memory; persists to <home>/graph.json after each mutation (atomic tmp-replace). |
falkordb_lite |
FalkorDBLiteGraphBackend |
5/6 (no snapshot) | The OSS/CLI default. Embedded FalkorDBLite via redislite over a local file — no server, no Docker. |
falkordb |
FalkorDBGraphBackend |
5/6 (no snapshot) | Full FalkorDB server over a redis URL; needs the optional falkordb client. |
neo4j |
Neo4jGraphBackend |
4/6 (no inspection, no snapshot) | “Shape-first production target”; native relationship vector index. |
postgres / chroma / hosted |
StubGraphBackend |
0/6 | Fail-closed seam: every port and provision raise CapabilityNotImplemented("graph.<profile>.<cap>.<method>"). Documented but unbuilt; backend list still shows them. |
Cross-profile gaps to internalize:
- Claim-key
mutation.invalidateraises on both Neo4j and FalkorDB (that invalidation path is unbuilt there). snapshot(export/import) is real only onin_memory/embedded.inspectionis real onin_memory/embedded/falkordbbut not Neo4j.- Net effect: FalkorDB is more complete than Neo4j (5 vs 4 ports), and the
OSS default
falkordb_liteis a first-class backend, not a stub.
Roadmap (not yet wired):
snapshoton falkordb/neo4j;inspectionon neo4j; thepostgres/chroma/hostedbackends (allStubGraphBackend); claim-keymutation.invalidateon neo4j/falkordb.
The shared engine room
Section titled “The shared engine room”The real storage logic is shared across backends, not duplicated per profile
(adapters/outbound/graph/). This is the implementation underneath the canonical
ports.
| Module | Role |
|---|---|
cypher.py |
The Position-B canonical write engine shared by BOTH the Neo4j and FalkorDB writers. Claims are stored as (:Entity {group_id, entity_key})-[:RELATES_TO {group_id, name, subject_key, object_key, source_ref, valid_at, invalid_at, created_at, …}]->(:Entity). The MERGE key includes source_ref so corroborating writes from different sources don’t collide; bitemporal stamps (valid_at/invalid_at = event time, created_at = system time); _supersede_singleton_predecessors stamps invalid_at on prior disagreeing live singleton claims. A Phase-0 spike proved this Cypher runs unchanged on FalkorDB. |
canonical_claim_query.py |
Shared FIND_CLAIMS_CYPHER, ENTITY_LABELS_CYPHER, row_from_record, the lexical embedding_score token-overlap scorer, stamp_similarity/stamp_scored_rows, CONTRACT_EDGE_KEYS. Both readers reuse these; the FalkorDB reader only swaps row normalization. |
claim_query_analytics.py / claim_query_semantic.py |
Give any claim-backed profile a real analytics projection and a fact_query-routed semantic search computed on read. |
apply_plan.py |
apply_mutation_batch (alias apply_reconciliation_plan) — the single async write door (high-level below; full flow in writing.md). |
writer_port.py |
GraphWriterPort — the narrow async mutation surface (ensure_indexes, 4 verbs, reset_pot), implemented by Neo4jGraphWriter and FalkorDBGraphWriter. Storage-internal; services depend on GraphBackend/GraphMutationPort, not this. |
in_memory_reader.py |
InMemoryClaimQueryStore — full filter semantics; match_mode = vector (embedder cosine over fact_embedding) or lexical (token overlap). |
context_graph_service.py |
Legacy ContextGraphPort DTO shim; apply_plan/apply_plan_async are disabled (raise CapabilityNotImplemented) — writes must go through GraphService.mutate. |
plan_stores/local_json.py, inbox_stores/local_json.py |
JSON workbench stores (plans, inbox) under the Potpie home. |
FalkorDBGraphProvider memoizes one graph handle so the Falkor reader and
writer share a single embedded instance.
The write door (high level)
Section titled “The write door (high level)”The canonical write door is two-phase: graph propose → graph commit --verify (Spine A, application/services/graph_workbench.py). propose
validates, lowers accepted ops, and persists a plan record with no graph write;
commit re-checks the conflict guard, enforces approval for medium/high risk,
calls the single backend write door, and --verify reads the committed claims
back.
flowchart LR cg_propose["potpie graph propose<br/>(validate · lower · persist plan)"] cg_commit["potpie graph commit --verify<br/>(re-check · apply · read-back)"] cg_apply["apply_mutation_batch<br/>(single backend write door)"] cg_writer["GraphWriterPort<br/>upsert_entities → upsert_edges → delete_edges → invalidate"] cg_propose --> cg_commit --> cg_apply --> cg_writerTwo corrections to the previous version of this doc:
graph mutateis a legacy wrapper, not the canonical door. It internally callsproposethencommitand emits a legacy warning (commands/graph.py). The direct-applyDefaultGraphService.mutate(Spine B) is now reached only throughrecord/context_record, never viagraph mutate.- There is a server-created
plan_idand a realpropose|commitcontract — the prior claim that none existed was wrong.
Optimistic concurrency on this path is coarse: the only version tracked is
{"_global": <total pot claim count>} — there are no per-subgraph versions. A
conflict fires only if the pot’s total claim count changed between propose and
commit. GraphMutationDiff.to_dict() emits entity_upserts, edge_upserts,
edge_deletes, invalidations, claims_asserted, claims_retracted,
claim_keys. The full tiered stack — semantic DSL (flat ops), validation/risk,
lowering, the 4-verb idempotent apply, inbox, and quality — is owned by
writing.md.
Persistence & per-pot scoping
Section titled “Persistence & per-pot scoping”Every fact is scoped by pot_id, which IS the Cypher group_id on every
:Entity node and :RELATES_TO edge. reset_pot is MATCH (n {group_id:$gid}) DETACH DELETE n. There is no cross-pot federation (an explicit anti-goal).
| Profile | Where it persists | Pot isolation |
|---|---|---|
in_memory |
Process-local, ephemeral | by group_id |
embedded |
One multi-pot <home>/graph.json, atomic rewrite per mutation |
by group_id |
falkordb_lite |
redislite file at falkordb_lite_path() (default .potpie/context_graph/falkordb.db), single keyspace context_graph |
by group_id |
neo4j |
External server, one DB | by group_id |
Relevant settings (domain/ports/settings.py): graph_db_backend()='neo4j',
falkordb_mode()='lite', falkordb_graph_name()='context_graph',
falkordb_lite_path()='.potpie/context_graph/falkordb.db', plus the Neo4j URI/
credentials.
Backend selection precedence
Section titled “Backend selection precedence”- Host / CLI:
CONTEXT_ENGINE_BACKEND(preferred) > legacyGRAPH_DB_BACKEND>default_backend_profile()=falkordb_lite. - Ingestion server:
settings.graph_db_backend()defaultneo4j, but it also routes throughbuild_backend, so FalkorDB works there too.
There is no NotImplementedError gate on FalkorDB anywhere — the old
“selecting falkordb raises NotImplementedError, use neo4j” guidance is obsolete.
backend use <profile> is advisory only (it suggests
CONTEXT_ENGINE_BACKEND, it does not persist a selection).
Setup & provisioning (orientation)
Section titled “Setup & provisioning (orientation)”potpie setup is the idempotent first-run flow. The CLI builds a SetupPlan
(config, storage, daemon, default default pot, source registration, skills),
ensures the daemon first when in daemon mode (host.daemon.ensure()), and each
backend self-provisions its store through provision(plan: SetupPlan) -> StepResult. Re-running is safe; each step is ensure-shaped and reports done | skipped | not_implemented | failed.
Two corrections worth stating here:
- The default backend a fresh
setupprovisions isfalkordb_lite(a localredislitefile), notembeddedorneo4j.postgres/chroma/hostedcannot be provisioned —StubGraphBackend.provision()raises (the prior “postgres creates the DB, enables pgvector, runs DDL” claim was aspirational). --scanis opt-in (default off):setupregisters the repo as a source but does not scan or ingest the working tree.
The full setup flag set and the canonical local journey live in
cli-flow.md.
Roadmap (not yet wired): a separate managed-backend login lifecycle (
potpie loginto a configuredcloud.backend_url, then managed pot selection). The plumbing exists, but managed routing raisesCapabilityNotImplemented.
Two ledgers: the live event store vs the external seam
Section titled “Two ledgers: the live event store vs the external seam”The older doc conflated two different things called a “ledger.” Keep them distinct.
- The internal event store (the real, live “ledger”). A Postgres-backed
context_eventstable plus reconciliation-run/work-event/batch tables, where every inbound episode/event/record actually lands and gets a lifecycle (adapters/outbound/postgres/…). Its consumer-state vocabulary isqueued/processing/done/error— not the six-statepending/processing/applied/failed_retryable/failed_terminal/timed_outlist the old doc described. This is what ingestion really uses. - The external Event Ledger seam.
domain/ports/ledger/client.py EventLedgerClientPort(cursor-advancingfetch, read-onlyquery,sources,health) +LedgerCursorStorePort. The cursor concept belongs only to this external seam, never to the internal Postgres store.
flowchart LR cg_int["internal Postgres event store<br/>context_events · queued/processing/done/error"] cg_ext["external Event Ledger seam<br/>EventLedgerClientPort + cursor (stubs)"] cg_graph["Graph Service → GraphBackend"] cg_int --> cg_graph cg_ext -. "pull (roadmap)" .-> cg_graphRoadmap (not yet wired): the external Event Ledger clients (
adapters/outbound/ledger/managed_client.py,self_hosted_client.py) are TODO stubs —fetchreturns an empty page,queryraises,healthreports unavailable. Only the in-processFixtureEventLedgerClient(tests) pages for real, andLocalLedgerCursorStorepersists cursors. Sopotpie ledger pull/query/statusexists but is non-functional against any real provider today.
The internal store, ingestion entry points, connectors (github/notion only — scanners are deleted), windowed batching, and the nudge trigger model are owned by ingestion-nudge.md.
Environment switches
Section titled “Environment switches”| Variable | Effect | Default |
|---|---|---|
CONTEXT_ENGINE_BACKEND / GRAPH_DB_BACKEND |
Backend profile (first wins) | falkordb_lite (host) / neo4j (ingestion server) |
CONTEXT_ENGINE_HOST_MODE |
daemon | in_process |
daemon |
CONTEXT_ENGINE_EMBEDDER |
none disables the bundled local embedder |
bundled local embedder |
CONTEXT_ENGINE_ONTOLOGY_SOFT_FAIL |
Downgrade instead of reject on ontology drift at apply time | off |
CONTEXT_ENGINE_AGENT_PLANNER_ENABLED |
Enable service-side LLM reconciliation | off |
CONTEXT_ENGINE_MAX_CHUNK_EVENTS |
Reconciliation chunk size | 20 |
CONTEXT_ENGINE_RECONCILIATION_ENABLED / _INFER_LABELS / _CONFLICT_DETECT / _AUTO_SUPERSEDE |
Reconciliation feature flags | on (planner off) |
CONTEXT_ENGINE_ALLOW_UNSIGNED_WEBHOOKS / GITHUB_WEBHOOK_SECRET |
GitHub webhook HMAC handling (fail-closed by default) | signed required |
POTPIE_HOOK_DEBUG / POTPIE_HOOK_TIMEOUT / POTPIE_BIN / POTPIE_POT |
Claude Code nudge-hook adapter env | — |
Roadmap (not yet wired):
CONTEXT_ENGINE_AGENT_PLANNER_ENABLEDturns on service-side LLM reconciliation, which is parked/non-canonical — canonical writes come from harness-authored semantic mutations and the deterministiccontext_recordpath.
Code map
Section titled “Code map”The active package is potpie/context-engine/. Fictional read-path modules from
the old doc (“Ontology Catalog / Identity Resolver / Read View Router”) have been
removed — those are methods on DefaultGraphService plus the declarative
GRAPH_VIEWS table and the ReadOrchestrator, not standalone components.
| Area | Path |
|---|---|
| Composition roots | potpie/runtime/composition.py (build_local_runtime); bootstrap/ingestion_server.py + standalone_container.py (ingestion HTTP root) |
| Public/local runtime boundary | context_engine.py; potpie/runtime/{clients,resource_manager,local_engine}.py |
| Daemon | lifecycle under potpie/daemon/; controller, protocol, server, transport, and coordination under potpie/runtime/ |
| Service interfaces (ports) | core/ports/graph_service.py; potpie/pots/contracts.py; potpie/skills/contracts.py |
| Graph capability ports | domain/ports/graph/{backend,mutation,semantic,inspection,analytics,snapshot}.py + domain/ports/claim_query.py |
| Read trunk | application/services/read_orchestrator.py, envelope_builder.py, application/readers/, domain/agent_envelope.py, domain/ranking.py, domain/agent_context_port.py |
| Graph Service (catalog/read/search-entities/mutate/status) | application/services/graph_service.py (methods, not separate modules) |
| Named views | domain/graph_views.py (GRAPH_VIEWS) |
| Write stack | application/services/{semantic_mutation_validator,semantic_mutation_lowering,graph_workbench,record_to_semantic,reconciliation_validation}.py; domain/{semantic_mutations,graph_plans,graph_inbox}.py |
| GraphBackend adapters | adapters/outbound/graph/backends/{in_memory,embedded,neo4j,falkordb,stub}_backend.py + __init__.py (build_backend) |
| Shared engine room | adapters/outbound/graph/{cypher,canonical_claim_query,claim_query_analytics,claim_query_semantic,apply_plan,writer_port,in_memory_reader,context_graph_service}.py + plan_stores/, inbox_stores/ |
| Internal event store | adapters/outbound/postgres/{reconciliation_ledger,ingestion_event_store,batch_repository,ledger}.py |
| External Event Ledger seam (stubs) | domain/ports/ledger/{client,cursor}.py; adapters/outbound/ledger/{managed_client,self_hosted_client,cursor_store}.py |
| CLI (typed-client and root-service routed) | potpie/cli/main.py + potpie/cli/commands/ |
| Ingestion HTTP server | adapters/inbound/http/ |
| Root capabilities | potpie/{config,pots,setup,skills}/; local identity under potpie/auth/{ports,adapters}/; cross-capability agent facade at potpie/agent_context.py |
Stable interfaces
Section titled “Stable interfaces”| Interface | File | Role |
|---|---|---|
GraphService |
core/ports/graph_service.py |
Graph reads, identity resolution, semantic mutation validate/apply, status, and projections. |
PotManagementService |
potpie/pots/contracts.py |
Pots, active-pot selection, sources, and readiness rollup. |
SkillManager + AgentTargetPort |
potpie/skills/contracts.py |
Catalog + per-harness install drift; advisory SkillNudge. |
GraphBackend |
domain/ports/graph/backend.py |
Six-capability storage bundle + profile + capabilities() + provision(). |
GraphMutationPort / ClaimQueryPort |
domain/ports/graph/mutation.py, domain/ports/claim_query.py |
Canonical source of truth (write / read). |
SemanticSearchPort / GraphInspectionPort / GraphAnalyticsPort / GraphSnapshotPort |
domain/ports/graph/{semantic,inspection,analytics,snapshot}.py |
Rebuildable projections. |
EventLedgerClientPort / LedgerCursorStorePort |
domain/ports/ledger/{client,cursor}.py |
External Event Ledger pull + per-(pot,source) cursor (clients are stubs today). |
ContextEngine / EngineClient / Daemon |
context_engine.py; potpie/runtime/clients.py; potpie/daemon/lifecycle.py |
Immutable context-bound engine, equivalent local/daemon clients, and product-owned lifecycle. |
An unbuilt capability raises domain.errors.CapabilityNotImplemented with a
dotted graph.<profile>.<cap>.<method> slot, which inbound adapters render as
the structured not-implemented contract — never a bare NotImplementedError.
Extension points
Section titled “Extension points”Add behavior at the narrowest service boundary that owns it. CLI adapts user
intent, the daemon hosts services, Pot Management owns pot/source lifecycle,
Graph Service owns graph operations, and GraphBackend owns physical storage.
| Change | Put it here | Rule |
|---|---|---|
| Read view | domain/graph_views.py (GRAPH_VIEWS) + a reader in application/readers/ |
Route through ReadOrchestrator and ClaimQueryPort; never query stores directly. READER_BACKED_INCLUDES must mirror the reader registry (coherence-enforced). |
| Semantic mutation op | semantic DSL + validator + lowering | Add deterministic validation and lowering before exposing it to agents. |
| Entity / predicate | domain/ontology.py |
One row in ENTITY_TYPES / EDGE_TYPES; add identity, endpoint rules, freshness, source-of-truth. Coherence guards fail import if it drifts. See ontology.md. |
| Graph backend | domain/ports/graph/ + a backend adapter |
Implement the canonical ports, preserve group_id pot isolation, pass conformance; declare real caps in capabilities(). |
| Skill | potpie/skills/ catalog + AgentTargetPort adapter |
Keep skill content harness-neutral; it is not graph data. See skills.md. |
| Pot behavior | potpie/pots/ |
Preserve the first-setup active default pot. |
| Setup / lifecycle step | the component’s provision/bespoke method + potpie/setup/orchestrator.py sequence |
Return a StepResult; raise CapabilityNotImplemented until built. |
Do not bypass the read trunk, query physical stores from CLI/readers, make a projection a second source of truth, put service logic in the daemon shell, or duplicate ontology enums in docs/CLI.
Invariants
Section titled “Invariants”- OSS graph use works with no cloud auth and no mandatory Docker/Neo4j/Postgres
(the default
falkordb_liteis embedded). - The CLI is the user and agent surface.
- The canonical claim store is the only source of truth; semantic/inspection/ analytics/snapshot are rebuildable projections.
- Every fact is scoped by
pot_id=group_id; no cross-pot federation. - The canonical write door is
propose→commit --verify;graph mutateis a legacy wrapper; reads never synthesize answers. - Pot Management owns pot/source lifecycle; Graph Service owns graph operations; the daemon hosts services but holds no business logic.
- Unbuilt capabilities fail-closed with a dotted
CapabilityNotImplementedslot, surfaced as the structured not-implemented contract.
See also
Section titled “See also”- vision.md — what the Context Graph is and why.
- ontology.md — entities, predicates, views, contract constants.
- querying.md — the read trunk, readers, ranking, envelopes.
- writing.md — the full write stack (DSL → validate → lower → apply), propose/commit, inbox, quality.
- ingestion-nudge.md — the internal event store, connectors, batching, and the nudge trigger model.
- cli-flow.md — the full command surface and the canonical journey.
- observability.md — logs/traces/metrics/readiness (its
graph.propose/graph.commitspan names match the corrected write door here).