You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Skillware today excels at one skill, one tool, one execute(). Hosts with many skills or fixed middleware order reinvent discovery merge, Directive budgeting, and sequential execute() loops.
Add (opt-in only — nothing changes until callers use the new APIs):
Add "chains" to _KNOWN_TOP_LEVEL_KEYS (removed from opaque extra when parsed, same pattern as mail).
Invalid legacy blocks do not break merge.
Chain YAML schema
chains:
<name>:
description: string # list/showwhen: string # human doc — when to pick this chainstop_on_error: true # default truesteps:
- id: scan # optional; referenced by when.prior_stepskill: category/skill_id # requiredparams: {} # static; merged into execute()input_from: # param -> bindingsource_text: host.source_textmap_out: # output field -> binding (optional)sanitized_text: next.raw_textwhen: # optional — skip step if condition falseprior_step: scan # step id | 0-based index | skill_id of earlier stepfield: is_safe # dot path in that step's outputequals: true # v1: strict equality only
Bindings
Prefix
Meaning
host.<key>
host_input[key] (dot path if nested)
next.<param>
Next step execute param (via map_out queue)
prev.<field>
Previous executed step output (dot path); skipped steps do not update prev for this rule — use explicit prior_step + field in input_from when needed
Step when: (conditional skip)
Rule
Behavior
Condition true
Run step normally
Condition false
Step skipped (not an error); recorded in ChainResult.steps[].status = "skipped"
prior_step missing / forward ref
validate fails; run fails closed
field missing in prior output
Treat as false → skip (document); warn in validate if manifest lacks field
Chain outcome
ok all executed steps succeeded; partial if any skipped and none failed; failed if any executed step failed and stop_on_error
Dry-run: resolve when against mock or prior dry-run outputs; mark steps would_skip.
Validation (validate_chain / chain validate)
Skills resolve via discovery (--strict → error; default → warn for missing optional deps).
Required host.* keys aggregated for chain show.
map_out keys checked against manifest outputs when present (warn, not fail, if absent).
when.prior_step must reference an earlier step.
No circular next/prev maps.
Example chains (.skillware.yaml.example)
1. sanitize_input (with conditional rewriter)
sanitize_input:
description: Scan untrusted text; compress only if safe.when: Untrusted text is about to enter model context.steps:
- id: scanskill: security/prompt_injection_firewallparams:
sensitivity: balancedinput_mode: autoinput_from:
source_text: host.source_textmap_out:
sanitized_text: next.raw_text
- skill: optimization/prompt_rewriterwhen:
prior_step: scanfield: is_safeequals: trueparams:
compression_aggression: medium
If unsafe, step 2 skipped; ChainResult.final = firewall output; status = partial.
2. preflight_untrusted_html
preflight_untrusted_html:
description: HTML-mode injection scan before parsing or summarization.when: Raw HTML or rich markup from the web enters the agent.steps:
- skill: security/prompt_injection_firewallparams:
input_mode: htmlsensitivity: balancedinput_from:
source_text: host.source_text
3. scan_then_gate
scan_then_gate:
description: Sanitize untrusted text, then evaluate token budget gate.when: Autonomous loop with untrusted ingest and a token ceiling.steps:
- id: scanskill: security/prompt_injection_firewallparams:
sensitivity: balancedinput_mode: autoinput_from:
source_text: host.source_text
- skill: monitoring/token_limiterparams:
action: checkinput_from:
task_id: host.task_idcurrent_token_count: host.current_token_countmax_allowed_tokens: host.max_allowed_tokens
Step 2 uses host metrics; firewall still runs as preflight.
CLI
Command
Purpose
skillware chain list
Names, description, when, step count
skillware chain show <name>
Steps, bindings, required host_input, step when
skillware chain validate [name]
CI-safe; exit 1 on errors
skillware chain run <name>
Execute via run_chain()
skillware chain dry-run <name>
Resolve params + evaluate when; no execute()
skillware chain run sanitize_input --var source_text="hello"
skillware chain run sanitize_input --var source_text=@file.html --json
skillware chain run scan_then_gate \
--var source_text=@in.txt --var task_id=job-1 \
--var current_token_count=12000 --var max_allowed_tokens=32000
Command
Purpose
skillware context show
Brief registry (filters mirror SkillContext)
skillware context show --skill ID --categories a,b --roots bundled --mode brief
skillware context export -o ctx.md
Debug export
New subcommands only — do not change existing command help or defaults.
Feature Description
Skillware today excels at one skill, one tool, one
execute(). Hosts with many skills or fixed middleware order reinvent discovery merge, Directive budgeting, and sequentialexecute()loops.Add (opt-in only — nothing changes until callers use the new APIs):
ctx = SkillContext()run_chain("sanitize_input", host_input={...})chains:in merged YAMLskillware context showskillware chain list | show | validate | run | dry-runCross-skill orchestration vocabulary: chain / chains / chaining only. Do not add framework
run_pipeline/pipelines:(reserved for in-skill actions e.g.uk_companies_house_handler, EVM config, unrelated domains).Skills still never call each other.
Backward compatibility (non-negotiable)
This release is strictly additive. Existing installs and scripts must behave identically unless they opt into new APIs.
SkillLoader.load_skill()unchangedexecute_moduleflagskillware listunchangedlist,doctor,test,config,paths,mail, … — newcontextandchainsubcommands onlychains:config.chainsis{}chains:placeholderchains: { default: [] }from early example — ignored safely (no steps key); config still loads; optional one-line warningskillware listconfig.extrafor other keystheme, custom keys — still merged as today; onlychainsmoves fromextra→ parsed field (likemail)import skillware/from skillware import SkillContextdoes not scan disk or run chainsrun_chain(),skillware chain run, or explicit host codepytest tests/passes; only extend tests where asserting newconfig.chainsshapeRegression gate before merge:
pytest tests/+black --check+flake8on touched modules.Locked decisions
from skillware import SkillContext→ctx = SkillContext()briefdefault;execute()auto-prepares;prepare()for tool-select disclosurerun_chain(),ChainResult,validate_chain(),load_chain(),list_chains()chains:first-class inSkillwareConfig; global + project merge; project wins on name clashchain add); YAML source of truthwhen:on step (see schema + example 4)Config —
chains:storage~/.config/skillware/config.yaml.skillware.yaml(walk-up)SkillwareConfig.chains: Dict[str, ChainDefinition](default{})."chains"to_KNOWN_TOP_LEVEL_KEYS(removed from opaqueextrawhen parsed, same pattern asmail).Chain YAML schema
Bindings
host.<key>host_input[key](dot path if nested)next.<param>map_outqueue)prev.<field>prevfor this rule — use explicitprior_step+ field ininput_fromwhen neededStep
when:(conditional skip)ChainResult.steps[].status = "skipped"prior_stepmissing / forward refvalidatefails;runfails closedfieldmissing in prior outputokall executed steps succeeded;partialif any skipped and none failed;failedif any executed step failed andstop_on_errorDry-run: resolve
whenagainst mock or prior dry-run outputs; mark stepswould_skip.Validation (
validate_chain/chain validate)--strict→ error; default → warn for missing optional deps).host.*keys aggregated forchain show.map_outkeys checked against manifestoutputswhen present (warn, not fail, if absent).when.prior_stepmust reference an earlier step.next/prevmaps.Example chains (
.skillware.yaml.example)1.
sanitize_input(with conditional rewriter)If unsafe, step 2 skipped;
ChainResult.final= firewall output;status=partial.2.
preflight_untrusted_html3.
scan_then_gateStep 2 uses host metrics; firewall still runs as preflight.
CLI
skillware chain listskillware chain show <name>host_input, stepwhenskillware chain validate [name]skillware chain run <name>run_chain()skillware chain dry-run <name>when; noexecute()skillware context showskillware context show --skill ID --categories a,b --roots bundled --mode briefskillware context export -o ctx.mdNew subcommands only — do not change existing command help or defaults.
Python API
SkillContext (wraps loader; does not replace it)
Internally uses
SkillLoader.load_skill(..., execute_module=False)for brief assembly; full load on prepare/execute only.Chains
ChainResult (stable public shape)
Rationale
equalsonly).Affected paths (optional)
skillware/context.py,skillware/chains.py,skillware/core/config.py,skillware/__init__.py,skillware/cli.py,.skillware.yaml.example,tests/test_skill_context.py,tests/test_chains.py,tests/test_config.py,tests/test_cli.py,docs/usage/skill_chaining.md,docs/usage/cli.md,CHANGELOG.mdImplementation Idea
Core (new modules — no breaking edits to loader public API)
skillware/context.py—SkillContext, filters, modes, prepare/execute, adapter delegationskillware/chains.py— parse, bind,whenevaluator, run/validate/dry-runskillware/core/config.py— parsechains, merge layers, legacy-safeskillware/__init__.py— exportSkillContextonly (chains fromskillware.chains).skillware.yaml.example— uncommentchains:with all 3 examplesCLI
contextandchainsubparsers (additive)config show— show parsed chain names count (keep reserved-section note for empty/legacy)Tests
tests/test_skill_context.py— filters, modes, prepare/execute, adapter parity with loadertests/test_chains.py— all 3 chains; when skip + when run; nested host_input; stop_on_error; dry-runtests/test_config.py— merge, project override, legacydefault: []still loadstests/test_cli.py— chain validate/run/dry-run smokepytest tests/regressionDocs
docs/usage/skill_chaining.md([Docs]: add dedicated skill chaining guide under docs/usage #297)docs/usage/cli.md,docs/usage/agent_loops.md, CHANGELOG[Unreleased]AddedAcceptance criteria
SkillContext()+ all discovery filters workchains:global + project merge; legacy placeholder safesanitize_inputskips rewriter whenis_safeis false; runs when truepreflight_untrusted_html,scan_then_gatepass API + CLI + testswhen:validated and visible inshow/dry-runchain validateCI-ready (exit 1 on structural errors)pipelinevocabulary for cross-skill chainingExplicitly out of scope (future issues)
skillware chain add(write YAML)whenoperators beyondequalsin,gt— laterrun_chainchainsparse hereTest plan
pytest tests/test_skill_context.py tests/test_chains.py tests/test_config.py tests/test_cli.pypytest tests/— full regressionchains: {default: []}; project overrides global chain name; when-skip partial; when-run ok; validate exit codeblack --check/flake8on new/changed PythonPR checklist
skill.pybehaviorSkillLoaderpublic methods unchanged (context/chains call in, not refactor)execute_module=Falsepath documented for brief mode