ldp-protocol 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,10 @@
1
+ /target
2
+ Cargo.lock
3
+ *.swp
4
+ *.swo
5
+ .DS_Store
6
+ .venv/
7
+ __pycache__/
8
+
9
+ # Private planning docs (local only)
10
+ /private/
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldp-protocol
3
+ Version: 0.1.0
4
+ Summary: LDP — LLM Delegate Protocol: identity-aware communication for multi-agent LLM systems
5
+ Project-URL: Homepage, https://github.com/sunilp/ldp-protocol
6
+ Project-URL: Documentation, https://github.com/sunilp/ldp-protocol/tree/main/sdk/python
7
+ Project-URL: Repository, https://github.com/sunilp/ldp-protocol
8
+ Project-URL: Issues, https://github.com/sunilp/ldp-protocol/issues
9
+ Author: Sunil Prakash
10
+ License-Expression: Apache-2.0
11
+ Keywords: a2a,agents,delegation,llm,mcp,multi-agent,protocol
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: httpx>=0.25
24
+ Requires-Dist: pydantic>=2.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.1; extra == 'dev'
29
+ Provides-Extra: server
30
+ Requires-Dist: starlette>=0.27; extra == 'server'
31
+ Requires-Dist: uvicorn>=0.23; extra == 'server'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # LDP Protocol — Python SDK
35
+
36
+ Identity-aware communication protocol for multi-agent LLM systems.
37
+
38
+ ```bash
39
+ pip install ldp-protocol
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### Create a delegate
45
+
46
+ ```python
47
+ from ldp_protocol import LdpDelegate, LdpCapability, QualityMetrics
48
+
49
+ class MyDelegate(LdpDelegate):
50
+ async def handle_task(self, skill, input_data, task_id):
51
+ return {"answer": "42"}, 0.95
52
+
53
+ delegate = MyDelegate(
54
+ delegate_id="ldp:delegate:my-agent",
55
+ name="My Agent",
56
+ model_family="claude",
57
+ model_version="claude-sonnet-4-6",
58
+ capabilities=[
59
+ LdpCapability(
60
+ name="reasoning",
61
+ quality=QualityMetrics(quality_score=0.85, cost_per_call_usd=0.01),
62
+ ),
63
+ ],
64
+ )
65
+ delegate.run(port=8090) # requires: pip install ldp-protocol[server]
66
+ ```
67
+
68
+ ### Discover and invoke
69
+
70
+ ```python
71
+ from ldp_protocol import LdpClient
72
+
73
+ async with LdpClient() as client:
74
+ identity = await client.discover("http://localhost:8090")
75
+ print(f"Found: {identity.name} ({identity.model_family})")
76
+
77
+ result = await client.submit_task(
78
+ "http://localhost:8090",
79
+ skill="reasoning",
80
+ input_data={"prompt": "Analyze the tradeoffs..."},
81
+ )
82
+ print(f"Output: {result['output']}")
83
+ print(f"Provenance: {result['provenance']}")
84
+ ```
85
+
86
+ ### Multi-delegate routing
87
+
88
+ ```python
89
+ from ldp_protocol import LdpRouter, RoutingStrategy
90
+
91
+ async with LdpRouter() as router:
92
+ await router.discover_delegates([
93
+ "http://fast-model:8091",
94
+ "http://deep-model:8092",
95
+ ])
96
+
97
+ # Route by quality, cost, latency, or balanced score
98
+ result = await router.route_and_submit(
99
+ skill="reasoning",
100
+ input_data={"prompt": "Complex analysis..."},
101
+ strategy=RoutingStrategy.QUALITY,
102
+ )
103
+ print(f"Routed to: {result['routed_to']['name']}")
104
+ ```
105
+
106
+ ## Links
107
+
108
+ - [Protocol specification](https://github.com/sunilp/ldp-protocol)
109
+ - [Research paper](https://arxiv.org/abs/2603.08852)
110
+ - [Rust reference implementation](https://github.com/sunilp/ldp-protocol)
@@ -0,0 +1,77 @@
1
+ # LDP Protocol — Python SDK
2
+
3
+ Identity-aware communication protocol for multi-agent LLM systems.
4
+
5
+ ```bash
6
+ pip install ldp-protocol
7
+ ```
8
+
9
+ ## Quick Start
10
+
11
+ ### Create a delegate
12
+
13
+ ```python
14
+ from ldp_protocol import LdpDelegate, LdpCapability, QualityMetrics
15
+
16
+ class MyDelegate(LdpDelegate):
17
+ async def handle_task(self, skill, input_data, task_id):
18
+ return {"answer": "42"}, 0.95
19
+
20
+ delegate = MyDelegate(
21
+ delegate_id="ldp:delegate:my-agent",
22
+ name="My Agent",
23
+ model_family="claude",
24
+ model_version="claude-sonnet-4-6",
25
+ capabilities=[
26
+ LdpCapability(
27
+ name="reasoning",
28
+ quality=QualityMetrics(quality_score=0.85, cost_per_call_usd=0.01),
29
+ ),
30
+ ],
31
+ )
32
+ delegate.run(port=8090) # requires: pip install ldp-protocol[server]
33
+ ```
34
+
35
+ ### Discover and invoke
36
+
37
+ ```python
38
+ from ldp_protocol import LdpClient
39
+
40
+ async with LdpClient() as client:
41
+ identity = await client.discover("http://localhost:8090")
42
+ print(f"Found: {identity.name} ({identity.model_family})")
43
+
44
+ result = await client.submit_task(
45
+ "http://localhost:8090",
46
+ skill="reasoning",
47
+ input_data={"prompt": "Analyze the tradeoffs..."},
48
+ )
49
+ print(f"Output: {result['output']}")
50
+ print(f"Provenance: {result['provenance']}")
51
+ ```
52
+
53
+ ### Multi-delegate routing
54
+
55
+ ```python
56
+ from ldp_protocol import LdpRouter, RoutingStrategy
57
+
58
+ async with LdpRouter() as router:
59
+ await router.discover_delegates([
60
+ "http://fast-model:8091",
61
+ "http://deep-model:8092",
62
+ ])
63
+
64
+ # Route by quality, cost, latency, or balanced score
65
+ result = await router.route_and_submit(
66
+ skill="reasoning",
67
+ input_data={"prompt": "Complex analysis..."},
68
+ strategy=RoutingStrategy.QUALITY,
69
+ )
70
+ print(f"Routed to: {result['routed_to']['name']}")
71
+ ```
72
+
73
+ ## Links
74
+
75
+ - [Protocol specification](https://github.com/sunilp/ldp-protocol)
76
+ - [Research paper](https://arxiv.org/abs/2603.08852)
77
+ - [Rust reference implementation](https://github.com/sunilp/ldp-protocol)
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ldp-protocol"
7
+ version = "0.1.0"
8
+ description = "LDP — LLM Delegate Protocol: identity-aware communication for multi-agent LLM systems"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Sunil Prakash" },
14
+ ]
15
+ keywords = ["llm", "agents", "protocol", "delegation", "multi-agent", "a2a", "mcp"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "pydantic>=2.0",
30
+ "httpx>=0.25",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ server = ["starlette>=0.27", "uvicorn>=0.23"]
35
+ dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "ruff>=0.1"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/sunilp/ldp-protocol"
39
+ Documentation = "https://github.com/sunilp/ldp-protocol/tree/main/sdk/python"
40
+ Repository = "https://github.com/sunilp/ldp-protocol"
41
+ Issues = "https://github.com/sunilp/ldp-protocol/issues"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/ldp_protocol"]
45
+
46
+ [tool.ruff]
47
+ target-version = "py310"
48
+ line-length = 100
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
@@ -0,0 +1,49 @@
1
+ """LDP — LLM Delegate Protocol.
2
+
3
+ Identity-aware communication protocol for multi-agent LLM systems.
4
+ LDP adds delegation intelligence on top of A2A and MCP: rich identity,
5
+ progressive payload modes, governed sessions, structured provenance,
6
+ and trust domains.
7
+ """
8
+
9
+ from ldp_protocol.types import (
10
+ LdpCapability,
11
+ LdpEnvelope,
12
+ LdpIdentityCard,
13
+ LdpMessageBody,
14
+ LdpSession,
15
+ NegotiatedPayload,
16
+ PayloadMode,
17
+ Provenance,
18
+ QualityMetrics,
19
+ SessionConfig,
20
+ SessionState,
21
+ TrustDomain,
22
+ )
23
+ from ldp_protocol.client import LdpClient
24
+ from ldp_protocol.delegate import LdpDelegate
25
+ from ldp_protocol.router import LdpRouter
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ __all__ = [
30
+ # Types
31
+ "LdpIdentityCard",
32
+ "LdpCapability",
33
+ "QualityMetrics",
34
+ "TrustDomain",
35
+ "PayloadMode",
36
+ "NegotiatedPayload",
37
+ "SessionConfig",
38
+ "SessionState",
39
+ "LdpSession",
40
+ "LdpEnvelope",
41
+ "LdpMessageBody",
42
+ "Provenance",
43
+ # Client
44
+ "LdpClient",
45
+ # Delegate
46
+ "LdpDelegate",
47
+ # Router
48
+ "LdpRouter",
49
+ ]
@@ -0,0 +1,203 @@
1
+ """LDP HTTP client — discover delegates, manage sessions, submit tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ import httpx
9
+
10
+ from ldp_protocol.types.identity import LdpIdentityCard
11
+ from ldp_protocol.types.messages import LdpEnvelope, LdpMessageBody
12
+ from ldp_protocol.types.payload import PayloadMode, negotiate_payload_mode
13
+ from ldp_protocol.types.provenance import Provenance
14
+ from ldp_protocol.types.session import LdpSession, SessionConfig, SessionState
15
+ from ldp_protocol.types.trust import TrustDomain
16
+
17
+
18
+ class LdpClient:
19
+ """Async HTTP client for LDP protocol communication.
20
+
21
+ Handles discovery, session management, and task submission.
22
+
23
+ Usage:
24
+ async with LdpClient() as client:
25
+ identity = await client.discover("http://localhost:8090")
26
+ result = await client.submit_task(
27
+ "http://localhost:8090",
28
+ skill="reasoning",
29
+ input_data={"prompt": "Analyze..."},
30
+ )
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ delegate_id: str = "ldp:client:default",
36
+ config: SessionConfig | None = None,
37
+ timeout: float = 30.0,
38
+ ):
39
+ self.delegate_id = delegate_id
40
+ self.config = config or SessionConfig()
41
+ self._http = httpx.AsyncClient(timeout=timeout)
42
+ self._sessions: dict[str, LdpSession] = {}
43
+
44
+ async def __aenter__(self) -> LdpClient:
45
+ return self
46
+
47
+ async def __aexit__(self, *args) -> None:
48
+ await self.close()
49
+
50
+ async def close(self) -> None:
51
+ """Close the HTTP client."""
52
+ await self._http.aclose()
53
+
54
+ async def discover(self, url: str) -> LdpIdentityCard:
55
+ """Fetch a delegate's identity card.
56
+
57
+ Args:
58
+ url: Base URL of the delegate (e.g., "http://localhost:8090").
59
+
60
+ Returns:
61
+ The delegate's identity card with full metadata.
62
+ """
63
+ endpoint = f"{url.rstrip('/')}/ldp/identity"
64
+ resp = await self._http.get(endpoint)
65
+ resp.raise_for_status()
66
+ return LdpIdentityCard.model_validate(resp.json())
67
+
68
+ async def send_message(self, url: str, envelope: LdpEnvelope) -> LdpEnvelope:
69
+ """Send an LDP message and receive a response."""
70
+ endpoint = f"{url.rstrip('/')}/ldp/messages"
71
+ resp = await self._http.post(
72
+ endpoint,
73
+ json=envelope.model_dump(by_alias=True),
74
+ )
75
+ resp.raise_for_status()
76
+ return LdpEnvelope.model_validate(resp.json())
77
+
78
+ async def establish_session(self, url: str) -> LdpSession:
79
+ """Establish a new LDP session with a delegate.
80
+
81
+ Performs the full handshake: HELLO -> CAPABILITY_MANIFEST ->
82
+ SESSION_PROPOSE -> SESSION_ACCEPT.
83
+ """
84
+ # Step 1: HELLO
85
+ hello = LdpEnvelope.create(
86
+ session_id="",
87
+ from_id=self.delegate_id,
88
+ to_id=url,
89
+ body=LdpMessageBody.hello(
90
+ delegate_id=self.delegate_id,
91
+ supported_modes=self.config.preferred_payload_modes,
92
+ ),
93
+ )
94
+ hello_resp = await self.send_message(url, hello)
95
+
96
+ # Step 2: SESSION_PROPOSE
97
+ session_id = str(uuid4())
98
+ propose = LdpEnvelope.create(
99
+ session_id=session_id,
100
+ from_id=self.delegate_id,
101
+ to_id=url,
102
+ body=LdpMessageBody.session_propose(
103
+ config={
104
+ "preferred_payload_modes": [
105
+ m.value for m in self.config.preferred_payload_modes
106
+ ],
107
+ "ttl_secs": self.config.ttl_secs,
108
+ }
109
+ ),
110
+ )
111
+ propose_resp = await self.send_message(url, propose)
112
+
113
+ if propose_resp.body.type == "SESSION_REJECT":
114
+ raise ConnectionError(
115
+ f"Session rejected: {propose_resp.body.reason}"
116
+ )
117
+
118
+ # Build session from response
119
+ negotiated_mode = propose_resp.body.negotiated_mode or PayloadMode.TEXT
120
+ identity = await self.discover(url)
121
+
122
+ session = LdpSession(
123
+ session_id=propose_resp.body.session_id or session_id,
124
+ remote_url=url,
125
+ remote_delegate_id=identity.delegate_id,
126
+ state=SessionState.ACTIVE,
127
+ trust_domain=identity.trust_domain,
128
+ ttl_secs=self.config.ttl_secs,
129
+ )
130
+ session.payload.mode = negotiated_mode
131
+
132
+ self._sessions[url] = session
133
+ return session
134
+
135
+ async def get_or_establish_session(self, url: str) -> LdpSession:
136
+ """Get an existing session or establish a new one."""
137
+ session = self._sessions.get(url)
138
+ if session and session.is_active:
139
+ return session
140
+ return await self.establish_session(url)
141
+
142
+ async def submit_task(
143
+ self,
144
+ url: str,
145
+ *,
146
+ skill: str,
147
+ input_data: Any,
148
+ session: LdpSession | None = None,
149
+ ) -> dict[str, Any]:
150
+ """Submit a task to a delegate and get the result.
151
+
152
+ Args:
153
+ url: Delegate URL.
154
+ skill: Skill to invoke.
155
+ input_data: Input data for the task.
156
+ session: Optional existing session (auto-establishes if None).
157
+
158
+ Returns:
159
+ Dict with 'output' and 'provenance' keys.
160
+ """
161
+ if session is None:
162
+ session = await self.get_or_establish_session(url)
163
+
164
+ task_id = str(uuid4())
165
+ submit = LdpEnvelope.create(
166
+ session_id=session.session_id,
167
+ from_id=self.delegate_id,
168
+ to_id=session.remote_delegate_id,
169
+ body=LdpMessageBody.task_submit(
170
+ task_id=task_id,
171
+ skill=skill,
172
+ input=input_data,
173
+ ),
174
+ payload_mode=session.payload.mode,
175
+ )
176
+
177
+ response = await self.send_message(url, submit)
178
+ session.touch()
179
+ session.task_count += 1
180
+
181
+ if response.body.type == "TASK_RESULT":
182
+ return {
183
+ "task_id": response.body.task_id,
184
+ "output": response.body.output,
185
+ "provenance": (
186
+ response.body.provenance.model_dump()
187
+ if response.body.provenance
188
+ else None
189
+ ),
190
+ }
191
+ elif response.body.type == "TASK_FAILED":
192
+ raise RuntimeError(f"Task failed: {response.body.error}")
193
+ else:
194
+ return {
195
+ "task_id": task_id,
196
+ "status": response.body.type,
197
+ "message": response.body.message,
198
+ }
199
+
200
+ @property
201
+ def active_sessions(self) -> int:
202
+ """Number of active sessions."""
203
+ return sum(1 for s in self._sessions.values() if s.is_active)