This guide describes SHADI APIs and integration patterns for applications.
# 1) Fetch human public key from GitHub and store DID artifacts.
cargo run -p shadictl -- did-from-github --user alice --out human.did.json
# 2) Store the human OpenPGP secret key locally.
cargo run -p shadictl -- put-key --key human/gpg --in /path/to/human-secret.asc
# 3) Derive agent identities and keys.
cargo run -p shadictl -- derive-agent-did --secret human/gpg --name agent-a --prefix agentsflowchart LR
A[GitHub GPG public key] --> B[did-from-github]
B --> C[Human DID + DID doc]
D[Local OpenPGP secret key] --> E[put-key]
E --> F[Secret store: human/gpg]
F --> G[derive-agent-did]
G --> H[Agent keys + DIDs]
H --> I[Secret store: agents/*]
To list secrets stored for an app or agent, use shadictl:
cargo run -p shadictl -- --list-keychain --list-prefix agents/
cargo run -p shadictl -- --list-keychain --list-prefix apps/Avoid printing secret values. Pass key names to helpers that resolve secrets inside SHADI.
To list or search long-term memory in the encrypted SQLCipher store, use the
shadictl memory helper so keys stay in SHADI:
cargo run -p shadictl -- -- memory list \
--db "${SHADI_TMP_DIR:-./.tmp}/shadi-memory.db" \
--key-name shadi/memory/sqlcipher_key \
--scope app --limit 50cargo run -p shadictl -- -- memory search \
--db "${SHADI_TMP_DIR:-./.tmp}/shadi-memory.db" \
--key-name shadi/memory/sqlcipher_key \
--scope app --query policy --limit 10Most applications integrate SHADI in three layers:
- Identity and session verification: validate a DID/VC presentation.
- Secrets access: gate reads and writes to the secret store.
- Sandbox execution (optional): run agents with OS-level restrictions.
The Rust integration surface lives in agent_secrets.
SecretStore: storage backend (platform keychain by default).AgentVerifier: verifies a session before secret access.SessionContext: session metadata used byAgentVerifier.AgentSecretAccess: gatekeeper for per-session secret access.SecretPolicy: per-secret policy metadata (currently default only).SecretBytes: zeroizing wrapper for secret material.
use agent_secrets::{
AgentSecretAccess, AgentVerifier, SecretError, SecretPolicy, SessionContext,
};
struct AllowVerifier;
impl AgentVerifier for AllowVerifier {
fn verify(&self, session: &SessionContext) -> Result<(), SecretError> {
if session.verified {
Ok(())
} else {
Err(SecretError::NotAuthorized)
}
}
}
fn main() -> Result<(), SecretError> {
let store = agent_secrets::default_store();
let verifier = AllowVerifier;
let access = AgentSecretAccess::new(store.as_ref(), &verifier);
let mut session = SessionContext::new("agent-1", "session-1");
session.verified = true;
access.put_for_session(&session, "app/config", b"secret", SecretPolicy::default())?;
let secret = access.get_for_session(&session, "app/config")?;
let value = secret.expose(|bytes| bytes.to_vec());
println!("secret len: {}", value.len());
Ok(())
}SessionContext includes:
agent_id: logical agent identifiersession_id: per-session identifierverified: whether verification succeededclaims: list of DID/VC claims
Your verifier should set verified after validating a DID/VC presentation.
The Python bindings are provided by shadi_py and expose ShadiStore,
PySessionContext, SqlCipherMemoryStore, SandboxPolicyHandle, and
run_sandboxed.
Install the extension module into the current Python environment with maturin:
uv run maturin develop --manifest-path crates/shadi_py/Cargo.tomlFor Rust-only builds, use:
just buildfrom shadi import ShadiStore, PySessionContext
store = ShadiStore()
# Provide a verification callback.
# It receives: agent_id, session_id, presentation_bytes, claims.
store.set_verifier(lambda agent_id, session_id, presentation, claims: True)
session = PySessionContext("agent-1", "session-1")
store.verify_session(session, b"didvc-presentation")
store.put(session, "app/config", b"secret")
print(store.get(session, "app/config"))
store.delete(session, "app/config")from shadi import ShadiStore, PySessionContext
def verify_didvc(agent_id, session_id, presentation, claims):
# Replace with real DID/VC validation.
return True
store = ShadiStore()
store.set_verifier(verify_didvc)
session = PySessionContext("agent-1", "session-1")
ok = store.verify_session(session, b"presentation-bytes")
if not ok:
raise RuntimeError("verification failed")
# Secrets are now gated by the verified session.
store.put(session, "app/config", b"value")set_verifier(callable)installs a DID/VC verifier callback.verify_session(session, presentation)calls the verifier and setssession.verified = Trueif the callback returns truthy.put/get/delete/list_keysrequire a verified session.
from shadi import SqlCipherMemoryStore
store = SqlCipherMemoryStore(
db_path="./.tmp/shadi-app/app_memory.db",
key_name="app/memory_key",
)
store.put("app", "latest_state", "{\"status\":\"ok\"}")
latest = store.get_latest("app", "latest_state")
print(latest.payload if latest else "no entry")from shadi import SandboxPolicyHandle, run_sandboxed
policy = SandboxPolicyHandle()
policy.allow_read_path(".")
policy.allow_write_path("./.tmp")
policy.block_network(False)
run_sandboxed(["/usr/bin/env", "python", "-V"], policy)Use shadictl to ingest OpenPGP keys and derive agent DIDs without invoking
OS gpg:
cargo run -p shadictl -- \
put-key --key human/gpg --in /path/to/human-secret.asc
cargo run -p shadictl -- \
derive-agent-did --secret human/gpg --name agent-a --prefix agentsGenerated keys and DID artifacts are stored under:
agents/agent-a/privateagents/agent-a/publicagents/agent-a/didagents/agent-a/diddoc
This flow starts from a human's GitHub GPG identity and produces a collection of agent DIDs and key material stored in the SHADI secret store.
- Fetch the public OpenPGP key from GitHub and create a DID document:
cargo run -p shadictl -- \
did-from-github --user alice --out human.did.jsonThis stores the human DID and DID document under:
github/alice/didgithub/alice/diddoc
- Import the human OpenPGP secret key locally (private material never comes from GitHub):
cargo run -p shadictl -- \
put-key --key human/gpg --in /path/to/human-secret.asc- Derive agent identities and keys from the human secret key:
cargo run -p shadictl -- \
derive-agent-did --secret human/gpg --name agent-a --prefix agents
cargo run -p shadictl -- \
derive-agent-did --secret human/gpg --name agent-b --prefix agentsEach derived agent writes keys and DID artifacts to:
agents/<agent>/privateagents/<agent>/publicagents/<agent>/didagents/<agent>/diddoc
- In your app, verify sessions using the human DID/VC and then grant secrets access to the agent sessions.
Rust and Python use the same underlying SHADI secret store. The "name" you see
in examples is just the secret key string (for example, app/config or
agents/agent-a/did). There is no separate store per language.
If you see different names between Rust and Python examples, it is only a convention difference. Pick a shared key naming scheme and use it consistently across both runtimes.
The sandbox launcher is a core SHADI feature. Agents should run under an OS sandbox by default to enforce least-privilege execution.
Invoke shadictl with policy flags and a command to execute:
cargo run -p shadictl -- \
--allow . \
--read / \
--net-block \
-- \
./your-agent --arg valueFor Python agents, you can run under the sandbox using the JSON policy runner:
./.venv/bin/python tools/run_sandboxed_agent.py \
--policy ./sandbox.json \
-- ./.venv/bin/python ./your_agent.pynet_allow in the policy file is enforced by the Python runner using a
best-effort network guard (not OS-enforced).
If keychain access is restricted in the sandbox, broker a secret outside the sandbox and inject it as an environment variable:
cargo run -p shadictl -- \
--inject-keychain app/config=APP_CONFIG \
-- \
./your-agentRust APIs return SecretError:
NotAuthorized: session verification failedInvalidInput: key does not exist or malformed inputStorageFailure: backend failure or OS error
In Python, these errors are reported as RuntimeError with the same message.
- Generate or import a human OpenPGP key and store it with
put-key. - Derive agent DIDs and keypairs with
derive-agent-did. - Implement a DID/VC verification callback in your app.
- Set
session.verified = trueonly after verification succeeds. - Use
AgentSecretAccess(Rust) orShadiStore(Python) to access secrets. - Run the agent under a sandbox policy if needed.