Skip to main content

Open Agent Control Panel

A control panel for A2A agents. Register them through the A2A Discover Protocol, see everyone who is online, submit tasks over A2A JSON-RPC, and follow those tasks — from one small FastAPI service with a dependency-free web UI.

PyPI Python License Protocol


What it does

The panel sits between you and a fleet of A2A agents. It plays three roles at once:

Role Surface What it gives you
Registry /.well-known/a2a-registry.json, /v1/agents Agents register themselves by AgentCard URL; the panel fetches, validates and probes the card
Control plane /api/panel/* + the bundled UI Every registered agent, its card, its skills, and the tasks you sent it
A2A client message/send, tasks/get, tasks/cancel Submit a task, watch it move, cancel it, continue the conversation

The registry half implements the A2A Discover Protocol (ADP) Core v1 contract: five operations, four invariants, and the SSRF rules in §7. The panel half is deliberately outside that contract — protocol endpoints stay exactly the protocol.

Why a registry at all

A2A defines how agents talk to each other, but not where to find them. ADP closes that gap with one claim and nothing more:

I fetched this card from this URL at lastOkAt, and its digest is cardHash. I claim nothing else.

No scores, no trust badges, no ranking, no proxying. Registration is not proof of ownership — it only proves that the URL served that card at that moment. The panel honours that: it never invents a "verified" flag, and it never changes a byte of a card.

Quick start

pip install open-agent-control-panel
oacp serve

Open http://127.0.0.1:8000. Anyone can now register an agent:

curl -X POST http://127.0.0.1:8000/v1/agents \
     -H 'content-type: application/json' \
     -d '{"url":"https://agent.example.com/.well-known/agent-card.json"}'

The response is the stored record plus a one-time manageToken. Keep it if you want to unregister later — it is not rotated and cannot be recovered.

Then open the agent in the UI, press Send task, and the panel calls the agent's A2A endpoint with message/send.

Try it locally

A real registration needs a public HTTPS URL, because the registry fetches the card itself. For a laptop demo, the repository ships a tiny echo agent and a development mode:

# terminal 1 — a demo agent on http://127.0.0.1:9100
pip install -e ".[dev]"
python examples/echo_agent.py

# terminal 2 — the panel, in development mode
oacp serve --port 8000 --allow-insecure-cards --allow-private-agents

# register the demo agent through the UI, or:
curl -X POST http://127.0.0.1:8000/v1/agents \
     -H 'content-type: application/json' \
     -d '{"url":"http://127.0.0.1:9100/.well-known/agent-card.json"}'

--allow-insecure-cards accepts http:// and private addresses for card URLs; --allow-private-agents lets the A2A client call private endpoints. Both are development switches: with either one on, the deployment is no longer ADP-compliant, and the panel says so in its own summary.

What the UI gives you

  • Agents — status (ok / down), failures, last successful probe, skills, tags, task counts; a detail page with endpoints, the full card and a raw-JSON view.
  • Tasks — every task the panel submitted, filterable by state, with a live view of the agent's latest output, the event log, and the raw request/response payloads.
  • Actions — register, re-fetch a card, send a task, follow up with another message, refresh from the agent, cancel, unregister, forget.
  • Zero build step — the front end is three static files (index.html, styles.css, app.js), served by the same process. No Node, no bundler, no CDN.

Protocol surface

Method Path Purpose
GET /.well-known/a2a-registry.json RegistryCard (name, apiBase, spec, probe interval, limits)
POST /v1/agents {"url": …} → fetch, validate, store; 201 + one-time manageToken
GET /v1/agents ?q=&skill=&tag=&status=&changedSince=&full=&limit=&cursor=
GET /v1/agents/{id} One AgentRecord, including the card
DELETE /v1/agents/{id} Unregister; requires X-ADP-Token

Records follow the contract exactly: id is derived from the normalized URL, cardHash is sha256(JCS(card)), checkedAt moves on every attempt, lastOkAt only on success, and changedAt/prevHash are always present (nullable) so clients need one branch, not two.

Panel endpoints (not part of ADP):

Method Path Purpose
GET /api/panel/summary Counts, deployment facts, RegistryCard
GET /api/panel/agents Agents with local task counts
GET /api/panel/agents/{id} Record + interfaces + recent tasks
POST /api/panel/agents/{id}/refresh Probe this agent now
DELETE /api/panel/agents/{id} Unregister with {"token": …}
POST /api/panel/agents/{id}/tasks Submit {"text": …} over A2A
GET /api/panel/tasks Local task ledger (agentId, state, q, limit, cursor)
GET /api/panel/tasks/{id} Task + event log + raw payloads
POST /api/panel/tasks/{id}/refresh tasks/get against the agent
POST /api/panel/tasks/{id}/cancel tasks/cancel against the agent
POST /api/panel/tasks/{id}/messages Follow-up message in the same task/context
DELETE /api/panel/tasks/{id} Forget the task locally

Two A2A generations

Agents in the wild speak two dialects. The client reads the card, picks one, and — if the agent answers method not found or invalid params — retries once with the other:

Card shape Methods Message shape
url + protocolVersion: 0.x message/send, tasks/get, tasks/cancel role: "user", parts tagged kind: "text"
supportedInterfaces / protocolVersion: 1.x SendMessage, GetTask, CancelTask role: "ROLE_USER", parts as {text}

Both responses are normalized to the same internal view, including task states such as TASK_STATE_INPUT_REQUIRED → input-required.

Configuration

Every setting has an environment variable (OACP_*) and most have a flag. Precedence is default < environment < flag.

Flag Variable Default Meaning
--db OACP_DB_PATH oacp.db SQLite file
--public-url OACP_PUBLIC_URL http://127.0.0.1:8000 External base URL, advertised in the RegistryCard
--name OACP_DISPLAY_NAME Open Agent Control Panel Registry display name
--contact OACP_CONTACT — Contact line in the RegistryCard
— OACP_SPEC_URL urn:adp:core:1.0.0 Identifier of the protocol spec
--panel-token OACP_PANEL_TOKEN — Require this token on /api/panel/*
--card-validation OACP_CARD_VALIDATION relaxed strict = ADP §6.3 exactly; relaxed also accepts A2A 0.3 cards
--probe-interval OACP_PROBE_INTERVAL 300 Seconds between probes for healthy agents
— OACP_DOWN_PROBE_INTERVAL 60 Seconds between probes for down agents
--no-probe OACP_PROBE_ENABLED true Disable the background probe loop
— OACP_PROBE_CONCURRENCY 4 Concurrent probes
— OACP_MAX_CARD_BYTES 1048576 Card size cap
— OACP_MAX_AGENTS_PER_DOMAIN 100 Per-host entry cap
— OACP_REGISTER_PER_HOUR 20 Per-IP registration attempts per hour
--allow-insecure-cards OACP_ALLOW_INSECURE_CARDS false Dev only: accept http/private card URLs
--allow-private-agents OACP_ALLOW_PRIVATE_AGENTS false Dev only: call private A2A endpoints
--a2a-timeout OACP_A2A_TIMEOUT 60 A2A request timeout in seconds
--host / --port — 127.0.0.1 / 8000 Bind address

Security model

The panel makes requests to URLs chosen by strangers, so the outbound path is the product.

  • https only for cards; userinfo and fragments are rejected.
  • DNS pinning — the resolved address is validated and then pinned into the connection (Host header + TLS SNI preserved), so a name cannot resolve to one address at check time and another at connect time. Mixed public/private answers are refused outright.
  • Manual redirects, at most three hops, each re-validated from scratch.
  • Streaming body cap, connect/total timeouts, no cookies, no caller credentials, TLS verification that cannot be switched off, and trust_env=False so environment proxies cannot silently take over DNS and connection establishment.
  • A2A calls use the same guard, which matters because a card can point anywhere.
  • Manage tokens are stored hashed; the plaintext exists only in the registration response and in the browser that performed it.
  • Optional panel token protects /api/panel/* while leaving the registry open enough for agents to register. That split is intentional: the protocol is public, the control plane is yours.

Deploy behind TLS (a reverse proxy is fine) and set --public-url to the external URL so the RegistryCard points somewhere reachable.

Development

python -m venv .venv && . .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
python -m pytest -q

Layout:

src/oacp/
  adp/        spec implementation: jcs, card checks, guarded fetcher, registry, routes, probe loop
  a2a/        JSON-RPC client with the two-dialect fallback
  tasks.py    local task ledger and A2A interactions
  api.py      panel API
  app.py      application factory, static UI, error mapping
  cli.py      `oacp serve`
  web/        index.html, styles.css, app.js
examples/     a tiny echo agent for local demos
tests/        pytest, with a mock A2A transport and a fake card fetcher

The test suite never touches the network: registration tests inject a fake fetcher and A2A tests inject an httpx.MockTransport.

Limitations

  • No streaming (message/stream, tasks/resubscribe); tasks are followed by polling.
  • Tasks are refreshed when you ask, not continuously.
  • Single process, single SQLite file: this is a control plane, not a cluster service.
  • No push notifications, no authentication schemes beyond A2A's own endpoint requirements.

License

Apache-2.0. See LICENSE and NOTICE.

Metadata

Release files for open-agent-control-panel 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for open-agent-control-panel 0.1.0
File Size Uploaded Provenance Actions
open_agent_control_panel-0.1.0.tar.gz 69.1 kB Without provenance Details for open_agent_control_panel-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for open-agent-control-panel 0.1.0
File Interpreter ABI Platform Actions
open_agent_control_panel-0.1.0-py3-none-any.whl Python 3 none any Details for open_agent_control_panel-0.1.0-py3-none-any.whl

Total release size: 135.1 kB

Release files / open_agent_control_panel-0.1.0.tar.gz

Download URL open_agent_control_panel-0.1.0.tar.gz
Size 69.1 kB
Tags Source
SHA-256 checksum
How to use checksums
109b0e2733ca704b2e9da76ef2514a0eec76e67fe0fc6d13794498286729e50c
BLAKE2b-256 checksum
How to use checksums
9dc0e89871398a1d50168e31058d0f354d0e87b849a9651f4381c51156b8f369
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Release files / open_agent_control_panel-0.1.0-py3-none-any.whl

Download URL open_agent_control_panel-0.1.0-py3-none-any.whl
Size 66.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
135f2fe89025dbf92feac852e3935a7f5d4d8269eaa791c4d7922f195ab1b260
BLAKE2b-256 checksum
How to use checksums
235775fcffc8b7c3de801482f3a14311696dfa3f5b4605f258966e07332bc4f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Security for open-agent-control-panel 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page