Skip to Content
Architecture · Acquisition

Signed source evidence, checked before storage.

A source signs its own records. The operator enrols it and activates a policy, and intake stores a batch only if every check passes.

What it is for

An agent evidence source is a system that already sees what an agentAgentA program that takes actions for an organisation. NovusLens records what it is allowed to do and what its activity reports show. did: a gatewayGatewayA service that accepts requests and passes them into another system. The Mycelium gateway gives NovusLens access to the mesh., a runtimeRuntimeThe software that runs a model or agent. A model runtime answers model requests; an agent runtime carries out the customer's agent actions., an approval service or an exporter the customer runs. It signs its own records and sends them to 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.. These records support the operator’s view of attempted operations, refusals and the agent’s recorded remitRemitA written list of an agent’s allowed actions, resources and destinations for information. The list states whether it is complete.. They must be signed by an enrolled source.

NovusLens reads that evidence and says what it does and does not establish. It does not grant runtime authority, approve interventions or change the agents. The customer’s exporter stays in the customer’s environment.

How it works

The record envelope

Every record has the same envelopeEnvelopeThe surrounding fields that identify a message, its sender, its type and its time, alongside the message's data.: version (1), a namespacedNamespaceA naming area that keeps one set of records apart from another. NovusLens uses the conway/ prefix so its keys do not occupy Mycelium's reserved names. id, type, source, subject, an event time at, the server’s received_at, and a typed payload. Times are non-negative milliseconds since 1 January 1970, in Coordinated Universal TimeUTCCoordinated Universal Time: a shared time reference that does not change with daylight-saving time. (UTC). The organisation comes from the signed batch, not the record.

Intake overwrites any receive time a source sends with the server’s clock. An event time more than 5 seconds ahead of that clock is refused as outside the clock allowance. PayloadsPayloadThe data carried inside a message, apart from the surrounding fields that identify and date it. are closed: a field the type does not define is refused, so an unknown grant condition cannot be silently dropped.

The bounds, and where each is checked:

BoundValueChecked in
One record64 KiBKiB and MiBBinary size units: one KiB is 1,024 bytes; one MiB is 1,048,576 bytes. Evidence limits use these units. of canonical JSONCanonical JSONA fixed way to write data: sort the field names and omit spaces between fields. Do not use infinity or invalid numbers. Sender and receiver can then sign and check identical content.validate in common_py/agent_evidence.py
One batch1 to 1000 records, at most 2 MiBstage in conway_reasoning/agent_evidence.py
Text (IDs, labels)Non-empty, at most 512 characterstext in common_py/agent_evidence.py
QuantitiesWhole numbers or plain decimal text, at most 1e30. Send a decimal as text, for example "1.25". Other number forms and true/false values are refused.number in common_py/agent_evidence.py

Canonical content is compact, sorted-key UTF-8UTF-8A standard way to represent written characters as bytes, including characters outside the English alphabet. JSONJSONJavaScript Object Notation: a text format for data, using named fields, lists and values. Configuration files and service messages use it., and non-finite numbers are refused. The contract says that reusing an ID with changed content fails, and that a duplicate batch keeps its first receive time.

Record types

The contract v1 record types, with the payload fields that matter most:

TypeWhat it saysNotes
authority_grantAn external grant: issuer, grant version, operation, resource, unit, an upper bound and a validity interval that includes its start and excludes its end.Issued outside NovusLens. The grant issuer must be enrolled.
operationAn operation attempted, completed, denied or failed, with an optional value and unit.A missing metric produces insufficient evidence, not a zero.
intervention_requestA requested action on a resource, with its requester and quantity.
intervention_authorisationAn approval or refusal of a request, citing a grant.The authoriser must be enrolled.
intervention_executionAn execution of an authorised request, with its state.A completion receiptReceiptA recorded acknowledgement of a request or action, with its identity, time and result. An evidence intake receipt confirms storage. It does not establish acceptance or prove that an agent acted. does not prove the intended effect.
outcome_observationAn observed value for a criterion over an explicit window, with an explicit completeness claim.Incomplete contradictory observations prevent reassurance.
correction, withdrawalAmend or withdraw an earlier record from the same source and subject.The original stays in history.
remit_declarationA complete or partial set of permitted operations, resources and destinations for a subject, with a completeness claim (contract 1.1).Eligible only when the issuer is enrolled as a remit issuer.
activity_observationAn activity seen against a remit, with optional runtime decision, execution state, policy revision and mapping status (contract 1.1 and 1.2).Not an operation: it carries no business value.
policy_deploymentA signed report that a policy revision was activated at named points, with a coverage claim (contract 1.3).An attributable report, never proof that every route is controlled.
acceptance_recordAn external accepted or rejected decision that cites outcome records.The acceptor must be enrolled. Not an intervention authorisation.

Contract 1.2 makes one point that readers often miss. A permit does not imply execution, and a refusal does not imply non-execution. The comparator derives an effect only from what the resource itself reported. A refused request that is not reported as blocked keeps an unknown effect and still counts.

Enrolling a source

