Skip to main content

chap-coordinator (Python)

Python reference implementation of the Collaborative Human-Agent Protocol (CHAP).

Covers Core plus every profile, 39 method handlers in total. Spec-aligned field names, error codes, and response shapes against profiles/*.md.

Install

pip install chap-coordinator

For the security-signed/1.0 profile (Ed25519 signing) and the OIDC binding hook:

pip install "chap-coordinator[crypto]"

For the MCP server transport (drive a Coordinator from any MCP client):

pip install "chap-coordinator[mcp]"

For the A2A server transport (expose a Coordinator as an A2A agent):

pip install "chap-coordinator[a2a]"

Companion modules

The base package is the protocol core. Two transport adapters ship in the same wheel under optional extras:

  • chap_coordinator.transports.mcp_server wraps a Coordinator as an MCP server. Every CHAP method becomes an MCP tool named chap.<method>. Reference stdio server at reference/mcp-server-py/. Spec target: MCP 2026-07-28, serving 2025-11-25 clients as well.
  • chap_coordinator.transports.a2a_server wraps a Coordinator as an A2A agent. Every CHAP method becomes an AgentSkill on the Agent Card. Reference FastAPI server at reference/a2a-server-py/. Spec target: A2A 1.0 (with v0.3 compatibility).

Inward citation helpers (wrap_mcp_tool_call, wrap_a2a_message_exchange, content_hash) live in chap_coordinator.transports.wrap. They take a completed external event and emit the matching CHAP audit entries with input/output hashes.

Quick start

from chap_coordinator import Coordinator, CoordinatorOptions

coord = Coordinator(CoordinatorOptions(default_profiles=[
    "core/1.0", "review/1.0", "whisper/1.0",
]))

coord.dispatch({"jsonrpc": "2.0", "id": "1",
    "method": "workspace.create",
    "params": {"workspace": "wsp_demo"}})

coord.dispatch({"jsonrpc": "2.0", "id": "2",
    "method": "participant.join",
    "params": {"workspace": "wsp_demo",
               "from": "human:me@local",
               "type": "human", "role": "reviewer"}})

coord.dispatch({"jsonrpc": "2.0", "id": "3",
    "method": "participant.join",
    "params": {"workspace": "wsp_demo",
               "from": "agent:bot",
               "type": "agent", "role": "drafter"}})

resp = coord.dispatch({"jsonrpc": "2.0", "id": "4",
    "method": "task.create",
    "params": {"workspace": "wsp_demo",
               "from": "human:me@local",
               "kind": "draft_response",
               "input": {"ticket_id": "INC-1"},
               "assignee": "agent:bot"}})

print(resp["result"])  # {"task_id": "tsk_...", "state": "created"}

What is implemented

This package implements CHAP Core plus every profile:

Profile Methods (per profile spec)
core/1.0 workspace.create, workspace.describe, workspace.set_profiles, participant.join, participant.leave, task.create, task.update, task.complete, audit.read
review/1.0 review.request, decide.approve, decide.reject, decide.override, abstain.declare, escalate.raise
whisper/1.0 whisper.ask, whisper.answer; lapse hook via coord.check_whisper_lapses(workspace_id, now)
deliberation/1.0 deliberate.open, deliberate.comment, deliberate.vote, deliberate.close
handoff/1.0 handoff.propose, handoff.accept, handoff.decline; carries multiple tasks; recipient may be a URI or group:
control/1.0 control.pause, control.resume, control.cancel, control.snapshot, control.rollback, control.supersede, control.set_mode_ceiling; pause/resume take scope: task/participant/workspace
routing/1.0 task.route, review.depth, escalate.auto; each emits a route_decision artefact
modes/1.0 Mode handling at task.create; trial mode forces review_required; control.set_mode_ceiling shared with control/1.0
security-signed/1.0 participant.rotate_key, participant.revoke_key; top-level sig field verified at dispatch when options.require_signatures=True
audit-scitt/1.0 audit.submit_to_scitt, audit.verify_receipt, audit.verify_chain; SCITT statements assembled and passed to a deployment-supplied submitter; local prev-hash chain linkage retained
identity-oidc/1.0 participant.join binding via options.verify_oidc_token; cnf.jwk pinning, step-up auth via enforce_step_up=True
identity-vc/1.0 participant.join binding via options.verify_vc; holder-key pinning

39 method handlers in total.

Architecture

The coordinator is transport-agnostic, persistence-agnostic, and UI-agnostic. It exposes a single entry point:

response = coord.dispatch(envelope)

Where envelope is a JSON-RPC 2.0 request and response is the JSON-RPC 2.0 response. Bind whichever HTTP, WebSocket, or in-process transport you like. State is held in memory; persist by subscribing to the audit listener.

A minimal stdlib HTTP server is at reference/python/server.py in the parent repo.

Determinism for tests

Set deterministic_ids=True and deterministic_clock=True on CoordinatorOptions for replayable demos and golden-file tests:

coord = Coordinator(CoordinatorOptions(
    deterministic_ids=True,
    deterministic_clock=True,
))

Identity binding (identity-oidc/1.0, identity-vc/1.0)

These profiles bind a participant's claimed identity to a verifiable external authority. The coordinator does not implement OIDC or VC verification itself; you supply callbacks:

def verify_oidc(token: str) -> dict | None:
    # Validate the token against your IdP; return claims dict or None
    # Include "cnf": {"jwk": {...}} to bind a signing key
    ...

def verify_vc(presentation: dict) -> dict | None:
    # Verify the VP; return subject claims dict or None
    # Include "cnf_jwk": {...} to bind a holder key
    ...

coord = Coordinator(CoordinatorOptions(
    verify_oidc_token=verify_oidc,
    verify_vc=verify_vc,
    enforce_step_up=True,  # require fresh auth_time for privileged methods
))

If a participant.join envelope includes oidc_token or vc_presentation parameters, the relevant hook is called and the bound key is added to the participant's key history.

Signed envelopes (security-signed/1.0)

To require signatures on every envelope:

coord = Coordinator(CoordinatorOptions(require_signatures=True))

Each participant registers one or more JWKs at participant.join via jwks: {keys: [...]}. Senders sign the JCS canonicalisation of the envelope with the top-level sig field removed, then attach the signature as a top-level sig: "ed25519:<kid>:<base64>" field. The Coordinator looks up the verifying key by (from, kid, ts), so historical envelopes verify across key rotation.

Key lifecycle:

  • participant.rotate_key is signed with the old key; the Coordinator marks valid_until on the old key and valid_from on the new one.
  • participant.revoke_key is an admin operation; subsequent envelopes signed with the revoked key are rejected.

Routing policies (routing/1.0)

A default policy ships out of the box. Override it with operator hooks:

def route(task, candidates):
    # Return {"selected": "...", "rationale": {...}}
    return {"selected": candidates[0], "rationale": {
        "policy_id": "round-robin",
        "summary": "first eligible",
    }}

coord = Coordinator(CoordinatorOptions(routing_policy=route))

Every call to task.route, review.depth, and escalate.auto emits a route_decision artefact recording the inputs, the policy id, and the rationale. task.route also updates the task's assignee to match the selected URI.

Audit and SCITT submission (audit-scitt/1.0)

Audit entries are hash-linked when the profile is active. audit.verify_chain replays the chain locally and confirms prev_hash continuity.

audit.submit_to_scitt builds a SCITT-shaped signed statement for each audit entry in the requested range and passes it to a deployment-supplied submitter:

def submitter(statement):
    # Submit to your SCITT transparency service; return the receipt
    ...

coord = Coordinator(CoordinatorOptions(scitt_submitter=submitter))

Without a submitter configured, audit.submit_to_scitt returns the statements so the caller can submit out-of-band.

Testing

pip install -e ".[dev]"
pytest

The suite covers the core library, the MCP and A2A transports, and the wrap helpers.

Spec fidelity

This implementation was reviewed against every profile spec under profiles/ and aligned with the documented field names, error codes, and response shapes. The full audit notes are in the parent repo's CHANGELOG.md, with the 0.2.1 entry covering the original Python implementation and 0.2.3 / 0.2.4 covering the MCP and A2A transport additions.

License

Apache 2.0. See the parent repository's LICENSE.

Specification

Tracks the spec at github.com/BrightbeamAI/chap/SPECIFICATION.md.

Download files

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

Source Distribution

chap_coordinator-0.2.9.tar.gz (102.4 kB view details)

Uploaded Source

Built Distribution

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

chap_coordinator-0.2.9-py3-none-any.whl (77.0 kB view details)

Uploaded Python 3

File details

Details for the file chap_coordinator-0.2.9.tar.gz.

File metadata

  • Download URL: chap_coordinator-0.2.9.tar.gz
  • Upload date:
  • Size: 102.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for chap_coordinator-0.2.9.tar.gz
Algorithm Hash digest
SHA256 00a207d17341c8ad2b98321174f0df6d0ec3ff91736db1965b453997441e6c16
MD5 12a1a2e61d2e5857309032643dade075
BLAKE2b-256 24e6c02db4a59208b865ce3fe989b24ff7c8b1ca252720245daa97950a606263

See more details on using hashes here.

File details

Details for the file chap_coordinator-0.2.9-py3-none-any.whl.

File metadata

File hashes

Hashes for chap_coordinator-0.2.9-py3-none-any.whl
Algorithm Hash digest
SHA256 b20632f55373b1ad3ff0a46da35383ff8b5fe44c3a4736d68037ad42c28d616a
MD5 48385f0669fb0f09370f7586fb42af33
BLAKE2b-256 c6dd1fab1edbb2c4bc71433992406e1ec3aa56482f72c7ca2e9a50f8d69fcdb0

See more details on using hashes here.

Supported by

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