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.
133 lines
6.1 KiB
TOML
133 lines
6.1 KiB
TOML
# curator — Pi scenario profile
|
|
#
|
|
# This file describes the configuration that is DEPLOYED.
|
|
#
|
|
# 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 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"
|
|
description = "Personal book / film / TV / music curation agent for the Curator service."
|
|
workspace = "/home/claw/pi-workspaces/curator"
|
|
session_dir = "/home/claw/.local/share/pi-curator/sessions"
|
|
service = "curator.service"
|
|
# Application code lives in a separate repository and is intentionally outside
|
|
# the agent's workspace.
|
|
backend = "/home/claw/codex-workspace/server-management/deploy/curator"
|
|
deploy = "managed"
|
|
|
|
[model]
|
|
provider = "zenmux"
|
|
primary = "openai/gpt-5.6-luna"
|
|
fallback = "x-ai/grok-4.6"
|
|
|
|
[model.thinking]
|
|
# 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 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 = 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
|
|
no_context_files = true # the ONLY switch that stops parent-dir AGENTS.md;
|
|
# AGENTS.override.md does not (verified)
|
|
approve = true # required to load .pi/SYSTEM.md
|
|
|
|
[personality]
|
|
# Both are system-prompt files, so --no-context-files does not affect them.
|
|
#
|
|
# 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]
|
|
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]
|
|
# 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 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
|
|
# CURATOR_* credential, including the Telegram bot token and the Radarr, Sonarr,
|
|
# Plex and Tavily keys. The provider key is read by pi itself from
|
|
# ~/.pi/agent/models.json and does not travel through the environment.
|
|
minimal = true
|
|
allowlist = [
|
|
"PATH", "HOME", "LANG", "LC_ALL", "LC_CTYPE", "TZ",
|
|
"NODE_OPTIONS", "SSL_CERT_FILE", "SSL_CERT_DIR", "NO_PROXY", "no_proxy",
|
|
]
|
|
# 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"
|