A source is enrolled by the operator with agent-source enrol. The source generates an Ed25519Ed25519The digital-signature method used to sign evidence. The private key signs the exact content; the public key checks that signature. signing key pairSigning key pairTwo related keys: the private key makes a signature and the public key checks it. The sender must keep the private key secret. and keeps the private half. The enrolmentEnrolmentThe operator's recorded permission for a source to send specified record types about named agents, using an identified signing key. takes the public half as base64Base64A way to write bytes using ordinary text characters, so a key or signature can be carried in a text field. through --public-key.

For a pilot without key management, --generate-key PATH writes a private keyPrivate keyA secret value used to create digital signatures. The source keeps it so others cannot sign evidence in its name. there. Mode 0600 lets only the owner read or write it. The command says the file must be handed to the source and deleted. NovusLens should not hold a source’s private key, because holding it would let NovusLens forge the evidence that the source is meant to answer for.

The enrolment file is a restriction, not a permission. It records:

  • public_key_b64, the key every batch must be signed with.
  • record_types, the only types the source may publish. At least one is required, and each must be a known type.
  • subject_cells, the bindings from each subject the source names to 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. in the organisation. At least one --bind SUBJECT=CELL is required.
  • grant_issuers, authorisers, acceptors and remit_issuers, the parties the source may name. A source cannot invent who may grant or approve.
  • environment, one of production, simulated or unknown. The default is unknown. The product labels simulated and unknown evidence as such.
  • enabled, which is set to true at enrolment.

Checking a batch before it is sent

agent-source check --batch FILE [--enrolment FILE] says exactly why each record would be refused. It prints one REFUSED line per problem, naming the record and the reason, and exits with status 1 if there is any problem. A batch with no problems exits with status 0.

Unlike intake, check does not stop at the first problem. Each record is checked against the schemaSchemaThe declared structure of data: which fields or tables exist and which values they accept. first. A duplicate ID within the batch is reported even when that record also fails its schema, so a collision cannot hide behind a schema error. Enrolment rules follow: record type and named parties must be enrolled. The subject needs a binding, and the record must name the batch’s source. Batch-level rules come first: org, source and batch_id must be non-empty, a cursorCursorA saved position in a sequence of records. A program uses it to continue from where it stopped. must be well formed, and records must be a non-empty list of no more than 1000. Without --enrolment, only the schema and batch rules run, and the command says so.

An illustrative line, in the form the command prints, is REFUSED ev-14: subject 'invoice-bot-2' is not bound to a cell in the enrolment.

Activation

agent-source activate --enrolment FILE [--setting KEY=MS] --viewer operator merges enrolments into the organisation’s policy and activates the result as the operator. The merge is additive. Activating one source replaces that source’s entry and leaves the others trusted. Criteria are recorded separately with agent-source criterion. Policy history keeps every revision.

Two operator settings are allowed: fleet_window_ms (one minute to one day) and evidence_horizon_ms (one hour to one year). The bounds are checked in agent_policy.py. The command runs in-process against the database through agent_policy.activate_on, which requires the operator role.

Intake

At intake, stage checks the signed envelope before anything is stored. The batch must be at most 2 MiB and name the correct organisation. Its signature must pass federation.verify_signature. Its source must be enrolled and enabled. The signing key must equal the enrolled key. Intake also refuses the public fixtureFixtureA prepared set of records used for a test or demonstration. A fixture is sample data, not a live customer's records. key outright.

A batch ID reused with different signed content is refused. A retry of the same signed envelope returns the original admission. Each record must pass its input checks. Its subject must be linked by the enrolment to an existing cell. Intake checks grant issuers, authorisers and acceptors; remit issuers are checked later during reads. The signed bytes are stored as an artifactArtifactA stored source file or evidence body that a record can cite. Keeping the exact content lets a reader check what supported the record., and the batch is queued in the outboxOutboxA database list of saved messages waiting to be published. A message stays on the list until its progress is recorded. in the same transactionTransactionA group of database changes that is saved together. If the group fails, its changes are not saved.. Intake stops at the first failing record and stores nothing from that batch.

Simulated illustration of the checks a batch passes before it is stored, and the points where it is refused.

The reference exporter and producer

reference_exporter.py is a worked example, and its own header says to copy it and not import it. It turns a gateway access log into an unsigned operation batch. Its decisions are the ones an exporter should copy. Operation names come from a declared table, and a path not in the table is counted and dropped. A 2xx status is completed, a 403 is denied, and a 5xx is failed. Any other status is dropped. A value is emitted only with its declared unit. The caller’s identity is not exported as an authoriser. An access log identifies who called, not who authorised the call. Record IDs are the stream plus the source’s request ID, and a malformed line is skipped and counted. The cursor records the last acknowledged line and the last line read. The batch ID is the cursor range. The coverage report says what was refused and why. The exporter does not sign anything.

