Human Loop Protocol SDK for wrapping agent harnesses
Project description
loops
Human Loop Protocol (HLP) Python SDK for responsible human-agent workflows.
HLP is the human-interaction control plane for existing agent harnesses. It models the responsibility loop around agent work: task delegation, checkpoint decisions, artifact review, ledger writes, and audit replay. It does not unify or replace harness execution mechanisms. Codex CLI, Claude Code CLI, Kimi CLI, Pi, and similar runtimes keep their own execution model and connect through adapters. OpenAI Agents SDK, OpenAI Python SDK, LangGraph, and CrewAI are shape-compatible entry points (thin call shims), not deep first-class integrations.
This project does not ship its own agent harness. The top-level loops package
is the HLP SDK: protocol objects, client, host, stores, event bus, and adapters
for wrapping external harnesses. The line-oriented TUI (loops-hlp-tui) is an
optional host/channel demo, not part of the protocol core.
What HLP Owns
Task: the bounded unit of human-agent work.Checkpoint: the point where an agent needs a human decision.ArtifactandReview: delivery and acceptance records.LedgerandAudit: append-only project state and replayable history.- Continuous control values:
task.amend,task.interrupt,steering_log,PermissionGrant, and checkpointproposed_actions. AgentAdapter: the explicit boundary from HLP into an agent harness or CLI.HarnessAdapter: the projection boundary from harness events into HLP's human-facing semantics.
HLP does not own tool calling, agent-to-agent routing, UI delivery, or the internal execution strategy of an agent harness.
Quick Start
Run the HLP workflow demo through the default Codex CLI adapter:
uv run loops-hlp-demo
Run adapter compatibility checks without external services:
uv run loops-hlp-adapters-demo
Run an offline Codex harness wrapping demo with an injected runner:
uv run loops-hlp-harness-demo
Run the dependency-free Codex harness adapter demo:
uv run loops-hlp-codex-harness-demo
Run the full local CLI lifecycle test against installed Codex, Kimi, and Claude Code:
uv run loops-hlp-local-cli-demo --adapters codex,kimi,claude
Run the line-oriented HLP TUI channel:
uv run loops-hlp-tui --adapter codex
uv run loops-hlp-tui --adapter pi
uv run loops-hlp-tui --adapter fake # offline, no external CLI
Live adapters (codex / pi) use harness-capable adapters
(CodexHarnessAdapter / PiHarnessAdapter) in chat prompt mode: free-text
prompts put the user message first (not the full “HLP adapter operation” JSON
dump). Lifecycle ops (block/resume/cancel) stay protocol-shaped. While the CLI
runs, the TUI streams compact harness events (⋯ agent start, live
⋯ agent: … text deltas, tool/status milestones) instead of only a wait timer.
After each prompt and checkpoint resolution, it projects human-facing events
into HLP and surfaces the final agent summary plus pending work (/inbox,
/approve, /reject, /review).
Live adapters block on the external process for each prompt. The TUI prints a
heartbeat while waiting and fails after --timeout seconds (default 60). If Pi
appears stuck, try:
uv run loops-hlp-tui --adapter pi --timeout 30
# or offline protocol UX without a model:
uv run loops-hlp-tui --adapter fake
Run the offline HLP-realtime promotion demo (soft merge → amend provenance, BCI-alone high-risk deny; no voice/BCI hardware):
uv run loops-hlp-realtime-demo
Soft-control harness E2E (multi soft → merge → amend/steer; default offline):
uv run loops-hlp-soft-e2e --adapters codex,pi --strict
uv run loops-hlp-soft-e2e --inventory
# real installed CLIs (opt-in):
uv run loops-hlp-soft-e2e --live --adapters pi --timeout 120
HLP_RUN_EXTERNAL_CLI_E2E=1 uv run pytest tests/external/test_hlp_soft_real_cli_e2e.py -q
In the TUI, buffer multiple soft controls then merge-promote (HLP-realtime D2):
uv run loops-hlp-tui --adapter fake
> do the work
> /soft 先别动 production 配置
> /soft --intent clarify 重点看 token 过期路径
> /softs
> /promote
# or one-shot: /promote focus on auth boundaries
# /soft list | pop | clear
Optional profile constants: HLP_REALTIME_PROFILE / HLP_REALTIME_SPEC_VERSION
(0.3.0-draft). Package version remains 0.2.0.
Run the offline BCI (brainwave) channel demo. HLP is compatible with
brain-computer-interface input by design (spec appendix C): a BCI decoder
is just a channel/sensor-plane producer of
ControlSignal(source_kind="bci") — HLP receives only promoted
responsibility events, never raw EEG frames. High-risk hard checkpoints
cannot be closed by BCI alone (D3, fail-closed; second-factor path shown).
No protocol changes, no EEG hardware:
uv run loops-hlp-bci-demo
Run the PR Review Desk host application (real embedding case, offline by default):
uv run loops-hlp-pr-desk
This is not another adapter smoke test. PRReviewDesk is a host that owns PR
domain language and inbox cards; it embeds HLPHost as the human-control plane
and projects a code-review harness through CodexHarnessAdapter. The offline
runner is deterministic and is the default CI path.
--live uses your installed Codex CLI. It can fail for environment reasons
outside HLP (unsupported default model, auth/provider mismatch, usage limits).
When that happens the desk prints a structured JSON error with codex_message
and hints instead of a traceback. Useful recovery options:
# deterministic host demo (recommended)
uv run loops-hlp-pr-desk
# live with an explicit model your Codex account supports
uv run loops-hlp-pr-desk --live --model "gpt-5.4"
Reviewer
-> PRReviewDesk (host)
-> HLPHost / HLPClient (Task / Checkpoint / Artifact / Review / Ledger / Audit)
-> CodexHarnessAdapter
-> code-review harness
The TUI is an optional host/channel over HLP. It renders prompt input, slash commands, human inbox approvals, artifact review, audit replay, and session transcript state while keeping model calls, tool execution, sandboxing, and the agent loop inside the selected harness adapter.
Adapter Depth
| Level | Adapters | Meaning |
|---|---|---|
| first-class | CodexCLIAdapter, CodexHarnessAdapter, PiHarnessAdapter, ClaudeCodeCLIAdapter, ClaudeCodeHarnessAdapter, KimiCLIAdapter, KimiHarnessAdapter, ProcessAgentAdapter, PromptCLIAdapter |
Real process/CLI boundary with correlation and (where applicable) harness event projection |
| shape-compatible | OpenAIAgentsSDKAdapter, OpenAIPythonSDKAdapter, LangGraphAdapter, CrewAIAdapter |
Thin Python framework entry points; useful for embedding experiments, not a claim of full harness parity |
| testing | In-memory fake adapters under loops.hlp.adapters |
Offline unit tests and demos only |
All four first-party CLIs share the same JSONL projection pipeline, reliable peek/ack event delivery, and chat/protocol prompt modes:
| CLI | Delegate adapter | Harness adapter (projection + peek/ack) | Wire mode | TUI |
|---|---|---|---|---|
| Codex | CodexCLIAdapter |
CodexHarnessAdapter |
codex exec --json |
yes |
| Pi | — | PiHarnessAdapter |
pi --mode json |
yes |
| Claude Code | ClaudeCodeCLIAdapter |
ClaudeCodeHarnessAdapter |
claude -p --output-format stream-json |
yes |
| Kimi | KimiCLIAdapter |
KimiHarnessAdapter |
kimi -p --output-format stream-json |
yes |
Harness adapters project explicit hlp / pi human-loop payloads into HLP
checkpoints and artifacts, validate correlation on every event line, and drop
transport-level duplicates (e.g. Claude Code's result envelope repeats the
final assistant text). In protocol mode, Codex and Claude Code additionally
enforce the reply envelope natively — --output-schema / --json-schema
against the shared HLP_RESULT_SCHEMA (closed object: run_id,
correlation_id, status, summary, error) — so the correlation echo no
longer depends on prompt discipline; Kimi and Pi have no such flag and are
covered by the robust extraction layer (markdown fences, prose, CRLF, unicode
variance all tested). End-to-end coverage: offline contract tests with
injected runners for every operation, a projection robustness suite
(tests/test_hlp_projection_robustness.py), plus an opt-in real-CLI
lifecycle suite (HLP_RUN_EXTERNAL_CLI_E2E=1, see Verification).
Session-resume continuity (default; session_continuity=False restores
one-shot behavior): delegate binds the CLI-native session id from the wire,
and follow-up ops (block / resume / steer / cancel) resume the same
native session — codex exec resume, claude --resume, kimi --session,
pi --session. Continuity means a new turn in the same session context,
the strongest form these CLIs offer today, not frozen-process resumption.
Persistence-disabling flags (--ephemeral, --no-session,
--no-session-persistence) are dropped automatically when continuity is on.
Proven live with the codeword probe (uv run loops-hlp-continuity-e2e):
the model recites a fact it was told before the checkpoint cycle — only
possible in a truly resumed session.
Handoff (ownership.transfer) goes one step further: on CLIs with native
session forking, the receiving agent inherits the full session context —
pi --fork and claude --resume --fork-session branch the source session
into a new one (verified live: the receiving run recites the pre-handoff
codeword without being told again). Codex and Kimi have no native fork yet;
handoff there stays a one-shot envelope carrying the structured context
(verified live as the documented fallback).
Industrial Profile (Reference)
HLP-industrial in this repository is a reference profile: per-task CAS /
idempotency, adapter outbox intent, reliable harness event peek/ack, permission
scope grammar, reducer-ready audit with optional hash chain, and wire schema /
version negotiation. It proves protocol semantics offline. It is not a
multi-writer production backend or managed control plane.
Recovery and timeout semantics are concrete and tested:
- Outbox recovery loop: adapter intents are persisted before side effects
and stay
pendingon crash.pending_adapter_outbox()(client and operations level) exposes them; retrying the original operation with the sameidempotency_keyreuses the persisted intent and never repeats committed side effects. - Checkpoint timeout sweep:
expire_due_checkpoints(now)expires every pending checkpoint pastexpires_atthrough the auditedcheckpoint.expireoperation; the reference policy keeps the task blocked (pure suspension, spec §7.2).
Transport (HTTP reference binding)
Spec §7.1 leaves transport open; this repository ships a stdlib-only reference binding (zero new dependencies) so hosts and channels can talk to a remote HLP server. The wire contract is the deliverable — production deployments are expected to rebind with their own framework.
uv run loops-hlp-serve --adapter fake --port 8471
curl -X POST http://127.0.0.1:8471/v1/ops/task.create \
-H 'Content-Type: application/json' -H 'X-HLP-Principal: user_alice' \
-d '{"params":{"principal":"user_alice","goal":"ship safely"}}'
curl http://127.0.0.1:8471/v1/events # SSE audit stream (hash-chained)
POST /v1/ops/<object.verb>— all 23 protocol operations plushuman.inbox,pending.outbox,checkpoints.expire_due; body carriesparams, optionalexpected_task_revision(CAS) andidempotency_key.- Errors map
ProtocolErrorcodes to their §6.1 HTTP analogies (400/401/404/408/409/410/412) with the §6.2 error object. - Mutating operations require the
X-HLP-Principalheader; real authentication stays deployment-side (spec §1.2 RBAC is not HLP's). GET /v1/events?after=<seq>— SSE stream of the §3.9 audit events;GET /v1/version,GET /v1/health.HttpHLPWireClient(loops.hlp.transport) is the matching wire-level reference client; results are wire dicts (the wire is the contract).
Non-goals for this binding: no framework dependency, no TLS, no RBAC, no
WebSocket/realtime soft-signal streaming, no remote adapter registration
(adapters stay server-local), and no typed from_wire reconstruction
(follow-up). The spec text is unchanged; converging §7.1 is a later proposal.
For Kimi, the smoke demo can build a temporary kimi-cli config from
~/.metaworker/config.yaml when native Kimi Code has no model configured. The
temporary file is created in the system temp directory and deleted after the
run.
Python SDK
Use HLPHost when embedding HLP in an application:
from loops import ArtifactPayload, CodexCLIAdapter, HLPHost
host = HLPHost.in_memory(adapter=CodexCLIAdapter())
client = host.client
task = await client.create_task(
principal="user_alice",
goal="Review PR #1234 for security issues",
type="code-review",
)
run = await client.delegate(
task.id,
agent_id="agent_codex",
capability="code-review",
input={"goal": task.spec.goal, "repository": "web"},
)
await client.start(task.id)
artifact = await client.commit_artifact(
task_id=task.id,
type="report",
payload=ArtifactPayload(
kind="inline",
uri="mem://report-v1",
checksum="sha256:report-v1",
),
produced_by=run.agent_id,
)
await client.amend(
task.id,
by="user_alice",
text="Focus on authentication and permission boundaries.",
intent="constrain",
)
Use HLPClient directly when you already own the store, event bus, or adapter:
from loops import HLPClient, SQLiteHumanLoopStore
client = HLPClient(store=SQLiteHumanLoopStore("hlp.db"))
Adapters
HLP separates two adapter directions:
AgentAdapter: HLP commands a harness or runtime to delegate, block, resume, handoff, or cancel work.HarnessAdapter: a harness projects human-facing events back into HLP as checkpoints, artifacts, reviews, ledger entries, and audit.
Named local coding-agent adapters use one-shot prompt mode so they match the real CLIs installed on a developer machine:
from loops import ClaudeCodeCLIAdapter, CodexCLIAdapter, CodexHarnessAdapter, KimiCLIAdapter, PiHarnessAdapter
codex = CodexCLIAdapter()
codex_harness = CodexHarnessAdapter()
pi_harness = PiHarnessAdapter()
kimi = KimiCLIAdapter()
claude = ClaudeCodeCLIAdapter()
Use CodexCLIAdapter when HLP only needs to delegate a one-shot Codex task.
Use CodexHarnessAdapter when Codex JSONL output should also project
human-facing events back into HLP checkpoints and artifacts.
Use PiHarnessAdapter when Pi JSON/JSONL output should project pi or hlp
human-facing events into the same HLP checkpoint and artifact flow.
ProcessAgentAdapter is still available for custom JSON-over-stdin/stdout
processes:
from loops import ProcessAgentAdapter
adapter = ProcessAgentAdapter(command=("my-agent", "run", "--json"))
Framework adapters accept native framework objects without adding those packages
to loops core dependencies:
from loops import CrewAIAdapter, LangGraphAdapter, OpenAIAgentsSDKAdapter
openai_agents = OpenAIAgentsSDKAdapter(agent=agent, runner=Runner)
langgraph = LangGraphAdapter(
graph=compiled_graph,
config={"configurable": {"thread_id": "t1"}},
)
crew = CrewAIAdapter(crew=my_crew)
The framework adapters above are delegate adapters by default. Unless the
underlying framework object exposes a real pause/resume primitive, HLP
block, resume, steer, handoff, and cancel are local contract records,
not a guarantee that the framework runtime has been physically paused.
OpenAI's Python SDK can be injected without making it a hard dependency:
from openai import AsyncOpenAI
from loops import OpenAIPythonSDKAdapter
adapter = OpenAIPythonSDKAdapter(
client=AsyncOpenAI(),
model="gpt-4.1",
)
Use HarnessAdapter semantics when an existing harness already has its own
execution loop and only needs a common human interaction surface:
from loops import CodexHarnessAdapter, HLPClient, PiHarnessAdapter
adapter = CodexHarnessAdapter(command=("codex", "exec", "--json"))
# Or: adapter = PiHarnessAdapter() # pi --mode json -p --no-session
client = HLPClient(adapter=adapter)
task = await client.create_task(
principal="user_alice",
goal="Review a generated patch",
)
run = await client.delegate(task.id, "agent_reviewer", capability="code-review")
await client.start(task.id)
await client.project_harness_events(run.run_id)
inbox = await client.human_inbox("user_alice")
CodexHarnessAdapter applies the same HarnessAdapter semantics to
codex exec --json output. It preserves HLP task_id as the run correlation
id and maps explicit Codex HLP events such as needs_approval, needs_input,
needs_choice, and artifact into the common HLP objects.
PiHarnessAdapter applies the same projection contract to Pi JSON/JSONL output
using pi.event lines or nested pi / hlp payloads.
Documentation
- Published site: ontheloops.com
- HLP spec: docs/specs/HLP.md
- Architecture overview: docs/architecture/OVERVIEW.md
- HLP implementation notes: docs/architecture/hlp.md
- Website source: docs/site
Verification
Default offline release verification:
uv run pytest -q
uv run pytest tests/conformance -q
uv run ruff check .
uv run ruff format --check .
uv run mypy loops
uv run python scripts/check_release_metadata.py
uv run python scripts/check_spec_site_sync.py
npm run build
npm run verify:site
Opt-in CLI lifecycle tests require installed local agent CLIs:
uv run loops-hlp-local-cli-demo --adapters codex,kimi,claude --strict
HLP_RUN_EXTERNAL_CLI_E2E=1 uv run pytest tests/external/test_hlp_real_cli_e2e.py -q
Contributing
See CONTRIBUTING.md. By contributing, you agree that your contributions are licensed under the Apache License 2.0.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loops_hlp-0.3.0.tar.gz.
File metadata
- Download URL: loops_hlp-0.3.0.tar.gz
- Upload date:
- Size: 162.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1cd41c5c9a6f17171f93a581e71f3f0ada4615f4f8238c50779e8d1b2244c334
|
|
| MD5 |
514be022c0f05a24b6db8c5ceecb7098
|
|
| BLAKE2b-256 |
822941dd4950baaf8517fa8d6e1fa92dc19303ade3d3edce371570e21b9b88af
|
Provenance
The following attestation bundles were made for loops_hlp-0.3.0.tar.gz:
Publisher:
release.yml on simplosophy/loops
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loops_hlp-0.3.0.tar.gz -
Subject digest:
1cd41c5c9a6f17171f93a581e71f3f0ada4615f4f8238c50779e8d1b2244c334 - Sigstore transparency entry: 2206322383
- Sigstore integration time:
-
Permalink:
simplosophy/loops@26b42acb6e687207d9979b2fa47a089587125644 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/simplosophy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@26b42acb6e687207d9979b2fa47a089587125644 -
Trigger Event:
push
-
Statement type:
File details
Details for the file loops_hlp-0.3.0-py3-none-any.whl.
File metadata
- Download URL: loops_hlp-0.3.0-py3-none-any.whl
- Upload date:
- Size: 142.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80b26c5724a060fec2d8436e41d105b038da733c30c52dcf991fd7881ab6272f
|
|
| MD5 |
5abe4f553b3be264f122a95ea24815d9
|
|
| BLAKE2b-256 |
7d533c1735d2c5284abf8cb8add5dc4d340ac3a82b38ee0e5f9196877345e1b9
|
Provenance
The following attestation bundles were made for loops_hlp-0.3.0-py3-none-any.whl:
Publisher:
release.yml on simplosophy/loops
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loops_hlp-0.3.0-py3-none-any.whl -
Subject digest:
80b26c5724a060fec2d8436e41d105b038da733c30c52dcf991fd7881ab6272f - Sigstore transparency entry: 2206322437
- Sigstore integration time:
-
Permalink:
simplosophy/loops@26b42acb6e687207d9979b2fa47a089587125644 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/simplosophy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@26b42acb6e687207d9979b2fa47a089587125644 -
Trigger Event:
push
-
Statement type: