A passport bolt-on maps an identity or verification source into the four-field AlgoVoi agent passport, content-addressed into a deterministic passport_ref. The bolt-on owns the mapping; the substrate owns the bytes. Anyone can build one today against the published 0.4.0 substrate, with no AlgoVoi service in the loop and no PII in the reference.
Everything you need is already public. The schema, the hash, the conformance vectors, and a worked reference adapter are all published. A passport bolt-on is a thin, auditable mapping plus one hash — not a new primitive.
The reference
passport_ref = "sha256:" + SHA-256(JCS({ agent_id, issuer, scope, validity_window }))
All four fields are byte-load-bearing: change the agent, the issuer, the scope, or the window and the passport_ref diverges. An empty field is rejected, not hashed. No name, document number, image, or other PII ever enters the reference — your bolt-on is responsible for keeping it that way (see Keeping it no-PII).
What you build against
Three published surfaces, pick one:
| You depend on | You get | Best when |
|---|
algovoi-agent-passport-lite | passport_ref(agent_id, issuer, scope, validity_window) directly | You just want the function |
algovoi-substrate >=0.4.0 | sha256_jcs(obj) — inline the four fields yourself | You already depend on the substrate |
algovoi-kyc-passport | Jumio / Sumsub mapping → passport_ref | You map a KYC vendor result |
All three produce the same bytes on the same input, and all reproduce the published agent_passport_lite_v1 conformance vectors.
The recipe
A bolt-on is three steps:
- Read the source. Take the verification result from your KYC vendor, IdP, directory, or registry.
- Map to the four fields. Derive a no-PII
agent_id, the issuer (who vouched), a deterministic scope (what was verified), and a validity_window. Fail closed: only emit a passport for a passed verification.
- Compute the reference. Hash the four fields. That
passport_ref is the agent_ref the Spend Guardrail (lite) decision binds, so identity composes straight into the pre-payment decision chain.
from algovoi_substrate import sha256_jcs
def passport_ref(agent_id, issuer, scope, validity_window):
for name, value in (("agent_id", agent_id), ("issuer", issuer),
("scope", scope), ("validity_window", validity_window)):
if not isinstance(value, str) or not value:
raise ValueError(f"{name} must be a non-empty string")
return "sha256:" + sha256_jcs({
"agent_id": agent_id, "issuer": issuer,
"scope": scope, "validity_window": validity_window,
})
That is the entire dependency: RFC 8785 JCS, SHA-256, and a JSON parser.
Worked example: KYC vendors
algovoi-kyc-passport is the reference passport bolt-on. It maps a Jumio or Sumsub identity-verification result into a passport_ref, fail-closed, with a no-PII subject helper.
pip install algovoi-kyc-passport · Apache-2.0 · Python and TypeScript byte-for-byte identical.
from algovoi_kyc_passport import from_jumio, from_sumsub, subject_ref
# Jumio KYX — only a PASSED decision yields a passport
ref = from_jumio(
agent_id=subject_ref("jumio", "acct-9931"), # content-addressed, no PII
decision="PASSED",
capabilities=["extraction", "liveness", "watchlistScreening"],
valid_from="2026-06-30", valid_until="2027-06-30",
)
# Sumsub — only a GREEN review yields a passport
ref = from_sumsub(
agent_id=subject_ref("sumsub", "applicant-771"),
review_answer="GREEN",
checks=["identity", "selfie"],
valid_from="2026-06-30", valid_until="2027-06-30",
level_name="basic-kyc",
)
import { fromJumio, subjectRef } from '@algovoi/kyc-passport';
const ref = fromJumio({
agentId: subjectRef('jumio', 'acct-9931'),
decision: 'PASSED',
capabilities: ['extraction', 'liveness', 'watchlistScreening'],
validFrom: '2026-06-30', validUntil: '2027-06-30',
});
The same pattern extends to any source — an OIDC id_token, a corporate directory record, a wallet attestation: read the result, map to the four fields, hash. Jumio and Sumsub are the worked vendors; the mapping is yours to write for any other.
Appear in the keystone control panel
A bolt-on can plug into the keystone control panel two ways, and algovoi-kyc-passport does both:
Feed the built-in passport step. to_passport_signals packages a kyc-passport into the panel’s passport-step signals (subject_ref, issuer_did, assurance, age_ms), so the panel evaluates a Jumio/Sumsub identity against the org’s issuer-trust, assurance, and freshness posture:
from algovoi_kyc_passport import subject_ref, from_jumio, to_passport_signals, JUMIO_ISSUER
subj = subject_ref("jumio", "acct-9931")
from_jumio(agent_id=subj, decision="PASSED", capabilities=["identity", "liveness"],
valid_from="2026-06-30", valid_until="2027-06-30")
signals = to_passport_signals(subject_ref=subj, issuer=JUMIO_ISSUER, assurance="HIGH", age_ms=age)
# panel: KeystoneControl().decide("passport", signals) -> ALLOW / REFER / DENY
Be auto-detected. The package registers under the algovoi.keystone.steps entry-point group, so the panel discovers and lists it with no configuration. The entry point points at a callable that returns a dict with at least step and ref:
[project.entry-points."algovoi.keystone.steps"]
passport_kyc = "algovoi_kyc_passport.keystone:keystone_step"
# algovoi_kyc_passport/keystone.py
def keystone_step() -> dict:
return {
"package": "algovoi-kyc-passport",
"step": "passport_kyc", # required: the panel row name
"ref": "passport_ref", # required: the ref it emits
"order": 1,
"posture": "KYC identity source (Jumio / Sumsub) -> passport_ref",
"default": {"__keystone": {"canon_version": "jcs-rfc8785-v1", "hash_algo": "sha256",
"required_fields": ["agent_id", "issuer", "scope", "validity_window"]}},
}
The panel’s discovery loads the entry point, calls it, and keeps the result if it’s a dict with step and ref. Your own bolt-on registers the same way — a zero-argument function returning that dict.
Keeping it no-PII
The passport_ref is content-addressed and shareable, so nothing private may enter it.
agent_id — use a content-addressed handle, never a raw name or document number. The subject_ref(vendor, account_id) helper hashes an opaque vendor account id into "sha256:" + SHA-256("vendor:account_id") for exactly this.
scope — encode the verification level and check set, not the underlying data. kyc-verified:identity+liveness:tier2, never a date of birth or address.
issuer — a DID or stable identifier for who vouched (did:web:jumio.com), not a person.
validity_window — dates only.
Validate your bolt-on
Recompute is the test. Run your bolt-on’s output against the public agent_passport_lite_v1 vector set (11 vectors, Python + Node runners). If your four-field hash reproduces those vectors byte-for-byte, your bolt-on is conformant and its references compose with every other passport on the substrate.
# your bolt-on must reproduce the frozen public vector
assert passport_ref(
"agent-001", "did:algo:issuer", "payments", "2026-06-21/2026-09-21"
) == "sha256:b3594e33998af01bd1ad208172c5c1ac586daa8c75781379f034d97e50b1a9be"
Where it composes
A passport_ref is the identity input to the open, pinned pre-payment decision chain. Spend Guardrail (lite) composes the agent (this passport_ref), the spend authority (a mandate reference), and the policy in force (a Policy Binding reference) into one recomputable decision. Because passport_ref is the same agent_ref the decision binds, identity, authority, and policy chain into a single offline-verifiable address.
Open tier vs commercial
A passport bolt-on computes and verifies the reference. Proving the passport’s lifecycle is the commercial Agent Passport.
| Bolt-on (open) | Agent Passport (commercial) |
|---|
Content-addressed passport_ref | yes | yes |
| Vendor / source mapping | yes | yes |
| Tamper detection (recompute) | yes | yes |
| Falcon-1024 signed credential | — | yes |
| Revocation + validity-window enforcement | — | yes |
| Issuer trust lists | — | yes |
Relationship to the open substrate
Passport bolt-ons sit directly on top of the open JCS Canonicalisation Substrate and compose with Spend Guardrail (lite), Agent Passport (lite), and Policy Binding. They use the same RFC 8785 JCS and SHA-256 primitives — no additional cryptographic dependencies, strictly additive over the frozen Layer 1.