Skip to Content
ArchitectureMycelium mesh nodesThe twin model and keys
Architecture · MeshMeshA set of connected Mycelium services that exchange updates and keep the NovusLens log.

The twin model, written under one prefix.

What it is for

The twinTwinNovusLens’s model of one organisation, built from records about its teams, services and how they rely on one another. It is only as current as those records. model is the vocabulary NovusLens uses to describe an organisation. A Cell represents a team, service, supplier or agent. An Edge records one consumer relying on one provider for a named capabilityCapabilityWhat a team can provide or an agent can do. NovusLens records the claim and the evidence that supports it.. Every claim carries where it came from and how strong that source is. The figures a reader sees, including the CPLCPLCritical path length. The total coordination weight of the longest chain of dependencies in the model. NovusLens reports it as index points, not elapsed time. (critical path length) and the C/A/RC/A/RThree separate scores for a team or service: Complexity, Agility and Recoverability. Each uses current evidence and the customer’s stated scoring rules. (Complexity, Agility and RecoverabilityRecoverabilityThe R in C/A/R: how well a team or service restores acceptable service after a failure. Recovery times and tested alternatives can supply evidence.) scores, are built from these records. Keys decide where each record sits in the mesh, so every writer and reader agrees on the same name for the same thing. For how these records reach the log, see Building and committing the twin.

Cells are capabilities; an edge is one cell relying on another. Provenance records where each claim came from, and C/A/R scores are held per layer. Fields shown are the main ones, not the full set.

How it works

Cells and edges

The model lives in conway_mesh/model.py. It reads or writes no files and sends no network requests, so it can be read and tested on its own.

  • Cell is a dataclassDataclassA Python class declared mainly to hold named fields. A mutable one can change after creation; a frozen one cannot. whose fields can change. Its cell_id is stable from birth. A rename, split or merge is recorded as a lineage event, not as a new identity. Identity and structure fields include label, parent_cell_id, kind, tier, provider, is_external, region and service_type. kind distinguishes domain (organisation) from infra (implementation). Duties and people use responsibility, accountable_owner, operational_owner and granted_authority.
  • is_external has three states. True marks a vendor boundary, False an internal unit, and None means not stated. A cellCellA box on the map that stands for a team, service, supplier or AI agent. It is a unit you can inspect, not necessarily a department or a person. whose status is not stated is named as a dependencyDependencyA link where one unit needs something from another. A dependency is not a reporting line: it shows where work relies on something else. and surfaced as a question. The model does not assume a value.
  • Edge is a dataclass whose fields cannot change after creation: consumer requires provider through capability. It carries a handoff, a layer, a provenance, an optional lead_time_days, and a contest_state that defaults to AGREED.

Layers

Layer has two members. asserted holds what people stated, usually in an interview. observed holds what connected sources measured. The model keeps both. A Cell’s C/A/R scores are held in a dictionary keyed by layer, and each Edge belongs to exactly one layer. The difference between the two layers is kept on purpose, because it is often the findingFindingA difference in the records that needs a decision. It includes its sources and can describe a slow handoff, disputed fact or missing owner..

Provenance rungs

Rung is the provenanceProvenanceThe record of where a value came from, who stated it and when. Every value keeps this record. ladder. It has three members, ordered by rank.

MemberRankMeaning in the code
ASSERTED0Conversational capture: what we were told
DOCUMENTED1A declared artifactArtifactA stored source file or evidence body that a record can cite. Keeping the exact content lets a reader check what supported the record.: what we were shown
MEASURED2Instrumented: what we measured

A Provenance attaches to one attribute of a Cell or to an Edge. It holds:

  • rung, the rungRungOne of three levels of support for a recorded value: told, shown or measured. The level appears beside the value. actually attained.
  • source, a required, non-blank string naming who stated the claim or where it came from. The check runs in __post_init__ through require_source, which raises ValueError for a blank value.
  • attainable, the ceiling for that attribute. It defaults to MEASURED. A ceiling below MEASURED describes the attribute, not a gap. The at_ceiling property is true when the attained rung’s rank is at or above the ceiling’s rank.
  • as_of_ms, the capture time. It may be None, which marks a claim with no date.
  • artifact_id and locator, optional links into the evidence the claim came from. Both are None for testimony.

