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.
This commit is contained in:
Kai
2026-08-28 01:10:58 -07:00
parent eaa3f6a8a1
commit b5a29b05e1
8 changed files with 401 additions and 102 deletions
+75 -35
View File
@@ -1,16 +1,15 @@
# curator — Pi scenario profile
#
# This file describes the configuration that is DEPLOYED. The phase-3 target,
# with tools, skills and a long-lived RPC session, is recorded in
# docs/plans/2026-08-curator-agent-refactor.md §4b, together with why each part
# cannot be enabled earlier.
# This file describes the configuration that is DEPLOYED.
#
# Current state: plan phase 0 (stop the bleeding). The agent has no tools; the
# Curator service gathers every fact and the model only classifies or phrases.
# Current state: plan phase 3. The agent has five read/propose tools served over a
# loopback bridge, and one long-lived pi process per Telegram chat.
#
# The launch contract lives here. Phase 3 makes curator/pi_agent.py read the
# rendered .pi/launch.json and fail closed if it is missing; until then
# pi_agent._isolation_args() is the enforcement point and must match this file.
# The enforcement point is PiLaunchConfig in
# pi-agent-config/shared/lib/py/pi_rpc.py, built by curator/pi_session.py. The
# flags below are asserted by
# test_pi_isolation_flags_match_the_deployed_contract, so this file and the code
# cannot drift silently.
[scenario]
name = "curator"
@@ -29,26 +28,31 @@ primary = "openai/gpt-5.6-luna"
fallback = "x-ai/grok-4.6"
[model.thinking]
# One level for every call. Intent classification runs at "high" at the front of
# every message, which is the dominant p50 latency contributor; splitting the
# levels per role needs the RPC client's set_thinking_level (phase 3).
all = "high"
# Split by role. The conversation turn has to reason about tool results, so it
# keeps "high". Intent classification and extraction produce JSON for a parser and
# run at the front of every message, where they were the dominant p50 contributor.
conversation = "high"
structured = "medium"
[session]
# One pi process per message, keyed by Telegram chat via a uuid5 of the chat id.
# No rotation: the process does not outlive the message. Phase 3 replaces this
# with a long-lived RPC process plus explicit rotation.
strategy = "process-per-message"
# One long-lived `pi --mode rpc` process per Telegram chat, keyed by a uuid5 of
# the chat id, plus one shared toolless process for structured tasks. A stable
# prompt prefix across turns is what makes provider prompt caching effective;
# a process per message paid for the whole prompt every time.
strategy = "long-lived-rpc"
rotate_after_prompts = 24
rotate_after_messages = 60
[isolation]
# Enforced in curator/pi_agent.py::_isolation_args. Verified effect is recorded
# in docs/evidence/2026-08-27-curator-phase0-prompt.md.
no_tools = true # phase 0 only. Also disables the skills mechanism:
# pi emits <available_skills> only when a tool named
# `read` is active, so --skill was a no-op and the
# media policy never reached the model. The policy
# now lives in the system-prompt files below.
no_extensions = true
no_tools = false # the agent has tools now
no_builtin_tools = true # bash / edit / write stay unreachable, extension
# tools stay reachable. An explicit `--tools`
# allowlist is deliberately NOT used: it filters the
# registry and would stop the extension registering
# anything at all.
no_extensions = true # ...except the one named under [resources]
no_skills = true
no_prompt_templates = true
no_themes = true
@@ -58,23 +62,48 @@ approve = true # required to load .pi/SYSTEM.md
[personality]
# Both are system-prompt files, so --no-context-files does not affect them.
system_prompt = ".pi/SYSTEM.md" # replaces pi's default prompt
append_system_prompt = ".pi/APPEND_SYSTEM.md" # durable domain responsibilities
context_files = [] # deliberately none
#
# SYSTEM.md contains a GENERATED tool list. It has to: pi omits its own tool list
# when --system-prompt is used, because the customPrompt branch returns before
# `toolsList` is assembled, so promptSnippet and promptGuidelines never reach the
# model. Measured effect of not having it: the agent called a tool in one run out
# of four and answered from memory in the other three.
# Regenerate with scripts/verify-generated.sh --fix.
system_prompt = ".pi/SYSTEM.md" # conversation, with tools
# A separate prompt for the toolless structured turns. Handing them the
# tool-bearing prompt would tell the model it can query the library when it
# cannot.
structured_system_prompt = ".pi/SYSTEM.structured.md"
append_system_prompt = ".pi/APPEND_SYSTEM.md" # durable domain policy
context_files = [] # deliberately none
[resources]
# Nothing is loaded from disk beyond the two system-prompt files.
extensions = []
skills = []
extensions = [".pi/extensions/curator-tools.ts"]
# Vendored into .pi/extensions/_shared/ by deploy-scenario.sh, because a tracked
# extension cannot resolve an import from shared/ once installed outside the repo.
shared_extensions = ["pi-guard-base.ts"]
# Deliberately empty, and it is not an oversight. pi emits the skills section only
# when a tool named `read` is active; Curator's tools are all domain-specific, so
# every --skill argument would be discarded in silence. Measured: with tools
# [query_library, lookup_online, counts] the prompt contained no skills section
# and no skill names, with and without --system-prompt. The media policy lives in
# APPEND_SYSTEM.md, which is unconditional.
skills = []
[tools]
allow = []
# Served by the backend at /tools from curator/contracts.py, so the tool the model
# sees and the endpoint that answers it are the same object. Listed here for
# review only; this file is not the source.
allow = ["query_library", "lookup_online", "book_reviews", "counts", "propose_write"]
[budget]
# One timeout per pi invocation. These do not compose: a single message can run
# interpret + query + answer, so the worst case is a multiple of this value.
# Phase 3 introduces one deadline per user message, enforced with RPC abort.
invocation_timeout_seconds = 120
# One deadline per user turn, enforced with the RPC abort command rather than by
# killing the process, so the session survives a slow answer and the next message
# does not pay to start up. The old per-invocation timeout did not compose: a
# single message could run interpret + query + answer and take three times the
# configured value.
turn_deadline_seconds = 180
startup_timeout_seconds = 60
[env]
# Explicit allowlist, enforced in pi_agent.ENV_ALLOWLIST. Notably absent: every
@@ -86,7 +115,18 @@ allowlist = [
"PATH", "HOME", "LANG", "LC_ALL", "LC_CTYPE", "TZ",
"NODE_OPTIONS", "SSL_CERT_FILE", "SSL_CERT_DIR", "NO_PROXY", "no_proxy",
]
extra = []
# The bridge URL and a per-conversation token, generated at start and passed only
# through the child's environment. The token grants access to this service's read
# tools and to propose_write, which the policy engine still adjudicates; it grants
# nothing else and outlives nothing.
extra = ["CURATOR_BRIDGE_URL", "CURATOR_BRIDGE_TOKEN"]
[bridge]
# Ephemeral loopback port, chosen by the kernel. Not configurable and not
# predictable; the agent's only route to the library.
bind = "127.0.0.1"
port = 0
auth = "per-conversation token, compared with compare_digest"
[secrets]
env_file = "/home/claw/.config/curator/curator.env"