agent_producer.py sends an already signed batch durably. Before sending, it writes the pending envelope to a checkpoint file. An fsyncfsyncAn operating-system call that asks for pending file changes to be saved to disk before the program carries on. saves it to disk; a file lock prevents competing writes. After an uncertain response it queries the receipt rather than inventing a new batch ID. It advances its local cursor only when the receipt’s document hashHashA fixed-length fingerprint calculated from content. Comparing fingerprints helps detect changes and match records. and cursor match what it sent. --resume retries the persisted envelope. The service tokenService tokenA secret value sent with requests between NovusLens services. The receiving service refuses requests with a missing or wrong value. comes from CONWAY_SERVICE_TOKEN, and HTTPHTTPHypertext Transfer Protocol: the rules a browser or program uses to send a request to a web service and receive its response. is allowed only for a local service.

mycelium_audit.py exports a MyceliumMyceliumThe application infrastructure underneath NovusLens. Its connected services exchange updates and keep the ordered structural log. node’s signed audit stream as an unsigned batch, through agent-source mycelium-audit. It is read-only. Invocations become operations. A denied call becomes a denied operation. A successful call becomes a completed operation: this means it returned, not that its intended effect happened. The requesting identity is recorded as the caller, never as an authoriser. A gap in sequence, or a broken link where the content hash is present, stops the export at that point.

The reference case and AgentFacts

agent-demo sends a deterministic fixture, labelled simulated. Five sources each sign their own records with a key generated for that organisation and kept in the demonstration’s state file. The approver is not the executor, and neither is the observer. The command has two phases, so that the correction arrives after the case has been read. The drift scenario uses the remit model.

agents and agent-facts are not evidence sources. They run agent_ingest.py, which reads signed AgentFactsAgentFactsA signed statement from an agent about its allowed work, its permissions and the person answerable for it. documents that an agent estateAgent estateThe collection of AI agents an organisation operates, together with the systems and providers they use. publishes about its agents. Those are a separate path with its own labelling, and this contract does not govern them.

Interfaces

SurfaceWhoWhat it does
agent-source enrolOperatorWrites an enrolment file for one source.
agent-source checkOperator or integratorLists every reason a batch would be refused.
agent-source activateOperatorMerges enrolments and settings into the policy.
agent-source criterionOperatorRecords the outcome criterion for an intervention case.
agent-source mycelium-auditIntegratorExports an unsigned batch from a Mycelium node.
agent-demoOperatorSends the simulated reference case.
POST /agent-evidence/ingestOperator, through the authenticated serviceStores a signed batch as pending projectionProjectionThe regular pass that brings the read model up to date with the canonical log. It runs every 30 seconds by default..
POST /agent-evidence/refreshOperatorPublishes pending references and runs projection.
GET /agent-evidence/receipt, /checkpointOperatorReturns the stored receipt and the last committed cursor.
GET /agent-evidence/caseViewer within scopeScopeThe work, data, activity or access that a rule, statement or calculation covers. For access, it names the records an account can see. For team measures, “Own operations” covers the selected unit. “Including descendants” adds the units beneath it.Reads a case, with its support and any supersession.

Invariants and failure behaviour

  • A source’s own claims establish no trust. A signature proves that the enrolled key signed these bytes. It does not prove that the events happened, that the list is complete, or that the payload is true. Booleans such as complete and decision are recorded as the source stated them.
  • Trust comes from the operator’s policy: enrolment, the key, subject bindings and issuer eligibility. Even then, projection is not acceptance of the evidence, not completion of a dependent assessment, and not proof of runtime execution.
  • A source that is disabled, or whose signature evidence has been purgedPurgeRemoving a stored document’s contents while keeping a record that it was removed and why. Conclusions that used it must be checked again., cannot supply current support. Its historical records remain queryable at historical cutoffs.
  • An incomplete remit inventory never proves a prohibition. An empty axis means the remit does not constrain that axis. A record of a refused attempt does not become a completed breach.
  • Intake refuses a batch whole. A stale cursor rolls back the batch and the cursor transition together.
  • NovusLens reads evidence and produces assessments from it. It never issues an authority_grant, approves an intervention, or runs or changes the agents that the evidence describes. A generated policy exportPolicy exportA file of rules generated from an agent’s recorded permissions, in Cedar or Rego format. NovusLens generates the file. The customer decides whether to use it. is generated, not deployed.

Where the contract and the code differ

  • check reads a batch without a signature. It does not verify the signature, enrolled signing key or 5-second clock allowance. It also does not confirm that the bound cell exists. Intake does all four. Its word “admissible” therefore means the schema and the enrolment rules only.
  • The contract describes a server-owned JSON file named by CONWAY_AGENT_EVIDENCE_CONFIG as the source of enrolments. The operator’s command writes the policy in the database instead. The docs site lists that variable as a development setting only.
  • In mycelium_audit.py, the event time at falls back to the sequence number when the record has no integer 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) stamp. The contract defines at as epochEpochFor timestamps, the starting point is 1 January 1970 at midnight in the shared world time reference. For evidence policy, it is the recorded version number. milliseconds, so the fallback gives a small number that is not a time.
  • The contract’s types section does not list acceptance_record or policy_deployment, although both are in the code’s record kinds and are described in later sections. Its acceptance status section also says acceptance records remain open. The later section and the code treat them as implemented.
Last updated