The module docstring is explicit that rungs grade the class of source, never whether the claim is true.

C/A/R scores

CarScores holds complexity, agility and recoverability, each an integer from 0 to 100 or None. None means unmeasured. The docstring says an unmeasured score is shown as unmeasured, and must not be read as a health score taken from Complexity alone. The class does not check the 0 to 100 range itself.

Handoff classes and the CPL index

An Edge’s handoffHandoffWork or information passed from one team or service to another. Its measured or stated completion time determines the dependency class. field (handoff) is one of Rigid, Buffered, Loose or Unknown. derive_handoff(lead_time_days) classifies an observed lead timeLead timeThe elapsed time between starting a request or handoff and completing it.:

  • one day or less is Loose,
  • more than one day and up to four days is Buffered,
  • more than four days is Rigid.

The overhead weights are Rigid 3.0, Buffered 1.0 and Loose 0.5. They are labelled in the code as a structural index, not as measured hours. The version string CPL_WEIGHTS_VERSION records which table produced a figure. Raw lead times should appear beside each CPL figure. Long delays may reflect batching, an external wait or policy, as well as couplingCouplingA link where one team or service depends on another. Its Loose, Buffered or Rigid class describes the time needed for a handoff..

Unknown has no weight. Edge.overhead_hours raises KeyError for it, so an unknown coupling is left out of CPL rather than given a default.

Ownership, dispute and finding states

These enumerations are fixed sets of named values. Nothing in the four modules changes them.

  • ContestState records whether the accounts agree (agreed), differ with no recorded resolution (contested_pending), or have one account confirmed while the history remains (contested_confirmed). These are data states; this module does not decide between accounts.
  • OwnershipState: owned and unowned. An unowned requirement records a need with no named provider.
  • FindingStatus: open, acknowledged, accepted and resolved. The docstring says finding lifecycle is owned by people. Agents surface findings and humans resolve them.

Keys

The shared key builders live in conway_mesh/keys.py. Those functions call key() and assert_ours() from conway_mesh/__init__.py. key() joins its parts under conway/ and rejects an empty part or a part that contains a slash. assert_ours() rejects any key that starts with one of the thirty reserved prefixes, or that is not under conway/. Both failures raise ReservedPrefixError, which is a ValueError. The full guard, its reserved list and the reasons for it are on the mesh overview.

Hybrid logical clockHybrid logical clockA number attached to a log entry to order it against other entries. It combines clock time with a counter, so entries can be ordered even when clocks do not agree exactly. (HLC) stamps order log entries. Model Context ProtocolMCPModel Context Protocol: a standard way for an AI application to discover and call named tools supplied by another program. (MCP) is the interface used to register reasoning tools.

