Skip to main content

Human-AI Collaboration Protocol (HCP)

The HTTP between humans and AI.

PyPI version Python 3.9+ Machine-checked spec (Lean 4) Protocol 1.0

HCP Architecture Diagram

HCP is a wire protocol for structured interaction between an AI agent and a person. The agent describes the shape of the data it needs or wants to show — a range, a choice, a tagged blob, or a recursive record — and any client renders that shape however its substrate allows: a slider on the web, a dial in XR, an ASCII bar in a terminal. The person can always answer in plain text instead, and every exchange is logged with the agent's reasoning attached.

The conformance contract that makes this work is machine-checked in Lean 4: any client that meets it is, from the agent's side, indistinguishable from any other. See docs/math.md for exactly what is and is not proven.


1. The Problem: Asymmetric Collaboration

Today's human-AI collaboration is asymmetric by construction: the human asks or commands, the AI responds. This worked while agents were limited. As they become capable, it becomes a ceiling.

The bottleneck is twofold. Architecturally, half of the collaboration action space is missing by design. Communicatively, everything is forced through a low-bandwidth, ambiguous channel: unstructured chat.

A. Input ambiguity

When an agent needs a specific parameter — a confidence integer, a boolean confirmation — it has to ask in prose.

  • Agent: "Please set the confidence level (0–100)."
  • User: "Just make it safe."
  • Result: the agent parses, guesses, or hallucinates.

B. The missing reverse gear

Capable agents need the other direction: to delegate ("I need domain expertise on X"), to request decisions ("Speed or accuracy?"), to suggest ("Three approaches — try them in this order"). Today all of that goes through chat, which is ambiguous (the agent cannot control how the answer is formatted) and stateless (no way to say whether the ask is blocking or background, or to track whether it was resolved).

2. The Solution: Symmetric Data Topology

HCP does not define UI. It defines the shape of data. The agent specifies the container; the client decides how to render it.

Input and output are the same object — a DataShape — and a Direction says whether the agent is reading (REQUEST) or writing (OFFER). The same shape that collects a value can display one.

The four shapes

Shape What it is Web (example) XR (example)
Discrete Selection from a finite set Dropdown / Radio / Badge Floating orbs
Continuous Magnitude within a range Slider / Gauge Dial / Throttle
Symbolic Opaque payload, tagged by media type Text field / File upload Dictation / Hologram
Composite Recursive record of shapes Form / Dashboard Control panel

Because HCP defines data rather than widgets, the agent is substrate-independent. The same Python that requests a "vector field" can surface as a file uploader in a browser, a holographic cube in a VR engine, or an input on hardware that does not exist yet — provided the client meets HCP's two conformance obligations, soundness (nothing invalid reaches the agent) and completeness (no valid answer is unreachable). That guarantee is a theorem, not a hope; §7 says what it covers.

The agent is the director; the client is the renderer.

3. How HCP relates to prior work

HCP is not a replacement for MCP. MCP connects agents to tools; HCP connects agents to people — and HCP ships as an MCP server, so the bridge between the two is one line of configuration.

Within human-facing protocols, HCP sits in the schema-only lineage — XForms, JSON-Schema forms, MCP elicitation: "send a schema, render it how you like" — rather than the component-catalog lineage (Adaptive Cards, A2UI). What it adds is the half that lineage never wrote down: a definition of what rendering a schema correctly means, a proof that meeting it is sufficient, the reverse direction, the agent's reasoning travelling with the request, and the outcome logged as a first-class record.

Why not just A2UI?

Unlike A2UI, HCP does not draw streamed UI inside an app's design system. It defines data shapes, and four kinds of robustness follow from that choice:

  • Type safety. A2UI validates the UI structure against a catalog schema; the person's answer travels back as an app-defined event, outside the protocol. HCP defines what a valid answer is for every shape and checks it on both sides — soundness and completeness, machine-checked in Lean 4. Nothing invalid reaches the agent.
  • Cross-device. A2UI adapts the agent to the device: the renderer advertises its catalog, the agent generates against it, and an unresolved component is an error, not a fallback. HCP adapts the device to the agent: four shapes, no catalog, and a primitive fallback that is proven to conform — so a terminal, a headset, or a client that does not exist yet needs nothing written for it.
  • Future-proof. UI vocabularies churn — Card, Row, Modal are this decade's screen language — and every widget toolkit that named how to show has been replaced. The categories of an answer have not: finite set, ordered continuum, opaque content, product of these. XForms (2003), MCP elicitation (2025), and A2UI's own input components all land on them. HCP standardizes those and nothing else.
  • Rich UI without a catalog. A2UI's expressiveness ends at its catalog, and it forbids code over the wire by design. In HCP an agent can ship a renderer as content: a SYMBOLIC text/html leaf beside the typed leaves it renders. The same message is a live, interactive 3D scene on a browser or headset and three numeric prompts on a terminal — and the interaction telemetry comes back as an ordinary result. The host validates what the renderer returns and runs it with an opaque origin (cookbook, Pattern 6). Where A2UI's ceiling is the catalog, HCP's ceiling is the client.

They compose: an A2UI renderer is a natural HCP client (CONTINUOUS → Slider, DISCRETE → ChoicePicker, COMPOSITE → a Column of fields, an A2UI action → an HCP result). HCP says what is being asked and what counts as an answer; on a screen, A2UI says how it looks — or the agent does.

Between Defines Rendered by If the person wants to say something else Correctness contract
MCP elicitation Agent (server) → person, via the client A flat JSON-Schema form Client's choice Accept / decline / cancel; no text in the result —
A2UI (Google, v0.9 / 1.0 RC) Agent → UI, with data sync and function calls back A component tree from the client's own catalog, streamed Catalog components (Flutter, React, Lit, Angular) App-defined —
AG-UI (CopilotKit) Agent ↔ frontend An event stream (state, messages, UI events) App's components Chat alongside —
Adaptive Cards (Microsoft) App → host Card components with declared fallback Host renderer App-defined —
HCP Agent ↔ person, both directions Four recursive data shapes; no widgets Any conforming substrate Reject always carries the person's text Soundness + completeness, machine-checked

The full comparison, with citations, is in the paper. This table is only meant to answer the first question a reader has.

4. Installation

pip install hcp

5. Usage: The Python SDK

HCP is a mixin. You do not rewrite your agent; you inherit HCPMixin and it can speak shapes instead of only text. The mixin keeps one piece of state — the registry of outstanding interactions — so that every answer is checked against the shape that was actually sent.

A. Architectural flexibility

HCP is an interface protocol, not an execution pipeline. The SDK translates agent intent into the wire protocol; how the intent is generated is up to you.

  • Pattern A — Single-pass. Inject HCP_PORTAL_PROMPT into the primary agent's system prompt; it emits HCP JSON natively. Low-latency copilots, strong foundation models.
  • Pattern B — Lazy-loading. The primary agent calls a lightweight tool that triggers a secondary, isolated LLM call to generate the interaction. Complex reasoning agents with tight token budgets.
  • Pattern C — Native fine-tuning. A model fine-tuned on the Co-Op Memory emits HCP JSON without a prompt.
  • Pattern D — Tool-call adapter. HCP_Request / HCP_Offer as tools: declare them (hcp_tools(format=...), any runtime format) or serve them — hcp.mcp_server.HCPMCPServer makes HCP an MCP server, so any MCP-capable agent adopts it by adding one server. Same validation and Co-Op Memory as Pattern A, no prompt. Recommended whenever tool calling is available.

B. Inherit the mixin

from hcp import HCPMixin, HCP_PORTAL_PROMPT

class BaseAgent(HCPMixin):
    # ... agent reasoning logic ...

C. The forward pass (intent → protocol)

Pass the LLM's raw output to hcp_forward(). It strips markdown fences, validates the schema, inflates the objects, registers the interaction, and logs the proposal.

    async def step(self, user_input):
        raw_llm_output = await self.llm.generate(user_input)   # Pattern A, B, or C
        try:
            hcp_payload = self.hcp_forward(raw_llm_output)     # str -> validated JSON-RPC message
            self.send_message(content=None, metadata={"hcp": hcp_payload})
        except ValueError as e:
            print(f"HCP Error: {e}")                           # hallucinated schema, min > max, ... -> fall back to text

Only well-formed shapes leave the agent: a paradoxical range (min > max), a default outside its own shape, or a malformed composite field raises instead of being forwarded. When your code — not the LLM — decides the interaction, call the typed helpers directly (hcp_request_continuous, hcp_offer_symbolic, …); both paths share one wrapper, so the wire format, registry, and log are identical.

D. Pattern B: lazy-loading

Add a trigger to the main agent's system prompt:

- Enable_HCP(context_summary, detailed_intention, detailed_next_step): Use this ONLY if you need to interact with the User.

Then run the micro-inference:

    async def execute_hcp(self, context_summary, detailed_intention, detailed_next_step):
        user_msg = (f"Context: {context_summary}\n"
                    f"Intention: {detailed_intention}\n"
                    f"Next Step: {detailed_next_step}\n")
        raw_llm_output = await self.llm.generate(HCP_PORTAL_PROMPT, user_msg)
        try:
            hcp_payload = self.hcp_forward(raw_llm_output)
            self.send_message(content=None, metadata={"hcp": hcp_payload})
        except ValueError as e:
            print(f"HCP Error: {e}")

