Port the sssf skill from ~/.agents/skills/sssf into this repo so it can be distributed and installed with the skills CLI (skills add INDigitalStudio/skills --skill sssf). - Copy the skill (SKILL.md, cookbooks, references, scripts, templates, and the visualizer app source) into sssf/. - Gitignore build/runtime artifacts: the visualizer's node_modules/ and dist/, Python bytecode, and the machine-specific repos.json. - Make the skill location-independent: install.py now stamps the skill's real path into the stamped justfile's skill_dir (replacing the hardcoded ~/.agents/skills/sssf), so 'just obs' finds the visualizer wherever the CLI installed the skill. - Update cookbooks to use <skill>/scripts/... instead of the hardcoded path, and document the skills CLI install command. - Update the repo README with install instructions.
8.9 KiB
Create ADW
Compose a new ADW script — a thin, deterministic Python workflow over agents already in the config. Design the chain first, then generate or hand-write it.
Step 1 — Design the chain
Answer four questions, in order:
- What agents, in what order? Pick from the roster (
adws/adw_sssf_config/sssf.config.yaml). The starter six cover most chains:
| Agent | Use when | Output type | Typical gates |
|---|---|---|---|
scout |
you need to FIND something first — read-only recon | ScoutOutput |
artifacts_exist |
planner |
the work needs a plan before code changes | PlanOutput |
artifacts_exist, files_non_empty |
builder |
code must change | BuildOutput |
diff_matches_claims |
reviewer |
the change must be confirmed to BE what was asked for | ReviewOutput |
artifacts_exist, verdict_consistent |
| (no tester) | verifying that it RUNS is a kind="code" phase over quality.py, not an agent |
QualityResult → as_envelope |
the exit code is the check |
documenter |
finished work needs a write-up (runs after a build, off the diff) | DocumentOutput |
artifacts_exist, files_non_empty |
| any agent, generic ask | one-off prompt, no special shape | GenericOutput |
as needed |
A new kind of agent needs a config entry + prompt pair + output type first — see update_config.md.
The suite and the reviewer answer different questions. "Does it run" is a test, and code can ask that. "Is this the thing that was asked for" is a review, and only an agent can. A green suite over a feature nobody requested is still a failed request, and neither one covers for the other.
-
Where does code act? Git branch/commit, migrations, deploys each get their own
kind="code"phase — never buried inside an agent phase.Running the suite is one of these — there is no tester agent. The command is written down in
quality.py, so akind="code"phase runs it (quality.run_tests(run)→quality.as_envelope(result, "tests")back into the builder) and the bounded repair loop is unchanged. An agent rediscoveringbun teston every run buys nothing a subprocess does not already know. Capturing what changed is one of these:changes.capture(run, ChangeCapture(base="main"))diffs the working tree against a resolved base, writescontext_handoff/changes.diff, andchanges.as_envelope(...)hands it to the next agent. A diff is two git commands, not a judgement call. -
Does anything loop? Test-fix cycles are bounded fix loops (see
update_adw.md), not phase retries. -
What does each call need to prove? Pick gates per call from
gates.py:artifacts_exist,files_non_empty,json_parses,diff_matches_claims,tests_pass("cmd")— or an inline one-off.
Step 2 — Ownership rules (the swim lanes depend on these)
kind="agent"→ownerMUST be an agent name from the config — it selects the harness (model, thinking, tools, prompts) AND the lane.ph.call()runs whoever owns the phase.kind="engineer"→owner=run.engineer. Every ADW opens with the engineer request phase — it is the system input record.kind="code"→owneris a short actor label ("git","db"); all code phases share the code lane.- Phase
namemust be unique within the run (plan,build,test_1,fix_1, …) — the UI keys blocks on it. descriptionis required and must earn its place. The name identifies the phase; the description explains it — what this phase does and why, in one sentence. It rides thephase_startevent and is the only line of intent the trace, the console, and the phase block ever show.PhaseParamsraises at construction on a blank description or one that merely restates the name (commit_plan: "Commit the plan"), so the rule fails before the phase opens rather than leaving an unreadable run in the db. Write"Put the spec on record before any code exists to blur it"instead.retries=Non an agent phase = extra gate-correction rounds re-sent into the same session (pi's--session-idcreates-or-continues, so context stays intact). Code-phase re-execution is not implemented in v1.
Step 3 — Generate or write it
uv run <skill>/scripts/make_adw.py --name review_docs --agents scout,builder
<skill> is the directory this skill was installed into (e.g. ~/.agents/skills/sssf or a repo's .claude/skills/sssf).
Writes adws/adw_review_docs.py: one agent phase per name, chained by previous=, starter agents mapped to their output types, unknown agents to GenericOutput. It does NOT create config entries or prompt files — do that first (update_config.md), or agents.validate() will stop the run and tell you what's missing.
The canonical skeleton
Every adw_*.py, generated or hand-written, is a uv single-file script with this shape:
#!/usr/bin/env -S uv run
# /// script
# dependencies = ["pydantic", "python-dotenv", "pyyaml", "rich"]
# ///
"""ADW Plan Build — plan the request, then implement the plan."""
import argparse
import sys
from adw_modules import agents, gates, git_helper, session, utils
from adw_modules.data_types import AgentCall, BuildOutput, PhaseParams, PlanOutput
REQUIRED_AGENTS = ["planner", "builder"] # names, never models
def main(prompt: str, config: str = "adws/adw_sssf_config/sssf.config.yaml", adw_id: str | None = None) -> int:
cfg = agents.load_config(config) # 1. point to config
agents.validate(cfg, REQUIRED_AGENTS) # 2. fail fast — nothing spawns on a half-valid config
run = session.ensure(cfg, adw_id) # 3. pin-or-create the session → the Run object
with run.phase(PhaseParams(name="request", kind="engineer", owner=run.engineer,
description="Capture the incoming ask")) as ph:
ph.log(input=prompt)
with run.phase(PhaseParams(name="plan", kind="agent", owner="planner",
description="Turn the request into an implementable plan")) as ph:
plan = ph.call(AgentCall(output_type=PlanOutput, prompt=prompt,
gates=[gates.artifacts_exist, gates.files_non_empty]))
with run.phase(PhaseParams(name="build", kind="agent", owner="builder", retries=1,
description="Implement the plan exactly")) as ph:
build = ph.call(AgentCall(output_type=BuildOutput, prompt=prompt, previous=plan,
gates=[gates.diff_matches_claims]))
with run.phase(PhaseParams(name="commit", kind="code", owner="git",
description="Commit the working tree")) as ph:
message = build.commit_message or f"sssf({run.adw_id}): {build.summary}"
ph.log(sha=git_helper.commit_all(message), message=message)
return run.finish()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("prompt", help="inline text or a path to a prompt file")
parser.add_argument("--config", default="adws/adw_sssf_config/sssf.config.yaml")
parser.add_argument("--adw-id", default=None, help="join or pin an existing session")
args = parser.parse_args()
sys.exit(main(utils.resolve_prompt(args.prompt), args.config, args.adw_id))
Non-negotiables
REQUIRED_AGENTS+agents.validate()— declare every agent name the script uses and validate before the first phase.- Every agent call declares a concrete output type from
data_types.py. No untyped handoffs. previous=carries the chain — the upstream envelope lands in the next agent'suser.mdas{{previous_envelope}}; bulky context moves throughcontext_handoff/files the envelope references.- The engineer request phase comes first, always.
- Four-param rule —
run.phase()andph.call()each take exactly one object; new helpers with >4 params get a data type. - Stay thin — sequencing and acceptance only; real logic goes in
adw_modules/(update_modules.md). - Committing is a code phase, and it needs a fallback.
PlanOutput,BuildOutput, andDocumentOutputeach carry acommit_messagethe agent writes for its own work product — the spec, the code, the write-up. It defaults to empty, so alwaysenvelope.commit_message or <fallback>, and commit each product with the message of the agent that made it (adw_simple_sdlc.pycommits three times and never crosses them).git_helper.commit_all(message)stages everything, commits, and returns the short sha; it raises a clear error when the cwd isn't a git repo or nothing changed, and that raise fails the phase.
Before you ship it
uv run adws/adw_<name>.py "a tiny real request"— watch it go green end to end.- Check the trace:
sqlite3 adws/adw_data/sssf.db "select seq,name,kind,owner,status from phases where adw_id='<id>' order by seq;" - Read the final
envelope.json— is the output type earning its fields, or should it be sharper?