A Human API for AI agents. Governed ask / notify / approve /
schedule verbs over consented channels, with a failover ladder, scoped
revocable tokens, per-touch budgets, and an audit trail for every attempt.
See SPEC.md for the full design (verbs, consent model, blessed
tokens, the abuse-resistance model, and the premises this v1 rests on).
Agents that reach real people keep reinventing the same five things badly: an unconsented channel, a rate limit nobody enforces, an audit trail that's just application logs, a credential that's either all-powerful or nonexistent, and a silent failure when the first channel doesn't work. cura-reach is the layer in between: one verb set, one policy layer, one audit shape, regardless of which channel actually carries the message.
pip install -e .
# Grant consent for a channel (opaque address reference, not stored in code):
python3 -m cura_reach.cli grant-consent --human alice --channel email --address alice@example.com
python3 -m cura_reach.cli grant-consent --human alice --channel task_queue --address queue:alice --priority 1
# Mint a scoped, revocable token for one agent, reaching only this human:
python3 -m cura_reach.cli mint-token --principal my-agent --scopes ask,notify --humans alice
# -> {"token_id": "tok_...", "secret": "..."} (shown once; store it yourself)
# Ask. Email adapter falls back to the task-queue channel if unconfigured:
python3 -m cura_reach.cli ask --token-id tok_... --secret ... --human alice --question "Ship it?"Or drive it from Python directly:
from cura_reach.bootstrap import build_engine
from cura_reach.models import ChannelType, Verb
engine, consent, tokens = build_engine()
consent.register_human("alice")
consent.grant("alice", ChannelType.EMAIL, "alice@example.com", source="onboarding")
token, secret = tokens.mint("my-agent", [Verb.ASK], ["alice"], budget_ref="budget:my-agent")
request = engine.ask(token.token_id, secret, "alice", "Ship it, or one more pass?")
print(request.status, request.channel_attempts)python3 demo/e2e_demo.pyRuns the full loop end to end with zero live network calls: mints a token,
grants consent on two channels, asks a consenting test human, watches the
email adapter fail over (unconfigured in a fresh checkout) to the
task-queue adapter (delivers via a local JSONL outbox), then resolves the
request and prints the full audit trail plus the budget draw it recorded.
The same flow is asserted in tests/test_e2e.py.
- Asks API: all four verbs (
cura_reach/engine.py), backed by SQLite (cura_reach/storage.py), zero external dependencies. - Consent registry: per-human, per-channel, append-only consent ledger
(
cura_reach/registry.py::ConsentRegistry). - Blessed tokens: scoped, revocable, no-enumeration, budget-backed
(
cura_reach/registry.py::TokenRegistry). - Failover ladder: email -> task queue -> LinkedIn/Slack/SMS stubs
(
cura_reach/engine.py::Engine._walk_ladder,cura_reach/adapters/). - Audit: Action-shaped, append-only, idempotent-on-write
(
cura_reach/audit.py). - Budgets: shared daily draw ceilings, fail-loud on exhaustion
(
cura_reach/registry.py::BudgetLedger). - A2A/MCP surface (v0): a stdio MCP server exposing the four verbs as
tools (
cura_reach/mcp_server.py).
See SPEC.md section 11 for what's explicitly out of scope for
v1, and section 12 for the premises this design rests on.
All configuration is environment-variable driven and generic (no vendor or deployment names baked in):
| Variable | Purpose | Default |
|---|---|---|
CURA_REACH_SMTP_HOST / _PORT / _USERNAME / _PASSWORD / _FROM_ADDRESS / _USE_TLS |
Email adapter (own-mailbox send) | unset (adapter reports unavailable) |
CURA_REACH_QUEUE_URL / _TOKEN |
Task-queue adapter (HTTP POST) | unset (falls back to a local outbox file) |
CURA_REACH_QUEUE_OUTBOX |
Local outbox path when no queue URL is set | .cura-reach/queue-outbox.jsonl |
CURA_REACH_DB |
SQLite path for the CLI/MCP server | .cura-reach/store.db |
CURA_REACH_MCP_TOKEN_ID / _MCP_TOKEN_SECRET |
The token an MCP session authenticates as | required for cura_reach.mcp_server |
pip install -e ".[dev]"
pytestApache 2.0 (see LICENSE), matching the operator's other public
infrastructure repos.