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
15 changes: 12 additions & 3 deletions .claude/agents/prose-walker.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,18 @@ any harness substitutions. Follow it exactly.
- Follow the prose literally, step by step, arm by arm. Where it names an
engine or knowledge call, run it from the project directory and use the
real response to decide which arm applies. Never predict a response.
- You also play the user, from the fixed script in the payload. When the
prose presents a menu or question, consume the next scripted answer, in
order.
- You also play the user. Where the payload gives scripted answers,
consume the next one in order as each menu or question arrives. Where
it instead describes how the user behaves, the prose has no fixed
number of questions to script — answer as that person would, in their
words, for as long as it keeps asking. A payload may carry both: the
script covers the discrete gates, the description covers the open
stretches.
- **Playing the user is not steering the walk.** However the payload
describes them, it says nothing about which arm to take or when a step
is finished — those you derive from the prose, exactly as before. A
described user who would happily stop talking is still not permission
to cut a loop the prose has not ended.
- **Never silently repair, reinterpret, or improve the prose.** Execute
what is written, even where it looks wrong. A broken instruction is the
finding — the single most damaging thing you can do is quietly do the
Expand Down
4 changes: 4 additions & 0 deletions tests/prose/cases/investigation-gathers-symptoms/act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Execute skills/workflow-investigation-entry/SKILL.md with arguments
$0=bugfix, $1=crash-fix, and continue into the processing skill it hands
off to. Stop once the prose turns from the user to the knowledge base —
do not query it.
37 changes: 37 additions & 0 deletions tests/prose/cases/investigation-gathers-symptoms/assert.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
The prose should have taken this path:

1. no investigation exists, so this is a fresh start — no resume choice
is put to the user
2. initialisation reads the work's seed and writes the investigation
file, placing what discovery already recorded into its Symptoms
section before anything is asked
3. **the interview happens anyway.** The section carrying the carrier's
account is not treated as symptoms already gathered — the seed is
where the questioning starts, not a reason to skip it
4. questioning opens broad and narrows on what the answers give back,
rather than putting the whole bank to the user at once
5. what the user says is written into the investigation file as it is
gathered, and committed
6. the questioning ends and the prose turns to the knowledge base, which
is where this walk stops

Further claims:

- nothing the user already said in discovery is put back to them as a
question — the carrier is read, not re-asked
- the user's ignorance is recorded as such: where they had no error
tracking, no logs, and no better date than last week, the file says so
rather than leaving those parts of the template as placeholders
- the analysis is untouched: no hypothesis, no code trace, no root cause.
Nothing has been investigated yet, only described

EXPECTED WORLD — from a work unit whose investigation had not begun:

- an investigation file at `.workflows/crash-fix/investigation/crash-fix.md`
carrying the template's structure
- its Symptoms section holding both what discovery captured and what the
interview added — the digital-only basket, the staging reproduction,
the absence of logs or a tracking link
- its Analysis and Fix Direction sections still unwritten
- the investigation registered as in progress on the work unit
- no other phase touched
32 changes: 32 additions & 0 deletions tests/prose/cases/investigation-gathers-symptoms/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"origin": "the interview the carrier was suppressing — symptom gathering must run even though initialisation has already written into the section its guard reads",
"entry": "workflow-investigation-entry",
"world": "claims",
"files": [
"skills/workflow-investigation-entry/SKILL.md",
"skills/workflow-investigation-entry/references/invoke-skill.md",
"skills/workflow-investigation-process/SKILL.md",
"skills/workflow-investigation-process/references/initialize-investigation.md",
"skills/workflow-investigation-process/references/template.md",
"skills/workflow-investigation-process/references/symptom-gathering.md",
"skills/workflow-shared/references/seed-context.md",
"skills/workflow-knowledge/references/knowledge-usage.md"
],
"conduct": "The user is relaying this bug rather than living it. Two customers hit it last week and they reproduced it once on staging with a digital-only basket; beyond that they are working from the report. They answer concretely about what they have actually seen, and say plainly when they do not know — they have no error-tracking link, no logs to hand, and nothing better than \"sometime last week\" for when it started. They do not volunteer detail past the question asked, and they do not speculate about the cause; that is what they think the investigation is for.",
"invariants": {
"engine_before_write": true,
"calls_include": [
"manifest get crash-fix.investigation.crash-fix status",
"topic start crash-fix investigation crash-fix"
],
"calls_in_order": [
"manifest get crash-fix.investigation.crash-fix status",
"topic start crash-fix investigation crash-fix"
],
"calls_exclude": [
"topic complete",
"knowledge.cjs index",
"topic reopen"
]
}
}
14 changes: 14 additions & 0 deletions tests/prose/cases/investigation-gathers-symptoms/fixture-state.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

// The bug is captured and the investigation has not begun — the same
// starting world the initialisation case uses. This one carries on
// through the interview that follows.

const m = require('../../mainlines/bugfix.cjs');

module.exports = {
build(h) {
m.init(h);
m.create(h);
},
};
6 changes: 6 additions & 0 deletions tests/prose/cases/investigation-gathers-symptoms/fixture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
A bugfix shaped in discovery and routed to investigation. The work unit
exists with its session log; nothing in the investigation phase has been
started, and no investigation file exists.

