Human-AI Collaboration Protocol (HCP)
The HTTP between humans and AI.
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,Modalare 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
SYMBOLICtext/htmlleaf 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_PROMPTinto 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_Offeras tools: declare them (hcp_tools(format=...), any runtime format) or serve them —hcp.mcp_server.HCPMCPServermakes 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 insidehcp_resolve. - Lifecycle. Every message with an
idis 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.
-
A high-fidelity client (Apple Vision Pro) recognises the group and renders a holographic manipulation gizmo.
-
A low-fidelity client (a text terminal) has no gizmo. In a component-catalog protocol, that is a render failure.
-
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)
| File | Size | Uploaded | |
|---|---|---|---|
| hcp-0.1.3.tar.gz | 39.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|