BuilderKey shapeWhat the docstring says it is for
commit_stream(org)conway/commit/<org>The structural-commit HLC stream, which is the projectionProjectionThe regular pass that brings the read model up to date with the canonical log. It runs every 30 seconds by default. feed and the audit trail
requirement(cell_id, capability, org=None)With org: conway/org/<org>/cell/<cell_id>/requires/<capability>. Without: conway/cell/<cell_id>/requires/<capability>The consumer side of a live dependency. The docstring says every product caller passes org. The bare form is kept for older readers
contest_stream(capability, run_scope)conway/contest/<capability>/<run_scope>A run-scoped stream that keeps every divergent account of a contested edge
authoritative(capability, run_scope)conway/authoritative/<capability>/<run_scope>A resolution written using consensusConsensusA process in which connected services agree on one result before accepting it as a shared decision., limited to one run and unchanged after its decision
telemetry_edge(org, capability)conway/telemetry/<org>/edge/<capability>Observed edge telemetryTelemetryMeasurements and activity records sent by a running system, such as ticket timings, network traffic or workflow results., held as plain key-valueKey-value storeStorage where a named key points to a value. Reading that key retrieves the value currently stored under it. that is refreshed. Liveness is read at query time
telemetry_edge_prefix(org)conway/telemetry/<org>/edge/The prefix over every edge feed for one org
telemetry_cell(org, cell_id)conway/telemetry/<org>/cell/<cell_id>Observed instrumentation for one cell
telemetry_cell_prefix(org)conway/telemetry/<org>/cell/The prefix over every cell feed for one org
evidence_pointer(org, artifact_id)conway/evidence/<org>/<artifact_id>A small pointer to an evidence artifact. The body is stored in PostgresPostgresThe database software that stores the tables read by the NovusLens screens, along with document bodies, access records and protected personnel records.
boundary_key(agent_id)conway/estate/boundary/<agent_id>An agent estateAgent estateThe collection of AI agents an organisation operates, together with the systems and providers they use.’s signed publication of agent facts and activity
presence_key(agent_id)conway/estate/presence/<agent_id>The periodic record that an estate agent is running
mcp_tool_registration(tool_name, node_id)tools/<tool_name>/<node_id>The MyceliumMyceliumThe application infrastructure underneath NovusLens. Its connected services exchange updates and keep the ordered structural log.’s own MCP registration convention. It is the only key built here outside conway/, and it does not pass through assert_ours()

Examples of callers under apps/ follow. The synthetic feeds and acquisition connectorsConnectorSoftware that reads records from a source system, such as an incident tool. A customer-written file connects those records to teams or services in NovusLens. write telemetry_edge and telemetry_cell. The telemetry reader scans telemetry_edge_prefix and telemetry_cell_prefix. The org chart, domain, domain taxonomyDomain taxonomyThe list of business areas, their owners and the teams or services that do the work in each area., regulation, agent, scorecard and finance ingesters write evidence_pointer. The reasoning operations module also writes it for transcripts, structured uploads and requested documents. The simulated estateSimulated estateA set of example agents supplied for testing and demonstrations. Their findings carry the label “(simulated estate)”. Real customer reports do not carry that label. writes boundary_key and presence_key. The correctorCorrectorThe NovusLens component that compares measurements with recorded statements and adds observations and differences to the log. writes authoritative. contest_stream is used only in a test, and conway_reasoning.mcp.register_tools calls mcp_tool_registration when registering reasoning tools.

Some connectors build additional telemetry prefixes through key(): flow_acquire.py for conway/telemetry/<org>/network-flow/, and car_axes.py for conway/telemetry/<org>/car-axis/.

The writer: TwinWriter

TwinWriter is in conway_mesh/twin.py. It is the one class that writes the twin to the mesh. The constructor takes host and port (default 127.0.0.1 and 8400) and an org (default "org"). It opens a MyceliumAgent, sets stream to commit_stream(org), and starts an empty list of handles and a last_hlc of 0.

MethodWhat it does
wait_healthy(timeout_secs=30.0)Polls the gatewayGatewayA service that accepts requests and passes them into another system. The Mycelium gateway gives NovusLens access to the mesh. health call until its status is ok. Raises TimeoutError if that does not happen in time
commit(value)Appends an already encoded commit to stream with agent.append(). Stores the returned HLC in last_hlc and returns it. _append is an alias for this method
write_cell(cell)Appends commits.cell_upsert(cell). It writes no key-value entry and no advertisement
write_edge(edge)Three writes, in this order: a capability advertisement, a requirement key, and an appended commit (see below)
open_finding(finding_id, kind, subject, detail)Appends commits.finding_open(...)
close()Drops each held advertisement, clears the list, and closes the agent. Errors from dropping and closing are ignored

