The curator Python backend (package, tests, systemd units, config templates, scripts) now lives under scenarios/curator/backend and is the single source of truth; the live checkout at the workspace path is a runtime copy. Exported from the app repo's tracked tree via git archive (no history, .pi/venv/caches excluded). 149 unit tests pass from the new location. profile.toml backend is now repo-relative (scenarios/curator/backend); verify-generated.sh resolves a relative backend against REPO_ROOT. verify-no-secrets ASSIGN heuristic now requires value entropy so vendored kwargs like token=extraction_token no longer false-positive. README documents the backend/ layout and the operator-owned app rollout step.
143 lines
6.4 KiB
TOML
143 lines
6.4 KiB
TOML
# curator — Pi scenario profile
|
|
#
|
|
# This file describes the configuration that is DEPLOYED.
|
|
#
|
|
# Current state: skill enablement phase 1. The agent has seven read/propose bridge
|
|
# tools, one restricted skill reader, 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 is tracked in this repository under scenarios/curator/backend
|
|
# and is the single source of truth. It deploys to the live workspace root
|
|
# (workspace, above), where .pi/ sits beside it. The restricted `read` tool can
|
|
# reach only Markdown under .pi/skills, never the Python backend or credentials.
|
|
backend = "scenarios/curator/backend"
|
|
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 # suppress defaults; the five explicit skill paths below still load
|
|
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"]
|
|
# Explicit paths are merged even with no_skills=true. The extension registers a
|
|
# restricted tool named `read`, which makes pi expose the skills block without
|
|
# making the backend repository or ~/.agents/skills readable.
|
|
skills = [
|
|
".pi/skills/curator-router",
|
|
".pi/skills/curator-books",
|
|
".pi/skills/curator-video",
|
|
".pi/skills/curator-music",
|
|
".pi/skills/curator-sources",
|
|
]
|
|
|
|
[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.
|
|
# `read` is review-only here: it is extension-registered and is not a backend
|
|
# contract tool served by /tools.
|
|
allow = [
|
|
"query_library", "lookup_online", "book_reviews", "fetch_source", "web_search",
|
|
"counts", "propose_write", "read",
|
|
]
|
|
|
|
[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"
|