The bridge has just written its plan file and the context was cleared —
this session opens cold at the entry skill.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"showClearContextOnPlanAccept": true,
"permissions": {
"allow": [
"Edit(.workflows/**)",
"Bash(mv .workflows/:*)"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
abfb1370246e172ddcb3fce5ec26fde9e10bd78028e98a3d2aaad9796adf1d86
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.cache/
.manifest.json.*.tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Discovery Session 001

Date: 2026-01-01
Work unit: crash-fix

## Description (as of session)

Checkout crashes when an order has no shipping address.

## Seed

(none)

## Imports

(none)

## Map State at Start

(n/a — single-topic work)

## Exploration

Reported by two users this week: the checkout page 500s at the
payment step for orders with no shipping address. Reproducible on
staging with a digital-only basket. No error surfaces to the user —
the page just fails. Confirmed as a bugfix; no design question here,
so it routes straight to investigation.

## Edits

(none)

## Topics Identified

(none)

## Conclusion

(none)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "crash-fix",
"work_type": "bugfix",
"status": "in-progress",
"created": "2026-01-01",
"description": "Checkout crashes when an order has no shipping address",
"phases": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"work_units": {
"crash-fix": {
"work_type": "bugfix"
}
}
}
8 changes: 7 additions & 1 deletion tests/prose/lib/cases.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
//
// case.json the values code branches on:
// { origin, entry, files[], answers[],
// stubs{name: trigger}, world: "claims" (optional),
// conduct (optional), stubs{name: trigger},
// world: "claims" (optional),
// invariants{} (optional) — see lib/invariants.cjs }
// fixture.md optional. Prose describing the starting world —
// what has already happened, where the session
Expand Down Expand Up @@ -111,6 +112,7 @@ function loadCase(id) {
dir,
rel: path.relative(ROOT, dir),
metaError,
rawMeta: meta,
origin: meta.origin || null,
entry: meta.entry || null,
files: (meta.files || []).map((spec) => {
Expand All @@ -120,6 +122,7 @@ function loadCase(id) {
: { path: spec.slice(0, hash).trim(), anchor: spec.slice(hash + 1).trim() };
}),
answers: meta.answers || [],
conduct: meta.conduct || null,
worldMode: meta.world || null,
invariants: meta.invariants || null,
stubs: Object.entries(meta.stubs || {}).map(([name, trigger]) => ({ name, trigger })),
Expand Down Expand Up @@ -220,6 +223,9 @@ function validateCorpus(cases) {
errors.push(`${at}: no ${FILES.fixtureState} — a case with no world records no `
+ 'actions and produces no delta, so nothing it claims can be answered');
}
if ('conduct' in (c.rawMeta || {}) && !c.conduct) {
errors.push(`${at}: ${FILES.meta} conduct is empty — describe the user or omit it`);
}
if (!c.act) errors.push(`${at}: no ${FILES.act}`);
if (!c.assert) errors.push(`${at}: no ${FILES.assert} — the expected trace is what catches a silent repair`);

Expand Down
3 changes: 2 additions & 1 deletion tests/prose/lib/prompts.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ function indent(text, by = ' ') {
return text.split('\n').map((l) => (l.length ? by + l : l)).join('\n');
}

function walkerPrompt({ worldDir, situation, task, scope, stubs, answers }) {
function walkerPrompt({ worldDir, situation, task, scope, stubs, answers, conduct }) {
const t = loadTemplate('walker');
const parts = [fill(t.world, { world_dir: worldDir })];
if (situation) parts.push(fill(t.situation, { situation }));
parts.push(fill(t.task, { task, scope }));
if (answers) parts.push(fill(t.answers, { answers }));
if (conduct) parts.push(fill(t.conduct, { conduct }));
if (stubs.length) {
const entries = stubs.map((s) => fill(t['stub-entry'], {
name: s.name,
Expand Down
17 changes: 17 additions & 0 deletions tests/prose/prompts/walker.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ SCOPE — the prose under walk:
SCRIPTED USER ANSWERS — consume in order, one per question the prose asks:
{{answers}}

=== conduct ===

PLAYING THE USER — how they behave where the prose stops being scripted.

A scripted answer covers a question with one right response. Some prose
does not work that way: it explores, and keeps exploring until it judges
it has enough. There is no fixed number of turns to script, so this says
what kind of person is on the other side instead. Answer as they would,
in their words, for as long as the prose keeps asking.

It describes the user, never the walk. It does not tell you which arm to
take, when to stop, or what the prose ought to do — those are yours to
derive as always, and nothing here relieves you of following the prose
literally.

{{conduct}}

=== stubs ===

HARNESS SUBSTITUTIONS — these are NOT part of the process you are walking.
Expand Down
1 change: 1 addition & 0 deletions tests/prose/run.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function cmdPrompt(argv) {
.join('\n'),
stubs: c.stubs.map((s) => ({ ...s, ...cases.readStub(s.name) })),
answers: c.answers.map((a, i) => ` ${i + 1}. ${a}`).join('\n'),
conduct: c.conduct,
}));
}

Expand Down