Trust graph

Harn's trust graph is the runtime-owned event stream for autonomy decisions. Every trigger dispatch now appends a hash-chained OpenTrustGraph TrustRecord to trust_graph plus the per-agent topic trust_graph.<agent_id>. It also mirrors a compact TrustGraphRecord projection to trust_graph.records for runtime policy and application queries. Harn still reads and mirrors the older trust.graph topic names for compatibility. The same stream also carries explicit promotion and demotion events recorded by harn trust promote and harn trust demote.

Record model

Each record carries:

  • schema ("opentrustgraph/v0.1"; v0 records still parse for one patch release window — see OpenTrustGraph CONFORMANCE.md §5)
  • record_id
  • agent
  • action
  • approver
  • outcome
  • trace_id
  • autonomy_tier
  • timestamp
  • cost_usd
  • chain_index
  • previous_hash
  • entry_hash
  • metadata (v0.1 reserves effects_grant, effects_used, and parent_record_id so chain validators can prove that a child agent's effects_used ⊆ parent.effects_grant)

The trust_graph.records projection carries the runtime query shape:

  • actor_id
  • action
  • approver
  • outcome
  • evidence_refs
  • trace_id
  • timestamp
  • autonomy_tier_at_time

See spec/opentrustgraph.md for the normative record model, chain export model, and sample event stream. The small public artifact that Harn Cloud receipts, Burin supervision UI planning, and external verifiers should link to is opentrustgraph-spec/. It contains the JSON Schema files and conformance fixtures used by Harn's runtime tests.

Autonomy tiers

Trigger manifests and dynamic trigger registrations can set autonomy_tier to one of:

  • shadow: run in dry-run mode. Mutating builtins are rejected and the runtime emits proposal metadata instead of performing the mutation.
  • suggest: emit a proposal and wait for an approval path before mutation.
  • act_with_approval: handlers execute, but direct mutating actions are still gated behind approval-aware surfaces.
  • act_auto: mutating actions run freely; dispatches are still logged.

At runtime, handlers can inspect the effective tier with handler_context().autonomy_tier. Harn resolves that effective tier from the manifest default plus the latest trust-graph control record for the agent. The dispatcher enforces the tier by translating it into a capability policy before handler code runs: shadow denies side effects, suggest and act_with_approval are read-only for direct builtins, and act_auto keeps the normal network-capable ceiling. Shadow and suggest rejections also emit proposal metadata for operators.

CLI

Query the trust graph:

harn trust query --agent github-triage-bot
harn trust query --agent github-triage-bot --outcome denied --json
harn trust query --summary
harn trust-graph verify-chain
harn trust-graph export --output chain.json

harn trust-graph export writes the canonical opentrustgraph-chain/v0 envelope. With no --output, it prints the JSON to stdout. Pipe it through any conformant verifier — e.g. the reference Python verifier:

harn trust-graph export | python3 opentrustgraph-spec/examples/python/verify_chain.py

Promote or demote an agent:

harn trust promote github-triage-bot --to act-auto
harn trust demote github-triage-bot --to shadow --reason "unexpected prod mutation"

--summary aggregates records per agent and reports:

  • success rate
  • mean cost
  • tier distribution
  • outcome distribution

Script APIs

Import std/trust for the focused TrustGraph API:

  • query(filters) returns TrustGraphRecord rows. Filters accept actor, actor_id, agent, action, outcome, since, until, autonomy_tier_at_time, tier, and limit.
  • record(decision) appends a trust decision and returns its TrustEntryId. Decisions accept actor_id (or agent), action, approver, outcome, trace_id, autonomy_tier_at_time (or autonomy_tier/tier), evidence_refs, cost_usd, and metadata.
  • score(actor_id, action?) returns a TrustScore.
  • policy_for(actor_id) returns the capability policy derived from trust history.
  • verify_chain() verifies the underlying OpenTrustGraph hash chain.

The native namespace exposes the same focused query as trust.query(...), matching the API shape harn::trust::query({actor: ..., action: ...}) in host integrations.

Import std/corrections for replay-for-teaching records:

  • record(correction: CorrectionInput) appends a CorrectionRecord with typed CorrectionDecision values for from_decision and to_decision, reason, applied_by, and scope (this_run, this_persona, or all), plus optional actor_id, action, trace_id, step, list<CorrectionEvidenceRef>, and metadata.
  • query(filters: CorrectionQueryFilters) returns corrections by actor, actor_id, agent, action, scope, since, until, and limit.

this_persona and all corrections are policy inputs. When a human keeps correcting an actor's decision path, policy_for(actor_id) caps that actor's derived side-effect level at read_only while the matching correction records remain applicable.

Import std/triggers and use:

  • handler_context() to inspect the current dispatch context, including agent, action, trace_id, and autonomy_tier
  • trust_record(agent, action, approver, outcome, tier) to append a manual trust record and return the full finalized record
  • trust_graph_record(decision) to append a decision dict and return its TrustEntryId
  • trust_query(filters) to query historical records from Harn code, including server-side limit and grouped_by_trace options
  • trust_graph_query(agent, action) to return a TrustScore summary for handler-side policy decisions
  • trust_graph_policy_for(agent) to return a capability policy derived from the agent's effective tier and recent outcomes
  • trust_graph_verify_chain() to verify the local hash chain

Example:

import "std/trust"

let records: list<TrustGraphRecord> = query({
  actor: "github-triage-bot",
  action: "merge_pr",
  outcome: "success",
  since: "2026-04-01T00:00:00Z",
})

The legacy trigger API remains available:

import "std/triggers"

let records = trust_query({
  agent: "github-triage-bot",
  outcome: "success",
  tier: "act_auto",
  limit: 100,
})

Grouped queries return trace buckets:

import "std/triggers"

let grouped = trust_query({
  since: "2026-04-19T18:00:00Z",
  limit: 500,
  grouped_by_trace: true,
})

self_review(...) also writes trust records with action pr.self_review. Its metadata currently includes:

  • rubric and rubric_preset
  • requested_rounds and completed_rounds
  • finding_count and blocking_finding_count
  • secret_scan_finding_count
  • finding_categories
  • summary
  • diff_bytes and diff_sha256

Portal API

The portal exposes GET /api/trust-graph for external UIs. Query parameters mirror the CLI filters: agent, action, limit, and grouped_by_trace. The response includes flat records, optional trace groups, per-agent summary rows, the current chain verification report, and the topic names the portal is reading. A supervision UI can project that response into the opentrustgraph-chain/v0 envelope by using the verification report's topic, total, root hash, and verified flag as chain metadata and the returned records as records.