The Skills System
- Implement a skill with a named hook point and an auto-trigger predicate, and register it in the skill registry
- Distinguish explicit skill invocation (/skillname prefix) from auto-triggered invocation and explain when each is appropriate
- Trace how a pre_synthesis skill modifies the synthesis prompt and how a post_wiggum skill produces a secondary artifact
What Skills Are
Skills are modular extensions that inject behavior into the pipeline at named hook points. They do not replace pipeline stages — they modify how those stages behave for specific task types or user intentions.
Five built-in skills:
| Skill | Hook | Mode | Description |
|---|---|---|---|
/annotate |
pre_synthesis | auto | Injects the Nanda 8-move annotated abstract framework into synthesis |
/kg |
post_synthesis | auto | Generates a D3.js knowledge graph from the output |
/panel |
post_wiggum | auto | Runs a 3-persona parallel evaluation panel |
/deep |
pre_research | auto | Forces MAX_SEARCH_ROUNDS, disables novelty gate |
/cite |
pre_synthesis | explicit | Requires source URL attribution for each claim |
The Four Hook Points
pre_research → runs before gather_research()
modify search strategy: MAX_SEARCH_ROUNDS, disable novelty gate
pre_synthesis → injected into the synthesis prompt
add task-specific formatting requirements, frameworks, citations
post_synthesis → runs after output is written
generate secondary artifacts: knowledge graphs, formatted exports
post_wiggum → runs after the verification loop completes
secondary evaluation: panel assessment, quality dashboards
Hook point choice determines what the skill can affect. A pre_synthesis skill can only influence what the model produces — it cannot change the search results or the final format of a file that's already been written. A post_synthesis skill has access to the final output but runs too late to affect synthesis.
Skill Registry
from dataclasses import dataclass
from typing import Callable, Optional
@dataclass
class Skill:
name: str
hook: str # "pre_research" | "pre_synthesis" | etc.
description: str
prompt_injection: Optional[str] # text to inject at the hook point
handler: Optional[Callable] # function to call at the hook point
auto_trigger: Callable # predicate(task, plan) -> bool
explicit_only: bool = False # if True, only activates via /skillname prefix
SKILL_REGISTRY: dict[str, Skill] = {
"annotate": Skill(
name="annotate",
hook="pre_synthesis",
description="Inject Nanda Annotated Abstract framework",
prompt_injection=NANDA_FRAMEWORK_PROMPT,
handler=None,
auto_trigger=lambda task, plan: any(
kw in task.lower()
for kw in ["paper", "abstract", "survey", "review"]
)
),
"deep": Skill(
name="deep",
hook="pre_research",
description="Force MAX_SEARCH_ROUNDS, disable novelty gate",
prompt_injection=None,
handler=lambda ctx: ctx.update({"max_rounds": MAX_SEARCH_ROUNDS,
"novelty_gate": False}),
auto_trigger=lambda task, plan: any(
kw in task.lower()
for kw in ["comprehensive", "exhaustive", "deep dive", "thorough"]
)
),
# ...
}
parse_skills() and auto_activate()
def parse_skills(task: str) -> tuple[str, set]:
"""Strip /skill tokens from task; return clean task + explicit activations."""
tokens = re.findall(r'/([a-z-]+)', task)
activated = {t for t in tokens if t in SKILL_REGISTRY}
clean_task = re.sub(r'/[a-z-]+\s*', '', task).strip()
return clean_task, activated
def auto_activate(task: str, plan: Plan, explicit: set) -> set:
"""Fire auto-trigger predicates; return full set of active skills."""
active = set(explicit)
for name, skill in SKILL_REGISTRY.items():
if skill.explicit_only:
continue
if name not in active and skill.auto_trigger(task, plan):
active.add(name)
log(f"[auto-activate] /{name} triggered")
return active
Building a Custom Skill
Adding a new skill requires three things:
1. Write the skill's handler or prompt injection:
# A post_synthesis skill that generates a one-page executive summary
EXEC_SUMMARY_PROMPT = """\
The following is a detailed technical document.
Produce a 200-word executive summary suitable for a non-technical audience.
Output ONLY the summary, no headers.
"""
def generate_exec_summary(output: str, output_path: str, producer_model: str):
prompt = EXEC_SUMMARY_PROMPT + "\n\n" + output[:4000]
summary = call_producer(prompt, producer_model)
summary_path = output_path.replace(".md", "-summary.md")
write_file(summary_path, summary)
log(f"[exec-summary] written to {summary_path}")
2. Register in SKILL_REGISTRY:
"exec-summary": Skill(
name="exec-summary",
hook="post_synthesis",
description="Generate a 200-word executive summary alongside the main output",
prompt_injection=None,
handler=generate_exec_summary,
auto_trigger=lambda task, plan: False, # explicit only
explicit_only=True
)
3. Wire the handler call in agent.py:
# In the post_synthesis hook section of agent.py:
for skill_name in active_skills:
skill = SKILL_REGISTRY[skill_name]
if skill.hook == "post_synthesis" and skill.handler:
skill.handler(output, output_path, producer_model)
That's it. The skill is now invokable with python agent.py "/exec-summary Research context engineering..." and can be tested in isolation by calling generate_exec_summary() directly.
Built-in Skills in Practice
# Annotate a paper with the Nanda 8-move framework:
python agent.py "/annotate https://arxiv.org/abs/2308.04079 output.md"
# Force comprehensive search (disable novelty gate):
python agent.py "/deep Survey all context engineering techniques 2023-2025..."
# Require source attribution for every claim:
python agent.py "/cite Explain the TRPO algorithm..."
# Knowledge graph (auto-triggers on 'knowledge graph' in task):
python agent.py "Build a knowledge graph of transformer attention mechanisms..."
# Panel evaluation (auto-triggers on high complexity):
# No prefix needed for high-complexity tasks — auto-activated
python agent.py "Comprehensive analysis of all major RL algorithms..."