Files
pi-agent-config/shared/lib/py/tests/test_pi_rpc_smoke.py
T
Kai b5a29b05e1 docs: mark phase 3 complete, record the skills and caching findings
Two findings that changed the plan rather than confirming it:

  23. Skills require a tool literally named `read`. Curator's tools are all
      domain-specific, so every --skill argument was discarded in silence. The
      planned split into curator-core / video-arr / books-ingest was inert before
      it was written; the policy stays in APPEND_SYSTEM.md. memo-inbox is
      unaffected because it registers a restricted `read` override, which is why
      the earlier note generalised wrongly from it.

  24. A long-lived session is worth far more than the startup it saves: 99.97% of
      input read from cache on a continuing conversation against 0% on a new one.
      That is what makes the generated tool list necessary rather than merely
      tidy -- anything varying at the front of the prompt destroys it -- and it
      makes rotation a cost to be bounded rather than applied eagerly.

profile.toml now describes the phase-3 configuration that is actually deployed,
including that the empty `skills` list is a finding and not an oversight.
pi_rpc gains --system-prompt support and no longer guesses whether a `read` tool
will exist; extension_registers_read has to be stated.

harness-layering.md records what transfers from a widely-shared account of
building a personal coding harness on pi, and what does not. The layering frame
holds and the cache-hit figure was the useful part. Its central recommendation --
installing third-party packages -- is disqualifying for an unattended agent
holding tracker credentials, and its discipline layer (AGENTS.md) is precisely
what we block, because it is discovered from every parent directory.
2026-08-28 01:10:58 -07:00

117 lines
4.9 KiB
Python

"""Zero-token smoke test for pi_rpc against a real pi process.
Sends only `get_state`, so no model call is made. Verifies that the launch
contract produces a live process, that the isolation flags land, and that the
process group is cleaned up.
Run: python3 shared/lib/py/tests/test_pi_rpc_smoke.py
"""
from __future__ import annotations
import asyncio
import os
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from dataclasses import replace # noqa: E402
import pathlib # noqa: E402
import tempfile # noqa: E402
from pi_rpc import PiLaunchConfig, PiRpcClient # noqa: E402
REPO = Path(__file__).resolve().parents[4]
HARNESS = REPO / "docs" / "evidence" / "probe-harness"
PI_BIN = os.environ.get("PI_BIN", "/home/claw/.npm-global/bin/pi")
def check(label: str, ok: bool, detail: str = "") -> bool:
print(f" {'PASS' if ok else 'FAIL'} {label}{(' — ' + detail) if detail else ''}")
return ok
async def main() -> int:
failures = 0
print("== build_args reflects the isolation baseline ==")
cfg = PiLaunchConfig(
pi_bin=PI_BIN,
workspace=HARNESS,
session_dir=Path("/tmp/pi-rpc-smoke-sessions"),
provider="zenmux",
model="openai/gpt-5.6-luna",
thinking="low",
display_name="pi_rpc smoke",
extensions=(HARNESS / "probe-ext.ts",),
skills=(HARNESS / ".pi" / "skills" / "probe-skill",),
session_id_prefix="", # ephemeral for the smoke test
receipt_tools=frozenset({"probe_plain_schema"}),
turn_deadline_seconds=45.0,
)
args = cfg.build_args(None)
for flag in ("--no-builtin-tools", "--no-extensions", "--no-skills",
"--no-prompt-templates", "--no-themes", "--approve", "--no-session"):
failures += not check(f"{flag} present", flag in args)
failures += not check("--tools absent (registry allowlist would block dynamic tools)",
"--tools" not in args)
print("== system prompt and the skills/read interaction ==")
sp = pathlib.Path(tempfile.mkdtemp()) / "SYSTEM.md"
sp.write_text("marker", encoding="utf-8")
withprompt = replace(cfg, system_prompt=sp, append_system_prompt=sp)
prompt_args = withprompt.build_args(None)
failures += not check("--system-prompt passed through", "--system-prompt" in prompt_args)
failures += not check("--append-system-prompt passed through",
"--append-system-prompt" in prompt_args)
# A dedicated agent whose tools are all domain-specific has no 'read', and pi
# then discards every --skill argument silently. Measured, not assumed.
noread = replace(cfg, skills=(sp.parent,), no_builtin_tools=True,
extension_registers_read=False)
failures += not check("skills without a read tool are flagged as ineffective",
not noread._read_reachable())
failures += not check("an extension that registers read is trusted",
replace(noread, extension_registers_read=True)._read_reachable())
failures += not check("an explicit allowlist naming read counts",
replace(noread, tools=("read", "counts"))._read_reachable())
failures += not check("an allowlist without read does not",
not replace(noread, tools=("counts",))._read_reachable())
print("== env is minimal ==")
os.environ["SMOKE_FAKE_SECRET"] = "must-not-propagate"
env = cfg.build_env()
failures += not check("secret withheld", "SMOKE_FAKE_SECRET" not in env)
failures += not check("PATH forwarded", "PATH" in env)
failures += not check(f"small env ({len(env)} vars)", len(env) < 20)
print("== live process: start, get_state, stop ==")
client = PiRpcClient(cfg)
try:
await client.start()
pid = client.pid
failures += not check("process running", client.running, f"pid={pid}")
state = await client.get_state()
failures += not check("get_state returned a model", bool(state.get("model")),
str(state.get("model", {}).get("id", "?")))
failures += not check("not streaming", state.get("isStreaming") is False)
failures += not check("thinkingLevel honoured", state.get("thinkingLevel") == "low",
str(state.get("thinkingLevel")))
stats = await client.get_session_stats()
failures += not check("get_session_stats works", "tokens" in stats)
finally:
await client.stop()
failures += not check("process stopped", not client.running)
if pid:
alive = subprocess.run(["ps", "-p", str(pid)], capture_output=True).returncode == 0
failures += not check("no orphan process", not alive)
print()
print("RESULT:", "all checks passed" if failures == 0 else f"{failures} check(s) failed")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))