These are the product’s technical docs at this revision. Historical version names and status notes are preserved; see Releases for download qualifications.
Read as MarkdownHubnot protocol v0 experiment
On this page
- Hubnot protocol v0 experiment
- Actor identity
- Event payload and identity
- Issue roots, revisions and reliable operations
- Identity continuity events
- Proposal candidates and revisions
- Git representation and refs
- Runs and governance events
- Optional policy rules
- Synchronization
- Known limits
- Alpha native resource kinds
- Independent audit facts and portable observations
This document records the format implemented by the operational alpha. It is a
testable sketch, not a compatibility promise. Existing version-0 event payloads
and event IDs were not changed when identity continuity was added. Agent memory
is the separate additive hn-memory/0 protocol documented in
memory-v0.md; memory records are not inserted into hn/0
actor chains or event kinds.
hn/0 is a breaking namespace reset, not a compatible spelling of nh/0.
The runtime accepts and writes only hn wire versions and refs/hn/*; legacy
wire bytes and refs/nh/* may remain immutable evidence but are ignored.
Actor identity
An actor is exactly one Ed25519 key pair. Its identifier is the lowercase hexadecimal SHA-256 digest of the 32-byte public key:
actor = hex(sha256(ed25519_public_key))
Each actor has one append-only event chain. A device group or key lineage is a projection over distinct actors; it is never a shared actor secret. The raw-base64 public key remains in every event so each event can be verified from its own signed bytes.
Event payload and identity
An event is UTF-8 JSON emitted without insignificant whitespace. The complete version-0 field order is:
{
"protocol": "hn/0",
"kind": "issue.open",
"actor": "<full-public-key-fingerprint>",
"actorName": "Alice",
"publicKey": "<raw-base64-Ed25519-public-key>",
"sequence": 1,
"timestamp": "2026-08-29T12:00:00Z",
"previous": "<full-previous-event-id>",
"subject": "<full-subject-event-id>",
"relationship": "device",
"targetActor": "<full-target-actor-fingerprint>",
"targetKey": "<raw-base64-target-public-key>",
"title": "An issue title",
"body": "Optional body",
"base": "<full-Git-commit-id>",
"head": "<full-Git-commit-id>",
"verdict": "approve",
"pipeline": "test",
"definition": "<full-pipeline-digest>",
"commit": "<full-Git-commit-id>",
"outcome": "passed",
"exitCode": 0,
"durationMs": 1,
"log": "<full-log-digest>",
"backend": "sandbox",
"platform": "linux/amd64",
"runner": "hn/<version>",
"policy": "<full-policy-digest>",
"evidence": ["<full-evidence-event-id>"],
"issue": {"title": "Current work", "body": "Description", "status": "open", "criteria": [], "labels": [], "assignees": [], "relations": [], "metadata": {}},
"parents": ["<full-prior-issue-revision-id>"],
"operation": "actor-scoped-operation-key",
"request": "<canonical-request-sha256-id>",
"intent": "revise"
}
This is a field inventory, not a valid single event: fields unused by an event
kind are omitted. previous identifies the preceding event in the same actor
chain. subject identifies the exact event on which a dependent fact relies.
Optional string, integer-zero, and empty-list fields are omitted by the current
encoder.
The event identifier is independent of Git's configured object-hash format:
event_id = "sha256:" + hex(sha256(exact_event_payload_bytes))
The signature is Ed25519 over those exact payload bytes. Verification does not
reserialize JSON. Every accepted event must have protocol hn/0, a positive
sequence, a valid RFC 3339 timestamp, a public key whose fingerprint equals
actor, and a valid signature. Full event IDs have the form
sha256:<64-lowercase-hex>.
The implemented kinds are:
issue.open
issue.comment
issue.revise
proposal.open
proposal.revise
review.submit
run.request
run.result
proposal.decision
proposal.merged
identity.authorize
identity.accept
Issue roots, revisions and reliable operations
The optional issue, parents, operation, request, and intent fields
are appended to the Event field inventory. When absent they are omitted, so
existing signed payload bytes and IDs remain unchanged. The hn.issue/1
versioned CLI envelope is separate from the signed hn/0 event protocol.
issue.open establishes stable identity. A legacy title/body opening maps to
an open issue; a rich opening additionally signs full IssueState with matching
title/body. issue.revise signs subject equal to the opening ID, a complete
issue state, and sorted unique parents identifying 1–200 same-issue opening
or revision events. It carries no separate title/body fields. issue.comment
signs its opening subject and body, without state or revision parents.
previous is the previous event in this actor's history; parents are the
issue-content ancestors and may belong to other actors. The verified projection
computes all maximal issue revisions and preserves conflicts without a selected
state. Explicit reconciliation appends a successor referencing the consumed
heads; staged reconciliation preserves all unconsumed heads.
IssueState contains title, body, open/closed status, criteria with local IDs, labels, assignees, typed relations and namespaced string metadata. Criteria order is preserved; label/assignee/relation sets are canonically sorted and reject duplicates. Assignment, criteria and status confer no policy or consumer execution authority. Root/parent/relation dependencies participate in exact replication closure and stay quarantined if suppliers are absent.
intent distinguishes open/revise/resolve/close/reopen/comment. When an
operation key is present, operation and request are both signed. The request
digest is SHA-256 over the canonical intent/kind/subject/parents/state/title/body
encoding; actor sequence, timestamp and operation key are excluded from that
encoding, while the operation key is separately scoped to its signer. The
reader recomputes the digest from signed semantics. Reusing a key for different
semantics fails; identical retries return the original event before checking
current issue heads. No cross-actor atomicity or shared operation authority is
implied.
See issues-v1 for complete limits, machine schema, directed graph, retry and staged-resolution examples. Older executables may reject the new kind; reverting an executable does not erase newly signed facts.
Identity continuity events
identity.authorize adds exactly these event-specific fields:
{
"relationship": "device",
"targetActor": "<full-target-actor-fingerprint>",
"targetKey": "<raw-base64-target-public-key>"
}
relationship is exactly device or successor. targetActor must be a full
64-hex fingerprint, must equal the fingerprint derived from targetKey, and
must differ from the signer. body may carry an explanation. Its ordinary
previous field is present unless it is the signer's first event.
identity.accept uses subject for the full authorization event ID. Its
signer and public key must exactly equal that authorization's target actor and
key. Its ordinary previous field is present unless it is the target actor's
first event.
The deterministic projection applies the following rules over verified facts:
- authorization without a matching acceptance is
pending; - one or more matching acceptances produce one accepted edge; duplicate exact claims remain inspectable but add no authority;
- an accepted
deviceedge keeps both actors active; - exactly one accepted acyclic
successoredge retires the predecessor in the identity projection; - multiple accepted outgoing successors are
competing-successors; - accepted successor cycles are
successor-cycleconflicts; - an acceptance whose authorization is absent is reported as a missing fact;
- a signer/key mismatch is a conflict, never a relationship;
- timestamps and cross-actor delivery order never pick a winner.
Edges, actors, conflicts, acceptance IDs, and event IDs are sorted for stable inspection. Identity projection is descriptive only. It does not edit policy and does not grant a maintainer, reviewer, runner, or decision role.
Proposal candidates and revisions
proposal.open signs a title and distinct base/head Git commit IDs.
proposal.revise is a new immutable candidate and signs:
subject full event ID of the exact predecessor candidate
base full resolved Git commit used as the revised diff base
head full resolved Git commit containing the revised code
body optional explanation
title omitted; inherited from the lineage root
The predecessor must be an available proposal.open or proposal.revise
event, and the same actor must sign both. Base and head are distinct commits.
Missing predecessors, actor mismatches, and cycles fail closed.
One predecessor may have multiple successors. IDs are sorted for deterministic projection, but no timestamp, delivery order, or actor-chain position creates a global latest winner. Evidence always binds one exact candidate; evidence for a predecessor or sibling cannot qualify a revision.
Git representation and refs
Every event is a Git commit whose tree contains:
event.json exact signed payload bytes
signature raw-base64 Ed25519 signature
A run.result tree also contains log.txt; the exact log bytes hash to the
signed log field. No other tree entry is accepted. The commit parent is the
preceding event commit for that actor. The signed previous event ID provides
an independent protocol chain.
Public roots are:
refs/hn/actors/<full-actor-fingerprint>
refs/hn/proposals/<candidate-event-sha256-without-prefix>
The candidate ref points to the exact signed head commit, making the proposed
code reachable for transport and garbage collection. A mismatch fails closed.
Accepted remote-tracking roots are local:
refs/hn/remotes/<remote>/actors/<full-actor-fingerprint>
refs/hn/remotes/<remote>/proposals/<candidate-event-sha256-without-prefix>
Actor chains begin at sequence 1, increase by one, and match both Git parents
and signed previous IDs. Different events at the same actor sequence are a
fork and are rejected by the current projection.
Runs and governance events
A run.request references a candidate through subject and signs the
repository-local pipeline name, SHA-256 ID of the exact pipeline JSON bytes,
and proposed Git head. A run.result references the request and repeats all
three bindings. It also signs passed or failed, exit code, duration, exact
log digest, backend, platform, and runner. Any mismatch is rejected.
These events are attestations. Signatures protect attribution and contents; they do not prove honest execution. The exact base policy chooses which actors' claims count.
A proposal.decision references one candidate, the SHA-256 ID of its exact
base policy bytes, and an accept or reject verdict. Accept decisions include
the exact review and run-result event IDs that satisfy policy. Rejections
require an explanation.
A proposal.merged event binds the candidate, proposed head, resulting Git
commit, base-policy digest, and acceptance-decision IDs. It is emitted after
Git creates the merge commit; branch publication remains a separate ordinary
Git action.
Optional policy rules
hn.policy/0 may include independent optional protectedChecks and automerge
sections. Absence or null disables each in supporting clients; either may be
enabled alone. No existing signed payload is re-encoded. Clients that do not
recognize a present field reject that policy rather than infer permissive rules.
See governance for the
full schema, selector bounds and commands.
Protection qualifies existing review.submit events. A protected change needs
distinct designated actors other than the candidate author, signing approval
for that exact candidate. Selectors come from the signed base policy and include
its policy file and every required pipeline definition. Tree comparisons include
removal, rename, mode/type and ancestor changes. A proposal.decision acceptance
includes the exact qualifying designated-review IDs in its existing evidence
array; local and quarantine admission enforce the same base-bound rule. Current
withdrawals affect later merge eligibility, not the validity of older signed
acceptance evidence.
A prospective manual merge separately inspects the current target. Enabled protection there requires revision when its exact policy digest differs from the candidate's base, or protected paths changed from base to target even under identical policy bytes. This prevents hidden reversion of a newer check to old base bytes. It does not reinterpret historical decisions or contained-merge repair under a later policy.
Automerge introduces no event kind, implicit role or automatic decision. A named base-policy rule restricts executing maintainers, authors, target branch and allowed paths in addition to existing current review/run/acceptance requirements. Default preview is keyless and read-only; execution is explicit. Version-one automation requires a full non-promisor repository, a clean checked-out rule target equal to the signed base, and no unfinished Git operation. It refuses policy amendments and protected changes for manual handling.
Automatic commit creation uses the verified head tree with signed base and head
as the two parents. An expected-old ref transaction guards the actual advance
and a final evidence/target check. This path uses git commit-tree, so ordinary
pre-merge-commit and pre-commit hooks are not invoked; required check authority
comes from native policy and pipeline evidence. Operator reference-transaction
hooks remain Git behavior. The resulting proposal.merged is the existing
signed fact binding candidate, head, result commit, base policy and exact
acceptance IDs. Missing-event repair uses ordinary hn merge; collaboration
and branch publication remain explicit separate actions.
Synchronization
hn replication select records full per-remote actor/candidate selectors and
positive budgets below .git/hn/. A saved selection is authoritative. With no
saved selection, version 0 uses bounded compatibility-all: it enumerates only
advertised refs/hn/actors/* and refs/hn/proposals/*, then applies the same
quarantine, budgets, validation, and promotion transaction.
Each selected ref is fetched with its own exact refspec into a generated bare quarantine repository. It is measured, structurally and cryptographically validated, and checked for exact relationships before objects are copied and accepted refs are atomically updated. Invalid, over-budget, or dependency-missing selections do not become accepted projection roots. Standard Git may download a selected pack before measurement, so the budgets are hard before promotion and retention, not portable pre-download network quotas. Replication v0 defines the transaction and shallow recovery boundary in detail.
Publication uses ordinary explicit Git pushes of local actor and candidate
refs. hn sync never publishes the repository's primary branch; use a
separate explicit git push for that outcome.
Known limits
- One actor remains single-writer; disconnected concurrent writers sharing a private key are unsupported.
- Planned rotation requires the predecessor key. Lost-key, compromise, social, and organizational recovery are not implemented.
- Continuity and selection never confer policy authority.
- There is no portable hard pre-download byte quota, moderation network, selective global deletion, redaction guarantee, or global erasure of already replicated immutable facts.
- JSON canonicalization and extension negotiation are not stable cross-language contracts yet.
- Hosted ref retention, large-scale behavior, and providers not listed in host compatibility remain unproven.
Alpha native resource kinds
The optional-native-resource expansion registers project, project-item,
discussion, discussion-reply and workflow .open / .revise facts under the
existing hn/0 envelope. Typed versioned JSON lives in body; the existing
subject, parents, intent, operation, request fields bind exact aggregate
history and actor-global replay intent. No identity, policy or CI authority is
added. New-kind raw JSON is strict and bounded (256 KiB, workflows 2 MiB);
existing signed bytes and legacy admission behavior remain unchanged. Older
alpha clients reject these new unknown kinds and must be upgraded before selecting
histories that contain them. Exact container/schema/answer references participate
in reader, quarantine and shallow recovery closure before promotion. See
Native resources for typed schemas, command grammar, conflict
projection, bounds and authority limits.
Independent audit facts and portable observations
The additive audit.rule-authorize, audit.rule-revoke, audit.request and
audit.result kinds use the existing signed hn/0 actor envelope with a strict
versioned audit payload. Absent audit fields remain omitted: earlier signed
encodings, signatures and IDs do not change. Older clients reject unknown kinds
and must be upgraded before selecting histories containing these facts.
Audit schemas define exact policy/scope, predecessor, target,
rule-definition, requester/executor, outcome, finding, log and backend bindings.
Results are observational: neither passed nor failed audit facts qualify as
run.result, review, proposal acceptance or merge evidence. Follow-up issues
use ordinary signed native issue state with finding/request/result links.
Historical audit admission validates recorded policy, exact relationships, source/target objects, typed attachments and bounded logs identically for local writes, fetched histories, shallow recovery and portable imports. Audit snapshot packs preserve exact required target/rule trees; an absent unreferenced parent of a snapshot target is allowed, while ordinary incomplete selected ancestry still refuses. Structural/type checks precede accepted visibility. Read validation needs no private audit anchor. Current authoring/execution separately requires explicit local adoption of a named authority branch and its current policy, roles and unconflicted rule heads; imported history never supplies that choice.
Project snapshots use hn.project-snapshot/1; uncompressed
portable tar archives use hn.project-archive/1, canonical manifests and
content-addressed blobs. These are new containers, not a rewrite of signed facts
or a standard git bundle. Exact selected native refs/dependencies are validated
in private quarantine before guarded promotion to remote-observation refs.
Signing-owned actor streams, private keys, local audit anchors and active
destination policy are not imported. Explicit selected committed history remains
exact, including historical sensitive bytes; export is not a secret scrubber.
Foreign records preserve source identity, attribution, topology and verified retrieved bytes. Reviews, users, permissions, CI and workflows remain inert observations and grant no native identity, policy or execution authority. Saved migration binds immutable observations, destination baselines, explicit decisions and dependent write payloads. Source inspection, archive creation and admission never implicitly execute content or publish code.