Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .agents/skills/verify-bercail/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
name: verify-bercail
description: >-
Drives this checkout's bercail CLI the way a user does, in a throwaway HOME:
deploy, doctor, desk restore --dry-run, handoff-spawn --info, bstack status.
Leaves evidence under .bercail/verify/. Use at /ready, or to prove a change to
the CLI, the deploy, handoff, desk restore, or bstack.
---

# verify-bercail

One command runs the whole proof, in about a minute and a half:

```bash
.agents/skills/verify-bercail/run.sh
```

It exits 0 when every step and check passed, and prints the evidence folder.

## Launch

`run.sh` makes a temp directory and points `HOME`, the `XDG_*` dirs and
`INSTALL_SRC` at it, with this checkout as the source. It clears the desk's
`HC_*` and `OPENCODE_CONFIG_DIR` variables, writes a `config.toml` with bstack on
and one pinned model, and runs `bercail reconfigure` to deploy into that HOME.

`hc` is the one stand-in: a script that answers for one live desk at a bare
shell and logs every call. The real `hc` would start or reach a Herdr core.
`opencode` is the real binary, with no plan connected.

## Doctor

`bercail doctor` on that HOME. Exit 1 is expected, because the temp HOME has no
Herdr layout or shell block. Pass means it reached its bstack section. Exit 2 or
higher is a crash.

## Drive

| Step | Proves |
|------|--------|
| `bercail desk restore --dry-run` on a seeded record | says `would resume`, sends no `hc send`, leaves the record unchanged |
| `handoff-spawn --info` | prints JSON |
| `bercail bstack status` | says `bstack: on` |
| deployed files | `ready.md` has no council step, `/council` is there, no `bstack-council-*` seats |
| `council = true`, then `bercail reconfigure` | a `bstack-council-claude` seat appears |

## Evidence

`.bercail/verify/<timestamp>/` (gitignored):

- `summary.tsv`: step, exit code, command.
- `checks.tsv`: `ok` or `FAIL` for each claim about the state the run left.
- `<step>.out`: each step's output.
- `hc-calls.log`: every call to the stand-in.
- `config.toml`: the config the run ended with.

Quote `summary.tsv`'s exit codes and the folder path in the `/ready` report.

## Cleanup

The script's trap removes the temp directory, on failure too. It starts no
daemons, and evidence stays. Prune old `.bercail/verify/` folders by hand.

## Not proven here

A real Herdr core, a real resume typed into a desk, and connected plans. For
those, run `bercail desk restore --dry-run` and `bercail doctor` on the real HOME.
116 changes: 116 additions & 0 deletions .agents/skills/verify-bercail/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# Drive this checkout's bercail in a throwaway HOME. See SKILL.md.
# usage: run.sh [evidence-dir] default: <repo>/.bercail/verify/<timestamp>
set -uo pipefail

repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
evidence="${1:-$repo/.bercail/verify/$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$evidence"
evidence="$(cd "$evidence" && pwd)"
real_opencode="$(mise which opencode 2>/dev/null || command -v opencode || true)"

# --- launch: nothing below can reach the real HOME or a real Herdr core ---
box="$(mktemp -d)"
trap 'rm -rf "$box"' EXIT
unset BASH_ENV HC_TERMINAL_ID HC_SESSION HC_SCOPE HC_TERMINAL_SOCKET OPENCODE_CONFIG_DIR
export __MISE_BASH_ENV_LOADED=1
export HOME="$box/home" XDG_CONFIG_HOME="$box/home/.config" XDG_DATA_HOME="$box/home/.local/share" XDG_CACHE_HOME="$box/home/.cache"
export XDG_STATE_HOME="$box/state" XDG_RUNTIME_DIR="$box/run" INSTALL_SRC="$repo"
export HC_BIN="$box/bin/hc" HC_CALLS="$evidence/hc-calls.log"
export PATH="$box/bin:$PATH"
mkdir -p "$HOME" "$XDG_RUNTIME_DIR" "$box/bin" "$XDG_STATE_HOME/bercail/desk-sessions" "$XDG_CONFIG_HOME/bercail"
: >"$HC_CALLS"
[[ -z "$real_opencode" ]] || ln -s "$real_opencode" "$box/bin/opencode"