E. The feedback loop (protocol → Co-Op Memory)

When the client answers, hcp_resolve enforces the agent's half of the contract and logs the outcome.

    def on_incoming_message(self, message):
        metadata = getattr(message, "metadata", {}) or {}

        if "hcp_response" in metadata:
            raw_payload = metadata["hcp_response"]              # a result, or an error response
        else:
            # Free text is a USER-INITIATED atom: fresh refId; it rejects every
            # outstanding blocking request, and the SDK records which ones.
            raw_payload = self.hcp_wrap_chat(message.content, message_id=message.id)

        data, used_proposal = self.hcp_resolve(raw_payload)

        if used_proposal is None:
            return                                              # system outcome (unrenderable / expired / cancelled): logged, nothing to consume
        if used_proposal:
            self.memory.add(f"[HCP] User accepted: {data}")     # validated against the requested shape
        else:
            ...                                                 # the person's text: handle it as chat

What hcp_resolve guarantees:

  • Soundness. An accepted value is checked against the shape that was sent, using hcp.validity.is_value_valid — a line-by-line transliteration of the Lean predicate. A value that does not fit never reaches your context. The agent does not trust the client.
  • The escape hatch. A rejection must carry the person's text.
  • SYMBOLIC narrowing. SYMBOLIC values are type-tagged, not type-checked. Register a codec per media type — hcp_register_codec("application/json", is_valid_json) — and it runs inside hcp_resolve.
  • Lifecycle. Every message with an id is resolved exactly once — by a result (the person's verdict) or an error (UNRENDERABLE, EXPIRED, CANCELLED, never counted as a rejection). hcp_cancel(refId) withdraws a stale offer. Offers that expect nothing back (expects_result=False) are notifications and are not logged.

The wire protocol is specified in docs/schema.md; the SDK reference is docs/SDK.md.

F. The Co-Op Memory hook

Override the hook to persist the log.

    def hcp_on_log(self, log_entry):
        """
        Invoked at emission (actor "Agent"), on every result (actor "User"),
        and on every system outcome (actor "System").

        {
            "timestamp": "iso-date...",
            "actor": "Agent" | "User" | "System",
            "payload": <the wire message verbatim>,
            "outcome": "UNRENDERABLE" | "EXPIRED" | "CANCELLED"   # system outcomes only
        }
        Join Agent and User entries on refId; a user-initiated text lists the
        requests it rejected under payload.result.context.resolves.
        """
        MyDatabase.save(log_entry)

6. Why HCP

A. Elastic fallback

Shapes are recursive, so complex structures — a physics configuration, a medical intake, a robot pose — are composites of simpler shapes, and a client that cannot render the whole can always render the parts.

The "3D gizmo" problem. An agent requests a 3D vector (X, Y, Z) to position a robotic arm.

  1. A high-fidelity client (Apple Vision Pro) recognises the group and renders a holographic manipulation gizmo.

  2. A low-fidelity client (a text terminal) has no gizmo. In a component-catalog protocol, that is a render failure.

  3. An HCP client decomposes the composite until it reaches shapes it can render:

    • Web: three sliders.

    • XR: three floating dials.

    • Terminal: three ASCII sliders driven by arrow keys.

      X-Axis: [<-- ███░░░░░░░ -->] 30% Y-Axis: [<-- ██████░░░░ -->] 60% Z-Axis: [<-- ████░░░░░░ -->] 40%

The agent gets its (X, Y, Z) on every substrate, and type safety survives the fallback: even in the terminal the person cannot type "hello" into a 0.0–1.0 range. The fallback is principled, not best-effort — the primitive one-widget-per-shape substrate is itself proven to conform.

B. Co-Op Memory

Cursor's Tab learns from whether you press Tab or keep typing: act + outcome. HCP records more. Every proposal carries the agent's rationale; every rejection carries the person's text. Each record is a complete tuple — reason, act, outcome, and (on rejection) the correction:

  • A veto: "wipe /var/logs" → "No. Those logs are needed for legal compliance."
  • A style correction: a functional refactor offered → the person types a class instead.
  • An abstraction mismatch: "how many retries?" → "I don't know the number; pick one that's safe."

usedProposal: false alone would conflate all three. The text tells them apart, so a capable model can derive labels from the record alone — no separate labeling pass. The derivation is still inference, not ground truth; docs/log-example.md walks through what the records do and do not tell you.

7. Formal specification

The conformance contract and its consequences are stated and machine-checked in hcp_proof/HCP_Proof.lean (Lean 4.20, core only, no sorry; the transfer theorems depend on no axioms at all).

Proven: the validity relation for all four shapes; that soundness + completeness imply the agent cannot distinguish one conforming client from another (substrate_indistinguishable); that a primitive substrate conforms, so a universal fallback exists; that two natural-looking clients — a trapping dropdown, a lenient text box — fail the contract, so the obligations bite; and that none of it depends on the numeric representation (Float, Int, Nat are all instances).

Not proven: that any particular real client conforms (each client's obligation, discharged by the conformance suite in tests/); anything about the content of SYMBOLIC payloads (only the tag); anything about rendering, layout, or timing.

docs/math.md explains the development section by section.

8. Advanced scenarios

Because HCP decouples parameters from rendering, it can drive a physics engine or an XR simulation without the agent knowing graphics programming. The cookbook has five patterns — Tab-style ghost text, a fluid simulation, an AR assembly guide, a SYMBOLIC-free 6-DOF robot arm, and Designer-style option galleries. One excerpt:

The robotic arm (kinetic alignment)

In spatial computing, "acceptance" is not a click. The person's hand has to match the agent's proposed motion in space and time.

  • Scenario: AR glasses showing a mechanic how to install a component.
  • Mechanism: an OFFER — a volumetric ghost hand waiting to be overlapped.
  • Acceptance: the person's tracked hand synchronises with the ghost's skeletal pose.
class IndustrialGuide(HCPMixin):

    def show_assembly_step(self):
        frames = [...]  # 2 s of skeletal keyframes: position, wrist quaternion, grip
        return self.hcp_offer_symbolic(
            rationale="Guide user to insert Servo Motor A with specific torque.",
            label="Sync with Ghost Hand",
            mimeType="application/x-skeletal-pose-stream+json",
            value={"target_object": "servo_motor_a", "animation_clip": frames, "tolerance_threshold": 0.95},
        )
  • Alignment: the person shadows the ghost hand; joint distance stays near zero → usedProposal: true.
  • Deviation: the person rotates their wrist 90° and reaches for a screwdriver → usedProposal: false, value { "detected_tool": "screwdriver", "hand_pose": [...] }. The agent proposed a push; the person corrected it to a screw. HCP captures the physical correction of the workflow.

9. Repository

hcp/          the package            docs/         intro · concepts · architecture · schema · cookbook · log-example · math · SDK
tests/        conformance suite      hcp_proof/    HCP_Proof.lean
python -m pytest tests -q          # 56 tests: the Lean case matrix plus every SDK enforcement path
lean hcp_proof/HCP_Proof.lean      # Lean 4.20; no output means every theorem checked

If you build a client, tests/test_validity.py is your conformance oracle: everything your client returns as accepted must satisfy is_value_valid, and everything is_value_valid accepts must be reachable in your UI.

Protocol version 1.0 is frozen. Within 1.x, receivers ignore unknown fields and new hcp/* methods; nothing existing changes. The versioning rules are in docs/schema.md §3.

10. So What?

  • Researchers get a high-fidelity record of human-AI interaction — reason, act, outcome, correction — in one format.
  • Developers get online-learning signal from real interactions, and a contract that makes the agent's guarantees independent of the frontend.
  • Founders deploy the same agent into any conforming client — CLI, Blender, spatial computing, a brain-computer interface — without touching the backend.

HCP is not designed just for this era.

The agent you write today runs unchanged on any conforming client — including the ones that do not exist yet.

Metadata

Release files for hcp 0.1.3

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

Source distribution (sdist)

Source distribution for hcp 0.1.3
File Size Uploaded
hcp-0.1.3.tar.gz 39.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hcp 0.1.3
File Interpreter ABI Platform
hcp-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 75.4 kB

Release files / hcp-0.1.3.tar.gz

Download URL hcp-0.1.3.tar.gz
Size 39.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a3735225eafac584d7c984ede8d1aec476fa0308971f35e6d05af18a59e70266
BLAKE2b-256 checksum
How to use checksums
a1fbc4f984712c631c077eda7f3ed68c895d1f206e5c6ffe155759abd474ba92
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.2.1 CPython/3.14.2 Darwin/25.0.0

Release files / hcp-0.1.3-py3-none-any.whl

Download URL hcp-0.1.3-py3-none-any.whl
Size 36.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
775650b581ffb6732f396016933ec4698c1675746f3f15f06586745951737d42
BLAKE2b-256 checksum
How to use checksums
970a5f5d3c1729f5b6fa5609eafecd533dce8745cedce81ace8198c8991916c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.2.1 CPython/3.14.2 Darwin/25.0.0

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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