polyc-agent-catalog 2026.9.0

The shipped Agent catalog (manifests/base/agents.yaml) as one typed artifact.
# Versioned Agent catalog (polychrome.dev/v1alpha1). Each Agent scopes a
# conversation to one tool surface. The control plane resolves a
# conversation's `agent_id` (or POLYCHROME_DEFAULT_AGENT) to one Agent here.
# It applies that Agent's tool surface at turn dispatch.
#
# This is the SOURCE OF TRUTH for the deployed agents — an agent's tool surface
# travels with the version, and a new built-in tool ships by editing this file in
# the same change, never by hand-patching a running Agent. Deploy the catalog
# with a plain apply, AFTER the Agent CRD (manifests/crds/crds.yaml) exists:
#
#   kubectl apply -f manifests/base/agents.yaml
#
# It is intentionally NOT in the base kustomization: bundling custom resources
# into `apply -k` would require the CRD to be Established first and break a
# fresh-cluster apply.
#
# `assistant` is the deployment default (POLYCHROME_DEFAULT_AGENT=assistant): a
# general chat + connector agent scoped to the READ-SAFE built-in tools — the
# history-navigation and wallet self-service families — and deliberately NOT the
# coding tools, so a visible `grep` can't shadow a connector tool and a general
# turn can't reach workspace-write. `ops` narrows the connector surface
# further. `coding` is the lone isolation specialist (workspace-write) —
# reached by handoff, not routing. `researcher` is a `__delegate_to` (#870)
# WORKER — `assistant` may delegate a single self-contained lookup task to it
# (see `assistant.canDelegateTo` below); it never runs as a routed/default
# agent itself. This is the live wiring the #874 fan-out cap/budget and the
# effort-scaling instructions below are exercised against on
# `just cli-send-local`.
---
apiVersion: polychrome.dev/v1alpha1
kind: Agent
metadata:
  name: assistant
  namespace: polychrome
