Background
Skill activation is currently implemented as a fake LLM tool named activate_skill. When the model calls it, the executor intercepts it, loads the skill body, injects it into context, and returns no tool result. This is documented as a special case throughout the codebase.
Problems this causes
1. Semantic mismatch — skills are instructions to the agent, not capabilities of the agent. Expressing them as tools inverts the relationship: the LLM thinks it invoked a capability; it actually received new instructions.
2. Pervasive special-casing:
executor.ts — separates skillCalls from toolCalls before execution
core.ts — yields a distinct skill_activated event; filters activate_skill out of plan mode tools inconsistently (it's in PLAN_MODE_TOOLS but skills can mutate context)
schema.ts — activateSkillDefinition is a separate export alongside toolToDefinition
3. Token cost — activate_skill appears in every prompt's tool list, consuming tokens even when no skill activation is expected.
4. Model confusion — the model sees activate_skill as a peer of bash, read, edit. It may call it at unexpected times or conflate it with a filesystem operation.
Proposed design
Skills are directives: structured content injected into the agent's context by the agent infrastructure, not by the LLM. Two options:
Option A — Pre-loading (simpler): skills are activated before the agentic loop begins, via explicit user invocation (/skill-name) or a lightweight classifier. The activate_skill tool is removed entirely. The model never sees it; it only sees the resulting injected content.
Option B — Directive message type (preserves model-driven activation): introduce a DirectivePart message type that the agent strips before sending to the LLM but processes internally. The model can still request skill activation via a structured output field (not a tool call), but the mechanism is explicit.
Option A is the recommended starting point — model-driven skill activation is rarely needed in practice, and pre-loading covers the common cases with far less complexity.
Acceptance criteria
Location
src/model/schema.ts — activateSkillDefinition
src/agent/executor.ts:113 — skill call separation
src/agent/core.ts:80,178 — activate_skill filtering and event emission
Background
Skill activation is currently implemented as a fake LLM tool named
activate_skill. When the model calls it, the executor intercepts it, loads the skill body, injects it into context, and returns no tool result. This is documented as a special case throughout the codebase.Problems this causes
1. Semantic mismatch — skills are instructions to the agent, not capabilities of the agent. Expressing them as tools inverts the relationship: the LLM thinks it invoked a capability; it actually received new instructions.
2. Pervasive special-casing:
executor.ts— separatesskillCallsfromtoolCallsbefore executioncore.ts— yields a distinctskill_activatedevent; filtersactivate_skillout of plan mode tools inconsistently (it's inPLAN_MODE_TOOLSbut skills can mutate context)schema.ts—activateSkillDefinitionis a separate export alongsidetoolToDefinition3. Token cost —
activate_skillappears in every prompt's tool list, consuming tokens even when no skill activation is expected.4. Model confusion — the model sees
activate_skillas a peer ofbash,read,edit. It may call it at unexpected times or conflate it with a filesystem operation.Proposed design
Skills are directives: structured content injected into the agent's context by the agent infrastructure, not by the LLM. Two options:
Option A — Pre-loading (simpler): skills are activated before the agentic loop begins, via explicit user invocation (
/skill-name) or a lightweight classifier. Theactivate_skilltool is removed entirely. The model never sees it; it only sees the resulting injected content.Option B — Directive message type (preserves model-driven activation): introduce a
DirectivePartmessage type that the agent strips before sending to the LLM but processes internally. The model can still request skill activation via a structured output field (not a tool call), but the mechanism is explicit.Option A is the recommended starting point — model-driven skill activation is rarely needed in practice, and pre-loading covers the common cases with far less complexity.
Acceptance criteria
activate_skilltool is removed fromschema.tsand the tool list/skill-nameare injected before the agentic loop beginsexecutor.tsorcore.tsskill_activatedevent is emitted by the CLI layer (user invocation), not by the agent loopLocation
src/model/schema.ts—activateSkillDefinitionsrc/agent/executor.ts:113— skill call separationsrc/agent/core.ts:80,178—activate_skillfiltering and event emission