Skip to content

Latest commit

 

History

History
170 lines (118 loc) · 12.6 KB

File metadata and controls

170 lines (118 loc) · 12.6 KB

skill+ Specification

A skill+ is a skill, plus an optional flow and an optional set of rules. Concretely: an Agent Skills directory with a SKILL.md, optionally extended with a flow (a stage outline) and a rules/ folder.

This document is the format spec (directories / files / fields / constraints). For why it's designed this way and the conceptual execution model → design.md. For how to use it and a worked example → ../README.md.

Modeled on the Agent Skills specification. Status: v0 draft; 🚧 marks open questions.

1. Directory structure

skill-name/
├── SKILL.md          # REQUIRED — the skill itself (frontmatter + body)
├── flow.py           # optional — the flow (stages + state) as a Python eDSL (§3)
├── rules/            # optional — soft & hard rules
├── references/       # optional — knowledge files (a when: makes one a tip)
├── scripts/          # optional — callable tool scripts
└── assets/           # optional — templates / static resources

SKILL.md is the only required file — on its own it is a valid plain skill. Adding a flow and/or rules/ makes it a skill+; remove those and it degrades back to a plain skill (skill+ ⊇ skill). Routing uses the same mechanism as skills (§9).

2. SKILL.md

YAML frontmatter + Markdown body. Fully compatible with the Agent Skills SKILL.md.

2.1 Frontmatter

Field Required Constraint
name yes 1–64; lowercase letters / digits / hyphens; no leading/trailing or doubled hyphens; matches the parent directory name
description yes 1–1024, non-empty; says what it does + when to use it (keywords for routing)
license no license name or reference
compatibility no ≤500; environment requirements (target product / system deps / network)
metadata no arbitrary string k-v
allowed-tools no space-separated pre-approved tools (experimental)

Illegal name examples: PDF-x (uppercase), -x (leading hyphen), x--y (doubled hyphen).

2.2 Body

The Markdown after the frontmatter: the opening paragraph is the brief (a one-line thesis / plan, always resident); the rest is the skill's resident, unconditional knowledge. Situation-specific knowledge does not go here — it goes in references/ with a when: (§5). Keep it under ~500 lines; push detail to references/ (§7, progressive disclosure).

3. The flow (process)

A flow declares the stages a task moves through (and, optionally, a typed state). It exists so a runtime can render a "map + you-are-here", and so knowledge/rules can be scoped to a stage. The flow is a map the model reads, not a program an engine executes (see design.md).

flow.py is provisional. A small Python eDSL is how this spec expresses the idea for now — it is not a claim that a Python eDSL is the right authoring surface. The flow is fundamentally an IR; the eDSL is just one front-end (a markdown outline, a YAML topology, or a more natural-language surface could compile to the same IR). The right surface is an open question (see design.md §6/§7). The primitives below define the IR's shape, not a final syntax.

3.1 flow.py

Declare the flow as a small Python eDSL. build(flow) declares an IR at build time (it does not run business logic); state is a State subclass (§4).

from skillplus import Flow, State

class MyState(State):              # domain state (§4)
    ...

def build(flow: Flow):             # declarative — builds the IR, does not execute
    flow.stage("load-profile", tool=scripts.get_profile)
    with flow.branch(lambda s: not s.profile_fresh):
        flow.stage("kyc", rule=rules.suitability)
    flow.ask("ask-preference", "ask sector / horizon / ESG")
    with flow.loop("screen", until=lambda s: len(s.candidates) <= 3, max=4):
        flow.stage("filter", tool=scripts.apply_filters)
        with flow.on_failure():
            flow.stage("relax", "nothing left → loosen one threshold and retry")
    with flow.fanout("deep-dive", over=lambda s: s.candidates):
        flow.agent("analyze", prompt="research {item.ticker}: fundamentals + news + risk",
                   verify="must give both bull and bear case")
    flow.stage("recommend", rule=rules.disclaimer)

Primitives

Primitive Form Meaning
flow.stage(name, desc="", *, tool=, rule=) leaf one step; tool= = spotlight (which scripts matter here, not an availability switch); rule= = bind a local rule
with flow.branch(cond) / with flow.otherwise() block branch; cond = predicate lambda s or description string; successive branch = ordered first-match, otherwise = else
with flow.loop(name, *, until, max) block iterate; termination = until (predicate / description) + max (hard cap)
with flow.fanout(name, *, over) block parallel fan-out; over = predicate returning an iterable / description; {item} available in prompts
with flow.on_failure() block fallback for the nearest enclosing step/block
flow.agent(name, *, prompt, verify=None) 🚧 leaf spawn a sub-agent worker; verify = exit gate
flow.ask(name, question) leaf HITL input: pause, ask, continue with the answer
flow.confirm(name, question) leaf HITL approval: pause for sign-off (a manual gate)

Two-form conditions: branch / until / fanout.over take either a predicate lambda s: (evaluated against typed state — mechanical, reliable) or a description string (judged by the model — a fuzzy fallback); prefer a predicate when it can be decided mechanically, especially for until. prompt / question are always description strings.

🚧 Progress signal (open, central): under guided execution, how does the model tell the runtime that a stage is done / which description branch it took / that a description until has converged, so the position can advance? The channel is undecided (see design.md). Predicate conditions the runtime evaluates itself; description conditions and stage-completion depend on this signal.

