Status: design draft (evolving). Thesis: a skill+ is a structured information enhancement of an agent loop. It gives the model a
flow(a map + where-you-are) and abriefas resident context, then injects tips (references) and soft/hardrulesby scenario (when(state)). The model still drives and participates — this is guided, not strict execution.For the exact format →
spec.md. For how to use it →../README.md.Strict execution (an engine that walks the flow, deterministic replay) is treated as further reading (§9), not the mainline.
In one line: a skill+ is a superset of a skill with a structured
flow+ scenario-injectedrules, so a runtime can inject context precisely, per turn, fromstate(instead of dumping prose up front). Delete the extras and it degrades to a skill.
A skill hands domain knowledge to the model as prose. skill+ takes one more step: the process itself is knowledge — and prose is a poor carrier for it (long, blurry, no sense of position). skill+ expresses process structurally and renders it for the model to read, and exposes scripts as callable tools, so the model can read the structured process, call tools, and participate in execution.
Stance: this is not meant to be strictly executed. Models keep getting stronger; the bet rides that trend — give the model a better carrier for process knowledge (structure > prose) + handy tools, and let it do more, rather than boxing it in with an engine (strict execution is, at bottom, "compensating for a model that isn't strong enough" — betting against the trend). The same skill+ produces better results as the model improves, with no rewrite. Strict execution instead caps the ceiling at the engine's structure.
| skill | skill+ | |
|---|---|---|
| Carrier | prose (a manual) | SKILL.md + a flow + scenario-injected tips / soft & hard rules |
| What the model sees | a wall of text, rebuilds structure itself, all at once | the flow + current position + the slice that matters now (anti-dilution) |
| Over time | the .md grows longer and noisier |
knowledge / scripts / rules accumulate by scenario, context stays lean |
| As models improve | helps only so much | the same skill+ gets better |
| Best for | one-shot, exploratory, fuzzy-boundary | repeated, clearly-staged, accumulating |
A skill+ sits on top of skills, it doesn't replace them: a worker the model spawns inside a skill+ can still load plain skills; fuzzy sub-tasks should stay plain skills. The dividing line: a skill hands structure to the model, a rigid workflow hands structure to code; skill+ takes the middle — hand structured process knowledge to the model to read, keep the judgment with the model.
Note: a skill+ with no
when:conditions at all (everything injected) just degenerates into a skill — skill+ is a strict superset of skill; the precision is opt-in.
A flow is a structured process handed to the runtime; it is not required to be a loop. Sequence, parallel (fanout), and loop (loop) all fit. A loop is just one kind of node, used when you need "iterate to convergence."
One-line characterization: a skill+ is a structured information enhancement layered onto an agent loop. A general ReAct-style runtime (build context → call model → run tools → compact) stays the engine; skill+ only adds structured information into the build-context step, in two layers:
- Resident layer (always present): the
flow(the IR map + current position, maintained as a side-effect of execution) + thebrief. A GPS-style "map + you-are-here" — cheap. - Enhanced layer (injected by
when(state)): tips (referenceswith awhen:), spotlighted tools (scripts), soft & hardrules. Only "the slice that matters now," to prevent context dilution.
The disease of a plain skill: it dumps all static knowledge every turn — ask about situation 1 and situations 2/3/4 are still in the prompt → dilution. skill+ keeps the brief resident + the detail injected by scenario (state-driven progressive disclosure). This is the essential advantage of skill+ over a skill: precision, not volume. Everything else (HITL, steering, compaction) is an existing capability of whatever loop hosts it; skill+ only adds information — if the host doesn't support a mechanism, skill+ does not fake it.
flow (eDSL or md) ──build──▶ flow IR ; SKILL.md / references / rules supply brief / tips / rules (each with a when)
│
each turn, at build-context: resident (flow + position + brief) + enhanced (tips/soft-rules matched by when(state)) ─▶ model
│ │ drives / participates
model calls tools / advances ◀────────────────────────────────────────────────────────────────────────────────────────┘
│
runtime: runs tools, merges state by field, records the trace, advances position; hard rules gate at the tool seam
The model is the driver; the flow is the map, scripts are the toolbox, tips/soft-rules are sticky notes; the runtime renders the map, exposes & runs tools, keeps state/trace, and enforces hard rules. Routing reuses an existing Agent Skills registry (scan each directory's SKILL.md frontmatter, no execution) — a skill+ is to a runtime what a skill is to a runtime (§2.8).
The flow's surface is a small embedded Python eDSL: build(flow) declares an IR (it is not command-line code that runs); state is declared as a pydantic State subclass. This eDSL is provisional — it's how this design expresses the idea for now, not a claim that a Python eDSL is the right authoring surface. The flow is fundamentally an IR; the eDSL is one front-end, and the right surface (quite possibly more natural-language) is an open question — see §6 on the eDSL-vs-NL tension.
Why a build-time / declarative eDSL. Embedded eDSLs come in two flavors — runtime-tracing (write real control-flow functions, the framework traces a graph as it runs; structure only exists once it runs) and build-time / declarative (a builder declares a static graph you can inspect before execution; cf. Burr's state-machine builder, LangGraph's StateGraph). skill+ wants the latter: build(flow) only constructs the IR — so the runtime can render the map / track position / drive when / attach hard rules ahead of time, with no build-vs-execute confusion. Take the good parts of Burr (state machine + on-the-board state) and explicit graph building (LangGraph), with a guided twist: conditions are description strings or state predicates, not an executed router; context-manager nesting stays readable.
Not chosen: runtime-tracing eDSLs (structure exists only when run, and it is an execution model); external DSLs (you maintain a parser); config languages (a separate toolchain, narrow ecosystem). The criterion: the format follows its consumer — the flow's consumer is the model (reading a map), not an engine (executing). Hence markdown is its human-readable view and the Python eDSL is its authoring surface.
Multi-front-end / IR-neutral: the runtime only knows the flow IR; the Python eDSL is the current front-end, while a markdown outline / future YAML / a more natural-language surface / a visual editor can all compile to the same IR.
Honest note on author skill:
flow.pyis technical (power users / domain experts). Non-technical authors take the "degrade to a skill" floor; flow usability is meant to improve by distilling a draft from a successful conversation + human editing, not forcing people to hand-write an eDSL from scratch.
| Component | What it is | How it activates |
|---|---|---|
tips (references with a when:) |
fine-grained static knowledge | injected into context when matched — the core of anti-dilution |
tool (scripts) |
scripts | always in the tool list, callable any turn; a flow's tool= only spotlights, it does not change availability |
soft rule (on_fail: advise/self-check) |
a judgment criterion | inject the criterion text, the model self-checks; never a machine block |
hard rule (on_fail: ask/deny/stop) |
machine-enforced / a strong instruction | .py hooks a gate, .md injects a strong instruction; can gate / can stop (§2.5) |
Three activation modes (don't lump them all as "injection"): tips / soft rules = injected into context by when; tool = always callable, tool= spotlights by flow position (not by when); hard rules = evaluated at a gate by when (not injected into context). The soft/hard line: hard = a gate (.py) or a strong instruction (.md) that can stop things; soft = injected/advisory, guides but never gates. The two axes (soft/hard × md/script) are orthogonal; exact format in spec.md §5.
Each turn the model gets: ① a flow summary (the whole graph, one line per step, each tagged done / ←current / not-yet + a key finding — the map + you-are-here); ② the current stage's detail (what to do, constraints); ③ the currently spotlighted tool; ④ matched tips / soft rules. A large flow doesn't blow up the prompt: the whole graph is just one line per step, only the local part expands. 🚧 The exact render format / token budget is open (§7).
rules/ exposes a host runtime's natural seams to the author declaratively: writing a rule (on_fail + .md/.py + optional when) attaches behavior to the right seam without the author touching any hook API. Conceptually, a guided agent loop offers a few seams, and on_fail decides which one a rule lands on:
| Seam (conceptual) | Return | Used as |
|---|---|---|
| before a tool call | allow / deny / modify / ask | ask-before-execute, irreversible actions, gate on a specific tool (the main gate) |
| after the model replies | accept / reject / rewrite | non-conforming output → force a regenerate |
| at context-build | inject text | inject a constraint |
| after a tool returns | rewrite the result | normalize / redact |
Soft rules (advise/self-check) inject text only and attach nothing. Hard rules (ask/deny/stop, and flow.ask/confirm) compile to a gate with a return value at the right seam. The author writes on_fail, not hooks; a .py hard rule reads live truth inside the gate (§5.2).
Global vs gated (independent of soft/hard): no when = resident (the gate is always live); a when = state-gated (live only when matched). A global hard rule is an always-on "iron law" gate — e.g. a rule with on_fail: deny and no when denies any output containing a forbidden case, regardless of stage. That's the most direct example of "a rule is an author's declarative intervention at a seam": one line of declaration = one global gate, with no hook code written.
HITL is a capability of the host loop; skill+ doesn't build its own, it only marks where on the IR a human is needed:
- A hard rule /
flow.ask/flow.confirmtriggers the host to pause and ask:flow.confirm(approve an action) /flow.ask(request information) /on_fail: ask(an ambient guard that travels with a tool). - User-initiated steering / follow-up tracks the host's overall capability — if the host supports it, it's available automatically; if the host doesn't, skill+ doesn't need to design for it. These are "the user talking to the loop," independent of whether any skill+ is running.
Corollary: the skill+ layer does not design steering / follow-up / arbitration — that's the host's job.
- On the board, external (not in the conversation context): a state store, dialect-neutral, select-then-upsert.
- Keyed by conversation; a run is just an execution instance that doesn't own state; each run keeps a trace as an audit residue.
- Fielded + merge semantics (list dedup / dict merge-or-clear / conflict fail-closed).
- Traceable ≠ replayable: under guided execution, record the trace the model actually took (observability). "Why this conclusion" is answered by the real trace; it need not be reproducible.
- Resume is light: load the conversation's state → the model reads state / history and continues. Heavy resume (deterministic replay) belongs to the strict path (§9).
skill+ does not rewrite the agent loop. A general ReAct loop (build context → call model → run tools → compact) stays as-is and knows nothing about flow/state/when. All of skill+'s logic is additive and attaches at the loop's existing seams (§2.5). A plain skill is the degenerate case — routing + resident injection only, no seams attached; a skill+ just attaches a few more callbacks (render the map, inject by when, merge state, advance position, gate hard rules).
| Concept | Conceptual seam | Plain-skill? |
|---|---|---|
description (frontmatter) |
registry match | yes (skill) |
brief + resident knowledge |
build-context, resident inject | yes (skill) |
references + when (tips) |
build-context, inject by when | skill+ |
flow |
IR; render map at build-context + advance after the model replies | skill+ |
state |
merge after a tool + state store | skill+ |
| soft rules | build-context inject | skill+ |
| hard rules | before-tool gate | skill+ |
ask / confirm |
the host's HITL channel | yes (host) |
Cost: no extra model calls. The main loop is still one model call per turn. Picking a branch, judging until convergence, whether a stage is done, soft-rule self-checks — all ride on that one reply, no separate router/judge call. Predicate conditions the runtime evaluates directly (0 model calls); a gate is .py code or .md text (0 model calls). Only a flow.agent worker / verify retry adds calls — and that's an opt-in sub-agent. On tokens: each turn's context is thicker but precisely picked by when (potentially cheaper than a fat skill dumping everything), and the structure cuts wasted wandering turns → total calls may even drop. This realizes §1's "precision, not volume."
The correction of emphasis: the core of skill+ is not "give the model a map" — it's this chain:
the flow is independently structured → it yields structured state (a well-defined position stage/round + schema'd domain fields)
→ state becomes the "condition variable" → each turn, assemble context precisely from state (position + matched tips + matched/global rules)
Serializing the whole flow into a text map is just a thickened "position view" within that context — secondary. The core is "use structured state as a condition to do turn-level injection / gating." The flow's real job = cut the process into a series of named hooks (stages), so knowledge / rules "live on a hook, and light up only on arrival" — e.g. a references/risk-levels.md with when: stage=deep-dive is injected only the first time you reach the per-candidate-research step (never before, never re-injected after; it stays in history until compaction). A fat skill dumps everything every turn → dilution. The literal meaning of anti-dilution = never inject the irrelevant + never re-inject the already-said (not "delete it as you pass the station"). This is the literal payoff of "precision, not volume," and something a plain skill can't give (it has no well-defined position to condition on).
Inject-once: a matched tip/rule becomes one context message in history and is injected once; later turns do not re-inject (it's already in history, the model still sees it), only re-injecting on "first match" or "matched again but compaction dropped it" (deduped by id / content hash). The only thing each turn refreshes is the dynamic "position breadcrumb" (where you are / which round — tiny); static tips/rules are injected once and done.
state.stage fidelity (three layers): ① predicate-grounding (exit conditions read real state — authoritative self-correction); ② injection as synchronizer (each turn injecting "you are at X" anchors the model to the cursor); ③ drift fallback (stuck → nudge / ran ahead → heuristic / left early → a done= predicate vetoes). The cursor is advisory, may drift, degrades gracefully; hard guarantees ("never skip suitability") use rules/gates, not the cursor.
| v0 (now) | v1 (planned) | |
|---|---|---|
| Condition source | stage/round (workhorse), domain state fields, external facts (read live) |
— |
| Expression in md | declarative small predicates in frontmatter (equality-first) | richer grammar (range / set / combinators) |
| Arbitrary logic | on the Python side: a flow.py lambda s / a .py rule's check(ctx) |
a lambda when hook for markdown tips |
Principle: a markdown when: is always a declarative small predicate (a deliberate boundary — markdown holds no code); "arbitrary flexibility" naturally lives on the Python side. The most minimal v0 can do only stage-based when (already the core value) with domain / external facts close behind.
A neutral "engine" of skill+ capabilities should be defined by the intersection of independent skill+s, not by any one of them. Rule of thumb: an abstraction enters the engine only if two or more independent skill+s need it; anything only one skill+ uses stays inside that skill+.
| Layer | Content | Warrant |
|---|---|---|
| engine (neutral) | flow (stage/branch/loop/fanout/agent) · routing · rendering · state (on the board) · registering & when-injecting tips/scripts/soft & hard rules · HITL · hard-rule→gate · observability |
needed by ≥2 domains |
| domain (❌ not in the engine) | a specific skill+'s domain concepts (its own names for the scripts/rules/state it instantiates) | only one skill+ uses it |
The engine knows "there are tips / tools / soft & hard rules"; it does not know any one skill+'s domain vocabulary. If a domain concept leaks into the engine, that's overfitting — back it out.
Worker (flow.agent) hard constraints: a worker returns a structured enum status (don't parse status from text prefixes); each worker is an independent sub-run (its own state/trace); concurrency has a hard cap + cooperative cancellation + first-write-wins on terminal state; retries have a loop-breaker; workers default to no nesting / no asking the user.
A maturity ladder: L1 a skill → L2 a skill + a standalone agent → L3 a plugin (hard rules become gates, soft rules become primitives) → L4 skill+-native (flow / injection / rules / HITL are first-class in the host runtime).
| Stage | Content | Acceptance |
|---|---|---|
| M1 | routing (extend an existing skill registry) + build(flow) → IR + two-layer context (resident flow/brief + when-injected tips/tool) + a state store (light resume) |
hand-write a skill+, the model reads the flow, calls tools, runs it to the end, state is reviewable |
| M2 | hard rules (gate) + soft rules + HITL (ask/confirm) + observability |
safe points hold, soft rules guide, the trace is auditable |
| M3 | knowledge / scripts / rules accumulate by scenario + a "retain" step (human-reviewed drafts) | produces a conclusion with a trace; gets sharper with use |
Compounding (retain): each "symptom → problem → cause" cycle feeds back as new tip/script/rule drafts, which require human review before landing — a skill+ does not silently rewrite its own judgment thresholds (no self-cheating). Baselines come from distilling "a run that actually worked," not a blank page.
Persist only facts that can't be re-derived + the internal position. External-world facts (the state of external systems, etc.) are read live at decision time, never cached as authority — a cached external fact can be changed outside the agent's control and go stale.
Injecting into the prompt may use a cheap cached snapshot (tolerates staleness); but a hard gate reads live truth at decision time. Keep the two paths separate: a guard that reads a cached field for a hard decision is a bug.
"A small script expressing run logic, plugged into a runtime" is a mature line in orchestration:
| Lineage | Representatives | How the main flow is expressed |
|---|---|---|
| imperative code | Temporal / DBOS / Inngest / Restate; Prefect / Airflow / Dagster; Claude Dynamic Workflows | real control flow |
| state machine as code | iWF | declarative state class + explicit IO |
| declarative DSL | Serverless Workflow, Argo / Step Functions | YAML/JSON topology |
Positioning: skill+ sits in the empty middle band between "strict engine" and "pure autonomy." The field splits along one axis — strict engines (LangGraph / Burr / CrewAI Flows: compile the flow into a graph an engine walks) and pure autonomy (hand it all to the model). skill+ is in between: the flow is an IR'd "map / condition base" but isn't executed; the runtime only injects context by state + gates with hard rules. This middle stance has research backing (StateFlow: each state carries its own prompt, injected on entry; FlowAgent: "a state machine is behavior guidance, the model is the driver") and little product occupation.
Two data points support the direction:
- A community proposal for a backward-compatible "Skills 2.0" (frontmatter extensions, lifecycle hooks) was floated and closed as not-planned — thought of, not built.
playbooks-ai(NL playbooks compiled to an IR and strictly executed — the most strict LLM-native attempt) sunset, the author's conclusion being that "guided skills won in practice" — falsifying the strict route from the other side.
The distinctive combination: (a backward-compatible superset of skill — delete flow/rules and it's a skill again) + (a deterministic flow-state → per-turn conditional injection, injected once and left in history) + (explicitly guided, not strict).
Honest re-positioning of the value: don't make "anti-dilution" the headline — it depreciates as context windows grow and models strengthen, and it's in tension with the "bet on stronger models" thesis (a stronger model needs you to manage its context less). The durable moats — the ones that grow more valuable as agents get more autonomous — are three: ① governance (rules as a declarative handle on gates, especially global "iron-law" hard gates — however strong the model, irreversible/compliance/safety gates are still required); ② a backward-compatible superset (a pragmatic adoption path that, literally, no one has shipped); ③ structured state as an audit/traceability base + a "retain" accumulation loop. In one line: not "manage context for a weak model," but "give increasingly autonomous agents structured guardrails + state + an accumulating process, while staying compatible with skills." Anti-dilution drops to a "nice, still-useful-today" bonus.
Two real risks: ① the anti-dilution dividend shrinks (hence the re-positioning to governance); ② eDSL vs the market's NL convergence — the market (Devin Playbooks, Decagon AOPs, and the playbooks-ai post-mortem) is converging on natural-language playbooks, while a Python eDSL is in tension with the "markdown is enough" low bar of skills. The hedge (degrade to a skill + generate a draft from a conversation) is real but unproven; whether the primary authoring surface should lean more NL needs revisiting.
To validate (with A/B, not more argument): ① conditional injection + rules vs. "fat skill + strong model" on accuracy / cost / turns; ② do global iron-laws + gates actually stop what should be stopped; ③ can authors (or "generate from a conversation") actually write a flow.
Sources: StateFlow · FlowAgent · Agent Skills · Burr · LangGraph · playbooks-ai (sunset) · Claude Dynamic Workflows · Decagon AOPs. Case-Based Reasoning (retrieve-reuse-revise-retain) is the theoretical backing for scenario accumulation / retain.
- Progress signal: how the model tells the runtime a stage is done / which description branch it took / that a description
untilconverged (a singleproceedvs a few verbs; forward-only in v0; fallback when the signal is forgotten). - State write API: NL output → field;
fanoutresults →dictby key; which fieldask/agentwrites. whengrammar: the exact declarative predicate grammar (equality / range / set /tool=…@env); field-name inference vs explicit.- Render format / token budget: how the
flowmap + matched enhancements are assembled for the model. - Flow details:
on_failuretrigger conditions & attribution; thefanout{item}template; whether aBREAKis needed; when a flow isdone; promoting a soft rule to a tunable judge. - Binding details: semantics when a step's
rule=and a rule's ownwhenboth apply (ignore / stack); howtool=/rule=reference non-.pyartifacts (name / path). - Routing trigger: auto-match on
descriptionvs an explicit invocation. - Distribution: whether to align with a plugin format so a skill+ and a skill ship together.
An early exploration had a runtime strictly execute a skill+: walk the flow verbatim, deterministic replay (re-run + step memoization, or event-sourcing), node-level resume, a fixed loop. Per §1.1, this is not the mainline — at bottom it "compensates for a model that isn't strong enough," and skill+ bets the other way. Strict execution is kept as further reading, considered only where deterministic replay / hard guarantees are genuinely required — and even then, usually only a few safety points need to be a gate (§2.5), not the whole graph.
Points (if ever needed): deterministic replay has a real cost (versioning, paradigm split, hard to mock — the price of betting against the model trend); strong guarantees (replayability / cheap models / audit-as-reproducibility) rise with strictness while flexibility falls — it's a dial, not a switch.
Default = guided (flow + scenario injection, model-driven); strict = optional, and may never need to be turned on as a whole.