# The one stand-in: hc, one live desk at a bare shell. Every call is logged.
cat >"$box/bin/hc" <<'HC'
#!/usr/bin/env bash
printf '%s\n' "$*" >>"$HC_CALLS"
case "$1" in
ls) printf '{"terminals":[{"address":"t1","state":"live"}]}\n' ;;
get) printf '%s\n' '{"state":"live","reason":null,"reported":false,"pid":7,"argv":["/usr/bin/bash"],"foreground":{"name":"bash","pid":7},"labels":{"bercail.role":"agent","bercail.space":"sp1"},"records":[]}' ;;
send) exit 0 ;;
*) exit 1 ;;
esac
HC
chmod +x "$box/bin/hc"

cat >"$XDG_CONFIG_HOME/bercail/config.toml" <<'TOML'
[agent]
command = "opencode"

[bstack]
enabled = true
claude = "claude-agent/opus#high"
TOML

bercail="$repo/bin/bercail"
failed=0
# step NAME MAX_EXIT CMD...: output to NAME.out, exit code to the summary.
step() {
local name="$1" max="$2" rc=0
shift 2
"$@" >"$evidence/$name.out" 2>&1 </dev/null || rc=$?
printf '%s\t%s\t%s\n' "$name" "$rc" "$*" >>"$evidence/summary.tsv"
if ((rc > max)); then
printf 'FAIL %s: exit %s (see %s/%s.out)\n' "$name" "$rc" "$evidence" "$name" >&2
failed=1
fi
}
# check NAME COMMAND...: a claim about the state the run left.
check() {
local name="$1"
shift
if "$@" >/dev/null; then
printf 'ok\t%s\n' "$name" >>"$evidence/checks.tsv"
else
printf 'FAIL\t%s\n' "$name" >>"$evidence/checks.tsv"
printf 'FAIL %s\n' "$name" >&2
failed=1
fi
}
: >"$evidence/summary.tsv"
: >"$evidence/checks.tsv"

oc="$XDG_CONFIG_HOME/opencode"
no_seats() { ! compgen -G "$oc/agents/bstack-council-*.md" >/dev/null; }
out_has() { grep -qF -- "$2" "$evidence/$1.out"; }
absent() { [[ -f "$1" ]] && ! grep -qE -- "$2" "$1"; }

# --- doctor: this HOME has no desk stack, so 1 is expected; a crash is not ---
step reconfigure 0 "$bercail" reconfigure
step doctor 1 "$bercail" doctor
check "doctor reached its bstack section" out_has doctor "bstack subscription"

# --- drive ---
record="$XDG_STATE_HOME/bercail/desk-sessions/t1.json"
# Built at run time: a session id on a command line would read as already open.
jq -n --arg s "ses_verify_$(date +%s%N)" --arg c "$repo" \
'{address: "t1", session: $s, cwd: $c, harness: "opencode", issue: "verify-1", space: "sp1", args: []}' >"$record"
before="$(sha256sum "$record")"
step desk-restore-dry-run 0 "$bercail" desk restore --dry-run
check "dry run would resume the desk" out_has desk-restore-dry-run "would resume"
check "dry run typed nothing" absent "$HC_CALLS" '^send'
check "dry run left the record alone" test "$before" = "$(sha256sum "$record")"

step handoff-info 0 "$repo/skills/handoff/scripts/handoff-spawn" --info
check "handoff --info is JSON" jq -e 'type == "object"' "$evidence/handoff-info.out"

step bstack-status 0 "$bercail" bstack status
check "bstack status is on" out_has bstack-status "bstack: on"
check "ready.md deployed with no council step" absent "$oc/commands/ready.md" '[Cc]ouncil'
check "council command still deployed" test -f "$oc/commands/council.md"
check "no council seats by default" no_seats

printf 'council = true\n' >>"$XDG_CONFIG_HOME/bercail/config.toml"
step reconfigure-council 0 "$bercail" reconfigure
check "council = true renders a seat" test -f "$oc/agents/bstack-council-claude.md"

cp "$XDG_CONFIG_HOME/bercail/config.toml" "$evidence/config.toml"

# --- result ---
if ((failed)); then
printf 'verify-bercail FAILED; evidence: %s\n' "$evidence"
exit 1
fi
printf 'verify-bercail passed; evidence: %s\n' "$evidence"
19 changes: 10 additions & 9 deletions bstack/agents/bstack-council.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
# managed by bercail (bstack). `bercail update` rewrites this file; delete this line to keep your own.
description: Read-only council reviewer on @family@. Tries to break a change and reports only findings it can show. Launch through the council skill, one per provider, in parallel.
description: Read-only council reviewer on @family@. Tries to break a change and reports only findings it proves with a run. Launch through the council skill, one per provider, in parallel, only when /council is asked for.
mode: subagent
model: @model@
color: "#f5a524"
Expand All @@ -21,13 +21,14 @@ permissions:
You are one reviewer on a council of models from different providers. Your job
is to find what is wrong with the change in your brief, not to praise it.

- Read the diff and the code around it. Run the tests or a small repro when a
finding depends on behavior. Do not edit files; scratch files go in /tmp.
- Read the diff and the code around it. Do not edit files; scratch files go in /tmp.
- Look for: wrong behavior, missed cases, broken callers, races, security holes,
data loss, and tests that pass for the wrong reason. Skip style unless it hides
a bug.
data loss, and tests that pass for the wrong reason.
- Prove each finding with a run: a failing test, a repro, or command output. The
lead reruns your command, and a finding that does not reproduce is dropped.
- Leave out anything you could not show with a run, and all style, naming, and
YAGNI comments.
- Report each finding as: severity (blocker, major, minor), `file:line`, what
breaks, the input or steps that show it, and how you checked (ran it, or read
it). Mark a finding you could not check as `unverified`.
- If you find nothing that breaks, say `no findings` and list what you checked.
Never invent a finding to have one.
breaks, the exact command, and what it printed.
- If nothing is proven, say `no findings` and list what you ran. Never invent a
finding to have one.
2 changes: 1 addition & 1 deletion bstack/agents/bstack-judge.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
# managed by bercail (bstack). `bercail update` rewrites this file; delete this line to keep your own.
description: The strongest reasoner. Use for the hardest changes, design decisions, plans, prose, and for settling a disagreement between reviewers.
description: The strongest reasoner. Use for the hardest changes, design decisions, plans, prose, and the intent check at /ready: does the diff match the issue.
mode: subagent
model: @judge@
color: "#a78bfa"
Expand Down
2 changes: 1 addition & 1 deletion bstack/agents/bstack.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
# managed by bercail (bstack). `bercail update` rewrites this file; delete this line to keep your own.
description: bercail's lead agent. Plans, routes work to the builder, judge, and council by model strength, and proves the result.
description: bercail's lead agent. Plans, routes work to the builder and judge by model strength, and proves the result.
mode: primary
model: @lead@
color: "#d97757"
Expand Down
2 changes: 1 addition & 1 deletion bstack/commands/council.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
# managed by bercail (bstack). `bercail update` rewrites this file; delete this line to keep your own.
description: Review the current change with one reviewer per provider (Claude, GPT, Grok) and keep only findings that hold up
description: Adversarial review on request. Reviewers must prove each finding with a run, and only findings a rerun confirms are reported
---
Load the `council` skill and follow it. Review target: $ARGUMENTS

Expand Down
18 changes: 12 additions & 6 deletions bstack/commands/ready.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
---
# managed by bercail (bstack). `bercail update` rewrites this file; delete this line to keep your own.
description: Finish the current work for review - prove it, run the council, stage it, and report
description: Finish the current work for review - prove it with tests, the repo's verify skill and formal checks, stage it, and report
---
Get the current work ready for my review in tuicr. $ARGUMENTS

!`git status --short 2>&1 | head -50`

Verify skill: !`ls "$(git rev-parse --show-toplevel 2>/dev/null)"/.agents/skills/verify-*/SKILL.md 2>/dev/null || echo none`

Fix what fails at each step, then rerun that step. Note every command, its exit code, and where its evidence is.

1. Re-read the job (the beads issue when there is one) and check each part of it is done.
2. Prove it on the real thing: run the tests and the command or app path the change touches. Fix what fails.
3. Run the `council` skill on the change. Fix the findings that hold up.
4. Run the `unslop` skill on the diff: debug output, dead code, narrating comments, unrelated edits.
5. Stage everything with `git add`. Do not commit or push.
6. Report in five lines or fewer: what changed, what you ran, what the council found, what is left.
2. Run the tests that cover the diff.
3. When the verify skill above is not `none`, read it and follow it from launch to cleanup.
4. Run any formal check whose coverage the diff touches: a `statechart` spec in `.bercail/statechart/` or committed in the repo. When none covers it, say so.
5. When a beads issue names the job, ask `bstack-judge` one question, giving it the issue and the diff: does the diff match the issue? It names what is missing or was never asked for, and it changes nothing.
6. Run the `unslop` skill on the diff: debug output, dead code, narrating comments, unrelated edits.
7. Stage everything with `git add`. Do not commit or push.
8. Report: what changed, each check with its exit code and evidence path, the judge's answer, what is left. In a headless handoff, also put the checks in the result file's `verification` array.
12 changes: 7 additions & 5 deletions bstack/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ that is strongest at it. These rules apply to every bstack agent and subagent.
- Load the `bstack` skill for anything beyond a one-line change. It holds the
playbooks (bug fix, feature, refactor, investigation, review, ship, handoff).
- Other bercail skills: `how` (how code works now), `why` (why it is that way,
from the record), `unslop` (clean prose and diffs), `council` (cross-provider
review), `autopilot` (drive a PR to green).
from the record), `unslop` (clean prose and diffs), `council` (adversarial
review, only when asked for), `autopilot` (drive a PR to green).

## How to work

Expand All @@ -36,8 +36,8 @@ that is strongest at it. These rules apply to every bstack agent and subagent.
| Role | Agent | Use it for |
|------|-------|------------|
| build | `bstack-builder` | Code: features, refactors, bug fixes, perf. Hand it a precise brief. |
| judge | `bstack-judge` | The hardest design calls, prose, plans, and settling disagreements. |
| council | `bstack-council-*` | Read-only review, one per connected provider. Run them through the `council` skill. |
| judge | `bstack-judge` | The hardest design calls, prose, plans, and the intent check at `/ready`. |
| council | `bstack-council-*` | Read-only adversarial review, when `[bstack] council = true`. Run them through the `council` skill, only when `/council` is asked for. |

- Briefs are self-contained: the goal, the files, the constraint, what done looks
like, and how to prove it. A subagent has none of your context.
Expand All @@ -56,4 +56,6 @@ that is strongest at it. These rules apply to every bstack agent and subagent.
No inventory of every file. When a beads issue names the job, also post that
report on it (`bd comment <id>`), so it outlives the desk's screen.
- In a headless handoff, write the result file the issue comment names
(`status` ready|blocked|failed, `summary`). `blocked` with a reason beats a guess.
(`status` ready|blocked|failed, `summary`, and optionally `verification`: a list
of `{kind: test|verify-skill|statechart, command, exit_code, evidence}`).
`blocked` with a reason beats a guess.
3 changes: 3 additions & 0 deletions config/agentic-dev/config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ effort = "high"
lead = "claude"
build = "grok"
judge = "claude"
# Render one bstack-council-<plan> reviewer per plan for /council. /council still
# works when off: the lead reviews alone.
council = false

# [graphite]
# Handoff detects Graphite by the branches gt tracks. Override it per repo, keyed
Expand Down
2 changes: 1 addition & 1 deletion docs/bstack/01-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ Each subagent and each council seat spends tokens from a plan. To spend less:

- Save the playbooks for work that needs them. A one-line fix doesn't.
- Point `build` at your cheapest plan in `[bstack]` (see [Make it yours](./08-make-it-yours.md)).
- Run `/council` once per change, at the end, not after every step.
- `/ready` does not run the council. Ask for `/council` only when a change earns it.

Next: [Work with the bstack agent](./02-the-bstack-agent.md).
6 changes: 3 additions & 3 deletions docs/bstack/02-the-bstack-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ the role that fits.
| X is broken | bug fix | a repro before a fix, the root cause |
| add or change behavior | feature | the data shape first, the real path proven |
| same behavior, better shape | refactor | behavior pinned before anything moves |
| review this | review | the council, findings checked |
| review this | review | tests and the verify skill; `/council` when you ask |
| the PR is open | ship | autopilot until green |
| several jobs at once | handoff | one beads issue per job |
| finish this branch | takeover | read the trail before touching it |
Expand All @@ -28,8 +28,8 @@ Read it once. It is short.
|------|-------|------|
| lead | `bstack` | your ask; plans, routes, checks, reports |
| build | `bstack-builder` | code from a precise brief |
| judge | `bstack-judge` | hard design calls, prose, and disagreements |
| council | `bstack-council-<plan>` | read-only review, one per plan with a model (connected, or pinned) |
| judge | `bstack-judge` | hard design calls, prose, and the intent check at `/ready` |
| council | `bstack-council-<plan>` | read-only adversarial review, one per plan with a model, when `[bstack] council = true` |
| autopilot | `bstack-autopilot` | an open PR to get green |

By default the lead and judge run on Claude and the builder on Grok. A role
Expand Down
Loading
Loading