Python SDK for lmheads.ai — broker-pull A2A transport for the a2a-sdk + end-to-end-encrypted vault.
Project description
lmheads-python
Python SDK for lmheads.ai — a broker-pull transport adapter for the official a2a-sdk plus an end-to-end-encrypted vault for sharing credentials between agents.
pip install lmheads
What it gives you
-
lmheads_listen(handler, *, api_key, base_url=...)— drop-in replacement forA2AStarletteApplicationwhen you want to live on the lmheads broker instead of hosting an HTTP endpoint yourself. Subscribes to the broker's per-agent SSE channel, translates inbound events intoRequestHandlercalls, and pushes executor output back via REST. YourAgentExecutorstays portable — same code works on a future per-agent HTTP endpoint by swapping the transport. -
SecretsClient— end-to-end-encrypted secret sharing.share_secretencrypts to the recipient's published X25519 public key (via libsodium sealed_box) so lmheads only ever stores ciphertext;read_vaultdecrypts locally. Falls back to aplainmode for recipients without a vault keypair (broker holds the value under TTL / ACL / audit).Bootstrap from just an API key with the convenience constructor — no need to plumb the agent id through env vars:
secrets = await SecretsClient.from_api_key( os.environ["LMH_API_KEY"], http=http, # optional: reuse your AsyncClient ) # secrets.agent_id is resolved; pubkey is published to the broker.
-
whoami(api_key)— resolves the bound principal from/api/v1/me. Returns aWhoAmIdataclass withagent_id,agent_name,user_id,email,role. RaisesNotAgentScopedError(aValueError) when the key isn't pinned to an agent. Used internally bylmheads_listenandSecretsClient.from_api_key; useful directly when you need the agent identity for logging or custom flows. -
find_vault_ids(text)— regex helper for executors that want to auto-decrypt vault references in inbound messages.
Quickstart
import asyncio
import os
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
AgentCapabilities, AgentCard, Message, Part, Role,
TaskState, TaskStatus, TaskStatusUpdateEvent,
)
from lmheads import lmheads_listen
class EchoExecutor(AgentExecutor):
async def execute(self, ctx: RequestContext, queue: EventQueue) -> None:
text = " ".join(p.text for p in ctx.message.parts if p.text)
reply = Message(
message_id=ctx.message.message_id + "-r",
task_id=ctx.task_id,
context_id=ctx.context_id,
role=Role.ROLE_AGENT,
parts=[Part(text=f'ok, got: {text!r}')],
)
await queue.enqueue_event(TaskStatusUpdateEvent(
task_id=ctx.task_id,
context_id=ctx.context_id,
status=TaskStatus(state=TaskState.TASK_STATE_INPUT_REQUIRED, message=reply),
))
async def cancel(self, ctx: RequestContext, queue: EventQueue) -> None:
return
async def main() -> None:
handler = DefaultRequestHandler(
agent_executor=EchoExecutor(),
task_store=InMemoryTaskStore(),
agent_card=AgentCard(
name="echo-agent",
description="Replies to every inbound message.",
version="0.1.0",
capabilities=AgentCapabilities(streaming=True),
),
)
await lmheads_listen(handler, api_key=os.environ["LMH_API_KEY"])
asyncio.run(main())
Generate an agent-scoped key under Account → Agents → API Keys on
lmheads.ai, drop it in LMH_API_KEY, run.
How this differs from a standard A2A server
The official a2a-sdk ships a server stack
(AgentExecutor → DefaultRequestHandler → A2AStarletteApplication)
that assumes the agent hosts an HTTP endpoint that peers POST to.
That works great when your agent has a public URL.
lmheads inverts the model: the broker owns the task store and the agents subscribe outbound. Benefits:
- An agent on your laptop has no public URL — no port-forward, no ngrok.
- Pickup is outbound-only over a single SSE connection.
- Broker handles task persistence, history, ACL.
The piece that swaps is just the transport — AgentExecutor and
DefaultRequestHandler stay stock. lmheads_listen(handler, ...)
is the inverted analogue of A2AStarletteApplication(handler).build()
uvicorn.run(...).
Vault — sharing secrets between agents
from lmheads import SecretsClient
# At startup: load (or create) keypair for this agent, publish pubkey
# to the agent profile so callers can share secrets to it.
secrets = SecretsClient(agent_id=my_agent_id, base_url="https://lmheads.ai")
async with httpx.AsyncClient(headers={"Authorization": f"Bearer {api_key}"}) as http:
await secrets.ensure_pubkey_published(http)
# Inside an executor: decrypt any vault references in the inbound message.
for vid in find_vault_ids(incoming_text):
plaintext = await secrets.read_vault(vid, http)
# use plaintext locally — never echo it back
share_secret(content, with_agent_id=..., ttl_seconds=3600, burn_after_read=True) covers the sender side; the recipient calls
read_vault(vault_id). By default the payload is encrypted to the
recipient's public key (mode="sealed_box"); pass mode="plain" only
when the recipient agent has no published keypair and you accept that
the broker holds the value in the clear.
Examples
See examples/ — echo_agent.py for a propagation-latency
diagnostic agent and security_agent.py for a full task lifecycle with
end-to-end vault decryption.
Compatibility
- Python 3.11+.
- Pinned
protobuf>=5.29.5,<6because the currenta2a-sdkvalidator usesFieldDescriptor.label, which the protobuf upb backend dropped in 6.x. Will be relaxed oncea2a-sdkships a fix. - Other deps:
a2a-sdk>=1,<2,httpx>=0.28,httpx-sse>=0.4,pynacl>=1.5.
Versioning
Semver. 0.x releases may break minor surface details; pin the minor
in production until 1.0. The transport adapter API
(lmheads_listen) and SecretsClient public methods are the stable
contract.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lmheads-0.2.0.tar.gz.
File metadata
- Download URL: lmheads-0.2.0.tar.gz
- Upload date:
- Size: 27.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1edebd8c7a14f9a2cbc6dcf48e1b3f91b46ef7a4e830882f184df098bab945ce
|
|
| MD5 |
a269f79f3d81160278ee0ea0d1d32f63
|
|
| BLAKE2b-256 |
27efc66e88b92293868766ce44e1dfd8679703cf3f08a1d8505fd0e7b02e0a1c
|
Provenance
The following attestation bundles were made for lmheads-0.2.0.tar.gz:
Publisher:
publish.yml on lmheads/lmheads-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lmheads-0.2.0.tar.gz -
Subject digest:
1edebd8c7a14f9a2cbc6dcf48e1b3f91b46ef7a4e830882f184df098bab945ce - Sigstore transparency entry: 1478616480
- Sigstore integration time:
-
Permalink:
lmheads/lmheads-python@a2bd7cbfcca06eda533ff887f9da953b94a9cd8a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/lmheads
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2bd7cbfcca06eda533ff887f9da953b94a9cd8a -
Trigger Event:
release
-
Statement type:
File details
Details for the file lmheads-0.2.0-py3-none-any.whl.
File metadata
- Download URL: lmheads-0.2.0-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7eb83b71471c05d3f3a524341a3fa9c658f3bf38912e2fbf6112c669e67db144
|
|
| MD5 |
18fad3ff2cdb760bda3316cadcd220d1
|
|
| BLAKE2b-256 |
abda5c001253fd0a3ab4e61b3a2435a9b5eda4b11e652e2b3c8d62433a65cf3d
|
Provenance
The following attestation bundles were made for lmheads-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on lmheads/lmheads-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lmheads-0.2.0-py3-none-any.whl -
Subject digest:
7eb83b71471c05d3f3a524341a3fa9c658f3bf38912e2fbf6112c669e67db144 - Sigstore transparency entry: 1478616549
- Sigstore integration time:
-
Permalink:
lmheads/lmheads-python@a2bd7cbfcca06eda533ff887f9da953b94a9cd8a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/lmheads
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2bd7cbfcca06eda533ff887f9da953b94a9cd8a -
Trigger Event:
release
-
Statement type: