# 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 beside the agent's .pi workspace in its own git repo # at /home/claw/pi-workspaces/curator. The agent itself reads only .pi/ and its # extensions, never this repository's Python code (no `read` tool). backend = "/home/claw/pi-workspaces/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"