aip-agents 0.1.0__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,36 @@
1
+ # Rust
2
+ target/
3
+ Cargo.lock
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.pyc
8
+ *.egg-info/
9
+ dist/
10
+ .venv/
11
+ *.so
12
+ **/.pytest_cache/
13
+
14
+ # Paper (local only)
15
+ paper/
16
+ texput.log
17
+
18
+ # IETF draft (local only)
19
+ ietf/
20
+
21
+ # Planning (local only)
22
+ planning/
23
+
24
+ # Archives
25
+ *.tar.gz
26
+
27
+ # IDE
28
+ .idea/
29
+ .vscode/
30
+ *.swp
31
+
32
+ # OS
33
+ .DS_Store
34
+
35
+ # Claude
36
+ .claude/
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: aip-agents
3
+ Version: 0.1.0
4
+ Summary: AIP identity and delegation for AI agent frameworks
5
+ Project-URL: Homepage, https://github.com/sunilp/aip
6
+ Project-URL: Documentation, https://github.com/sunilp/aip#aip-agents
7
+ Project-URL: Repository, https://github.com/sunilp/aip
8
+ Author-email: Sunil Prakash <sunil@sunilprakash.com>
9
+ License-Expression: Apache-2.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Security :: Cryptography
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: base58>=2.1
18
+ Requires-Dist: biscuit-python>=0.4
19
+ Requires-Dist: cryptography>=43.0
20
+ Requires-Dist: pydantic>=2.0
21
+ Requires-Dist: pyjwt[crypto]>=2.9
22
+ Provides-Extra: adk
23
+ Requires-Dist: google-adk>=1.0; extra == 'adk'
24
+ Provides-Extra: all
25
+ Requires-Dist: crewai>=0.80; extra == 'all'
26
+ Requires-Dist: google-adk>=1.0; extra == 'all'
27
+ Provides-Extra: crewai
28
+ Requires-Dist: crewai>=0.80; extra == 'crewai'
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # aip-agents
35
+
36
+ AIP identity and delegation for AI agent frameworks. Add cryptographic identity, scoped delegation chains, and audit-ready token flows to your CrewAI and Google ADK agents in 5 lines of code.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install aip-agents[crewai] # CrewAI
42
+ pip install aip-agents[adk] # Google ADK
43
+ pip install aip-agents[all] # Both
44
+ ```
45
+
46
+ ## Quick Start: CrewAI
47
+
48
+ ```python
49
+ from crewai import Crew, Agent, Task
50
+ from aip_agents.adapters.crewai import AIPCrewPlugin
51
+
52
+ plugin = AIPCrewPlugin()
53
+
54
+ researcher = Agent(role="researcher", ...)
55
+ writer = Agent(role="writer", ...)
56
+ crew = Crew(agents=[researcher, writer], tasks=[...])
57
+
58
+ plugin.register(crew) # Each agent gets an AIP identity + delegation token
59
+ crew.kickoff()
60
+ ```
61
+
62
+ ## Quick Start: Google ADK
63
+
64
+ ```python
65
+ from google.adk import Agent, Runner
66
+ from aip_agents.adapters.adk import AIPAdkPlugin
67
+
68
+ plugin = AIPAdkPlugin()
69
+
70
+ agent = Agent(name="coordinator", sub_agents=[worker1, worker2], ...)
71
+ runner = Runner(agent=agent)
72
+
73
+ plugin.register(runner) # Walks agent tree, creates delegation chains
74
+ runner.run("task")
75
+ ```
76
+
77
+ ## What You Get
78
+
79
+ When you register a plugin, every agent gets:
80
+
81
+ 1. **Cryptographic identity** - An Ed25519 keypair and AIP identifier (`aip:key:ed25519:z...`)
82
+ 2. **Delegation chain** - When a parent agent delegates to a sub-agent, a Biscuit token chain records the delegation with attenuated scope
83
+ 3. **Tool call headers** - `X-AIP-Token` headers ready to attach to outgoing tool/MCP calls
84
+
85
+ Enable logging to see it in action:
86
+
87
+ ```python
88
+ from aip_agents import AIPConfig
89
+
90
+ plugin = AIPCrewPlugin(AIPConfig(log_tokens=True))
91
+ ```
92
+
93
+ Output:
94
+ ```
95
+ [AIP] Identity created: researcher -> aip:key:ed25519:z6Fk3...
96
+ [AIP] Delegation: manager -> researcher [scope: web_search] [chain depth: 2]
97
+ [AIP] Tool call: researcher -> web_search [chain depth: 3, verified]
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ ```python
103
+ AIPConfig(
104
+ app_name="my-app", # Root identity label
105
+ auto_identity=True, # Auto-assign identity to every agent
106
+ auto_delegation=True, # Auto-create delegation chains
107
+ persist_keys=False, # Save keys to ~/.aip/keys/
108
+ log_tokens=False, # Log token operations
109
+ default_scope=None, # Default scope for root token
110
+ )
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ - **Identity**: Each agent gets an Ed25519 keypair. The public key becomes the agent's AIP identifier.
116
+ - **Tokens**: Authority tokens use [Biscuit](https://www.biscuitsec.org/) - an append-only cryptographic token that enforces scope can only narrow, never widen.
117
+ - **Delegation**: When Agent A delegates to Agent B, a new block is appended to A's token with B's identity and attenuated scope. The chain is cryptographically verifiable.
118
+ - **Tool calls**: Tokens are attached via `X-AIP-Token` header, compatible with [AIP MCP middleware](https://github.com/sunilp/aip) for end-to-end verification.
119
+
120
+ ## Links
121
+
122
+ - [AIP Specification](https://github.com/sunilp/aip/tree/main/spec)
123
+ - [AIP Paper (arXiv:2603.24775)](https://arxiv.org/abs/2603.24775)
124
+ - [GitHub](https://github.com/sunilp/aip)
@@ -0,0 +1,91 @@
1
+ # aip-agents
2
+
3
+ AIP identity and delegation for AI agent frameworks. Add cryptographic identity, scoped delegation chains, and audit-ready token flows to your CrewAI and Google ADK agents in 5 lines of code.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install aip-agents[crewai] # CrewAI
9
+ pip install aip-agents[adk] # Google ADK
10
+ pip install aip-agents[all] # Both
11
+ ```
12
+
13
+ ## Quick Start: CrewAI
14
+
15
+ ```python
16
+ from crewai import Crew, Agent, Task
17
+ from aip_agents.adapters.crewai import AIPCrewPlugin
18
+
19
+ plugin = AIPCrewPlugin()
20
+
21
+ researcher = Agent(role="researcher", ...)
22
+ writer = Agent(role="writer", ...)
23
+ crew = Crew(agents=[researcher, writer], tasks=[...])
24
+
25
+ plugin.register(crew) # Each agent gets an AIP identity + delegation token
26
+ crew.kickoff()
27
+ ```
28
+
29
+ ## Quick Start: Google ADK
30
+
31
+ ```python
32
+ from google.adk import Agent, Runner
33
+ from aip_agents.adapters.adk import AIPAdkPlugin
34
+
35
+ plugin = AIPAdkPlugin()
36
+
37
+ agent = Agent(name="coordinator", sub_agents=[worker1, worker2], ...)
38
+ runner = Runner(agent=agent)
39
+
40
+ plugin.register(runner) # Walks agent tree, creates delegation chains
41
+ runner.run("task")
42
+ ```
43
+
44
+ ## What You Get
45
+
46
+ When you register a plugin, every agent gets:
47
+
48
+ 1. **Cryptographic identity** - An Ed25519 keypair and AIP identifier (`aip:key:ed25519:z...`)
49
+ 2. **Delegation chain** - When a parent agent delegates to a sub-agent, a Biscuit token chain records the delegation with attenuated scope
50
+ 3. **Tool call headers** - `X-AIP-Token` headers ready to attach to outgoing tool/MCP calls
51
+
52
+ Enable logging to see it in action:
53
+
54
+ ```python
55
+ from aip_agents import AIPConfig
56
+
57
+ plugin = AIPCrewPlugin(AIPConfig(log_tokens=True))
58
+ ```
59
+
60
+ Output:
61
+ ```
62
+ [AIP] Identity created: researcher -> aip:key:ed25519:z6Fk3...
63
+ [AIP] Delegation: manager -> researcher [scope: web_search] [chain depth: 2]
64
+ [AIP] Tool call: researcher -> web_search [chain depth: 3, verified]
65
+ ```
66
+
67
+ ## Configuration
68
+
69
+ ```python
70
+ AIPConfig(
71
+ app_name="my-app", # Root identity label
72
+ auto_identity=True, # Auto-assign identity to every agent
73
+ auto_delegation=True, # Auto-create delegation chains
74
+ persist_keys=False, # Save keys to ~/.aip/keys/
75
+ log_tokens=False, # Log token operations
76
+ default_scope=None, # Default scope for root token
77
+ )
78
+ ```
79
+
80
+ ## How It Works
81
+
82
+ - **Identity**: Each agent gets an Ed25519 keypair. The public key becomes the agent's AIP identifier.
83
+ - **Tokens**: Authority tokens use [Biscuit](https://www.biscuitsec.org/) - an append-only cryptographic token that enforces scope can only narrow, never widen.
84
+ - **Delegation**: When Agent A delegates to Agent B, a new block is appended to A's token with B's identity and attenuated scope. The chain is cryptographically verifiable.
85
+ - **Tool calls**: Tokens are attached via `X-AIP-Token` header, compatible with [AIP MCP middleware](https://github.com/sunilp/aip) for end-to-end verification.
86
+
87
+ ## Links
88
+
89
+ - [AIP Specification](https://github.com/sunilp/aip/tree/main/spec)
90
+ - [AIP Paper (arXiv:2603.24775)](https://arxiv.org/abs/2603.24775)
91
+ - [GitHub](https://github.com/sunilp/aip)
@@ -0,0 +1,17 @@
1
+ """aip-agents: AIP identity and delegation for AI agent frameworks."""
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.identity_manager import AIPIdentity, IdentityManager
5
+ from aip_agents.core.token_manager import TokenManager
6
+ from aip_agents.core.key_store import KeyStore
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ __all__ = [
11
+ "AIPConfig",
12
+ "AIPIdentity",
13
+ "IdentityManager",
14
+ "KeyStore",
15
+ "TokenManager",
16
+ "__version__",
17
+ ]
@@ -0,0 +1 @@
1
+ """Framework adapters for AIP identity."""
@@ -0,0 +1,3 @@
1
+ """Google ADK adapter for AIP identity and delegation."""
2
+ from aip_agents.adapters.adk.plugin import AIPAdkPlugin
3
+ __all__ = ["AIPAdkPlugin"]
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.identity_manager import IdentityManager
5
+ from aip_agents.core.logger import AIPLogger
6
+ from aip_agents.core.token_manager import TokenManager
7
+
8
+
9
+ class AIPAdkPlugin:
10
+ """AIP identity and delegation plugin for Google ADK."""
11
+
12
+ def __init__(self, config: AIPConfig | None = None):
13
+ self._config = config or AIPConfig()
14
+ self._identity_manager = IdentityManager(self._config)
15
+ self._token_manager = TokenManager(self._identity_manager, self._config)
16
+ self._logger = AIPLogger(enabled=self._config.log_tokens)
17
+ self._agent_tokens: dict[str, str] = {}
18
+ self._agent_scopes: dict[str, list[str]] = {}
19
+ # Tracks the name of the authority (chain root) whose keypair signed the biscuit
20
+ self._authority_name: dict[str, str] = {}
21
+
22
+ @property
23
+ def identity_manager(self) -> IdentityManager:
24
+ return self._identity_manager
25
+
26
+ @property
27
+ def token_manager(self) -> TokenManager:
28
+ return self._token_manager
29
+
30
+ def register(self, runner) -> None:
31
+ root_agent = runner.agent
32
+ self._register_agent_tree(root_agent, parent_name=None, authority_name=None)
33
+
34
+ def _register_agent_tree(
35
+ self,
36
+ agent,
37
+ parent_name: str | None,
38
+ authority_name: str | None,
39
+ ) -> None:
40
+ name = agent.name
41
+ identity = self._identity_manager.register(name)
42
+ self._logger.identity_created(name, identity.aip_id)
43
+ scope = self._extract_tool_names(agent)
44
+ self._agent_scopes[name] = scope
45
+
46
+ if parent_name is None:
47
+ # Root agent: issue a chained authority token under its own key
48
+ token = self._token_manager.issue_chained(name, scope=scope)
49
+ self._agent_tokens[name] = token
50
+ self._authority_name[name] = name
51
+ self._logger.token_issued(name, scope, "chained")
52
+ effective_authority = name
53
+ else:
54
+ # Sub-agent: delegate from parent's token using the chain authority's key
55
+ parent_token = self._agent_tokens.get(parent_name)
56
+ if parent_token is not None:
57
+ # Always use the chain authority name so from_base64 uses the right pubkey
58
+ chain_authority = authority_name or parent_name
59
+ delegation_token = self._token_manager.delegate(
60
+ parent_token=parent_token,
61
+ parent_name=chain_authority,
62
+ child_name=name,
63
+ attenuated_scope=scope,
64
+ context=f"Sub-agent delegation: {parent_name} -> {name}",
65
+ )
66
+ self._agent_tokens[name] = delegation_token
67
+ self._authority_name[name] = chain_authority
68
+ depth = self._token_manager.chain_depth(delegation_token)
69
+ self._logger.delegation(parent_name, name, scope, depth)
70
+ effective_authority = chain_authority
71
+ else:
72
+ token = self._token_manager.issue_chained(name, scope=scope)
73
+ self._agent_tokens[name] = token
74
+ self._authority_name[name] = name
75
+ effective_authority = name
76
+
77
+ for sub_agent in getattr(agent, "sub_agents", []):
78
+ self._register_agent_tree(
79
+ sub_agent,
80
+ parent_name=name,
81
+ authority_name=effective_authority,
82
+ )
83
+
84
+ def get_agent_token(self, name: str) -> str | None:
85
+ return self._agent_tokens.get(name)
86
+
87
+ def get_agent_scope(self, name: str) -> list[str]:
88
+ return self._agent_scopes.get(name, [])
89
+
90
+ def get_chain_depth(self, name: str) -> int:
91
+ token = self._agent_tokens.get(name)
92
+ if token is None:
93
+ raise ValueError(f"No token for agent '{name}'")
94
+ return self._token_manager.chain_depth(token)
95
+
96
+ def get_tool_call_headers(self, name: str) -> dict[str, str]:
97
+ token = self._agent_tokens.get(name)
98
+ if token is None:
99
+ return {}
100
+ return {"X-AIP-Token": token}
101
+
102
+ def _extract_tool_names(self, agent) -> list[str]:
103
+ names = []
104
+ for tool in getattr(agent, "tools", []):
105
+ if hasattr(tool, "name"):
106
+ names.append(tool.name)
107
+ elif isinstance(tool, str):
108
+ names.append(tool)
109
+ return names if names else ["*"]
@@ -0,0 +1,3 @@
1
+ """CrewAI adapter for AIP identity and delegation."""
2
+ from aip_agents.adapters.crewai.plugin import AIPCrewPlugin
3
+ __all__ = ["AIPCrewPlugin"]
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.identity_manager import IdentityManager
5
+ from aip_agents.core.logger import AIPLogger
6
+ from aip_agents.core.token_manager import TokenManager
7
+
8
+
9
+ class AIPCrewPlugin:
10
+ """AIP identity and delegation plugin for CrewAI."""
11
+
12
+ def __init__(self, config: AIPConfig | None = None):
13
+ self._config = config or AIPConfig()
14
+ self._identity_manager = IdentityManager(self._config)
15
+ self._token_manager = TokenManager(self._identity_manager, self._config)
16
+ self._logger = AIPLogger(enabled=self._config.log_tokens)
17
+ self._agent_tokens: dict[str, str] = {}
18
+ self._agent_scopes: dict[str, list[str]] = {}
19
+
20
+ @property
21
+ def identity_manager(self) -> IdentityManager:
22
+ return self._identity_manager
23
+
24
+ @property
25
+ def token_manager(self) -> TokenManager:
26
+ return self._token_manager
27
+
28
+ def register(self, crew) -> None:
29
+ for agent in crew.agents:
30
+ role = agent.role
31
+ identity = self._identity_manager.register(role)
32
+ self._logger.identity_created(role, identity.aip_id)
33
+ scope = self._extract_tool_names(agent)
34
+ self._agent_scopes[role] = scope
35
+ if self._config.auto_delegation:
36
+ token = self._token_manager.issue_chained(role, scope=scope)
37
+ else:
38
+ token = self._token_manager.issue(role, scope=scope)
39
+ self._agent_tokens[role] = token
40
+ self._logger.token_issued(role, scope, "chained" if self._config.auto_delegation else "compact")
41
+
42
+ def get_agent_token(self, role: str) -> str | None:
43
+ return self._agent_tokens.get(role)
44
+
45
+ def get_agent_scope(self, role: str) -> list[str]:
46
+ return self._agent_scopes.get(role, [])
47
+
48
+ def create_delegation(self, parent_role: str, child_role: str, task_description: str, scope: list[str] | None = None) -> str:
49
+ parent_token = self._agent_tokens.get(parent_role)
50
+ if parent_token is None:
51
+ raise ValueError(f"No token found for agent '{parent_role}'")
52
+ effective_scope = scope or self._agent_scopes.get(child_role, [])
53
+ delegation_token = self._token_manager.delegate(
54
+ parent_token=parent_token,
55
+ parent_name=parent_role,
56
+ child_name=child_role,
57
+ attenuated_scope=effective_scope,
58
+ context=task_description,
59
+ )
60
+ depth = self._token_manager.chain_depth(delegation_token)
61
+ self._logger.delegation(parent_role, child_role, effective_scope, depth)
62
+ self._agent_tokens[child_role] = delegation_token
63
+ return delegation_token
64
+
65
+ def get_tool_call_headers(self, role: str) -> dict[str, str]:
66
+ token = self._agent_tokens.get(role)
67
+ if token is None:
68
+ return {}
69
+ return {"X-AIP-Token": token}
70
+
71
+ def _extract_tool_names(self, agent) -> list[str]:
72
+ names = []
73
+ for tool in getattr(agent, "tools", []):
74
+ if hasattr(tool, "name"):
75
+ names.append(tool.name)
76
+ elif isinstance(tool, str):
77
+ names.append(tool)
78
+ return names if names else ["*"]
@@ -0,0 +1,8 @@
1
+ """Core identity, token, and key management."""
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.identity_manager import IdentityManager, AIPIdentity
5
+ from aip_agents.core.key_store import KeyStore
6
+ from aip_agents.core.token_manager import TokenManager
7
+
8
+ __all__ = ["AIPConfig", "AIPIdentity", "IdentityManager", "KeyStore", "TokenManager"]
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass, field
2
+
3
+
4
+ @dataclass
5
+ class AIPConfig:
6
+ """Configuration for AIP agent identity and delegation."""
7
+
8
+ app_name: str = "aip-app"
9
+ auto_identity: bool = True
10
+ auto_delegation: bool = True
11
+ persist_keys: bool = False
12
+ log_tokens: bool = False
13
+ default_scope: list[str] | None = None
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.key_store import KeyStore
5
+ from aip_core.crypto import KeyPair
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class AIPIdentity:
10
+ """An agent's AIP identity."""
11
+ name: str
12
+ aip_id: str
13
+ public_key_bytes: bytes
14
+ keypair: KeyPair
15
+
16
+
17
+ class IdentityManager:
18
+ """Creates and caches AIP identities for agents."""
19
+
20
+ def __init__(self, config: AIPConfig):
21
+ self._config = config
22
+ persist_dir = None
23
+ if config.persist_keys:
24
+ import os
25
+ persist_dir = os.path.expanduser(f"~/.aip/keys/{config.app_name}")
26
+ self._key_store = KeyStore(persist_dir=persist_dir)
27
+ self._identities: dict[str, AIPIdentity] = {}
28
+ self._root = self._create_identity(config.app_name)
29
+
30
+ def _create_identity(self, name: str) -> AIPIdentity:
31
+ if name in self._identities:
32
+ return self._identities[name]
33
+ kp = self._key_store.get_or_create(name)
34
+ aip_id = f"aip:key:ed25519:{kp.public_key_multibase()}"
35
+ identity = AIPIdentity(
36
+ name=name,
37
+ aip_id=aip_id,
38
+ public_key_bytes=kp.public_key_bytes(),
39
+ keypair=kp,
40
+ )
41
+ self._identities[name] = identity
42
+ return identity
43
+
44
+ def root_identity(self) -> AIPIdentity:
45
+ return self._root
46
+
47
+ def register(self, agent_name: str) -> AIPIdentity:
48
+ return self._create_identity(agent_name)
49
+
50
+ def get(self, agent_name: str) -> AIPIdentity | None:
51
+ return self._identities.get(agent_name)
52
+
53
+ def all(self) -> list[AIPIdentity]:
54
+ return list(self._identities.values())
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+
3
+ from aip_core.crypto import KeyPair
4
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
5
+
6
+
7
+ class KeyStore:
8
+ """Manages Ed25519 keypairs for agents. In-memory by default, optional disk persistence."""
9
+
10
+ def __init__(self, persist_dir: str | None = None):
11
+ self._keys: dict[str, KeyPair] = {}
12
+ self._persist_dir = Path(persist_dir) if persist_dir else None
13
+ if self._persist_dir:
14
+ self._persist_dir.mkdir(parents=True, exist_ok=True)
15
+
16
+ def get_or_create(self, name: str) -> KeyPair:
17
+ if name in self._keys:
18
+ return self._keys[name]
19
+
20
+ if self._persist_dir:
21
+ key_file = self._persist_dir / f"{name}.key"
22
+ if key_file.exists():
23
+ raw = key_file.read_bytes()
24
+ private_key = Ed25519PrivateKey.from_private_bytes(raw)
25
+ kp = KeyPair(private_key)
26
+ self._keys[name] = kp
27
+ return kp
28
+
29
+ kp = KeyPair.generate()
30
+ self._keys[name] = kp
31
+
32
+ if self._persist_dir:
33
+ key_file = self._persist_dir / f"{name}.key"
34
+ key_file.write_bytes(kp.private_key_bytes())
35
+ key_file.chmod(0o600)
36
+
37
+ return kp
38
+
39
+ def has(self, name: str) -> bool:
40
+ if name in self._keys:
41
+ return True
42
+ if self._persist_dir:
43
+ return (self._persist_dir / f"{name}.key").exists()
44
+ return False
@@ -0,0 +1,27 @@
1
+ class AIPLogger:
2
+ """Structured logging for AIP token operations."""
3
+
4
+ def __init__(self, enabled: bool = False):
5
+ self._enabled = enabled
6
+
7
+ def identity_created(self, name: str, aip_id: str) -> None:
8
+ if self._enabled:
9
+ print(f"[AIP] Identity created: {name} -> {aip_id}")
10
+
11
+ def token_issued(self, agent_name: str, scope: list[str], mode: str) -> None:
12
+ if self._enabled:
13
+ print(f"[AIP] Token issued: {agent_name} scope={scope} mode={mode}")
14
+
15
+ def delegation(self, parent: str, child: str, scope: list[str], chain_depth: int) -> None:
16
+ if self._enabled:
17
+ print(
18
+ f"[AIP] Delegation: {parent} -> {child} "
19
+ f"[scope: {','.join(scope)}] [chain depth: {chain_depth}]"
20
+ )
21
+
22
+ def tool_call(self, agent_name: str, tool_name: str, chain_depth: int) -> None:
23
+ if self._enabled:
24
+ print(
25
+ f"[AIP] Tool call: {agent_name} -> {tool_name} "
26
+ f"[chain depth: {chain_depth}]"
27
+ )
@@ -0,0 +1,111 @@
1
+ import time
2
+
3
+ from aip_agents.core.config import AIPConfig
4
+ from aip_agents.core.identity_manager import IdentityManager
5
+ from aip_token.claims import AipClaims
6
+ from aip_token.compact import CompactToken
7
+ from aip_token.chained import ChainedToken
8
+ from aip_token.error import TokenError
9
+
10
+
11
+ class TokenManager:
12
+ """Issues compact tokens and chained delegation tokens for agents."""
13
+
14
+ DEFAULT_TTL = 3600
15
+
16
+ def __init__(self, identity_manager: IdentityManager, config: AIPConfig):
17
+ self._id_mgr = identity_manager
18
+ self._config = config
19
+
20
+ def issue(self, agent_name: str, scope: list[str], ttl: int = DEFAULT_TTL) -> str:
21
+ identity = self._id_mgr.get(agent_name)
22
+ if identity is None:
23
+ raise TokenError(f"Agent '{agent_name}' not registered", "identity_unresolvable")
24
+ root = self._id_mgr.root_identity()
25
+ now = int(time.time())
26
+ claims = AipClaims(
27
+ iss=root.aip_id,
28
+ sub=identity.aip_id,
29
+ scope=scope,
30
+ max_depth=0,
31
+ iat=now,
32
+ exp=now + ttl,
33
+ )
34
+ return CompactToken.create(claims, root.keypair)
35
+
36
+ def verify(self, token_str: str, required_scope: str) -> CompactToken:
37
+ root = self._id_mgr.root_identity()
38
+ verified = CompactToken.verify(token_str, root.public_key_bytes)
39
+ if not verified.has_scope(required_scope):
40
+ raise TokenError(
41
+ f"Token does not authorize '{required_scope}'",
42
+ "scope_insufficient",
43
+ )
44
+ return verified
45
+
46
+ def issue_chained(
47
+ self,
48
+ agent_name: str,
49
+ scope: list[str],
50
+ max_depth: int = 5,
51
+ budget_cents: int | None = None,
52
+ ttl: int = DEFAULT_TTL,
53
+ ) -> str:
54
+ identity = self._id_mgr.get(agent_name)
55
+ if identity is None:
56
+ raise TokenError(f"Agent '{agent_name}' not registered", "identity_unresolvable")
57
+ token = ChainedToken.create_authority(
58
+ issuer=identity.aip_id,
59
+ scopes=scope,
60
+ budget_cents=budget_cents,
61
+ max_depth=max_depth,
62
+ ttl_seconds=ttl,
63
+ keypair=identity.keypair,
64
+ )
65
+ return token.to_base64()
66
+
67
+ def delegate(
68
+ self,
69
+ parent_token: str,
70
+ parent_name: str,
71
+ child_name: str,
72
+ attenuated_scope: list[str],
73
+ context: str,
74
+ budget_cents: int | None = None,
75
+ ) -> str:
76
+ parent_identity = self._id_mgr.get(parent_name)
77
+ child_identity = self._id_mgr.get(child_name)
78
+ if parent_identity is None:
79
+ raise TokenError(f"Agent '{parent_name}' not registered", "identity_unresolvable")
80
+ if child_identity is None:
81
+ raise TokenError(f"Agent '{child_name}' not registered", "identity_unresolvable")
82
+ chained = ChainedToken.from_base64(
83
+ parent_token, parent_identity.public_key_bytes
84
+ )
85
+ delegated = chained.delegate(
86
+ delegator=parent_identity.aip_id,
87
+ delegate=child_identity.aip_id,
88
+ scopes=attenuated_scope,
89
+ budget_cents=budget_cents,
90
+ context=context,
91
+ )
92
+ return delegated.to_base64()
93
+
94
+ def authorize_chained(self, token_str: str, tool: str) -> None:
95
+ for identity in self._id_mgr.all():
96
+ try:
97
+ chained = ChainedToken.from_base64(token_str, identity.public_key_bytes)
98
+ chained.authorize(tool, identity.public_key_bytes)
99
+ return
100
+ except Exception:
101
+ continue
102
+ raise TokenError(f"No valid authority found for tool '{tool}'", "scope_insufficient")
103
+
104
+ def chain_depth(self, token_str: str) -> int:
105
+ for identity in self._id_mgr.all():
106
+ try:
107
+ chained = ChainedToken.from_base64(token_str, identity.public_key_bytes)
108
+ return chained.current_depth()
109
+ except Exception:
110
+ continue
111
+ raise TokenError("Cannot determine chain depth", "token_malformed")
File without changes
@@ -0,0 +1,57 @@
1
+ """Example: Add AIP identity and delegation to Google ADK agents.
2
+
3
+ Usage:
4
+ pip install aip-agents[adk]
5
+ python adk_example.py
6
+ """
7
+
8
+ from google.adk import Agent, Runner
9
+ from aip_agents import AIPConfig
10
+ from aip_agents.adapters.adk import AIPAdkPlugin
11
+
12
+ # 1. Create the plugin
13
+ plugin = AIPAdkPlugin(AIPConfig(
14
+ app_name="research-pipeline",
15
+ log_tokens=True,
16
+ ))
17
+
18
+ # 2. Define sub-agents
19
+ summarizer = Agent(
20
+ name="summarizer",
21
+ model="gemini-2.5-flash",
22
+ instruction="Summarize the research findings concisely.",
23
+ description="Summarization specialist",
24
+ tools=[],
25
+ )
26
+
27
+ fact_checker = Agent(
28
+ name="fact_checker",
29
+ model="gemini-2.5-flash",
30
+ instruction="Verify claims against sources.",
31
+ description="Fact verification agent",
32
+ tools=[],
33
+ )
34
+
35
+ # 3. Define coordinator with sub-agents
36
+ coordinator = Agent(
37
+ name="coordinator",
38
+ model="gemini-2.5-flash",
39
+ instruction="Route tasks to the appropriate sub-agent.",
40
+ description="Task coordinator",
41
+ tools=[],
42
+ sub_agents=[summarizer, fact_checker],
43
+ )
44
+
45
+ # 4. Create runner and register plugin
46
+ runner = Runner(agent=coordinator)
47
+ plugin.register(runner)
48
+
49
+ # 5. Inspect delegation chains
50
+ print("\n--- Delegation Chain Depths ---")
51
+ for name in ["coordinator", "summarizer", "fact_checker"]:
52
+ depth = plugin.get_chain_depth(name)
53
+ print(f"{name}: depth {depth}")
54
+
55
+ print("\n--- Tool Call Headers ---")
56
+ headers = plugin.get_tool_call_headers("summarizer")
57
+ print(f"X-AIP-Token: {headers.get('X-AIP-Token', 'N/A')[:50]}...")
@@ -0,0 +1,74 @@
1
+ """Example: Add AIP identity and delegation to a CrewAI crew.
2
+
3
+ Usage:
4
+ pip install aip-agents[crewai]
5
+ python crewai_example.py
6
+ """
7
+
8
+ from crewai import Agent, Crew, Task, Process
9
+ from aip_agents import AIPConfig
10
+ from aip_agents.adapters.crewai import AIPCrewPlugin
11
+
12
+ # 1. Create the plugin (with logging to see what happens)
13
+ plugin = AIPCrewPlugin(AIPConfig(
14
+ app_name="research-crew",
15
+ log_tokens=True,
16
+ ))
17
+
18
+ # 2. Define your agents as normal
19
+ researcher = Agent(
20
+ role="researcher",
21
+ goal="Find accurate information about AI agent identity protocols",
22
+ backstory="Expert at web research and source verification",
23
+ tools=[],
24
+ allow_delegation=False,
25
+ )
26
+
27
+ writer = Agent(
28
+ role="writer",
29
+ goal="Write clear, concise summaries of research findings",
30
+ backstory="Technical writer specializing in AI systems",
31
+ tools=[],
32
+ allow_delegation=False,
33
+ )
34
+
35
+ # 3. Define tasks
36
+ research_task = Task(
37
+ description="Research the current state of AI agent identity protocols",
38
+ expected_output="A summary of existing protocols and gaps",
39
+ agent=researcher,
40
+ )
41
+
42
+ write_task = Task(
43
+ description="Write a blog post based on the research",
44
+ expected_output="A 500-word blog post",
45
+ agent=writer,
46
+ )
47
+
48
+ # 4. Create crew and register plugin
49
+ crew = Crew(
50
+ agents=[researcher, writer],
51
+ tasks=[research_task, write_task],
52
+ process=Process.sequential,
53
+ )
54
+
55
+ plugin.register(crew)
56
+
57
+ # 5. Access tokens programmatically
58
+ print("\n--- Agent Identities ---")
59
+ for agent_name in ["researcher", "writer"]:
60
+ identity = plugin.identity_manager.get(agent_name)
61
+ print(f"{agent_name}: {identity.aip_id}")
62
+
63
+ print("\n--- Tool Call Headers ---")
64
+ headers = plugin.get_tool_call_headers("researcher")
65
+ print(f"X-AIP-Token: {headers.get('X-AIP-Token', 'N/A')[:50]}...")
66
+
67
+ # 6. Create delegation
68
+ delegation_token = plugin.create_delegation(
69
+ parent_role="researcher",
70
+ child_role="writer",
71
+ task_description="Write summary based on research findings",
72
+ scope=["write", "summarize"],
73
+ )
74
+ print(f"\nDelegation chain depth: {plugin.token_manager.chain_depth(delegation_token)}")
File without changes
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aip-agents"
7
+ version = "0.1.0"
8
+ description = "AIP identity and delegation for AI agent frameworks"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Sunil Prakash", email = "sunil@sunilprakash.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: Apache Software License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Security :: Cryptography",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+ dependencies = [
24
+ "cryptography>=43.0",
25
+ "PyJWT[crypto]>=2.9",
26
+ "pydantic>=2.0",
27
+ "base58>=2.1",
28
+ "biscuit-python>=0.4",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ crewai = ["crewai>=0.80"]
33
+ adk = ["google-adk>=1.0"]
34
+ all = ["aip-agents[crewai,adk]"]
35
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.24"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/sunilp/aip"
39
+ Documentation = "https://github.com/sunilp/aip#aip-agents"
40
+ Repository = "https://github.com/sunilp/aip"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["aip_agents"]
44
+
45
+ [tool.hatch.build]
46
+ root = "."