Agent Provenance in Muse
Overview
Muse is built for the agent-first world. Agents and humans are equal first-class cryptographic principals. Every commit carries structured provenance — who wrote this commit, with which model, using which toolchain, and can that attribution be verified cryptographically?
Principals and trust chains
Human operator (gabriel)
↓ provisions at spawn time
Agent (agentception-abc123)
↓ signs each commit with its own keypair
CommitRecord.signature ← Ed25519, embedded public key, offline-verifiable
Trust is established at provisioning time, not commit time. When an
agent is spawned, the operator registers its dedicated keypair with MuseHub
via POST /api/identities/agent. The server sets spawned_by = operator.handle
and stores the trust chain. Every subsequent commit signed by the agent's key
carries that chain back to the operator.
Commit-level fields
CommitRecord in muse/core/store.py carries optional agent provenance fields:
| Field | Description |
|---|---|
agent_id |
Stable identifier for the agent or human ("agentception-abc123", "gabriel") |
model_id |
AI model version used ("claude-sonnet-4-6") |
toolchain_id |
Agent framework version ("agentception/v1") |
prompt_hash |
SHA-256 of the system prompt (no raw text stored) |
signature |
Base64url-encoded Ed25519 signature (86 chars) over the provenance payload |
signer_public_key |
Base64url-encoded raw Ed25519 public key (43 chars) — embedded for offline verification |
signer_key_id |
First 16 hex chars of SHA-256(public key bytes) — for logging |
format_version |
Must be 7 for a signed commit |
Signing model (format_version 7)
Signatures use Ed25519 (RFC 8032 / FIPS 186-5). The signed input is a SHA-256 digest binding the commit's identity to its authorship claims:
provenance_payload = SHA-256(
commit_id \0 author \0 agent_id \0 model_id \0 toolchain_id \0 prompt_hash
)
Any post-signing mutation of author, agent_id, model_id, toolchain_id,
or prompt_hash is detected by muse verify.
The signer_public_key field embedded in the commit record allows anyone to
verify without access to the private key or any external service (offline
verification).
Key storage
Human operator key
~/.muse/keys/localhost_10003.pem # one key per hub hostname
~/.muse/identity.toml:
["localhost:10003"]
type = "human"
handle = "gabriel"
key_path = "/Users/gabriel/.muse/keys/localhost_10003.pem"
Agent-dedicated key (on-disk, long-lived)
~/.muse/keys/localhost_10003__agentception-abc123.pem # hostname__agent_id
~/.muse/identity.toml:
["localhost:10003#agentception-abc123"] # compound key
type = "agent"
handle = "agentception-abc123"
key_path = "/Users/gabriel/.muse/keys/localhost_10003__agentception-abc123.pem"
provisioned_by = "gabriel"
capabilities = ["push", "pull"]
Ephemeral agent key (in-memory, subprocess injection)
For short-lived agent runs, agentception generates a keypair in memory and injects it via environment variables:
export MUSE_AGENT_KEY="-----BEGIN PRIVATE KEY-----\n..."
export MUSE_AGENT_HANDLE="agentception-abc123"
get_signing_identity() checks MUSE_AGENT_KEY first (highest priority),
then the compound identity.toml entry, then falls back to the human key.
Provisioning a dedicated agent key
On-disk agent key (operator workflow)
# 1. Generate the agent's keypair
muse auth keygen --hub http://localhost:10003 --agent-id agentception-abc123
# 2. Register with MuseHub (stores trust chain: spawned_by = gabriel)
muse auth register --hub http://localhost:10003 \
--agent-id agentception-abc123 \
--handle agentception-abc123 \
--provisioned-by gabriel
# 3. Sign commits as the agent
muse commit -m "feat: add harmony voice" \
--agent-id agentception-abc123 \
--model-id claude-sonnet-4-6 \
--sign
Ephemeral agent key (agentception workflow)
from agentception.services.agent_identity import register_agent_identity
identity = await register_agent_identity(
run_id="abc123",
model_id="claude-sonnet-4-6",
scope=["push:agentception"],
)
# Inject into Claude Code subprocess
env = {**os.environ, **identity.env_vars()}
# env["MUSE_AGENT_KEY"] = "-----BEGIN PRIVATE KEY-----\n..."
# env["MUSE_AGENT_HANDLE"] = "agentception-abc123"
subprocess.Popen(["claude-code", ...], env=env)
Signing resolution order (commit time)
get_signing_identity(repo_root, agent_id=...) tries in this order:
MUSE_AGENT_KEYenvironment variable — PEM bytes injected at spawn- Agent-specific entry
"hostname#agent_id"in~/.muse/identity.toml - Human entry
"hostname"in~/.muse/identity.toml(fallback for operators)
Signing and verification API
muse/core/provenance.py provides:
from muse.core.provenance import (
sign_commit_ed25519, verify_commit_ed25519,
public_key_fingerprint, encode_public_key,
sign_commit_record, provenance_payload,
)
# Sign the provenance payload.
payload = provenance_payload(commit_id, agent_id="agentception-abc123", author="gabriel")
sig = sign_commit_ed25519(payload, private_key)
# Embed the public key in the commit for offline verification.
raw_bytes, pub_b64 = encode_public_key(private_key)
fprint = public_key_fingerprint(raw_bytes)
# Verify (offline — no hub required).
assert verify_commit_ed25519(payload, sig, raw_bytes)
# Convenience: sign a commit in one call.
sig, pub_b64, fprint = sign_commit_record(
commit_id, "agentception-abc123", private_key,
author="gabriel", model_id="claude-sonnet-4-6",
)
Querying provenance
muse show <ref> # human-readable, includes provenance
muse show <ref> --json # machine-readable full record
muse log --format json | jq '.[] | .agent_id'
muse shortlog --by agent # group commits by agent
muse code lineage "file.py::Symbol" # provenance chain of a specific symbol
muse verify # verify all Ed25519 signatures in history
Related files
| File | Role |
|---|---|
muse/core/provenance.py |
Ed25519 signing/verification, provenance_payload |
muse/core/identity.py |
Identity store, compound key support, resolve_signing_identity |
muse/core/keypair.py |
Key generation, agent-specific paths, load_private_key_from_pem |
muse/cli/config.py |
get_signing_identity, MUSE_AGENT_KEY env resolution |
muse/core/store.py |
CommitRecord, CommitDict — provenance fields |
muse/core/verify.py |
run_verify — Ed25519 signature verification |
tests/test_agent_signing.py |
Agent signing unit/integration tests |
tests/test_provenance.py |
Ed25519 primitives tests |
tests/test_security_agent_impersonation.py |
Security/tamper-detection tests |