a1-ai 2.8.0

A1 — The cryptographic identity and authorization layer that turns anonymous AI agents into accountable, verifiable entities. One Identity. Full Provenance.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
# A1 Python SDK

[![PyPI](https://img.shields.io/pypi/v/a1.svg)](https://pypi.org/project/a1/)
[![Python](https://img.shields.io/pypi/pyversions/a1.svg)](https://pypi.org/project/a1/)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](https://github.com/dyologician/a1/blob/main/LICENSE-MIT)

Python SDK for [A1](https://github.com/dyologician/a1) — cryptographic chain-of-custody for recursive AI agent delegation.

A1 gives every AI agent a verifiable passport and produces an independently verifiable receipt for every authorized action. It closes the **Recursive Delegation Gap**: the inability to prove, in a multi-agent delegation chain, which human authorized the action at the end.

---

## Requirements

- Python 3.9+
- An A1 gateway running locally or remotely (see [gateway setup]#running-the-gateway)

---

## Installation

**Base client:**

```bash
pip install a1
```

**With framework integrations:**

```bash
pip install "a1[langchain]"        # LangChain
pip install "a1[langgraph]"        # LangGraph
pip install "a1[llamaindex]"       # LlamaIndex
pip install "a1[autogen]"          # AutoGen v0.4
pip install "a1[crewai]"           # CrewAI
pip install "a1[semantic-kernel]"  # Microsoft Semantic Kernel
pip install "a1[openai]"           # OpenAI Agents SDK
pip install "a1[all]"              # All framework integrations
```

**With KMS backends:**

```bash
pip install "a1[vault-aws]"        # AWS KMS
pip install "a1[vault-gcp]"        # GCP Cloud KMS
pip install "a1[vault-hashicorp]"  # HashiCorp Vault Transit
pip install "a1[vault-azure]"      # Azure Key Vault
```

**With SIEM exporters:**

```bash
pip install "a1[siem-datadog]"     # Datadog Logs
pip install "a1[siem-splunk]"      # Splunk HEC
pip install "a1[siem-otel]"        # OpenTelemetry (OTLP)
```

---

## Quick Start

### 1. Start the gateway

```bash
git clone https://github.com/dyologician/a1.git
cd a1
cp .env.example .env      # fill in at minimum A1_PG_PASSWORD
docker compose up -d
```

The gateway listens on `http://localhost:8080`.

### 2. Issue a passport

```bash
cargo install a1-cli
a1 passport issue \
  --namespace my-agent \
  --allow "trade.equity,portfolio.read" \
  --ttl 30d \
  --out passport.json
```

### 3. Guard a function

```python
from a1.passport import a1_guard, PassportClient

client = PassportClient(
    gateway_url="http://localhost:8080",
    passport_path="passport.json",
)

@a1_guard(client=client, capability="trade.equity")
async def execute_trade(symbol: str, qty: int, signed_chain: dict, executor_pk_hex: str) -> dict:
    return {"status": "filled", "symbol": symbol, "qty": qty}
```

The `@a1_guard` decorator calls the gateway's `/v1/passport/authorize` endpoint, verifies authorization, and raises `A1AuthorizationError` if the agent is not authorized.

### 4. Use the base client directly

```python
from a1 import A1Client

client = A1Client(base_url="http://localhost:8080")

result = await client.authorize(
    chain=signed_chain,
    intent_name="trade.equity",
    intent_params={"symbol": "AAPL", "qty": 100},
    executor_pk_hex=agent_pk_hex,
)

print(result.authorized)           # True
print(result.chain_depth)          # 2
print(result.chain_fingerprint)    # hex string
```

---

## MCP — Claude Code and Cursor support

A1 ships a built-in [Model Context Protocol](https://spec.modelcontextprotocol.io/) server. Any MCP-compatible agent or IDE can authorize through A1 **without any code changes**.

Add to your `.mcp.json` (or Claude Code / Cursor settings):

```json
{
  "mcpServers": {
    "a1": {
      "type": "http",
      "url": "http://localhost:8080/mcp"
    }
  }
}
```

The MCP server exposes A1's authorize, cert-issue, revoke, and passport-check tools directly to the agent. No decorators, no imports, one config entry.

---

## A1 Studio

Open `http://localhost:8080/studio` in your browser after starting the gateway. Studio lets you issue passports, inspect delegation chains, manage agents, test MCP tools, and view audit logs — no CLI or code required.

---

## Framework Integrations

### LangChain

```python
from a1.langchain_tool import A1AuthorizationTool
from a1.passport import PassportClient

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

tool = A1AuthorizationTool(
    name="execute_trade",
    description="Execute an equity trade. Input: JSON with symbol and qty.",
    intent_name="trade.equity",
    client=client,
    func=execute_trade_fn,
    chain=agent_chain,
    executor_pk_hex=agent_pk,
)
```

### LangGraph

```python
from a1.langgraph_tool import a1_node, A1StateSchema
from a1.passport import PassportClient

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

class AgentState(A1StateSchema):
    messages: list
    symbol: str
    receipt: dict | None

@a1_node(intent_name="trade.equity", client=client, propagate_receipt=True)
async def execute_trade(state: AgentState) -> AgentState:
    await broker.place_order(state["symbol"])
    return state  # receipt is automatically added to state["receipt"]
```

### LlamaIndex

```python
from a1.llamaindex_tool import a1_llamaindex_tool
from a1.passport import PassportClient

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

tool = a1_llamaindex_tool(
    fn=read_portfolio_fn,
    intent_name="portfolio.read",
    client=client,
    resolve_context=lambda kwargs: {"chain": agent_chain, "executor_pk_hex": agent_pk},
    name="read_portfolio",
    description="Read portfolio holdings.",
)
```

### AutoGen v0.4

```python
from a1.autogen_tool import build_a1_function_tool
from a1.passport import PassportClient

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

tool = build_a1_function_tool(
    fn=execute_trade,
    intent_name="trade.equity",
    client=client,
    chain=agent_chain,
    executor_pk_hex=agent_pk,
)
```

### CrewAI

```python
from a1.crewai_tool import A1AuthorizationTool

tool = A1AuthorizationTool(
    func=execute_trade,
    intent_name="trade.equity",
    gateway_url="http://localhost:8080",
    chain=agent_chain,
    executor_pk_hex=agent_pk,
)
```

### Semantic Kernel

```python
from a1.semantic_kernel_tool import a1_sk_function
from a1.passport import PassportClient
from semantic_kernel import Kernel

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

class TradingPlugin:
    @a1_sk_function(intent_name="trade.equity", client=client, description="Execute equity trade.")
    async def execute_trade(self, symbol: str, signed_chain: dict, executor_pk_hex: str) -> str:
        return f"Traded {symbol}"

kernel = Kernel()
kernel.add_plugin(TradingPlugin(), plugin_name="trading")
```

### OpenAI Agents SDK

```python
from a1.openai_tool import a1_openai_function
from a1.passport import PassportClient

client = PassportClient(gateway_url="http://localhost:8080", passport_path="passport.json")

@a1_openai_function(intent_name="trade.equity", client=client)
async def execute_trade(symbol: str, qty: int) -> str:
    return f"Filled {qty} shares of {symbol}"
```

---

## JWT Bridge (OIDC / SSO)

Exchange an existing OIDC or SAML JWT token for a scoped A1 `DelegationCert` — no manual key ceremony required. Useful for enterprises already running SSO.

```python
from a1 import A1Client

client = A1Client(base_url="http://localhost:8080")

cert = await client.exchange_jwt(
    token=jwt_bearer_token,         # your OIDC access token
    capabilities=["files.read"],
    ttl_seconds=3600,
    delegate_pk_hex=agent_pk_hex,
)

# Use cert to build a delegation chain for /v1/authorize
```

Configure the gateway with `A1_JWT_JWKS_URL` and `A1_JWT_ALLOWED_CAPS`.

---

## Delegation Negotiation

An agent can request a scoped cert from the gateway without a pre-shared chain:

```python
from a1 import A1Client

client = A1Client(base_url="http://localhost:8080")

offer = await client.negotiate(
    intent_name="files.read",
    agent_pk_hex=my_public_key_hex,
    requested_ttl_seconds=3600,
)

# offer.cert is a DelegationCert signed by the gateway
```

---

## Swarm Management

```python
from a1 import A1Client

client = A1Client(
    base_url="http://localhost:8080",
    admin_secret=os.environ["A1_ADMIN_SECRET"],
)

# Create a swarm
swarm = await client.create_swarm(
    swarm_name="trading-fleet",
    capabilities=["trade.equity"],
    ttl_days=30,
    signing_key_hex=root_key_hex,
)

# Add a member
await client.add_swarm_member(
    swarm_id=swarm["swarm_id"],
    agent_pk_hex=worker_pk_hex,
    role="worker",
    capabilities=["trade.equity"],
    ttl_seconds=3600,
    signing_key_hex=root_key_hex,
)

# Remove a member
await client.remove_swarm_member(
    swarm_id=swarm["swarm_id"],
    agent_did=worker_did,
)

# List active members
members = await client.list_swarm_members(swarm["swarm_id"])
```

---

## On-chain Anchoring (ZK)

Anchor a zero-knowledge chain commitment on-chain for immutable audit trails:

```python
from a1 import A1Client

client = A1Client(base_url="http://localhost:8080")

anchor = await client.anchor(
    commitment=zk_chain_commitment,
    passport_did="did:a1:...",
    network="ethereum",    # ethereum | polygon | base | arbitrum | solana
)

print(anchor["anchor_hash_hex"])
print(anchor.get("evm_calldata"))          # for EVM chains
print(anchor.get("solana_instruction_data"))  # for Solana
```

---

## Multi-Tenant

Send `X-A1-Tenant-ID` on every request to scope all revocation and nonce operations to that tenant:

```python
from a1 import A1Client

client = A1Client(
    base_url="http://localhost:8080",
    default_headers={"X-A1-Tenant-ID": "acme"},
)

# All authorize, revoke, and nonce calls are now scoped to tenant "acme"
result = await client.authorize(...)
```

Enable on the gateway with `A1_MULTI_TENANT=true`.

---

## Batch Authorization

```python
from a1 import A1Client

client = A1Client(base_url="http://localhost:8080")

results = await client.authorize_batch(
    chain=signed_chain,
    intents=[
        {"name": "trade.equity", "params": {"symbol": "AAPL"}},
        {"name": "portfolio.read"},
    ],
    executor_pk_hex=agent_pk_hex,
)

# All-or-nothing: if any intent fails, no nonces are consumed
for r in results:
    print(r.intent_name, r.authorized)
```

---

## KMS Signing Backends

For production deployments, the root passport key should live in a KMS — not in a file.

```python
from a1.vault import AwsKmsSigner, HashiCorpVaultSigner, GcpKmsSigner, AzureKeyVaultSigner

# AWS KMS (requires: pip install "a1[vault-aws]")
signer = AwsKmsSigner(key_id="alias/a1-passport-root", region="us-east-1")

# HashiCorp Vault Transit (requires: pip install "a1[vault-hashicorp]")
signer = HashiCorpVaultSigner(
    vault_addr="https://vault.corp.example.com",
    key_name="a1-passport-root",
    token=os.environ["VAULT_TOKEN"],
)

# GCP Cloud KMS (requires: pip install "a1[vault-gcp]")
signer = GcpKmsSigner(
    project="my-project",
    location="global",
    key_ring="a1-keys",
    key="passport-root",
    key_version="1",
)

# Azure Key Vault (requires: pip install "a1[vault-azure]")
signer = AzureKeyVaultSigner(
    vault_url="https://my-vault.vault.azure.net",
    key_name="a1-passport-root",
)
```

Verification is always local — zero KMS calls at authorization time.

---

## SIEM Integration

```python
from a1.siem import DatadogLogExporter, SplunkHecExporter, OpenTelemetryExporter, CompositeExporter

exporter = CompositeExporter([
    DatadogLogExporter(
        api_key=os.environ["DD_API_KEY"],
        service="trading-agents",
        env="production",
    ),
    SplunkHecExporter(
        url="https://splunk.corp.com:8088",
        token=os.environ["SPLUNK_HEC_TOKEN"],
        index="ai-audit",
    ),
])

exporter.export_dict(audit_event)
```

OpenTelemetry:

```python
from a1.siem import OpenTelemetryExporter

exporter = OpenTelemetryExporter(
    endpoint="http://otel-collector:4318",
    service_name="trading-agents",
)
```

---

## Middleware (ASGI / WSGI)

A1 includes request-level middleware helpers so you can protect entire routes or inject verified passport context into any async framework (FastAPI, Starlette, Litestar, etc.).

```python
from a1 import protect, inject_passport, a1_context, get_context, A1Context

# FastAPI example — protect a whole router
from fastapi import FastAPI, Depends

app = FastAPI()

@app.middleware("http")
async def a1_middleware(request, call_next):
    return await inject_passport(request, call_next, client=passport_client)

@app.get("/trade")
async def trade(ctx: A1Context = Depends(get_context)):
    print(ctx.namespace)         # "my-trading-agent"
    print(ctx.capability_mask)   # hex bitmask
    return await broker.place_order(...)

# Manual context propagation
set_context(A1Context(namespace="tenant-acme", capability_mask="..."))
ctx = get_context()
```

`MiddlewareError` is raised (and returns HTTP 403) when the passport is missing, expired, or the capability check fails.

---

## OpenTelemetry Tracing

Every authorization attempt can emit a structured OTEL span so your APM (Datadog, Jaeger, Honeycomb, any OTLP backend) has full distributed trace context through agent delegation chains.

```bash
pip install "a1[siem-otel]"
```

```python
from a1.otel import A1Tracer
from a1 import PassportClient

tracer = A1Tracer(service_name="acme-trading-bot")

# Option 1 — wrap an existing PassportClient
client = tracer.instrument_passport_client(PassportClient("http://localhost:8080"))
# All authorize() calls on `client` now emit spans automatically.

# Option 2 — decorator on any async function
@tracer.trace_capability("trade.equity")
async def execute_trade(symbol: str, qty: int) -> dict:
    return await broker.place_order(symbol, qty)
```

All spans use the `dyolo.a1.*` attribute namespace. The `trace_id` and `span_id` are attached to the A1 `AuditEvent` if the gateway's OTEL exporter is configured, giving end-to-end correlation from the HTTP request to the on-disk audit log.

**Noop tracer** (no-op for testing / non-OTEL environments):

```python
from a1.otel import noop_tracer
tracer = noop_tracer()
```

---

## API Reference

### `A1Client`

```python
from a1 import A1Client

client = A1Client(
    base_url="http://localhost:8080",
    timeout=10.0,               # seconds, default 10
    admin_secret=None,          # required for admin endpoints
    default_headers=None,       # e.g. {"X-A1-Tenant-ID": "acme"}
)

# Authorization
await client.authorize(chain, intent_name, intent_params, executor_pk_hex)
await client.authorize_batch(chain, intents, executor_pk_hex)

# Certificates (admin)
await client.issue_cert(delegate_pk_hex, intents, ttl_seconds, extensions)
await client.revoke_cert(fingerprint)
await client.revoke_certs_batch(fingerprints)

# JWT bridge
await client.exchange_jwt(token, capabilities, ttl_seconds, delegate_pk_hex)

# Negotiation
await client.negotiate(intent_name, agent_pk_hex, requested_ttl_seconds)

# Anchoring
await client.anchor(commitment, passport_did, network)

# Swarm (admin)
await client.create_swarm(swarm_name, capabilities, ttl_days, signing_key_hex)
await client.add_swarm_member(swarm_id, agent_pk_hex, role, capabilities, ttl_seconds, signing_key_hex)
await client.remove_swarm_member(swarm_id, agent_did)
await client.list_swarm_members(swarm_id)

# Utility
await client.health()
await client.well_known()
```

### `PassportClient`

```python
from a1.passport import PassportClient

client = PassportClient(
    gateway_url="http://localhost:8080",
    passport_path="passport.json",
    signer=None,           # optional VaultSigner for KMS-backed keys
    admin_secret=None,
)

await client.authorize(intent_name, executor_pk_hex)
await client.issue_sub(delegate_pk_hex, capabilities, ttl_seconds)
await client.inspect()
```

### `@a1_guard`

```python
from a1.passport import a1_guard, PassportClient

@a1_guard(client=client, capability="trade.equity")
async def my_tool(param: str, signed_chain: dict, executor_pk_hex: str) -> str:
    ...
```

The decorator injects `signed_chain` and `executor_pk_hex` if not provided by the caller. Raises `A1AuthorizationError` if the gateway rejects the authorization.

---

## Error Handling

```python
from a1.client import A1AuthorizationError, A1GatewayError

try:
    result = await client.authorize(...)
except A1AuthorizationError as e:
    # Denied: expired cert, scope violation, replay attack, etc.
    print(f"Denied: {e.reason}, error_code={e.error_code}")
except A1GatewayError as e:
    # Network error or unexpected gateway status
    print(f"Gateway error: {e}")
```

---

## Running the Gateway

```bash
git clone https://github.com/dyologician/a1.git
cd a1
cp .env.example .env      # fill in at minimum A1_PG_PASSWORD
docker compose up -d
curl http://localhost:8080/healthz
```

Key environment variables:

| Variable | Description |
|---|---|
| `A1_SIGNING_KEY_HEX` | 32-byte hex Ed25519 seed — **generate and set in production** |
| `A1_MAC_KEY_HEX` | 32-byte hex HMAC key — **generate and set in production** |
| `A1_ADMIN_SECRET` | Bearer token for admin endpoints — **required in production** |
| `A1_REDIS_URL` | Redis URL for production nonce/revocation stores |
| `A1_PG_URL` | Postgres URL for production nonce/revocation stores |
| `A1_JWT_JWKS_URL` | JWKS endpoint for JWT bridge (`/v1/jwt/exchange`) |
| `A1_NEGOTIATE_CAPABILITIES` | Comma-separated caps the negotiate endpoint may issue |
| `A1_MULTI_TENANT` | Set `true` to enable `X-A1-Tenant-ID` header enforcement |
| `A1_AI_KEY` | Anthropic API key — enables AI proxy for Studio |
| `GATEWAY_ADDR` | Bind address (default: `0.0.0.0:8080`) |

See `.env.example` in the repository root for the full list.

---

## Testing

```bash
pip install -e ".[dev]"
pytest
pytest --cov=a1 --cov-report=term-missing
```

---

## License

MIT OR Apache-2.0. See [LICENSE-MIT](https://github.com/dyologician/a1/blob/main/LICENSE-MIT) and [LICENSE-APACHE](https://github.com/dyologician/a1/blob/main/LICENSE-APACHE).

---

*Part of the [A1](https://github.com/dyologician/a1) ecosystem. Built and maintained by dyolo ([@dyologician](https://github.com/dyologician)).*