Facets
A facet is a Markdown-defined persona that controls prompt framing, tool access, skill access, model pinning, and session color. The frontmatter declares the facet settings, and the body supplies the system-prompt framing the model works under. Where Instructions Live helps you choose between a facet, a skill, and an AGENTS.md file; Prompting and Input covers day-to-day switching.
Switching facets changes the system prompt and the available tools while keeping
the same conversation. The shipped plan and execute facets work together:
the model researches and writes a plan in plan, then handoff_plan moves the
work into execute, which gives the model its edit and shell tools. The shipped
orchestrate facet coordinates complex work through subagents while the parent
model retains responsibility for dependencies, verification, and synthesis.
Each facet supplies its own system prompt, and that prompt sits ahead of the entire conversation in every request. Switching facets therefore causes a cache miss on the next call: the conversation arrives under a prompt prefix the provider has not cached, so the call gets no cache discount unless the provider still holds a live cache entry for the facet you switched to. That first call rebuilds the cache from the new prefix, and later calls return to the cached rate.
Where facets live
Section titled “Where facets live”A facet file goes in .polytoken/facets/ in your project, or in
$XDG_CONFIG_HOME/polytoken/facets/ (default: ~/.config/polytoken/facets/)
for facets you want everywhere. The filename is the facet’s name: scribe.md
defines a facet named scribe, and the name key in the frontmatter must
match the filename, or Polytoken rejects the file.
Facets override by replacement, not by merge. When two facets share a name, the
more specific one wins: a project facet overrides a global one, and either
overrides a shipped facet such as plan, execute, or orchestrate. A custom
plan is your plan, not an edited copy of the shipped one.
The body is a template
Section titled “The body is a template”The body below the frontmatter is a template; see Templating for the syntax. Polytoken renders the facet body into the facet’s complete system-prompt framing and adds no base prompt of its own.
In most facets you will want Polytoken’s shipped base prompt under your own
framing. Include it with transclude at the top of the body:
{{ transclude("polytoken://system_prompts/facet.md") }}
Your job is to help the user with their documents.The shipped plan, execute, and orchestrate facets all start this way, then
add their own lines. The base prompt carries tool-use guidance and other
behavior the model depends on. Leaving the transclude out means writing that
guidance yourself.
@skill: references in a facet template
Section titled “@skill: references in a facet template”A facet template can include @skill:<name>. Polytoken resolves the reference when the facet activates and applies that facet’s own polytoken.skills_allow and polytoken.skills_deny lists. A broken, denied, or polytoken.disable_model_invocation: true reference fails activation. The HTTP facet route returns 422, and the switch_facet tool returns an error. During startup or rewind, Polytoken restores a fallback facet instead. Repeated switches do not stack duplicate skill reminders, and compaction does not resurrect skills from a facet you left.
Customizing the shipped plan facet
Section titled “Customizing the shipped plan facet”The shipped plan facet investigates with read-only shell access for repository
inspection but with project editing tools withheld, writes a handoff plan, runs
the built-in plan-reviewer, and asks you before moving work into execute.
The plan facet exposes shell_exec so the model can run repository-state
commands that built-in tools cannot cover, such as git status, git log, and
gh. The facet prompt steers the model toward grep, glob, and file_read
for file inspection, and both the prompt and the autonomous classifier prohibit
any shell command that modifies files, the working tree, or system state. The
reviewer checks the plan against your project override when
project_vars.plan_facet.plan_spec_override is set, or against Polytoken’s
default plan shape otherwise.
Replace the whole facet with .polytoken/facets/plan.md when you want to change
that behavior, the tools the facet exposes, or the prompt outside the plan
format. The plan facet does not track its own planning work with todos, because
todos persist into the execute facet after a handoff; the todo tools stay
available for when you ask for them by name.
To keep the shipped behavior and change only the handoff-plan format, set a
project variable for the shipped plan facet. The variable replaces the default
plan format rather than adding to it, and plan-reviewer checks plans against
that same override, so write the complete plan-format instructions you want
both the model and the reviewer to follow. See
Template Reference
for the exact key and YAML shape.
A small facet
Section titled “A small facet”This example defines a scribe facet in .polytoken/facets/scribe.md, giving
the model a documents-assistant framing and Notion tools:
---name: scribepolytoken: tools: [mcp__notion] color: "#7c3aed"---{{ transclude("polytoken://system_prompts/facet.md") }}
Your job is to help the user with their documents. You have full access to theirNotion workspace through its tools. Work from what the user asks for.mcp__notion grants every tool from the Notion MCP server. To grant the server
but exclude one tool, add a tools_deny:
tools: [mcp__notion] tools_deny: [mcp__notion__delete_page]Frontmatter keys
Section titled “Frontmatter keys”Conventional keys like name sit at the top level. Every Polytoken-specific key
is polytoken.<key>. The table below lists the keys a facet can use.
| Key | Type | Default | Meaning |
|---|---|---|---|
name | string | required | The facet’s name. Must match the filename. |
polytoken.model | string | active model | Pins the facet to a model. Without it, the facet uses the active model. |
polytoken.fallback_models | list | none | Models to fall back to when the primary is unavailable. |
polytoken.tools | list | none | The tools the facet exposes. Accepts literal names, mcp__<server> for all of a server’s tools, tag!ALL for every tool, and tag!ALL_MCP for every MCP tool. |
polytoken.tools_deny | list | none | Tools to remove from the granted set. Literal names only, using the full mcp__<server>__<tool> name for an MCP tool. |
polytoken.undeferred_tools | list | none | The granted tools whose full definitions appear up front rather than through tool search. Literal names only. |
polytoken.skills_allow | list | allow all | The skills the facet permits, as names or tag!<name> groups. Leaving it out means no restriction, so every skill is allowed. |
polytoken.skills_deny | list | none | The skills the facet blocks. Deny wins over allow. |
polytoken.color | string | none | The facet’s accent color, as #RRGGBB. Sets both light and dark. |
polytoken.color_light | string | none | The accent color for light themes. Overrides color. |
polytoken.color_dark | string | none | The accent color for dark themes. Overrides color. |
polytoken.autonomous_hint | string | none | Guidance for the autonomous permission classifier: in Autonomous mode, Polytoken adds this text to the classifier’s context for each tool call made in this facet, steering the allow, ask, and deny decision. See what the classifier sees for the complete input context. |
polytoken.compaction_hint | string | none | Guidance Polytoken appends to the compaction summary prompt when this facet is active, naming the context the summary should preserve for this facet’s work. |
polytoken.facet_transitions.<target>.allowed | boolean | open | Whether this facet may switch to <target>. With no transitions declared, every switch is allowed. |
polytoken.facet_transitions.<target>.condition | string | none | When set, switching to <target> first asks you to confirm, showing this text. |
When a facet omits all three color fields, Polytoken generates a deterministic accent from the facet name for both light and dark themes. The two generated colors share the same hue but differ in lightness so each reads against its background.
Tool and skill access notes
Section titled “Tool and skill access notes”switch_facet is a regular tool. List it in polytoken.tools to let a facet
switch to another facet. The shipped execute facet includes it; plan leaves
it out, so the model cannot leave planning on its own and uses handoff_plan to
transition the work to you.
handoff_plan submits the active plan for your approval; you can implement it
in a fresh context or in the current one, or cancel. The model names the target
facet when it calls handoff_plan; the facet argument defaults to execute,
and orchestrate is available the same way. Polytoken has no config key for
this default, so when you want a handoff to orchestrate, tell the model in the
plan facet prompt or in your request.
Polytoken checks the plan files in the session plan directory before approval.
Polytoken refuses the handoff when the directory contains another plan that has
not already been approved or when a previously approved plan changed. The
refusal does not switch facets or clear the context. Set confirm_plan_path to
the active plan filename or its full path when you have reviewed the newer or
changed files and want Polytoken to adopt the current directory contents.
The shipped plan facet runs the built-in plan-reviewer before handing off;
the review is a recommendation, not a requirement. You decide at the approval
step whether to proceed, and you can skip review entirely. The reviewer evaluates the current
saved plan, so its findings correspond to the version you are approving.
Leaving polytoken.skills_allow out means no restriction, so the facet gets every
skill. Once you write a non-empty list, though, it is a commitment: if your
patterns match no skills, through a misspelled name or an empty tag! group,
Polytoken fails the facet’s skill access closed rather than reverting to allow-all: the facet runs with no skills.
The tag!ALL and tag!ALL_MCP shorthands belong in polytoken.tools. The
lowercase tag!<name> form is for skills only, in polytoken.skills_allow and
polytoken.skills_deny, and Polytoken rejects it in a tool list.
Goal tools and unresolved tool names
Section titled “Goal tools and unresolved tool names”The four goal lifecycle tools are propose_goal, read_goal,
complete_goal, and block_goal. When your facet lists any of them in
polytoken.tools, the model can call them directly. This holds for every
model, including models that load tools on demand, where a facet tool would
otherwise wait in a catalog until the model searches for it. Subagents never
receive these tools; they belong to the main session.
A tool name your facet declares can fail to resolve. A typo, an extension
tool that is not installed, or an MCP server that is not connected all leave
the name without a backing tool. Polytoken warns instead of rejecting the
facet, because membership depends on what is loaded at runtime. You see the
warning in the daemon log when Polytoken starts and when you reload its
configuration. polytoken validate facet prints a note for names it cannot
recognize statically. The model receives a one-time reminder naming the
unresolved tools the first time it would be affected. Fix the spelling or
load the missing tool; a facet that declares an unresolvable name keeps
working, and calls to the missing tool fail.
To see exactly which tools a facet resolves to, request the
GET /tools/effective route with the daemon’s bearer token. It returns the
active facet’s tool plan, or any named facet’s plan when you pass
?facet=<name>. The plan lists the tools the model can call directly, the
tools waiting in the deferred catalog, the tools hidden by permission rules,
and any unresolved names. A named facet’s plan reflects the model a switch
to that facet would activate, so a facet that pins a different model shows
that model’s tool loading. The request changes nothing: it never switches
the facet or the model.
Loading changes
Section titled “Loading changes”Polytoken loads facets when it starts and when you reload its configuration. Editing a facet file does not take effect until then.