Python SDK and CLI for running an agent from a Harness Configuration Protocol (HCP) TOML file.
This package does not implement an agent by itself. It is a Python control layer around this chain:
Python app / hcp CLI -> hcp-sdk -> pi-hcp -> pi-acp -> Pi -> model provider
Use it when you want Python code or scripts to:
- validate that an HCP TOML file is readable
- ask
pi-hcpto prepare a Pi runtime directory - start the prepared ACP agent process
- create/load an ACP session
- send a text prompt and collect text chunks from
session/update
| What | Name |
|---|---|
| PyPI package | harness-configuration-protocol |
| Python import | hcp |
| CLI command | hcp |
Current implementation is wrapper mode only:
hcp-sdkshells out topi-hcp --dry-runfor runtime preparation.hcp-sdkthen launchespi-hcpas the ACP stdio process.- Native Python HCP materialization is planned but not implemented.
That means HCP features such as skills, embedded resources, MCP, hooks, model config, and workspace staging are handled by pi-hcp, not by Python code in this repository.
pip install harness-configuration-protocolFor local development:
cd hcp-sdk
pip install -e '.[dev]'Wrapper mode needs:
- a readable HCP TOML file
pi-hcponPATH, or an explicit--pi-hcp-command/pi_hcp_commandpi-acponPATH, or an explicit--acp-command/acp_command- provider environment variables required by your HCP config
Default unit tests do not require Node, Pi, pi-acp, provider credentials, or network access.
Save as hcp.toml or copy examples/minimal-hcp.toml. For a broad all-sections example, see examples/full-hcp.toml:
version = 1
[run]
cwd = "."
agent_dir = ".pi-agent"
offline = false
verbose = false
[env]
files = [".env"]
override = true
required = ["OPENAI_API_KEY"]
[env.set]
PI_SKIP_VERSION_CHECK = "1"
[model]
provider = "openai"
id = "gpt-4o-mini"
api = "openai-completions"
base_url = "https://api.openai.com/v1/"
api_key_env = "OPENAI_API_KEY"
context_window = 128000
max_tokens = 16000
[tools]
allow = []Then create .env beside it:
OPENAI_API_KEY=sk-...Validate TOML readability:
hcp validate hcp.tomlAsk pi-hcp to prepare the runtime and print redacted metadata:
hcp prepare hcp.toml --jsonRun one prompt:
hcp run hcp.toml --prompt "Say hello in one sentence."Use explicit command paths when pi-hcp or pi-acp are not on PATH:
hcp run hcp.toml \
--prompt "Say hello" \
--pi-hcp-command 'node /path/to/pi-hcp/dist/index.js' \
--acp-command '/path/to/pi-acp/dist/index.js'import asyncio
from hcp import connect_agent
async def main() -> None:
async with connect_agent(
"hcp.toml",
pi_hcp_command="pi-hcp",
acp_command="pi-acp",
) as agent:
await agent.initialize()
await agent.new_session()
text = await agent.prompt_text("Say hello in one sentence.")
print(text)
asyncio.run(main())Reads the TOML file and checks basic HCP wrapper compatibility:
from hcp import validate_config
config = validate_config("hcp.toml")
print(config.path)
print(config.data)This is not a full semantic validator. It checks that the file exists, is TOML, is a table, and uses version = 1.
Runs pi-hcp --config <path> --dry-run and returns preparation metadata:
from hcp import prepare_runtime
runtime = prepare_runtime(
"hcp.toml",
pi_hcp_command="pi-hcp",
acp_command="pi-acp",
)
print(runtime.cwd)
print(runtime.agent_dir)
print(runtime.acp_command)
print(runtime.to_redacted_dict())Prepares runtime, starts pi-hcp, connects with agent-client-protocol, and yields an HcpAgentSession:
async with connect_agent("hcp.toml") as agent:
await agent.initialize()
session = await agent.new_session()
text = await agent.prompt_text("Summarize the current workspace.")Useful methods:
| Method | Purpose |
|---|---|
initialize() |
Send ACP initialize request |
new_session() |
Create a new ACP session using the prepared cwd |
load_session(session_id) |
Load an existing session id |
prompt_text(text, timeout=None) |
Send a text prompt and return collected assistant text |
close() |
Close ACP connection and stop process |
Yes, HCP configs can provide Pi skills. In wrapper mode, pi-hcp resolves and writes them into Pi settings.
Use existing skill files:
[skills]
enabled = true
paths = [
"./skills/code-review/SKILL.md",
"/absolute/path/to/skills/risk/SKILL.md",
]Or embed a skill directly in the HCP config. See also examples/skills-hcp.toml and examples/full-hcp.toml:
[resources]
embedded_dir = ".hcp-embedded"
[[resources.embedded]]
kind = "skill"
path = ".pi/skills/risk/SKILL.md"
encoding = "utf-8"
content = """
# Risk Skill
Use this skill only for high-risk fixture validation.
"""
sha256 = "<sha256-of-content>"After preparation, pi-hcp materializes embedded skills under embedded_dir and adds the resulting SKILL.md path to Pi settings.json.
Rules of thumb:
- Use
[skills].pathsfor normal local skills. - Use
[[resources.embedded]] kind = "skill"when the HCP file should carry the skill content. - Prefer absolute paths in generated configs. Relative paths are resolved by
pi-hcpagainst the configured run context. - Keep one skill per directory with a
SKILL.mdfile.
[resources]
system_prompt_path = "sys.md"
append_system_prompt_paths = ["append.md"]
context_files = false[mcp]
config_path = "base-mcp.json"
imports = ["cursor"]
[mcp.settings]
toolPrefix = "risk"
[mcp.servers.echo]
command = "node"
args = ["server.js"]
cwd = "server-cwd"[hooks.PreToolUse.audit]
matcher = "bash"
command = "./hooks/audit.sh"
timeout = 2
run_async = true[session]
snapshot_json = '''{"version":1,"id":"seed-session","messages":[{"role":"system","content":"seeded"}]}'''[workspace]
root = "workspace-root"
[[workspace.entries]]
name = "data"
source = "local:data.txt"
target = "data.txt"
mode = "copy"You can pass extra environment variables from Python:
async with connect_agent(
"hcp.toml",
env={"OPENAI_API_KEY": "sk-..."},
secrets=["sk-..."],
) as agent:
...secrets are redacted from SDK subprocess diagnostics. They are not a secret manager. Do not commit .env files or TOML files containing API keys.
The SDK raises HcpConfigError for config-read failures and HcpLaunchError for wrapper/launch failures. Both inherit from HcpError.
Common failures:
| Symptom | Likely cause |
|---|---|
Unable to execute pi-hcp command |
pi-hcp path is wrong or not on PATH |
pi-hcp dry-run failed |
HCP semantic/config/provider-env error reported by pi-hcp |
did not return JSON |
wrong command passed as pi-hcp, or wrapper printed non-JSON during dry run |
| ACP connection failed | pi-hcp launched but pi-acp/Pi failed during startup |
| empty prompt text | provider returned no assistant chunks, runtime notices were filtered, or prompt failed upstream |
Run deterministic tests:
uv run pytestRun lint/type-oriented checks if available in your environment:
uv run ruff check .Live tests are opt-in and may consume API credits. They verify:
hcp-sdk -> pi-hcp -> pi-acp -> Pi -> provider
Example with Novita:
cd /home/aka/harness-mono/hcp-sdk
export HCP_SDK_RUN_LIVE=1
export HCP_SDK_RUN_LIVE_PROVIDER=1
export HCP_SDK_LIVE_PI_HCP_COMMAND='node /home/aka/harness-mono/pi-hcp/dist/index.js'
export HCP_SDK_LIVE_ACP_COMMAND='/home/aka/harness-mono/reference-only/pi-acp/dist/index.js'
export NOVITA_API_KEY='...'
export NOVITA_BASE_URL='https://api.novita.ai/openai/v1/'
export NOVITA_MODEL='deepseek/deepseek-v4-flash'
export NOVITA_CONTEXT_WINDOW='128000'
export NOVITA_MAX_TOKENS='32000'
uv run pytest tests/test_live_full_chain.pyBefore launching pi-hcp, pi-acp, or Pi, provider-backed tests run a small OpenAI-compatible chat/completions preflight against the configured provider. 401 or 403 means provider credential, endpoint, or model-access failure before HCP launch. Raw API keys are redacted from diagnostics.
Real-config tests exercise riskier HCP combinations: env/model, runtime artifacts, packages, resources, skills, MCP, hooks, session import, interaction, and failure diagnostics.
Prep-only run, no provider prompt:
cd /home/aka/harness-mono/hcp-sdk
export HCP_SDK_RUN_LIVE=1
export HCP_SDK_RUN_REAL_CONFIG=1
export HCP_SDK_LIVE_PI_HCP_COMMAND='node /home/aka/harness-mono/pi-hcp/dist/index.js'
export HCP_SDK_LIVE_ACP_COMMAND='/home/aka/harness-mono/reference-only/pi-acp/dist/index.js'
uv run pytest tests/test_live_real_config.py::test_real_config_high_risk_preparation_artifacts \
tests/test_live_real_config.py::test_real_config_failure_diagnostics_are_contextual_and_redactedProvider-backed run:
cd /home/aka/harness-mono/hcp-sdk
export HCP_SDK_RUN_LIVE=1
export HCP_SDK_RUN_REAL_CONFIG=1
export HCP_SDK_RUN_LIVE_PROVIDER=1
export HCP_SDK_LIVE_PI_HCP_COMMAND='node /home/aka/harness-mono/pi-hcp/dist/index.js'
export HCP_SDK_LIVE_ACP_COMMAND='/home/aka/harness-mono/reference-only/pi-acp/dist/index.js'
export NOVITA_API_KEY='...'
export NOVITA_BASE_URL='https://api.novita.ai/openai/v1/'
export NOVITA_MODEL='deepseek/deepseek-v4-flash'
export NOVITA_CONTEXT_WINDOW='128000'
export NOVITA_MAX_TOKENS='32000'
uv run pytest tests/test_live_real_config.pyUse your own config:
export HCP_SDK_LIVE_CONFIG=/path/to/hcp.toml
# or
export PI_HCP_LIVE_CONFIG=/path/to/hcp.tomlUser-provided config files are not printed in diagnostics. Diagnostics include scenario/layer markers such as [scenario:interaction][layer:provider], and known secret values are redacted.
- RFC-0001: Environment Forwarding Protocol (EFP) — backend-neutral contract for routing agent environment tools to local/remote execution environments.
- RFC-0002: Harness Configuration Protocol (HCP) — TOML configuration contract for recreating a Pi coding-agent runtime.
Implemented:
- package metadata and CLI entrypoint
- TOML readability validation
- wrapper-mode
pi-hcp --dry-runpreparation - ACP connection/session helper
- text prompt collection from
session/update - redacted subprocess diagnostics
Not implemented natively in Python yet:
- full HCP schema validation
- native runtime preparation
- native skill/resource materialization
- native session import/export helpers
- workspace staging backend interface
- interactive terminal/file/tool callbacks beyond text collection