Skip to main content

Python SDK for the AgentWrit broker -- ephemeral scoped credentials for AI agents via Ed25519 challenge-response

Project description

AgentWrit

AgentWrit Python SDK

License: MIT Python 3.10+ Type checked: mypy strict

Ephemeral, task-scoped credentials for AI agents.
Built on Ed25519 challenge-response and the Ephemeral Agent Credentialing v1.3 pattern.

Why · Install · Prerequisites · Quick Start · Lifecycle · Demo · Scopes · Delegation · Errors · Architecture · Docs


Why AgentWrit?

AI agents need credentials to access databases, APIs, and file systems. Most teams give agents shared API keys or inherit user permissions — both create over-privileged, long-lived, unauditable access. AgentWrit takes a different approach:

  • Ephemeral identities — every agent gets a unique Ed25519 keypair, generated in memory and never persisted to disk
  • Task-scoped tokens — credentials are limited to exactly what the agent needs (read:data:customers, not read:*:*)
  • Short-lived by default — tokens expire in minutes, not hours or days
  • Delegation chains — agents can delegate a subset of their permissions to other agents; the broker rejects any attempt to widen

This SDK is the Python client for the AgentWrit broker — the broker is the credential authority, and this SDK is how your Python code talks to it.

Installation

Install from GitHub (not yet on PyPI):

uv add git+https://github.com/devonartis/agentwrit-python.git

Or with pip:

pip install git+https://github.com/devonartis/agentwrit-python.git

For local development:

git clone https://github.com/devonartis/agentwrit-python.git
cd agentwrit-python
uv sync --all-extras

Requirements: Python 3.10+. The SDK also needs a broker and credentials — see Prerequisites.

Prerequisites

The SDK is a client. It does not run the broker, and it does not mint its own credentials. Before any code in Quick Start will work, you need three things:

1. A reachable AgentWrit broker. The broker is a separate service that issues and validates tokens.

  • Have a platform team running one? Ask them for the broker URL.
  • Running it yourself? Stand one up locally — the broker repo ships a docker compose setup. From this repo:
    docker compose up -d   # pulls devonartis/agentwrit from Docker Hub
    

2. App credentials (client_id + client_secret). These are issued by the broker operator/admin when they register your app and set its scope ceiling. The SDK cannot create them for you.

  • Have a broker admin? Ask them to register your app and send you the client_id and client_secret.
  • You are the admin? Use the included setup script (it registers an app and prints both values):
    export AGENTWRIT_ADMIN_SECRET="<your-broker-admin-secret>"
    uv run python demo/setup.py
    

3. Environment variables set on the process that uses the SDK:

export AGENTWRIT_BROKER_URL="http://localhost:8080"   # from step 1
export AGENTWRIT_CLIENT_ID="<from step 2>"
export AGENTWRIT_CLIENT_SECRET="<from step 2>"

Auth is lazy — the SDK doesn't talk to the broker until your first create_agent() call. If that call raises AuthenticationError, your client_id or client_secret is wrong (or the operator rotated them). If it raises TransportError, the broker URL is unreachable.

Quick Start

Assumes Prerequisites are met — broker reachable, app registered, env vars set.

import os
from agentwrit import AgentWritApp, validate

# Connect to the broker (lazy — no auth until first create_agent)
app = AgentWritApp(
    broker_url=os.environ["AGENTWRIT_BROKER_URL"],
    client_id=os.environ["AGENTWRIT_CLIENT_ID"],          # from broker admin
    client_secret=os.environ["AGENTWRIT_CLIENT_SECRET"],  # from broker admin
)

# Create an agent with specific scope
agent = app.create_agent(
    orch_id="my-service",
    task_id="read-customer-data",
    requested_scope=["read:data:customers"],
)

# Use the token as a Bearer credential
import httpx
resp = httpx.get(
    "https://your-api/data/customers",
    headers=agent.bearer_header,
)

# Validate the token (any service can do this)
result = validate(app.broker_url, agent.access_token)
print(result.claims.scope)  # ['read:data:customers']

# Release when done — token is dead immediately
agent.release()

Agent Lifecycle

# Create — agent gets a SPIFFE identity and scoped JWT
agent = app.create_agent(orch_id="svc", task_id="task", requested_scope=["read:data:x"])

# Use — agent.access_token is a standard Bearer JWT
print(agent.agent_id)      # spiffe://agentwrit.local/agent/svc/task/a1b2c3d4
print(agent.scope)         # ['read:data:x']
print(agent.expires_in)    # 300 (seconds)

# Renew — new token, same identity, old token revoked
agent.renew()

# Delegate — pass a subset of scope to another agent (equal or narrower)
delegated = agent.delegate(delegate_to=other.agent_id, scope=["read:data:x"])

# Release — self-revoke, idempotent
agent.release()

MedAssist AI Demo

The demo/ directory contains MedAssist AI — an interactive healthcare demo that showcases every AgentWrit capability against a live broker.

What it does: A FastAPI web app where you enter a patient ID and a plain-language request. A local LLM (OpenAI-compatible) chooses which tools to call, and the app dynamically creates broker agents with only the scopes those tools need for that specific patient. Every step — scope enforcement, cross-patient denial, delegation, token renewal, release — appears in a real-time execution trace.

What it demonstrates:

Capability How the demo shows it
Dynamic agent creation Agents spawn on demand as the LLM selects tools — clinical, billing, prescription
Per-patient scope isolation Each agent's scopes are parameterized to one patient ID
Cross-patient denial LLM asks for another patient's records → scope_denied in the trace
Delegation Clinical agent delegates write:prescriptions:{patient} to the prescription agent
Token lifecycle Renewal and release shown at end of each encounter
Audit trail Dedicated audit tab showing hash-chained broker events

Running the demo

# 1. Start the AgentWrit broker
docker compose up -d

# 2. Register the demo app with the broker (one-time setup)
export AGENTWRIT_ADMIN_SECRET="your-admin-secret"
uv run python demo/setup.py
# → Prints client_id and client_secret

# 3. Configure demo/.env (copy from demo/.env.example)
cp demo/.env.example demo/.env
# Fill in: broker URL, client_id, client_secret, LLM endpoint

# 4. Run it
uv run uvicorn demo.app:app --reload --port 5000
# Open http://127.0.0.1:5000

For architecture diagrams, step-by-step traces, and a live presentation script, see demo/BEGINNERS_GUIDE.md and demo/PRESENTERS_GUIDE.md.

Scope Format

Scopes are three segments: action:resource:identifier

read:data:customers          — read customer data
write:data:order-abc-123     — write to a specific order
read:data:*                  — wildcard: read ANY data resource

Wildcard * only works in the identifier (third) position. Action and resource must match exactly.

from agentwrit import scope_is_subset

scope_is_subset(["read:data:customers"], ["read:data:*"])     # True
scope_is_subset(["write:data:customers"], ["read:data:*"])    # False (write != read)
scope_is_subset(["read:logs:customers"], ["read:data:*"])     # False (logs != data)

Delegation

Agents delegate a subset of their scope to other agents. Delegation cannot widen authority — equal or narrower scope is accepted; any scope the delegator doesn't hold is rejected.

# A has broad scope
agent_a = app.create_agent(
    orch_id="pipeline", task_id="orchestrator",
    requested_scope=["read:data:partition-7", "read:data:partition-8"],
)

# A delegates ONLY partition-7 to B
delegated = agent_a.delegate(
    delegate_to=agent_b.agent_id,
    scope=["read:data:partition-7"],
)

# Validate: delegated token has only partition-7
result = validate(app.broker_url, delegated.access_token)
print(result.claims.scope)  # ['read:data:partition-7']

Error Handling

from agentwrit.errors import AuthorizationError, TransportError

try:
    agent = app.create_agent(orch_id="svc", task_id="t", requested_scope=scope)
except AuthorizationError as e:
    print(e.status_code)        # 403
    print(e.problem.detail)     # "scope exceeds app ceiling"
    print(e.problem.error_code) # "scope_violation"
