attest-sdk 0.1.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ pip-wheel-metadata/
20
+ share/python-wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+ .pytest_cache/
26
+ .coverage
27
+ *.cover
28
+ .hypothesis/
29
+ .venv/
30
+ venv/
31
+ ENV/
32
+ env/
33
+ .env
34
+ .env.local
35
+
36
+ # Node / TypeScript
37
+ node_modules/
38
+ dist/
39
+ build/
40
+ *.tsbuildinfo
41
+ .npm
42
+ npm-debug.log*
43
+ yarn-debug.log*
44
+ yarn-error.log*
45
+
46
+ # Go
47
+ *.o
48
+ *.a
49
+ *.so
50
+ *.exe
51
+ *.exe~
52
+ *.dll
53
+ *.so
54
+ *.dylib
55
+ server/attest
56
+
57
+
58
+ # IDEs
59
+ .vscode/
60
+ .idea/
61
+ *.swp
62
+ *.swo
63
+ *~
64
+ .DS_Store
65
+ *.sublime-project
66
+ *.sublime-workspace
67
+
68
+ # OS
69
+ .DS_Store
70
+ Thumbs.db
71
+
72
+ # Environment
73
+ .env
74
+ .env.local
75
+ .env.*.local
76
+
77
+ # Build outputs
78
+ bin/
79
+ out/
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: attest-sdk
3
+ Version: 0.1.0b1
4
+ Summary: Python SDK for the Attest cryptographic agent credential service
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27
7
+ Requires-Dist: pyjwt[crypto]>=2.8
8
+ Provides-Extra: anthropic
9
+ Requires-Dist: anthropic>=0.25; extra == 'anthropic'
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest; extra == 'dev'
12
+ Requires-Dist: pytest-asyncio; extra == 'dev'
13
+ Requires-Dist: respx; extra == 'dev'
14
+ Provides-Extra: langgraph
15
+ Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
16
+ Requires-Dist: langgraph>=0.2; extra == 'langgraph'
@@ -0,0 +1,148 @@
1
+ # attest-sdk
2
+
3
+ Python SDK for the [Attest](https://github.com/attest-dev/attest) cryptographic agent credential service.
4
+
5
+ Attest issues RS256-signed JWTs to AI agents. Each token carries:
6
+ - `att_scope` — list of `"resource:action"` permission strings
7
+ - `att_chain` — ordered delegation lineage (list of JTIs)
8
+ - `att_depth` — delegation depth (0 = root)
9
+ - `att_intent` — SHA-256 hex of the original instruction
10
+ - `att_tid` — task tree UUID shared across the chain
11
+ - `att_uid` — originating human user ID
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install attest-sdk
17
+
18
+ # With LangGraph integration
19
+ pip install "attest-sdk[langgraph]"
20
+ ```
21
+
22
+ ## Basic usage
23
+
24
+ ```python
25
+ from attest import AttestClient, IssueParams, DelegateParams
26
+
27
+ client = AttestClient(
28
+ base_url="http://localhost:8080",
29
+ api_key="your-api-key",
30
+ )
31
+
32
+ # Issue a root credential
33
+ token = client.issue(IssueParams(
34
+ agent_id="orchestrator-v1",
35
+ user_id="user-42",
36
+ scope=["research:read", "gmail:send"],
37
+ instruction="Draft and send the quarterly report",
38
+ ttl_seconds=3600,
39
+ ))
40
+ print(token.claims.att_tid) # task tree UUID
41
+ print(token.claims.att_scope) # ["research:read", "gmail:send"]
42
+
43
+ # Delegate to a child agent (scope must be a subset)
44
+ child = client.delegate(DelegateParams(
45
+ parent_token=token.token,
46
+ child_agent="email-agent-v1",
47
+ child_scope=["gmail:send"],
48
+ ))
49
+
50
+ # Offline verify — fetch JWKS once, reuse it
51
+ jwks = client.fetch_jwks()
52
+ result = client.verify(token.token, jwks=jwks) # no server call needed
53
+ if result.valid:
54
+ print("Issuer:", result.claims.iss)
55
+ else:
56
+ print("Invalid:", result.warnings)
57
+
58
+ # Check revocation
59
+ is_revoked = client.check_revoked(token.claims.jti)
60
+
61
+ # Revoke (cascades to all descendants)
62
+ client.revoke(token.claims.jti, revoked_by="orchestrator")
63
+
64
+ # Audit trail for the whole task tree
65
+ chain = client.audit(token.claims.att_tid)
66
+ for event in chain.events:
67
+ print(event.event_type, event.agent_id, event.created_at)
68
+ ```
69
+
70
+ ## Async client
71
+
72
+ ```python
73
+ import asyncio
74
+ from attest import AsyncAttestClient, IssueParams
75
+
76
+ async def main():
77
+ async with AsyncAttestClient(api_key="your-api-key") as client:
78
+ token = await client.issue(IssueParams(
79
+ agent_id="async-agent",
80
+ user_id="user-1",
81
+ scope=["files:read"],
82
+ instruction="Read the file",
83
+ ))
84
+ jwks = await client.fetch_jwks()
85
+ result = await client.verify(token.token, jwks=jwks)
86
+ print(result.valid)
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ## LangGraph integration
92
+
93
+ ```python
94
+ from typing import TypedDict
95
+ from attest import AttestClient
96
+ from attest.integrations.langgraph import AttestState, attest_tool, AttestNodes
97
+
98
+ client = AttestClient(api_key="your-api-key")
99
+
100
+ # 1. Extend AttestState with your own fields
101
+ class MyState(AttestState):
102
+ messages: list
103
+ instruction: str
104
+ user_id: str
105
+
106
+ # 2. Issue at graph entry — stores JWT in state["attest_tokens"]["orchestrator-v1"]
107
+ graph.add_node("issue", AttestNodes.issue(
108
+ client=client,
109
+ agent_id="orchestrator-v1",
110
+ scope=["research:read", "gmail:send"],
111
+ instruction_key="instruction",
112
+ user_id_key="user_id",
113
+ ))
114
+
115
+ # 3. Enforce scope at tool call — raises AttestScopeError if not covered
116
+ @attest_tool(scope="gmail:send", agent_id="email-agent-v1")
117
+ def send_email(state: MyState, to: str, body: str) -> str:
118
+ ...
119
+
120
+ # 4. Delegate when spawning a sub-agent
121
+ graph.add_node("spawn_email_agent", AttestNodes.delegate(
122
+ client=client,
123
+ parent_agent_id="orchestrator-v1",
124
+ child_agent_id="email-agent-v1",
125
+ child_scope=["gmail:send"],
126
+ ))
127
+
128
+ # 5. Revoke at graph teardown
129
+ graph.add_node("cleanup", AttestNodes.revoke(
130
+ client=client,
131
+ agent_id="orchestrator-v1",
132
+ ))
133
+ ```
134
+
135
+ ## Offline verification note
136
+
137
+ Once you have fetched the JWKS with `client.fetch_jwks()`, you can verify
138
+ any token from the same server without making additional network calls:
139
+
140
+ ```python
141
+ jwks = client.fetch_jwks() # one network call
142
+
143
+ for token_str in incoming_tokens:
144
+ result = client.verify(token_str, jwks=jwks) # pure local crypto
145
+ ```
146
+
147
+ The public key is stable for the lifetime of the server instance; cache it
148
+ as long as your process runs.
@@ -0,0 +1,108 @@
1
+ """Attest Python SDK.
2
+
3
+ Attest issues RS256-signed JWTs to AI agents, carrying cryptographic scope,
4
+ delegation lineage, and task-tree provenance.
5
+
6
+ Quick start::
7
+
8
+ from attest import AttestClient, IssueParams
9
+
10
+ client = AttestClient(api_key="your-api-key")
11
+ token = client.issue(IssueParams(
12
+ agent_id="my-agent",
13
+ user_id="user-123",
14
+ scope=["files:read", "files:write"],
15
+ instruction="Summarise the quarterly report",
16
+ ))
17
+ print(token.claims.att_tid) # task tree UUID
18
+ print(token.claims.att_scope) # ["files:read", "files:write"]
19
+ """
20
+
21
+ from attest.checksum import compute_agent_checksum
22
+ from attest.client import (
23
+ AsyncAttestClient,
24
+ AttestAPIError,
25
+ AttestClient,
26
+ AttestError,
27
+ AttestScopeError,
28
+ AttestVerifyError,
29
+ )
30
+ from attest.scope import is_subset, normalise_scope, parse_scope
31
+ from attest.verifier import AttestVerifier
32
+ from attest.types import (
33
+ AuditChain,
34
+ AuditEvent,
35
+ DelegateParams,
36
+ DelegatedToken,
37
+ IssueParams,
38
+ VerifyResult,
39
+ AttestClaims,
40
+ AttestToken,
41
+ )
42
+
43
+ # Framework integrations (lazy — only usable when the framework is installed).
44
+ from attest.integrations.langgraph import (
45
+ AttestNodes,
46
+ AttestState,
47
+ AttestStateGraph,
48
+ current_attest_token,
49
+ attest_tool,
50
+ )
51
+ from attest.integrations.openai_agents import (
52
+ AttestContext,
53
+ AttestRunHooks,
54
+ attest_tool_openai,
55
+ )
56
+ from attest.integrations.anthropic_sdk import (
57
+ AttestSession,
58
+ AsyncAttestSession,
59
+ current_attest_session,
60
+ attest_tool_anthropic,
61
+ )
62
+
63
+ __version__ = "0.1.0"
64
+
65
+ __all__ = [
66
+ # Clients
67
+ "AttestClient",
68
+ "AsyncAttestClient",
69
+ # Errors
70
+ "AttestError",
71
+ "AttestAPIError",
72
+ "AttestScopeError",
73
+ "AttestVerifyError",
74
+ # Types
75
+ "AttestClaims",
76
+ "AttestToken",
77
+ "DelegatedToken",
78
+ "VerifyResult",
79
+ "AuditChain",
80
+ "AuditEvent",
81
+ "IssueParams",
82
+ "DelegateParams",
83
+ # Scope utilities
84
+ "is_subset",
85
+ "parse_scope",
86
+ "normalise_scope",
87
+ # LangGraph integration
88
+ "AttestState",
89
+ "AttestNodes",
90
+ "AttestStateGraph",
91
+ "attest_tool",
92
+ "current_attest_token",
93
+ # OpenAI Agents SDK integration
94
+ "AttestContext",
95
+ "AttestRunHooks",
96
+ "attest_tool_openai",
97
+ # Anthropic SDK integration
98
+ "AttestSession",
99
+ "AsyncAttestSession",
100
+ "current_attest_session",
101
+ "attest_tool_anthropic",
102
+ # Checksum utility
103
+ "compute_agent_checksum",
104
+ # Standalone verifier
105
+ "AttestVerifier",
106
+ # Version
107
+ "__version__",
108
+ ]
@@ -0,0 +1,15 @@
1
+ """Agent checksum computation for att_ack claim."""
2
+
3
+ import hashlib
4
+ import json
5
+
6
+
7
+ def compute_agent_checksum(system_prompt: str, tools: list[dict] | None = None) -> str:
8
+ """SHA-256 of canonical JSON of {system_prompt, tools}.
9
+
10
+ Uses sort_keys=True and compact separators so the result is deterministic
11
+ regardless of key insertion order or Python version.
12
+ """
13
+ payload = {"system_prompt": system_prompt, "tools": tools or []}
14
+ canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
15
+ return hashlib.sha256(canonical.encode()).hexdigest()