write_edge does the following.

  1. It advertises the capability svc under the name <org>/<capability>. The advertisement’s attributes are provider_cell, consumer_cell, handoff, layer, provenance_rung, attainable_rung, and source. It adds lead_time_days when the edge has one. The handle is kept in handles. The org prefix keeps two tenants’ capabilities apart on the live mesh.
  2. It sets keys.requirement(consumer, capability, org=org) to b"1". This is the consumer side of the dependency.
  3. It appends commits.edge_upsert(edge) to the stream, and returns that HLC.

The three steps do not succeed or fail together as one atomic operationAtomic operationAn operation whose changes succeed together or fail together, rather than leaving only some of them applied.. The code does not roll back an earlier step if a later one fails.

Handles are held until close(). The module docstring says an advertisement that is not dropped lingers for about 60 seconds and affects the next run. TwinWriter has no automatic cleanup step, so the drop happens only when a caller uses it as a context managerContext managerA Python construct that runs setup when entering a block and cleanup when leaving it, including when an error occurs. or calls close() itself. The CLICLICommand-line interface: commands you type into a terminal to operate software. entry points in build.py and percell.py use the context manager.

The completeness sweep: percell.py

percell.py is a single-process command. It looks for gaps in each domain Cell and writes one finding per Cell that has any. Run it as ./lens exec -- python -m conway_mesh.percell --org <org>, with --host (default 127.0.0.1) and --port (default 8400). It connects to Postgres through conway_graph.db.

sweep(conn, tw, org) makes two queries. The first reads every Cell in the org with kind = 'domain' and is_external not true. That includes cells whose is_external is NULL. The second reads attribute provenance rows where rung <> attainable_rung and disputed is false. For each Cell it records a gap when both granted_authority and accountable_owner are empty, and a gap for each attribute below its ceiling. If there are gaps, it commits one completeness-gap finding with the id completeness-<org>-<cell_id>. Because the id is deterministic, the docstring says a re-run does not add duplicates. The function returns the findings it found, and main() prints one line for each cell and a total.

The docstring says the sweep is not yet pooled across nodes. Several instances would each need to advertise the same capability, and that is not built.

Interfaces

ModuleUsed byCalls out to
model.pytwin.py (for Cell and Edge) and the callers that build recordsNothing. It is pure Python
keys.pytwin.py, commits.py, the connectors and the testsconway_mesh/__init__.py (key, assert_ours)
twin.pyThe build and seeding commands, the connectors, and percell.pyThe mycelium SDKSDKSoftware development kit: a package of code that helps another program use a service. NovusLens uses mycelium-py to talk to Mycelium. (MyceliumAgent) against the gateway, and commits.py
percell.pyThe conway_mesh.percell commandTwinWriter, commits.py, and Postgres through conway_graph.db

twin.py imports the SDK at module load. It reads MYCELIUM_DIR, which defaults to /Volumes/Scratch/Mycelium, but that path is used only when CONWAY_SDK_FROM_CHECKOUT=1 is set. The module refuses that override with SystemExit when CONWAY_PROFILE is customer. Otherwise it prints a warning and imports the SDK from the checkout.

Invariants and failure behaviour

  • A claim without a non-blank source cannot be built. Provenance raises ValueError at construction, and it is frozen afterwards.
  • A key part that is empty or contains a slash raises ValueError when the key is built. A key outside conway/, or under a reserved prefix, raises ReservedPrefixError. Both happen before any write.
  • An Unknown handoff has no CPL weight. Asking for its overhead_hours raises KeyError.
  • write_cell, write_edge, commit and open_finding do not catch errors and do not retry. A failure from the SDK reaches the caller.
  • close() swallows errors from the drop and close calls, so a failure there does not hide the original error.
  • A restart does not restore handles. Advertisements from an earlier run expire on their own, and a new run creates new ones. The durable record is the commit stream, and the requirement keys are overwritten by the next write.
  • percell only opens findings. It does not resolve or change them, because people decide their status.
Last updated