Agent evidence, signed at the source.
Agent evidence is what an agentAgentA program that takes actions for an organisation. NovusLens records what it is allowed to do and what its activity reports show.’s operator sends NovusLens about what the agent was authorised to do and what it did. The sourceSourceA document, interview statement, measurement or export that supports a value. Each source has a date and a stated scope. signs each batch. NovusLens accepts it only from an enrolled source and returns a 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.. The sender can check when records appear in 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.. This page describes contract version 1 as the code implements it. For the complete rules and their reasons, cite docs/contracts/agent_evidence_v1.md.
Enrolment and activation by an operator, then the send, receipt and projection loop between the producer, the intake and the twin.
What it is for
A customer’s agents act on systems the customer owns. NovusLens stores these records on the customer’s infrastructure. They describe agent authorityAuthorityWhat a team or agent is allowed to decide or do. For an agent, this comes from a named person or system that grants permission., operations, requested and approved interventions, and observed outcomes. Those records are evidence in the sense of the twin: they say what a source reported, signed by that source, and when it reported it.
NovusLens does not run the agent, does not enforce the authority it records, and does not decide whether an agent behaved correctly. NovusLens refuses sources that are not enrolled and leaves refused records out of the twin. Everything else is shown with its source.
Before you start
- A running NovusLens twin, with the reasoning service reachable and an operator account. See Installation.
- An Ed25519Ed25519The digital-signature method used to sign evidence. The private key signs the exact content; the public key checks that signature. 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. for the source. The public keyPublic keyA value used to check a digital signature. Sharing it lets others check signatures but does not let them create new signatures. is enrolled. The private keyPrivate keyA secret value used to create digital signatures. The source keeps it so others cannot sign evidence in its name. stays with the producer, and the contract states the custody rules.
- The record types you intend to send, and the cellsCellA 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. each subject is bound to. Records for an unbound subject are refused.
- A written decision on which of your systems emit which record types. The enrolmentEnrolmentThe operator's recorded permission for a source to send specified record types about named agents, using an identified signing key. declares them, and a source cannot send a type it did not declare.
Record types
A record has a type drawn from twelve kinds. Each kind has the fields below as required, and may have the optional fields listed in the contract and the validator.
| Type | Required payloadPayloadThe data carried inside a message, apart from the surrounding fields that identify and date it. fields | What it records |
|---|---|---|
authority_grant | issuer, grant_version, operation, resource, unit | An authority given to an agent for an operation on a resource. |
operation | operation, resource, state | An operation the agent performed or was observed performing. |
intervention_request | case_id, action, resource, requester, unit | A request to intervene on a resource. |
intervention_authorisation | request_id, authoriser, grant_id, decision | An authoriser’s decision on a request. |
intervention_execution | request_id, authorisation_id, executor, state, action, resource | An execution of an authorised intervention. |
outcome_observation | execution_id, criterion, unit | An observed outcome against a named criterion. |
acceptance_record | request_id, acceptor, decision, purpose, policy_ref, policy_version, resource | An acceptor’s decision on an outcome. |
correction | target_id, replacement_id, reason | A correctionCorrectionA change to a recorded value or record. The earlier version stays in the history, and any conclusion that depended on it is checked again. that replaces an earlier record. |
withdrawal | target_id, reason | A withdrawalWithdrawalA source’s removal of a value or record from current use. Its history stays, and NovusLens checks conclusions that depended on it again. of an earlier record. |
remit_declaration | issuer, remit_version, responsible_role | The 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. a source declares it is permitted to act within, and whether that scope is complete. |
activity_observation | operation, resource, evidence_ref | An activity seen against a remitRemitA written list of an agent’s allowed actions, resources and destinations for information. The list states whether it is complete., with the decision, execution and mapping status the runtimeRuntimeThe software that runs a model or agent. A model runtime answers model requests; an agent runtime carries out the customer's agent actions. reported. |
policy_deployment | policy_revision, deployed_by | What the runtime reports it activated: the policy revision, where, when and with what coverageCoverageWhat the available records cover, and what is missing. A count such as “3 of 5 dependencies measured” names the measured portion and the gap.. |
The record envelopeEnvelopeThe surrounding fields that identify a message, its sender, its type and its time, alongside the message's data. has these fields: version (must be 1), id, type, source, subject, at, received_at and payload. The sender sets all of them except received_at, which the intake overwrites with its own clock. An at value more than five seconds ahead of the intake clock is refused.
Enumerated values are fixed by the code. An activity observation’s runtime decision is permit, deny or indeterminate. Intervention authorisation decisions are approved, refused or unresolved. Acceptance decisions are accepted or rejected. An execution is attempted, blocked, completed, failed or unknown. A mapping status is mapped, unmapped or ambiguous. A value that is absent is recorded as unstated, and nothing is inferred from it.
Signing
A batch is a document with these fields: org, source, batch_id and records, plus an optional cursor. The reference producer requires a cursorCursorA saved position in a sequence of records. A program uses it to continue from where it stopped. even though intake permits batches without one. records contains between 1 and 1,000 records. The signed envelope is at most 2 MiBKiB and MiBBinary size units: one KiB is 1,024 bytes; one MiB is 1,048,576 bytes. Evidence limits use these units. (2,097,152 bytes) in 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.. The intake checks both limits, and the producer checks the 2 MiB bound before it sends. A single record is at most 64 KiB. The reasoning service also refuses any request body over 4 MiB before it reaches the intake.
The batch is sent as an envelope:
| Field | Value |
|---|---|
document | The batch document above. |
alg | ed25519. Any other value is refused. |
public_key_b64 | The source’s public key, base64Base64A way to write bytes using ordinary text characters, so a key or signature can be carried in a text field.. |
signature_b64 | An Ed25519 signatureDigital signatureA check calculated with a signing key that can detect whether a record has changed. Identifying the signer also requires a trusted link between that key and its owner. over the canonical form of document. |
The canonical form is JSON with sorted keys, compact separators and no NaN or infinity values. The intake verifies the signature against the enrolled key, so a change to any byte of the document after signing fails.
Enrolment
A source is enrolled per organisation by an operator with the agent-source enrol command. The command writes an enrolment file. Its flags name the source, key, subjects, record types and permitted parties. See the command-line table for every flag and default.
The enrolment file records the fields the intake checks: enabled, public_key_b64, subject_cells, record_types, grant_issuers, authorisers, acceptors, remit_issuers and environment. Intake checks grant issuers, authorisers and acceptors against their lists. Remit issuer enrolment is checked when records are read, so an unenrolled remit issuer cannot support a conclusion.
Before sending, a producer checks a batch with agent-source check --batch <file>, optionally with --enrolment. The check reports format and enrolment problems before sending. It does not check the signature, event clock or current cell existence. Intake checks those on arrival.
Activation
An enrolment does not take effect on its own. An operator merges enrolments into the organisation’s policy with agent-source activate, which takes repeated --enrolment files, --setting KEY=MS for the two settings fleet_window_ms and evidence_horizon_ms, and --viewer. Activation is additive by default, so activating one source does not withdraw the others. Only an operator may activate.
Customer installations use the command-line activation above. The APIAPIApplication programming interface: a defined way for one program to request data or actions from another. variant, POST /agent-evidence/policy/activate, is for a service configured with the development enrolment file. Its body contains only viewer and org. The service reads the enrolments from its own server-owned configuration file, set by CONWAY_AGENT_EVIDENCE_CONFIG, and assigns the policy 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. itself. Editing the configuration file alone changes nothing until the activation is run.
A disabled source, or one whose key has been rotated out, stops being supported. Its records lose current support, as the enrolment and activation guide describes.
Sending and receipts
The reference producer is agent_producer.py. It persists the pending envelope to a state file before it transmits anything, so a crash cannot lose a batch that was signed. It asks for a receipt before it retries, so a batch the intake has already admitted is not sent again as new. Its command takes --batch or --resume, with --state, --viewer and --url.
The producer posts to POST /agent-evidence/ingest with the X-Conway-Service-Token header set from CONWAY_SERVICE_TOKEN. It sends over HTTPHTTPHypertext Transfer Protocol: the rules a browser or program uses to send a request to a web service and receive its response. only to localhost, and it bounds the body at 2 MiB.
A sender checks progress with GET /agent-evidence/receipt. A receipt is in one of four states:
| State | Meaning |
|---|---|
stored-pending-projection | The intake admitted the batch and stored it. The twin does not yet show it. |
published-pending-projection | The batch is published to the projectionProjectionThe regular pass that brings the read model up to date with the canonical log. It runs every 30 seconds by default. queue, and not yet projected. |
projected | The records are in the twin’s read modelRead modelThe database tables used by the map, Ask and Find. NovusLens rebuilds these tables from its history of recorded changes.. |
evidence-unavailable | The records cannot be shown, for example because their source is no longer supported. |
POST /agent-evidence/refresh drains pending projection in rounds, and returns projected when the queue is empty, or projection-incomplete when it is not. A background worker does the same at regular intervals. Each pass handles at most 20 organisations and 20 batches per organisation. It republishes after 60 seconds, with retry waits increasing from 2 to 60 seconds. A database lock prevents competing workers processing the same organisation.
Cursors
A batch may carry a cursor: {stream, previous, next}. The previous value is null only for the first transition in a stream. A batch whose previous does not match the saved stream position is refused. It cannot move the stream backwards. An exact retry of an admitted batch returns the original admission, not a new one.
Refusals
The intake refuses a batch as a whole, or a record within it, with a reason. These are the refusal messages the code produces:
| Message | Cause |
|---|---|
| Agent evidence signature failed | The signature does not verify against the enrolled key, or the document changed after signing. |
| Agent evidence tenant mismatch | The batch’s org is not the organisation it was sent to. |
| Agent evidence source not enrolled or disabled | The source has no enrolment for this organisation, or it is disabled. |
| Public fixtureFixtureA prepared set of records used for a test or demonstration. A fixture is sample data, not a live customer's records. signing key not accepted by authenticated intake | A development key was used against the authenticated intake. |
| batch ID reused with different content (signed envelope) | The same batch_id was sent with different content. |
| batch ID reused with a different cursor | The same batch_id was sent with a different cursor. |
| A bounded records list is required | The batch has no bounded records list. |
| Source may not publish this record type | The record’s type is not in the source’s record_types. |
| Agent evidence subject is not bound to a cell | The record’s subject has no --bind in the enrolment. |
| Event timestamp exceeds clock allowance | The at value is more than five seconds ahead of the intake clock. |
| Grant issuer not enrolled for this source | An authority_grant names an issuer not in grant_issuers. |
| Authoriser not enrolled for this source | An intervention_authorisation names an authoriser not in authorisers. |
| Acceptor not enrolled for this source | An acceptance_record names an acceptor not in acceptors. |
Any record that fails the validator is refused with the validator’s message, which names the missing or unsupported field.
Endpoints
The reasoning service runs on port 8500 by default. The agent evidence routes are:
| Method and path | Purpose |
|---|---|
POST /agent-evidence/ingest | Admit a signed batch. |
POST /agent-evidence/refresh | Drain pending projection. |
GET /agent-evidence/receipt | Read a batch’s receipt state. |
GET /agent-evidence/checkpoint | Read the intake’s checkpoint. |
GET /agent-evidence/case | Read a case. |
GET /agent-evidence/requests | List intervention requests. |
GET /agent-evidence/activities | List activity observations. |
GET /agent-evidence/delivery | Read the delivery worker’s state. |
POST /agent-evidence/policy/activate | Activate enrolments (operator only). |
POST /agent-evidence/criteria | Record the outcome criterion for an intervention case. |
agent_ingest.py is a separate path. It reads AgentFactsAgentFactsA signed statement from an agent about its allowed work, its permissions and the person answerable for it. documents, which are platform-signed descriptions of agents, and it is not the evidence intake. The two use different endpointsEndpointA particular address on a service that accepts a defined kind of request. and produce different records.
What NovusLens claims and does not claim
NovusLens claims the following. It admits a batch only if the enrolled source signed it. It records the record types, subjects and fields the source sent, labelled with that source. It shows the receipt state of each batch and the time of its projection. It reports a comparison against the recorded remit, and states when that comparison is incomplete. It labels the environment of a case as simulated, production or unknown from the enrolments behind it.
NovusLens does not claim the following. It does not verify that the source’s records are true beyond its signature and enrolment. It does not enforce the authority it records or stop the agent. It issues no verdict on whether an agent behaved correctly. A detector classifies activity against the remit, and NovusLens reports the result without issuing a verdict. A policy_deployment record is a report of what the runtime says it activated, not proof that every route is controlled.