except TransportError:
    print("Broker unreachable")

Architecture

graph TB
    subgraph App["Your Application"]
        direction TB
        Client["AgentWritApp"]
    end

    subgraph Broker["AgentWrit Broker"]
        direction LR
        AuthGroup["App Auth<br/>/v1/app/auth<br/>/v1/app/launch-tokens"]
        CredGroup["Credentials<br/>/v1/challenge<br/>/v1/register"]
        MgmtGroup["Management<br/>/v1/delegate<br/>/v1/token/validate<br/>/v1/token/renew<br/>/v1/token/release"]
    end

    Agents["Your AI Agents"]
    APIs["Protected APIs"]

    Client ==>|"HTTPS"| Broker
    Client -.->|"create_agent()"| Agents
    Agents ==>|"Bearer JWT"| APIs

    style App fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a5f
    style Broker fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#78350f
    style Agents fill:#d1fae5,stroke:#10b981,stroke-width:2px
    style APIs fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px

Authority Chain

Operator (root of trust)
  │  registers app, sets scope ceiling
  ▼
Application (your code — AgentWritApp)
  │  creates agents within ceiling
  ▼
Agent (ephemeral SPIFFE identity + scoped JWT)
  │  delegation cannot widen scope (equal or narrower allowed)
  ▼
Delegated Agent (sub-agent, max 5 hops)

Documentation

Guide Description
Concepts Roles, scopes, delegation, trust model, and standards
Getting Started Install, connect, and create your first agent
Developer Guide Delegation patterns, scope gating, error handling
API Reference Every class, method, parameter, and exception
Testing Guide Unit tests, integration tests, running the test suite

For broker setup and administration, see the AgentWrit broker documentation.

Standards Alignment

Standard What it addresses
NIST IR 8596 Unique AI agent identities via SPIFFE IDs
NIST SP 800-207 Zero-trust per-request validation
OWASP Top 10 for Agentic AI (2026) ASI03 (Identity/Privilege Abuse), ASI07 (Insecure Inter-Agent Communication)
IETF WIMSE Delegation chain re-binding
IETF draft-klrc-aiagent-auth-00 OAuth/WIMSE/SPIFFE framework for AI agents

Contributing

See CONTRIBUTING.md for the full workflow: uv setup, live-broker verification (clone agentwrit or use your own broker), and evidence to include in PRs so maintainers can review broker-facing changes confidently.

Quick local checks (no broker required for unit tests):

git clone https://github.com/devonartis/agentwrit-python.git
cd agentwrit-python
uv sync --all-extras

uv run ruff check .
uv run mypy --strict src/
uv run pytest tests/unit/

License

This SDK is licensed under the MIT License.

The AgentWrit broker is licensed separately under PolyForm Internal Use 1.0.0. See the broker repo for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentwrit-0.3.0.tar.gz (5.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentwrit-0.3.0-py3-none-any.whl (21.8 kB view details)

Uploaded Python 3

File details

Details for the file agentwrit-0.3.0.tar.gz.

File metadata

  • Download URL: agentwrit-0.3.0.tar.gz
  • Upload date:
  • Size: 5.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.20

File hashes

Hashes for agentwrit-0.3.0.tar.gz
Algorithm Hash digest
SHA256 699eb63fd2d65b9607df1d4f772c6873a42dac02b9e7fb509c65703218ca5d86
MD5 17cc49624b982d5391c7113643de3434
BLAKE2b-256 f77cb9f0a74f79153c3cf2efecb4fb3111ca32dd2e2e571d73ad99f907cc6229

See more details on using hashes here.

File details

Details for the file agentwrit-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: agentwrit-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 21.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.20

File hashes

Hashes for agentwrit-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 87abd32fbcdf819e19acde7b6a0e0d0134fb8690fff5b5226a624597b12b7672
MD5 46774d71c023727d02444259aeace3c7
BLAKE2b-256 92c1b290a307b5c9206fc379f79093b592f4fbc891a57825e663218be4f45365

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page