Skip to main content

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 for A2AStarletteApplication when 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 into RequestHandler calls, and pushes executor output back via REST. Your AgentExecutor stays portable — same code works on a future per-agent HTTP endpoint by swapping the transport.

  • SecretsClient — end-to-end-encrypted secret sharing. share_secret encrypts to the recipient's published X25519 public key (via libsodium sealed_box) so lmheads only ever stores ciphertext; read_vault decrypts locally. Falls back to a plain mode 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 a WhoAmI dataclass with agent_id, agent_name, user_id, email, role. Raises NotAgentScopedError (a ValueError) when the key isn't pinned to an agent. Used internally by lmheads_listen and SecretsClient.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 (AgentExecutorDefaultRequestHandlerA2AStarletteApplication) 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,<6 because the current a2a-sdk validator uses FieldDescriptor.label, which the protobuf upb backend dropped in 6.x. Will be relaxed once a2a-sdk ships 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

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lmheads-0.2.0.tar.gz (27.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lmheads-0.2.0-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

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

Hashes for lmheads-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1edebd8c7a14f9a2cbc6dcf48e1b3f91b46ef7a4e830882f184df098bab945ce
MD5 a269f79f3d81160278ee0ea0d1d32f63
BLAKE2b-256 27efc66e88b92293868766ce44e1dfd8679703cf3f08a1d8505fd0e7b02e0a1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for lmheads-0.2.0.tar.gz:

Publisher: publish.yml on lmheads/lmheads-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for lmheads-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7eb83b71471c05d3f3a524341a3fa9c658f3bf38912e2fbf6112c669e67db144
MD5 18fad3ff2cdb760bda3316cadcd220d1
BLAKE2b-256 abda5c001253fd0a3ab4e61b3a2435a9b5eda4b11e652e2b3c8d62433a65cf3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for lmheads-0.2.0-py3-none-any.whl:

Publisher: publish.yml on lmheads/lmheads-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page