Add sssf skill, installable via the skills CLI
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.
This commit is contained in:
parent
a42608f602
commit
2cc766aabe
98 changed files with 12508 additions and 0 deletions
161
sssf/references/handoff.md
Normal file
161
sssf/references/handoff.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# Handoff Reference
|
||||
|
||||
The envelope schema, the two-channel output contract, and the session directory layout — how context transfers in code, not in conversation.
|
||||
|
||||
## Two output channels, exactly
|
||||
|
||||
An agent may produce output in two ways and no others:
|
||||
|
||||
1. **Reference files** written into `context_handoff/` — plans, notes, artifacts for the agents that follow.
|
||||
2. **A final valid-JSON response** — the envelope, its direct response and nothing else.
|
||||
|
||||
Code does the rest: parse the response against the output type the call declared, persist it as `envelope.json`, and inject it into the next agent's user prompt.
|
||||
|
||||
## Envelope schema
|
||||
|
||||
Every output type extends `EnvelopeBase`:
|
||||
|
||||
```python
|
||||
class EnvelopeBase(BaseModel):
|
||||
status: Literal["success", "fail"] # the only required field
|
||||
summary: str = "" # one sentence: what happened
|
||||
artifacts: list[str] = [] # paths written, usually inside context_handoff/
|
||||
notes_for_next_agent: str = "" # what the next agent must know
|
||||
```
|
||||
|
||||
`status` is load-bearing: an envelope that parses but reports `status="fail"` raises, failing the phase. An agent declaring its own failure is not a successful phase.
|
||||
|
||||
The starter types in `adw_modules/data_types.py`:
|
||||
|
||||
```python
|
||||
class GenericOutput(EnvelopeBase):
|
||||
"""Fallback for an agent with no sharper contract yet."""
|
||||
|
||||
class PlanOutput(EnvelopeBase):
|
||||
commit_message: str = "" # imperative git subject for the PLAN FILE itself
|
||||
|
||||
class BuildOutput(EnvelopeBase):
|
||||
changed_files: list[str] = []
|
||||
commit_message: str = "" # consumed by the git commit phase
|
||||
|
||||
class ScoutOutput(EnvelopeBase):
|
||||
findings: list[ScoutFinding] = [] # ScoutFinding: {file: str, note: str}
|
||||
|
||||
class ReviewOutput(EnvelopeBase):
|
||||
approved: bool = False # the verdict; status is only "did the review run"
|
||||
findings: list[ReviewFinding] = [] # ReviewFinding: {requirement, met: bool, evidence}
|
||||
blocking: list[str] = [] # what must change before approval
|
||||
|
||||
class DocumentOutput(EnvelopeBase):
|
||||
document_path: str = "" # the write-up's home in the repo
|
||||
documented_files: list[str] = []
|
||||
commit_message: str = ""
|
||||
```
|
||||
|
||||
`commit_message` defaults to empty, so a git phase consuming it always needs a fallback — see `cookbooks/create_adw.md`.
|
||||
|
||||
**Each `commit_message` describes its own agent's work product, never the next one's**: `PlanOutput`'s covers the spec file, `BuildOutput`'s the code, `DocumentOutput`'s the write-up. A chain that commits once can use whichever fits; a chain that commits per step (`adw_simple_sdlc.py`) needs all three, and reusing one agent's sentence for another's diff is how a commit log starts lying.
|
||||
|
||||
There is no test output type: running the suite is a `kind="code"` phase, and its `QualityResult` reaches the next agent through `quality.as_envelope`.
|
||||
|
||||
Two of these are adapters rather than agent reports — code shaped as an envelope so an agent can be handed a deterministic result through the same door: `VerifyOutput` (a lint/test block's result) and `ChangesOutput` (a captured `git diff`, from `changes.as_envelope`). The consuming agent cannot tell the difference, which is the point.
|
||||
|
||||
The envelope is a **manifest of claims**. Gates verify those claims after the fact — declared artifacts exist and are non-empty, declared changes appear in the diff, declared tests actually pass. See `cookbooks/update_modules.md`.
|
||||
|
||||
## The typed-output rule
|
||||
|
||||
**Every agent call passes a concrete output type**, and the agent's final JSON is parsed against exactly that type. No untyped handoffs.
|
||||
|
||||
```python
|
||||
plan = ph.call(AgentCall(output_type=PlanOutput, prompt=prompt,
|
||||
gates=[gates.artifacts_exist]))
|
||||
```
|
||||
|
||||
The user prompt asks for the shape; the type enforces it. They always travel as a pair, which is what lets one agent serve many calls — same system prompt, different user prompt + output type per call site. Output types live in code, never in `sssf.config.yaml`.
|
||||
|
||||
**Parse failure is not a restart.** If the response doesn't parse or doesn't validate, the harness re-prompts the **same session** with a correction naming the required fields — bounded by `JSON_FIX_ATTEMPTS` in `agents.py` (2). Gate violations use the identical mechanism, bounded instead by the phase's `retries`. A cold restart would throw away the context that produced the near-miss.
|
||||
|
||||
In v1 there is no separate continue call to make: `agent_pi.run()` passes `--session-id`, which pi treats as create-or-continue, so running an agent and continuing it are the same call with the same id. Before parsing, the harness also tolerates a fenced `json` code block or prose wrapped around the object — but the prompt still asks for bare JSON, and every failed attempt is persisted as an invalid envelope row.
|
||||
|
||||
## Injecting the previous envelope
|
||||
|
||||
`prompts.py` renders the agent's `user.md`, substituting:
|
||||
|
||||
| Placeholder | Value |
|
||||
|---|---|
|
||||
| `{{prompt}}` | the engineer's ask (or the ADW's per-call prompt) |
|
||||
| `{{previous_envelope}}` | the upstream envelope JSON, from `AgentCall(previous=...)` |
|
||||
| `{{context_handoff_dir}}` | absolute path to this session's `context_handoff/` |
|
||||
|
||||
A `user.md` declares one h3 per incoming datum, then the task, then the output contract:
|
||||
|
||||
````markdown
|
||||
# Scout Task
|
||||
|
||||
## Variables
|
||||
|
||||
### prompt
|
||||
|
||||
{{prompt}}
|
||||
|
||||
### previous_envelope
|
||||
|
||||
{{previous_envelope}}
|
||||
|
||||
### context_handoff_dir
|
||||
|
||||
{{context_handoff_dir}}
|
||||
|
||||
## Task
|
||||
|
||||
Find what `prompt` asks about. Write findings into `context_handoff_dir`, then emit your `Report` JSON.
|
||||
|
||||
## Report
|
||||
|
||||
Respond with ONLY valid JSON matching `ScoutOutput` — no prose before or after:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"summary": "<one sentence on what you found>",
|
||||
"findings": [
|
||||
{ "file": "src/server.ts", "note": "<why this file matters>" }
|
||||
],
|
||||
"artifacts": ["<context_handoff_dir>/scout_findings.md"]
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
The `## Report` section shows the exact JSON shape of the declared output type — that is the agent's output contract, and it lives in `user.md` because the shape belongs to the *use*, not the identity. The matching `system.md` stays static: Purpose + Instructions only.
|
||||
|
||||
## Session directory layout
|
||||
|
||||
```
|
||||
adws/adw_data/sessions/{adw_id}/
|
||||
├── agent_map.json agent name → coding-agent session_id + model
|
||||
├── context_handoff/ the ONE place agents write files for the agents that follow
|
||||
└── {agent_name}/
|
||||
├── prompts/ exact prompts sent (system.md + user.md), saved before execution
|
||||
├── pi_sessions/ pi's own session state for this agent
|
||||
├── raw_output.jsonl full JSONL stream from the coding agent, appended live
|
||||
└── envelope.json the final valid-JSON response — captured, validated, persisted by code
|
||||
```
|
||||
|
||||
`session.ensure(cfg, adw_id)` mints or joins the id and creates these dirs. One `context_handoff/` per session, shared by every agent — the single location for cross-agent files.
|
||||
|
||||
## agent_map.json and resuming
|
||||
|
||||
```json
|
||||
{
|
||||
"planner": {"session_id": "sssf-a1b2c3d4-planner-9f2e",
|
||||
"model": "google/gemini-3.6-flash", "coding_agent": "pi"},
|
||||
"builder": {"session_id": "sssf-a1b2c3d4-builder-71ac",
|
||||
"model": "google/gemini-3.6-flash", "coding_agent": "pi"}
|
||||
}
|
||||
```
|
||||
|
||||
This map is the key that lets a later ADW rejoin each agent's **existing context window**. Run `adw_build.py --adw-id a1b2c3d4` after `adw_plan.py` and the builder resumes its own session rather than starting cold.
|
||||
|
||||
The map records the model each session was created with. If config drift changes an agent's model, that agent starts a **fresh** session and the map is updated — never a bad resume. `agent_sessions` in `sssf.db` is the queryable mirror of this file.
|
||||
|
||||
**Files are the raw record; the db is the queryable mirror.** Losing `sssf.db` loses nothing that can't be rebuilt from `raw_output.jsonl`, `envelope.json`, and `agent_map.json`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue