Agent
Skills, Tools, and the @-Namespace
14 min read
An agent that only does what its developers hard-coded is a feature. An agent whose owner can teach it new procedures — in plain prose, on the device, without writing a line of code — is something closer to a colleague. This article is about the layer of Pixie's harness that makes that possible: tools as fixed capabilities, skills as user-authored instruction bundles, and the single @-namespace that ties every reference together so nothing ever dangles.
This is the third top-level article of the series, building on the framing in Agent and the planner/executor split in The L1–L2–L3 Harness; one Mechanism article, Mechanism: Skill Bundles & Sharing, sits beneath it. Coming in, you need to know that Pixie's default brain is a small on-device model, that a planner picks what to do from a short menu, and that tools are bound into an execution session only after the plan names them.
1. Two kinds of capability
Everything the agent can do — beyond generating text — comes in exactly two shapes.
A tool is a fixed capability implemented in code. It has a schema (typed
arguments), a concrete implementation, and it does one thing: search the Notebook,
find a file on one of your devices, read the calendar. Users cannot create tools; they
ship with the app. Pixie's canonical tool registry is a small Swift enum, SkillTool,
and today it holds six entries:
@-slug |
Bound tool | What it does |
|---|---|---|
@check-notebook |
check_notebook |
Search the agent's distilled private Notebook (see Mechanism: Memory & the Notebook) |
@find-file |
find_file |
Find one of the user's own files across their devices by description |
@list-devices |
list_devices |
List the user's device ids (plumbing for find_file) |
@grep-memory |
grep_memory |
Last-resort search of the full long-term memory log |
@current-date |
current_date |
Today's date, plus concrete ranges for "this week" etc. |
@calendar |
queryCalendar |
List calendar events for a described time range |
Notice the two names per row. The slug (find-file) is the human-facing,
kebab-cased handle a user types; the tool name (find_file) is what the executor
actually binds — the identifier the model's tool-calling machinery sees. The slug is
the address; the tool name is the thing at the address.
A skill is the opposite in almost every way: it is authored, not compiled. A skill is a bundle of instructions — ordinary prose describing a procedure — that mentions the tools (and other skills) it needs. It is the unit the on-device planner reasons over. Where a tool answers "what can be done," a skill answers "how, in what order, and when to bother."
The design bet of this layer is that procedures are where users want control. Nobody wants to implement a calendar API; everybody has opinions about what "check my schedule" should actually do.
2. Skills: procedures written in prose
A skill (AgentSkill in the code) has a deliberately small anatomy:
id— its stable slug, which is also its@-handle (e.g.check-schedule);shortDescription— one to three lines. This is the menu entry: it is the only part of the skill the planner ever sees in its system prompt;detailedBody— the full steps, written in prose, with@-mentions of the tools and skills it uses;enabledandadvertised— two booleans we will unpack in §5, because they answer different questions.
Here is the built-in Check Schedule skill, verbatim in spirit:
Look up what's on the user's calendar.
Relative dates (today, tomorrow, this weekend, next week) depend on today's date, which you do NOT know on your own — so FIRST use
@current-dateto get today's date and work out the date range the user means. Then use@calendarto fetch the events in that range and summarize them briefly.
Two things are happening in that little body, and both are load-bearing.
The mentions are the toolset. There is no separate manifest saying "this skill
requires tools X and Y." A regex-level parser (AgentSkill.parseMentions) scans the
body for @slug tokens — kebab-case, first-seen order, deduplicated — and that list
is the skill's tool requirement. When the planner picks Check Schedule, the union of
its @-mentions is exactly what gets bound into the execution session. The document
you write for the model to read is, simultaneously, the machine-readable dependency
list. One artifact, two audiences, and they can never disagree with each other.
The body encodes ordering knowledge the model lacks. A small on-device model does
not know today's date. Left alone, it will confidently answer "what's on this
weekend?" with events from a hallucinated week. The skill bakes the fix into the
procedure itself: first @current-date, then @calendar. This is the whole point
of skills as a layer — sequencing wisdom lives in editable prose, not in model
weights or app code.
The split between shortDescription and detailedBody is the context-economy trick
from The L1–L2–L3 Harness, applied here: the planner's prompt carries only the
one-liners (a menu, cheap in tokens), and the full steps enter context only in the
execution pass, after a plan has named the skill. You pay for detail exactly when it
is about to be followed.
3. One namespace to rule the references
Skills mention tools. Skills also mention other skills — the built-in Search Info
skill's body says "THEN use @fetch-file…", and Fetch File is itself a skill wrapping
two file tools. So what does an @-token actually refer to?
The answer is a deliberate design decision: skills and tools share one flat slug
namespace, and slugs are unique across both. The resolver (SkillRegistry.resolve)
takes a slug and returns a tagged result — this is a skill, or this is a tool, or this
is nothing:
enum SkillRef {
case skill(AgentSkill)
case tool(SkillTool)
}
Because uniqueness is enforced at creation time (you cannot name a skill find-file;
the slug allocator checks tool slugs and existing skill ids), any @-mention
anywhere — in a skill body, in the planner's emitted plan — is unambiguous without
qualification. There is no @tool:find-file versus @skill:fetch-file syntax to get
wrong. The grammar the user writes and the grammar the model emits are the same
grammar.
When a plan comes back naming its handles, one function — SkillRegistry.toolsForPlan
— expands them into the concrete toolset to bind. Tools pass through directly; skills
expand to their mentions, recursively, since a skill body may reference other
skills. The expansion is cycle-guarded (a skill that transitively mentions itself
simply stops expanding there rather than looping forever) and the result is
deduplicated in first-appearance order. Concretely, for the built-ins:
plan: "I'll use @search-info to find their PAN number."
│
▼
@search-info (skill) — body mentions:
@check-notebook ──────────────▶ tool check_notebook
@fetch-file (skill) — body mentions:
@list-devices ───────────▶ tool list_devices
@find-file ──────────────▶ tool find_file
@grep-memory ─────────────────▶ tool grep_memory
│
▼
bound toolset: [check_notebook, list_devices, find_file, grep_memory]
That flat list is exactly what the execution session receives — how those bound tools are then called, one compact signature at a time through a single meta-tool, is the subject of Mechanism: CallTool — One Tool to Bind Them.
4. Renames that never dangle
A namespace made of human-chosen names has a classic failure mode: rename something,
and every reference to the old name silently rots. In most systems that gives you a
compiler error. Here the "compiler" is a language model reading prose at plan time —
a dangling @old-name would not crash; it would just quietly resolve to nothing, and
a skill would lose a tool without anyone noticing. Silent capability loss is the worst
kind of bug, so the namespace refuses to allow it, at two chokepoints.
Slugs are minted, not merely typed. When a skill is created or its handle edited,
SkillRegistry.makeSlug kebab-cases the title (lowercase; any run of
non-alphanumerics collapses to a single -) and checks availability against both
populations — tool slugs and other skills' ids. On a collision it appends a numeric
suffix (meeting-prep, meeting-prep-2, …) rather than failing. An unchanged handle
keeps its current slug, so editing a skill's steps never perturbs its address.
Renames cascade. When a save actually changes the slug, SkillStore.commit
replaces the skill's row and then runs SkillRegistry.applyRename, which rewrites
every @old to @new across all skill bodies. The rewrite is careful in the way
that matters: whole-slug only and case-insensitive — the pattern requires that the
character after the slug not be alphanumeric-or-hyphen, so renaming @plan cannot
mangle a mention of @plan-trip. After the cascade, the store persists the whole
list atomically. There is no state in which a reference points at a name that no
longer exists.
This is a small mechanism, but it is what makes the prose-as-manifest idea from §2 safe. You get the ergonomics of free-text instructions with the referential integrity of a symbol table.
Storage, for completeness, is deliberately boring: the skill list is a small JSON blob
in UserDefaults (key pixie.skills.v1), seeded with three built-ins — Fetch File,
Check Schedule, Search Info — on a fresh install. Config-sized data, config-grade
storage.
5. Enabled versus advertised
Each skill carries two booleans that sound similar and are not.
enabled answers: does this skill exist for the agent at all? An enabled skill
validates as an @-reference and expands inside other skills. A disabled one is
inert.
advertised answers: is this skill offered to the planner as a top-level
choice? Only advertised (and enabled) skills appear in the planner's menu. An
enabled-but-unadvertised skill is fully usable — but only by being @-mentioned
from another skill.
Why would you ever want a skill the planner can't pick? Because menus create competition. The built-in Fetch File skill is enabled but not advertised: it is reachable only through Search Info, which wraps it. If Fetch File sat in the menu next to Search Info, the planner — a small model choosing from one-liners — would sometimes pick the narrower skill for a query the broader one should own, search the files, find nothing, and stop, never having checked the Notebook or long-term memory.
The tool registry has the same knob, called advertiseInMenu, and its current
settings encode a real lesson from evaluation runs. Early on, @check-notebook was
advertised as a standalone handle — and the planner grabbed it for anything vaguely
shaped like "what do I know about the user," hit an empty Notebook, and gave up.
Today only @current-date is advertised standalone (for a bare "what day is it?");
the notebook, file, calendar, and memory tools are all reachable only through the
skills that wrap them, so every information lookup flows through Search Info's
check-everything procedure and every calendar question picks up the resolve-the-date
step first.
The general principle: the menu is a routing surface, and what you leave off it is as much a design decision as what you put on it. Wrapping a tool in a skill is how you attach mandatory procedure to raw capability; unadvertising the tool is how you make the procedure actually mandatory.
6. The Alignment menu: the user's steering surface
All of this converges in one screen: Settings → Agent → Alignment. It is where the user decides what their agent is told, and it renders the decision as a single, readable document.
The screen has three parts:
A Skills multiselect. The selected skills become the planner's menu — their short descriptions, nothing more. And here is the quietly important rule: selecting at least one advertised skill is what turns planning on. There is no separate "enable the planner" toggle (there used to be; it was retired). If you select no skills, the agent is a plain conversationalist on the direct path; the moment you select one, the plan→bind→execute machinery from The L1–L2–L3 Harness is live. The capability switch and the capability list are the same control, so they cannot disagree.
A Tools multiselect. Separately from skills, each tool in a small set (
PromptTool: Notebook, Files, Calendar, Date) can have its "here's what you have and how to use it" blurb appended to the system prompt for the direct, non-planning path. One honest subtlety, stated in the UI itself: these toggles are prompt composition only. Turning a blurb on does not grant access to anything, and turning it off does not revoke access — actual capability grants live in Settings → Agent → Context. This toggle controls whether the model is told about a tool, which is a different lever from whether the tool is attached. (All four default to off now, because the default agent plans over skills, and the skills carry the tools they need.)The instructions editor. At the top, an editable block: the agent's base instructions, which the user can rewrite entirely (with a reset-to-default escape hatch). Below it, dimmed and uneditable, the text that will be appended automatically: the enabled tool blurbs, then the skills menu. The two render as one cohesive document — because that is literally what the model receives — and a token count in the corner prices the whole thing. The count is exact when Apple's on-device tokenizer is available (the same tokenizer the system model uses), and falls back to a
~-prefixed characters-over-four estimate when it isn't.
What actually gets appended is worth seeing, because it demystifies the planner menu. With the default built-ins selected, the tail of the system prompt looks like:
Your skills:
- @search-info: The skill to use whenever you need to find some information
the user asked for — it checks every place that information could live.
- @check-schedule: Check the user's calendar — meetings, appointments,
free/busy — for a specific day or a relative one like today, tomorrow, …
Your tools:
- @current-date: You do NOT know the current date/day on your own — use this
for ANY question about today's date or day of the week.
That is the entire planning interface: a handful of one-liners, each addressed by its
@-handle. The planner declares "I'll use @search-info to find their PAN number,"
the handles are parsed out of that sentence, the namespace expands them (§3), and
execution begins. The user who edits a summary in the skill editor is, quite
directly, editing their agent's decision criteria.
Calling this screen "Alignment" is only half a joke. Alignment for a personal, on-device agent is not a training-time abstraction — it is whose instructions sit in the prompt, which procedures are offered, and what the model is told it can do. This screen makes all three inspectable and editable, with a price tag in tokens.
7. Authoring a skill: the editor walkthrough
The other screen, Settings → Agent → Skills, is where skills are made. The list
shows each skill by its @-handle with its one-liner; a + creates a new one. The
editor has three fields, and each enforces the invariants from earlier sections at
the moment of typing rather than at save time.
Handle. The skill's @-name. The field sanitizes as you type — lowercased, any
run of non-alphanumerics (spaces included) collapsing to a single - — so the
"no spaces" rule is a physical impossibility rather than a validation error. The
footer shows the resolved slug live (The agent references this skill as @meeting-prep), which diverges from what you typed only when a collision forced a
numeric suffix.
Summary. The one-liner for the planner menu. The footer says exactly what it is for: "shown in the planner menu so the agent knows when to reach for this skill." Writing a good summary is writing a good routing rule.
Instruction. The steps, in a mention-aware text editor. Typing @ opens a live
picker over the whole namespace — matching skills and tools by slug or display name
(a skill never suggests itself) — and tapping an entry inserts its slug. Every
mention in the body is tinted by what it resolves to: orange for a skill, blue for
a tool, so the two kinds of reference are visually distinct as you compose. A
mention that resolves to nothing gets neither color, and the editor lists it below
with a warning: @whatever — unknown, remove it to save. The Save button is disabled
until the handle is non-empty and every mention resolves. A skill with a dangling
reference cannot exist, by construction — this is the front half of the guarantee
whose back half is the rename cascade (§4). Self-reference, incidentally, is legal:
a skill may mention its own handle, and the cycle guard in the expander (§3) keeps
that from becoming an infinite loop.
Saving an edited handle routes through SkillStore.commit with the original id, so
the @old → @new cascade runs across every other skill's body before anything is
persisted. Renaming is safe enough to be casual.
One more affordance sits in the skill list: a long-press offers "Share with a contact." A skill you have refined is a procedure worth giving away — but sharing one means shipping its transitive @-closure (a skill is useless without what it mentions), reviewing it before import, and re-slugging on collision at the receiving end so their namespace stays consistent too. That pipeline — sealed-message bundle and all — is its own article: Mechanism: Skill Bundles & Sharing.
8. What this layer buys
Step back and look at the shape. The fixed, dangerous, code-level things — reading
files, touching the calendar, grepping memory — are a closed set of six tools with
schemas and implementations the app controls. Everything policy-like above them —
what to check first, what to never skip, when a capability should even be offered —
lives in prose the user can read, edit, author, and share, addressed through one
@-grammar that the user's editor, the skill bodies, and the model's own emitted
plans all share. Referential integrity is enforced at both ends (mint-time uniqueness,
rename-time cascade), so the whole structure can be refactored as fearlessly as code
under a good IDE.
And the steering surface is honest: the Alignment screen shows the exact document the model receives, tool blurbs are labeled as descriptions rather than grants, and the planning machinery is on precisely when the menu it plans over is non-empty.
A recurring theme of this series applies here in miniature: the interesting design work is not adding capability — it is deciding how capability is named, offered, and withheld. A tool the planner can't see, wrapped in a skill it can, behaves better than the tool alone.
Deeper dives from here:
- Mechanism: CallTool — One Tool to Bind Them — how the
toolset that
@-expansion produces is actually invoked, one signature at a time. - Mechanism: Skill Bundles & Sharing — the transitive closure, review-before-import, and collision re-slugging behind "Share with a contact."
- Mechanism: Memory & the Notebook — what
@check-notebookand@grep-memoryare actually searching.
Next: The Homebase.