spec:
  description: >-
    General chat and connector assistant. Default agent for conversations with
    no explicit agent. No built-in coding tools.
  instructions: >-
    You are Polychrome, a helpful general-purpose assistant. Respond naturally
    and directly to conversation. Do NOT assume the conversation is about any
    particular product, brand, or domain, and do NOT describe yourself in terms
    of the tools you happen to have. You also have access to external tools
    (such as deploying or listing services); reach for them ONLY when the
    user's request clearly needs one. For greetings, small
    talk, or general questions, just reply directly and do not mention tools at
    all. Don't reintroduce yourself mid-conversation — if asked your name,
    answer in a few words (not a full self-description) and get straight to
    the answer.

    Several tools share the same verbs ("unlink", "remove", "disconnect") for
    different targets — a platform identity, an email, a wallet. When a
    request's exact target doesn't clearly match one specific tool's own
    description, ask which one they mean instead of guessing — never
    substitute a different target's tool just because it's the one you can
    call with the most confidence.

    You can hand a single, self-contained lookup task to the researcher
    worker. Delegate sparingly and scale effort to the task: a simple lookup
    or a single fact needs no delegation at all — just answer it directly. A
    direct comparison across a handful of items (2-4) may warrant one
    delegated lookup per item, run in parallel. Reach for more delegated
    lookups only when the task is genuinely broad and naturally splits into
    independent pieces of research; decomposing a narrow task into many
    delegated lookups wastes time for no benefit.
  # The read-safe built-in tools for a general assistant: recall its own history
  # and manage its own spending wallet. NO coding tools (those live on `coding`,
  # a workspace-write handoff target). Add a new read-safe built-in here to ship
  # it with the version.
  #
  # This list also grants `invite`, `revoke`, `demote`, and `promote`: the
  # four access-management tools that structurally always escalate to a human
  # (`Capability::GrantAccess`/`Capability::RevokeAccess`/`Capability::ManageAdmin`/
  # `Capability::GrantAdmin` all sit outside `Capability::ALL`, so none can
  # ever be satisfied without an explicit approval) and whose
  # mint/removal/demotion/promotion is re-checked admin-gated, trusted-side,
  # at execution time — which is why they may ride the general assistant
  # rather than a narrower agent. Omitting any of the four from this list is
  # the #699 "advertised but not executable" trap:
  # `every_advertised_builtin_is_executable` /
  # `every_shipped_builtin_grant_is_advertised_and_owned`
  # (crates/harness/tests/builtin_surface_guard.rs) fail loudly if it recurs.
  #
  # `unlink_identity` and `delete_provisional_persona` (`#1599`) reuse
  # `revoke`'s never-granted `RevokeAccess` marker — same always-escalates
  # story, same #699 trap if omitted. `list_provisional_personas` is their
  # read-only sibling: admin-gated at execution but NOT a never-granted
  # marker, so it composes freely once this grant names it. `list_admins` is
  # the read-only report over what `invite`/`revoke`/`demote`/`promote`
  # change, and sits in the same position: admin-gated trusted-side at
  # execution rather than by grant-list membership, and NOT a never-granted
  # marker, so an admin can ask who the admins are without a fresh approval
  # each time.
  #
  # `web_search_grounding` (#1226) scopes the PROVIDER'S OWN native
  # web-search-grounding primitive, not a callable built-in — omitting it here
  # means `assistant` never grounds an answer on the public web at all,
  # regardless of taint state; it is NOT auto-granted the way it used to be
  # hardcoded on for every agent before #1226.
  #
  # `web_fetch`/`paid_fetch` (fixing a live gap found post-#1236): the harness
  # now correctly scopes both to `builtinTools`, but nobody added them here
  # when that filter went in, so `assistant` — the agent every general
  # conversation actually runs as — had no way to fetch a URL a user pasted,
  # paid or not. `conversation_read_tool_result` closes a matching gap: the control
  # plane's own compaction marker tells the model to call this tool to read a
  # cleared result again, but `assistant` never granted it. `routine_list` is
  # the read tool the whole routine family depends on (INV-RL4). It rides the
  # general assistant for the same reason `wallet_roster`/`invite` do. The
  # control plane gates it trusted-side at execution. The grant list does not
  # gate it. An admin lists every routine (#1872). Other people list only the
  # routines they create.
  # `routine_create` (#1497): the tracer-bullet create verb joins the same
  # family here — admin-gated and always approval-gated at execution, not by
  # grant-list membership; an admin describing a routine in plain language
  # in the general assistant conversation is the whole point of the feature.
  # `routine_pause`/`routine_resume`/`routine_delete` (#1495): the mutation
  # trio joins the same family for the same reason — admin- and
  # observation-handle-gated at execution (delete is also approval-gated),
  # never by grant-list membership; pausing, resuming, or deleting a
  # previously-listed routine from the general assistant conversation is the
  # rest of the chat-managed lifecycle this family exists for.
  # `routine_fire` (#1498): the sixth verb, same reasoning — admin- and
  # observation-handle-gated at execution and always approval-gated, letting
  # an admin test-fire a previously-listed routine from the general assistant
  # conversation before trusting its schedule.
  # `routine_duplicate` (#1805): the seventh verb, same reasoning — admin- and
  # observation-handle-gated at execution and always approval-gated, letting
  # an admin mint a caller-owned copy of a previously-listed routine from the
  # general assistant conversation.
  #
  # `routine_set_scope` (#1806): the eighth verb, same reasoning — admin- and
  # observation-handle-gated at execution and always approval-gated, letting
  # an admin flip a previously-listed routine's sharing between `public` and
  # `private` from the general assistant conversation, same lifecycle family
  # as the rest of the routine-admin grants above.
  #
  # `routine_allow_denial`/`routine_revoke_grant`/`routine_refire_attended`
  # (POLY-32): owner-gated, same reasoning as the trio above — a routine's
  # owner allows a tool a fire denied, revokes a standing grant, or re-fires
  # the setup rehearsal attended, all from the general assistant conversation
  # against a previously-listed routine.
  #
  # `unlink_self` (post-incident consolidation) replaces the former separate
  # `wallet_unlink`/`unlink_email` grants: one tool, one `target` argument
  # (`"email"`/`"wallet"`), intrinsically approval-required — a wrong `target`
  # is one validated branch in one handler now, never a different ungated
  # tool executing on its own. See `polyc_tools::unlink_self`'s doc for the
  # incident this closed.
  #
  # `ask_question` (#1660): a structured, sibling alternative to the
  # instruction above ("ask which one they mean instead of guessing") — a
  # real tool schema for a genuinely ambiguous or exploratory decision point,
  # not just plain conversational text. Opt-in and explicit: it is granted
  # ONLY to this exploratory, general-purpose agent, not to narrowly-scoped
  # execution agents (`coding`, `ops`) or the delegated `researcher` worker,
  # which has no human to ask. Omitting it here is the same "advertised but
  # not executable" trap the comment above documents for the access-management
  # trio — `every_catalog_ask_question_tool_is_granted_to_some_shipped_agent`
  # (crates/harness/tests/builtin_surface_guard.rs) fails loudly if it recurs.
  #
  # The `conversation_*` tools read this conversation's own committed record,
  # trusted-side. They default on, so an UNSCOPED turn would compose them
  # without this grant. But this list is not empty, and a non-empty
  # `builtinTools` admits a default-on built-in only when it names it
  # (`polyc_tools::builtin_admits`). Leaving them out is therefore the #1204
  # trap for the family every general conversation reaches it from: five tools
  # with a working executor arm that no persona can call.
  # `every_catalog_conversation_read_tool_is_granted_to_some_shipped_agent`
  # (crates/harness/tests/builtin_surface_guard.rs) fails if one drops off.
  #
  # The seven names below the new five are the PREDECESSOR surface. They are
  # kept for the rollout window only. They do not keep the old tools working:
  # the new control plane has no executor arm for them, so they fail from the
  # first deploy step.
  #
  # They are kept for a rollback of the control plane. `resolve_agent` reads
  # this document on every turn, independently of the image. If the control
  # plane is rolled back, a list with both name sets gives the read tools back
  # on the next turn. A list with only the new names gives the fleet no read
  # tools, and an engineer must apply this file a second time during an
  # incident.
  #
  # Delete the seven old names after the harness roll is complete and no
  # pre-roll conversation is open.
  builtinTools:
    - conversation_find
    - conversation_read_turn
    - conversation_read_tool_result
    - conversation_recent_turns
    - conversation_list_tool_calls
    # Rollout window only. Delete after the harness roll (see above).
    - history_search
    - history_peek
    - history_grep
    - history_result_peek
    - conversation_query
    - recent_turns
    - tool_call_history
    - wallet_status
    - wallet_history
    - wallet_deposit_address
    - wallet_link
    - wallet_roster
    - wallet_set_policy
    - wallet_update_limit
    - link_email
    - unlink_self
    # The deliberate memory write (#1139): its spec is intrinsically
    # approval-gated (never auto-approved, never a cacheable approval), and
    # the control plane re-checks the write trusted-side — the same hard
    # scope predicate and PII refusal as post-turn extraction. Classified as
    # a management builtin (Decision A, polyc_tools::management) alongside
    # the admin tools below: it does not default on, so THIS grant is what
    # gives the assistant the tool at all — dropping this line un-advertises
    # and un-executes it for the agent, same as dropping `wallet_status`.
    - memory_write
    # The deliberate memory READ. It is a management builtin for the same
    # reason the write is — a persona's notes span conversations — so it does
    # not default on and THIS grant is what gives the assistant the tool. It
    # carries no approval gate: the grant is the whole control surface, because
    # a read persists nothing.
    - memory_recall
    - invite
    - revoke
    - demote
    - promote
    - list_admins
    - unlink_identity
    - delete_provisional_persona
    - list_provisional_personas
    - web_search_grounding
    - web_fetch
    - paid_fetch
    - routine_list
    - routine_create
    - routine_pause
    - routine_resume
    - routine_delete
    - routine_fire
    - routine_duplicate
    - routine_set_scope
    - routine_allow_denial
    - routine_revoke_grant
    - routine_refire_attended
    - ask_question
  # `coding` is reached by handoff, not routing (see the file header) — this is
  # the only handoff target for the default agent today. Add a new name here
  # if another agent becomes a handoff target; canHandoffTo is now enforced
  # at handoff emission (#877), so an omission here means a denied handoff,
  # not a silent no-op.
  canHandoffTo:
    - coding
  # `__delegate_to` (#870) target: `assistant` may hand a single
  # self-contained lookup task to `researcher` and read its answer back
  # in-process, within the SAME turn — see the effort-scaling guidance
  # above. canDelegateTo is enforced at `__delegate_to` resolution the same
  # way canHandoffTo is: an omission here means a denied delegation, not a
  # silent no-op.
  canDelegateTo:
    - researcher
  # Fan-out width cap (#874): at most 3 concurrent `__delegate_to` calls per
  # batch — comfortably covers the "compare a handful of items" shape the
  # instructions above describe, below the crate default ceiling.
  delegateMaxFanout: 3
  # Turn-scoped total delegate budget (#874): at most 6 delegate calls across
  # the whole turn, so a multi-step research conversation can re-delegate a
  # few times without opening the door to an unbounded re-decompose loop.
  delegateTurnBudget: 6
  # Empty ⇒ the namespace's default-enabled connectors (no extra restriction).
  toolsEnabled: []
  sandboxMode: read-only
---
apiVersion: polychrome.dev/v1alpha1
kind: Agent
metadata:
  name: researcher
  namespace: polychrome
spec:
  description: >-
    Single-task web lookup worker. Reached only by `__delegate_to` from
    `assistant` — never routed or run as a conversation's own agent.
  instructions: >-
    You are a research worker handling exactly one self-contained task. You
    see only the task (and any context) you were given — no other
    conversation history. Answer the task directly and concisely; don't
    introduce yourself or describe what you're about to do.
  # This list gives the worker only the tools for a web lookup.
  # The worker does not use the local workspace. Thus the list does not
  # include `file_read`, `grep`, or `glob`.
  # The control plane uses this list directly. Before #2286, the control
  # plane also removed the tools that write to the workspace. That default
  # is not necessary now. Each worker gets its own workspace directory.
  # The list includes `web_search_grounding` for a reason. #1226 put native
  # search grounding behind a grant. That change added the name to
  # `assistant` only. This worker always runs as a delegated worker. Thus
  # the worker needs its own grant.
  builtinTools:
    - web_fetch
    - web_search_grounding
  toolsEnabled: []
  # This agent's OWN step budget when DELEGATED TO as a worker (#870) — a
  # single lookup task needs few round-trips.
  delegateStepBudget: 4
  sandboxMode: read-only
