Skip to content
Open
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
32 changes: 32 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(python -m pytest *)",
"Bash(python3 -m pytest *)",
"Bash(py -m pytest *)",
"Bash(python -m shop *)",
"Bash(python3 -m shop *)",
"Bash(py -m shop *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git push *)",
"Bash(gh run list *)",
"Bash(gh run view *)",
"Bash(gh run watch *)",
"Bash(gh pr view *)",
"Bash(gh pr list *)",
"Bash(gh pr checks *)",
"Bash(gh pr diff *)",
"Bash(gh pr create *)",
"Bash(gh pr comment *)",
"Bash(gh issue list *)",
"Bash(gh issue view *)",
"Bash(gh issue create *)",
"Bash(gh issue comment *)"
]
}
}
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Keep LF everywhere, so `workshop` and the scripts run the same on a Windows checkout.
* text=auto eol=lf
54 changes: 45 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ Amounts are integer cents; `python3 -m shop` prints a receipt.

Routines (`/schedule`, the optional part of exercise 5) need a subscription login;
with an API key only, you run the same prompts by hand, which the exercise expects.
- macOS or Linux. On Windows, use WSL; the commands below assume a POSIX shell.
- macOS, Linux or Windows. On Windows, use PowerShell, with
[Git for Windows](https://git-scm.com/downloads/win) installed: Claude Code runs its
hooks and shell commands through the Git Bash it brings.

**On Windows**, type `py workshop` wherever these pages say `./workshop`, and `py`
where they say `python3`. Everything else is the same: the prompts you paste into
Claude Code, `git`, `gh` and `claude` do not change.

**Not yet tested on Windows: exercise 3, run 2.** The `fairmind-coding` plugin runs
its loop gate as `python3` inside Git Bash, and a Python installed from python.org or
with `winget` is called `python` and `py` there, not `python3`. `./workshop doctor`
checks it on Windows. If it shows ✗, do run 2 alongside someone on macOS or Linux.

## Before the workshop · 20 minutes

Expand All @@ -34,18 +45,39 @@ the review secret; the GitHub App it cannot see, so check that one by hand.
in your own account. Make it **public**: branch rulesets, which exercise 5 uses,
need a public repository or a paid plan. Your exercise work will be visible.
Then clone your copy and `cd` into it.
2. **Install the tools:** [Claude Code](https://claude.com/claude-code), logged in
with your subscription or started with `export ANTHROPIC_API_KEY=<your key>`;
2. **Install the tools:** [Claude Code](https://claude.com/claude-code),
[GitHub CLI](https://cli.github.com) then `gh auth login`, `jq`, Python 3.10 or
later, and `python3 -m pip install pytest`.
3. **Install the plugin** (exercise 3). Inside Claude Code:
later, and `python3 -m pip install pytest`. On Windows, in PowerShell:

```powershell
winget install Anthropic.ClaudeCode
winget install Git.Git
winget install GitHub.cli
winget install jqlang.jq
winget install Python.Python.3.12
py -m pip install pytest # in a new terminal, after the installs
```

Log Claude Code in with your subscription, **or** give it the API key in the
terminal you will work in (every new terminal needs it again):

```bash
export ANTHROPIC_API_KEY=<your key> # macOS, Linux
$env:ANTHROPIC_API_KEY = "<your key>" # Windows PowerShell
```

3. **Trust your copy.** In the folder of your copy, run `claude` once, answer **yes**
to the question about trusting the folder, then type `/exit`. Until you do, Claude
Code ignores `.claude/settings.json`, which pre-approves the test and `git`/`gh`
commands the exercises run, and `/goal` (exercise 3) is not available.
4. **Install the plugin** (exercise 3). Inside Claude Code:

```
/plugin marketplace add FairMind-Gen-AI-Studio/fairmind-plugins-public
/plugin install fairmind-coding@fairmind-plugins
```

4. **Let Claude review your pull requests** (exercises 4 and 5). Two things, both on
5. **Let Claude review your pull requests** (exercises 4 and 5). Two things, both on
your copy:
- install the [Claude GitHub App](https://github.com/apps/claude) and give it
access to your copy: the review posts through it;
Expand All @@ -58,10 +90,10 @@ the review secret; the GitHub App it cannot see, so check that one by hand.
gh secret set CLAUDE_CODE_OAUTH_TOKEN # paste it when asked
```

5. **Check, then set up:**
6. **Check, then set up:**

```bash
./workshop doctor # every line should be ✓
./workshop doctor # every line should be ✓ (Windows: py workshop doctor)
./workshop setup # labels, CODEOWNERS, the main ruleset
```

Expand Down Expand Up @@ -89,15 +121,19 @@ your own `main`, so the pull requests you open go to your copy.
| `.github/workflows/review.yml` | Claude reviews every pull request, then labels it `reviewed` |
| `.github/ISSUE_TEMPLATE/defect.yml` | a defect with a stable key and a Reproduce block |
| `.github/CODEOWNERS`, `.github/rulesets/main.json` | the paths that stay human, and what GitHub enforces |
| `.claude/settings.json` | the commands the exercises may run without asking: tests, `git`, and the `gh` verbs that read or comment. Merging and raw API calls still ask |
| `.claude/skills/review-loop/` | the skill you write in exercise 4 |
| `scripts/fake_deploy.py` | the fake production log for exercise 2 |
| `workshop` | `doctor`, `setup`, `reset`, `deploy` |
| `workshop` | `doctor`, `setup`, `reset`, `events`, `hook`, `deploy` |

## If something goes wrong

| Symptom | Cause and fix |
|---|---|
| `./workshop doctor` shows ✗ | the line after the arrow says what to install or run |
| Claude asks permission for every test or `git` command | the folder is not trusted: run `claude` there once and answer yes |
| `./workshop` does nothing on Windows | type `py workshop …` instead |
| `claude` says Git Bash was not found (Windows) | install Git for Windows, then open a new terminal |
| `reset` says it could not start an exercise | your `main` has diverged from the template's; ask the facilitator |
| the review check is green and nothing was posted | it ran for ~15 s: the workflow file differs from `main`'s, or the app is not installed on your copy |
| the review check is red: no review credential | `gh secret set ANTHROPIC_API_KEY` on your copy (or the subscription token) |
Expand Down
100 changes: 79 additions & 21 deletions kit/ex1.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,100 @@ One turn of Claude Code is many passes: the model asks for a tool, the harness r
it, the result goes back in, until the model answers without asking for a tool.
Here you count those passes, then make the harness record them for you.

> **Commands** go in your terminal; on Windows type `py workshop` wherever this page
> says `./workshop`. **Prompts** go inside Claude Code and are the same everywhere.

## The task

`python3 -m shop` prints a receipt with amounts like `24.5 EUR`. Money should always
show two decimals: `24.50 EUR`.

## Steps

1. **Run the task headless, streaming every event to a file.**
### 1 · Start clean

```bash
./workshop reset ex1
```

**Expect:** `on branch ex1-work, starting point of ex1`.

### 2 · Run the task headless, streaming every event to a file

One line: copy all of it.

```bash
claude -p "Receipts must always show two decimals, like 24.50 EUR. Fix it and add a test." --output-format stream-json --verbose --permission-mode acceptEdits > run.jsonl
```

It prints nothing for a minute or two: every event goes into `run.jsonl`.
`--verbose` is required, because in print mode stream-json refuses without it.
`--permission-mode acceptEdits` lets it edit files; the test commands it runs are
pre-approved in `.claude/settings.json`.

### 3 · Count the tool calls, and the passes

```bash
./workshop events run.jsonl
```

**Expect:** a numbered list of tool calls (`Bash`, `Read`, `Edit`…), the number of
turns the run reports, and the last message Claude wrote.

### 4 · Make the harness log every tool call

```bash
./workshop hook
```

It writes `.claude/settings.local.json` and prints it. **Read it before going on:** a
`PostToolUse` hook with matcher `*` runs a command after every tool call. The hook gets
the event as JSON on its stdin; the command appends the event's `tool_name` to
`tool-log.txt`. The file is gitignored and `./workshop reset` removes it.

Put the code back the way it was, so Claude has the same work to do again:

```bash
git stash -u
```

Run the step 2 command again, then compare what the hook saw with what the stream
recorded:

```bash
./workshop events run.jsonl
```

Open `tool-log.txt` in your editor, next to that output.

```bash
claude -p "Receipts must always show two decimals, like 24.50 EUR. Fix it and add a test." \
--output-format stream-json --verbose > run.jsonl
```
**Expect:** the same tools in the same order, **or fewer**. The hook fires on
successful calls only; a call that fails fires `PostToolUseFailure`, which this hook
does not listen to.

`--verbose` is required: in print mode, stream-json refuses without it.
### 5 · Find the moment it decided to stop

2. **Count the tool calls, and the passes.**
```bash
claude
```

```bash
jq -r 'select(.type=="assistant") | .message.content[] | select(.type=="tool_use") | .name' run.jsonl
jq 'select(.type=="result") | .num_turns' run.jsonl
```
```text
Open run.jsonl. It is the event stream of a headless Claude Code run. Find the last
assistant message that contains no tool_use block and quote it. Then tell me what
decided that this was the end of the turn: a test, a hook, a limit, or the model
itself?
```

3. **Make the harness log every tool call.** Create `.claude/settings.json`:
## Variant · make the hook catch the failures too

```json
{"hooks": {"PostToolUse": [{"matcher": "*", "hooks": [
{"type": "command", "command": "jq -r '.tool_name' >> tool-log.txt"}]}]}}
```
In the same session:

Undo the fix (`git checkout shop/`), run step 1 again, and read `tool-log.txt`.
The hook gets each event as JSON on stdin. It fires on successful calls only;
failed ones fire `PostToolUseFailure`.
```text
Add a PostToolUseFailure hook to .claude/settings.local.json that runs exactly the
same command as the PostToolUse one. Then tell me why PostToolUse alone missed the
failed calls.
```

4. **Find the moment it decided to stop.** The last assistant message with no tool
call. Who chose it?
Run `git stash -u` and the step 2 command again, and read `tool-log.txt`.

## Watch for

Expand Down
89 changes: 77 additions & 12 deletions kit/ex2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,103 @@
**15 minutes · ring 2, `/loop` · branch: `./workshop reset ex2`**

`/loop` repeats a prompt: on a fixed interval (`/loop 2m …`) or at a pace Claude picks
after each pass (`/loop …`, one minute to one hour). It has no exit condition of its
own. Run the two loops below **at the same time, in two terminals**: each one spends
after each pass (`/loop …`, one minute to one hour). A loop on a fixed interval never
ends by itself. A self-paced loop ends when Claude decides the job is done, **if your
prompt says what done means.**

You run two loops **at the same time**, each in its own terminal. Each one spends
most of its life waiting.

> **Commands** go in your terminal; on Windows type `py workshop` wherever this page
> says `./workshop`. **Prompts** go inside Claude Code and are the same everywhere.

## Loop A · watch a pipeline until it is green

This branch has a bug that makes CI fail. The pipeline takes about two minutes.

**Terminal 1:**

```bash
git push -u origin ex2-work # CI runs on every push
./workshop reset ex2
git push -u origin ex2-work
claude
```

Then, in Claude Code, write a `/loop` that watches the CI run of this branch, fixes
what fails, and **stops** when it is green. You choose the interval and the stop.
Count the passes it took.
The push starts CI. Paste this in Claude Code:

```text
/loop Watch the CI run of branch ex2-work. Each pass, run:
gh run list --branch ex2-work --workflow CI --limit 1
- Still queued or in progress: say "waiting", nothing else, and check again in about
two minutes.
- Failed: read why with gh run view <run id> --log-failed, fix the cause in the code,
run the tests, then commit and push. The push starts a new CI run.
- Succeeded, on the commit that is at the tip of ex2-work: tell me how many passes
this loop took and what you fixed, then stop the loop. Do not check again.
```

**Expect:** a few `waiting` passes, one fix and push, more `waiting`, then a summary
and the loop ends. Count the passes.

### Variant · forget the stop

Once Loop A has ended, try the same watch with a fixed interval and **no stop rule**:

```text
/loop 1m Run gh run list --branch ex2-work --workflow CI --limit 1 and tell me the
status of the latest run.
```

Let it fire three times after the run is green. Every pass costs tokens and tells you
the same thing. Then stop it. A fixed-interval loop does not stop itself, and `Esc`
only stops self-paced ones:

```text
What scheduled tasks do I have? Cancel the CI status one.
```

## Loop B · a fix going to production

**Terminal 2:** start the fake production log. It writes one line a minute, and a
regression comes later. Leave this terminal running.

```bash
./workshop deploy # one line a minute into deploy.log; a regression comes later
./workshop deploy
```

**Terminal 3:**

```bash
claude
```

```text
/loop Watch deploy.log: a fix just went to production, and the log gets one line per
minute. Each pass, read only the lines added since your last pass.
- Every new line says status=ok: say "quiet", then wait longer than last time before
the next check: 1 minute, then 2, then 5, then stay at 5.
- Any new line says status=degraded: tell me the minute and the error_rate, and go
back to checking every minute.
- Once the log has reached minute=20: summarise what you saw and stop the loop.
```

In the room, minutes stand in for hours.

**Expect:** the waits get longer while the log is quiet, drop back to one minute
around `minute=7`, and get longer again once the regression is over.

### Variant · a clock with no judgement

```text
/loop 1m Read the last line of deploy.log and tell me the status.
```

In a second Claude Code session, ask the agent to watch `deploy.log` and to **re-arm
its own loop** with longer waits after every quiet check, and to go back to the
shortest wait and tell you on any regression. In the room, minutes stand in for
hours: 1, 2, 5.
Count how many answers in a row are identical. Then cancel it as in Loop A.

## Watch for

- The forgotten stop: the loop keeps firing after the pipeline is green, and every
pass is paid for.
- The interval: one minute feels responsive and mostly buys identical answers.
- This loop dies with your session. Which of these watches should have been a
- These loops die with your session. Which of these watches should have been a
routine (`/schedule`), which keeps running with the laptop closed?
Loading
Loading