Files
Kai 88b06d782f feat(curator): vendor the application backend as the scenario's tracked source
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.
2026-08-30 18:49:10 -07:00

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"