---
apiVersion: polychrome.dev/v1alpha1
kind: Agent
metadata:
  name: ops
  namespace: polychrome
spec:
  description: >-
    Service operations: deploy, list, and tear down service instances via the
    scaffold connector.
  instructions: >-
    You are Polychrome, managing service instances. Use the scaffold
    connector's tools to list, deploy, check, or tear down instances. Don't
    reintroduce yourself mid-conversation — if asked your name, answer in a
    few words (not a full self-description) and get straight to the answer.
  builtinTools: []
  toolsEnabled:
    - scaffold
  sandboxMode: read-only
---
apiVersion: polychrome.dev/v1alpha1
kind: Agent
metadata:
  name: coding
  namespace: polychrome
spec:
  description: >-
    In-workspace authoring and build. The isolation specialist — reached by
    handoff (workspace-write sandbox), never general routing.
  instructions: >-
    You are Polychrome, working in an isolated sandbox to make the requested
    changes. Use the shell, file, and search tools directly; describe what
    you changed and why in plain terms when you're done. You're reached by
    handoff mid-conversation, so a brief note that you're now working in the
    sandbox is fine — just don't follow it with a full self-description.
  # The Codex-style coding core. workspace-write keeps its effects in a real
  # sandbox boundary (the reason this is a separate agent).
  builtinTools:
    - shell_exec
    - file_read
    - file_write
    - file_edit
    - glob
    - grep
  toolsEnabled: []
  sandboxMode: workspace-write