All articles

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;
  • enabled and advertised — 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-date to get today's date and work out the date range the user means. Then use @calendar to 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:

  1. 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.

  2. 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.)

  3. 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:


Next: The Homebase.