A runtime only ever consumes the flow IR; flow.py is one front-end that compiles to it. Rationale for the design in design.md.

4. state

When using flow.py, declare a pydantic class extending the base State (class MyState(State)). The base carries the mechanics; the subclass declares only domain fields.

  • The base provides: identity + persistence (a conversation-scoped key, a state store); per-field merge / reducers (list dedup / dict per-key / scalar conflict fail-closed; Annotated[list, reducer] to override); read-only flow position (s.stage / s.round); a validation anchor for when: and condition predicates.
  • The base holds no domain fields (pushing domain semantics into the base = overfitting).
  • Store only "facts that can't be re-derived" + internal position; external-world facts (state of external systems, etc.) are read live at decision time, not cached as authority (rationale in design.md).

state drives two things at once: ① the flow's current position, and ② the when: triggers of references/rules.

🚧 Write API (open, central): how the outputs of a tool / worker / flow.ask / flow.agent land as typed fields — the convention is "return a patch, merge by field name", but NL-output → field, fanout results → dict by key, and which field ask/agent writes are undecided (see design.md).

5. references/, scripts/, rules/

references/ (knowledge; a when: makes it a tip)

Markdown knowledge files. Frontmatter with a when: → injected into context when it matches (a tip); without a when: → deep knowledge loaded on demand. Keep them flat and small.

scripts/ (tools)

Scripts (Python / Bash / …), always in the tool list, callable in any turn; a flow's tool= only spotlights, it does not change availability. Self-contained or with stated deps; give clear errors. May be referenced by "module:symbol".

rules/ (soft & hard rules)

Each rule has two orthogonal axes:

  • on_fail (soft / hard): adviseself-checkaskdenystop; the first two are soft (never block), the last three are hard (gate / stop).
  • Delivery (file extension): .md = injected into context, the model reads it (trust the model); .py / .sh = a host hook, machine-enforced. The two axes are independent (a .md can be a hard rule, a script can be a soft rule).

The source of truth is the file's frontmatter / header fields (on_fail + optional when). Filenames carry no semantics (optional rules/hard/, rules/soft/ subdirs are pure organizational sugar). Binding: globally via its own when:, or locally by a flow step's rule=. Multiple rules: soft = collect, don't block; hard = a single decisive verdict.

assets/ (static resources)

Templates / images / data files, same as Agent Skills; referenced on demand by SKILL.md / references / scripts, never resident in context.

6. when

The trigger condition for references (tips) and rules: a structured predicate + an optional natural-language note. Predicate fields come from a namespace union (not just state fields):

when: stage=kyc            # flow position (runtime-maintained, §4)
when: candidates_left<=3   # domain state field (validated against the schema)
when: tool=push@prod       # tool-call context (guarding a tool, §8)
when: market=volatile      # external / derived fact ("read live, not stored", §4; namespace is open)
when: field source Example Validation
domain state field risk_level=R5 against the State schema
flow position stage= / round= against flow node names
tool-call context tool= / @env hard rules guarding tool use (§8)
external / derived fact market= open, not strictly validated (external facts read live)

No bare prose (parsing drifts), no bare code (over-promises an execution engine). 🚧 The predicate grammar (equality / range / set / @env) is undecided.

v0 / v1: v0 = the declarative predicates above (stage is the workhorse; domain fields / external facts follow); arbitrary logic does not go into a markdown when: — it lives on the Python side (a flow.py lambda s or a .py rule's check). v1 = richer grammar + a lambda when for markdown tips. See design.md.

7. Progressive disclosure (two-layer context)

Layer Content
Resident description (routing) + brief + SKILL.md resident knowledge + the flow map (with current position)
Enhanced (by when:) matched tips (references) + soft rules (rules/*.md) + the current stage's spotlighted tool
On demand references deep knowledge without a when:, script output

Compare Agent Skills: description (~100 tokens) → SKILL.md + flow → tips / soft rules → references. 🚧 Render format / token budget undecided (design.md).

8. Execution semantics of soft/hard rules & HITL

  • Soft (advise / self-check): inject text, the model self-checks, never a machine block.
  • Hard (ask / deny / stop) and flow.ask / flow.confirm: can gate / can stop. A .py rule's check(ctx) reads live truth at decision time (not the cached state used for injection).

Which host seam a rule lands on is an implementation detail (see design.md). The author only writes on_fail / when or flow.ask/confirm, and never touches a hook API.

9. Routing

A runtime scans each directory's SKILL.md frontmatter (static — it does not execute flow.py) and matches on description. A matched entry that has a flow → structured execution (skill+); without one → a plain skill. No new registry is needed — an existing Agent Skills registry is extended to recognize "a skill directory that also has a flow / rules/" as a skill+. Execution model in design.md.

10. Validation (🚧 not implemented)

skillplus validate <dir>: checks the frontmatter and name rules; that the flow builds a valid IR; that rules/ have a legal on_fail; that every when: field belongs to a known namespace (state fields against the schema; stage/round against flow nodes; tool/external facts per their source, external facts not strictly validated, §6); that scripts/ and rules/*.py load — mirroring the Agent Skills validator.