entropy-machines 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,7 @@ the cycle repeats.
11
11
 
12
12
  **A PRD creates issues, not the other way round.** The questions in a PRD are
13
13
  the decisions only you can make. Everything downstream follows from your answers.
14
+ The tracker backend is a six-operation contract and can be swapped — see [TRACKER-ADAPTER.md](docs/TRACKER-ADAPTER.md).
14
15
 
15
16
  ```
16
17
  YOU ANSWER A PRD
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: designer
3
+ description: UX alignment agent for dialogue docs and PRD layout. Produces visual aids (mermaid diagrams, component mockups), pressure-tests UX decisions, and ensures docs are scannable with clear options and tight feedback loops. Read-only — no code changes, no commits.
4
+ ---
5
+
6
+ You are reviewing UX alignment for dialogue docs in this repo.
7
+
8
+ Your job is to make sure docs are **scannable**, options are **clear**, and
9
+ feedback loops are **tight**. You produce visual aids and challenge UX
10
+ decisions — you do not implement code.
11
+
12
+ ## What you do
13
+
14
+ - **Visual aids**: mermaid sequence/flow diagrams, ASCII component mockups,
15
+ annotated screenshots. Anything that helps the owner see the system.
16
+ - **Layout review**: page flow, response box placement, discuss block
17
+ clarity, navigation structure. Flag when a page buries its question or
18
+ makes the owner hunt for what needs attention.
19
+ - **Option clarity**: every multi-option discuss block should have exactly
20
+ one marked as recommended, trade-offs stated as bullets not paragraphs,
21
+ and the question itself answerable without reading the whole page.
22
+ - **Grill-me challenge**: when asked, pressure-test a UX decision. Ask the
23
+ hard questions: what happens when there are 20 of these? What does the
24
+ owner see after ignoring this for a week? Where does this break on
25
+ mobile / in a narrow terminal? The point is to find the weak spots
26
+ before the owner does.
27
+
28
+ ## What you never do
29
+
30
+ - Implement code changes
31
+ - Commit, push, or merge
32
+ - Override owner UX rulings — you advise, the owner decides
33
+ - Edit files outside docs (no lib/, no bin/, no tests/)
34
+
35
+ ## Output
36
+
37
+ Your final message reports:
38
+
39
+ - What you reviewed and what you found
40
+ - Any diagrams or mockups you produced (as mermaid code blocks or inline)
41
+ - Specific recommendations, each as one bullet with a clear action
@@ -0,0 +1,123 @@
1
+ #!/bin/sh
2
+ # bin/auto-verify — zero-cost poller that emits a verifier Agent call.
3
+ #
4
+ # bin/auto-verify <issue-id>
5
+ #
6
+ # Polls for HANDOFF.md in the worker's worktree (matched by .dispatch-issue
7
+ # marker). Zero tokens while polling — pure shell. When found, outputs a
8
+ # structured Agent JSON block with the worktree path baked in. The
9
+ # orchestrator receives this via task notification and pastes it.
10
+ #
11
+ # Run via Bash(run_in_background: true) alongside the worker Agent call.
12
+
13
+ set -e
14
+
15
+ id="$1"
16
+ if [ -z "$id" ]; then
17
+ echo "usage: bin/auto-verify <issue-id>" >&2
18
+ exit 1
19
+ fi
20
+
21
+ ENTROPY_MACHINES_ROOT=$(git rev-parse --show-toplevel)
22
+ ctx="$ENTROPY_MACHINES_ROOT/.dispatch-context/$id.md"
23
+ NOTES_PY="$ENTROPY_MACHINES_ROOT/lib/notes.py"
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # POLL — zero tokens. Pure shell, 30-second intervals, 20-minute timeout.
27
+ # ---------------------------------------------------------------------------
28
+ main_wt="$ENTROPY_MACHINES_ROOT"
29
+ worker_wt=""
30
+ polls=0
31
+ max_polls=40
32
+
33
+ echo "auto-verify($id): polling for .dispatch-issue=$id + HANDOFF.md (30s intervals, max ${max_polls})"
34
+ while [ -z "$worker_wt" ] && [ "$polls" -lt "$max_polls" ]; do
35
+ for wt in $(git worktree list --porcelain | grep '^worktree ' | sed 's/worktree //'); do
36
+ [ "$wt" = "$main_wt" ] && continue
37
+ if [ -f "$wt/.dispatch-issue" ] && [ "$(cat "$wt/.dispatch-issue" | tr -d '[:space:]')" = "$id" ]; then
38
+ if [ -f "$wt/HANDOFF.md" ]; then
39
+ worker_wt="$wt"
40
+ break
41
+ fi
42
+ fi
43
+ done
44
+ if [ -z "$worker_wt" ]; then
45
+ sleep 30
46
+ polls=$((polls + 1))
47
+ fi
48
+ done
49
+
50
+ if [ -z "$worker_wt" ]; then
51
+ echo "auto-verify($id): TIMEOUT — no HANDOFF.md after $max_polls polls" >&2
52
+ exit 1
53
+ fi
54
+
55
+ echo "auto-verify($id): HANDOFF.md found at $worker_wt"
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # EMIT — a structured Agent call for the verifier. The orchestrator pastes
59
+ # this to the Agent tool. The verifier runs in the SAME session, so it can
60
+ # SendMessage to the worker for real interrogation.
61
+ # ---------------------------------------------------------------------------
62
+ prompt_file="$ENTROPY_MACHINES_ROOT/.dispatch-context/.verifier-prompt-$id.tmp"
63
+ trap 'rm -f "$prompt_file"' EXIT
64
+
65
+ cat > "$prompt_file" << VEOF
66
+ VERIFIER for issue $id.
67
+
68
+ A worker agent named "$id" completed implementation in worktree: $worker_wt
69
+ Dispatch context (full issue detail): $ctx
70
+
71
+ Read BOTH files before doing anything else.
72
+
73
+ ## STEP 1 — INTERROGATE THE WORKER
74
+
75
+ Run: bin/handoff $id --interrogate
76
+ This prints the questions. Use ListAgents to find the worker agent named
77
+ "$id" and get its address. Send ALL questions via SendMessage:
78
+ SendMessage({to: "<agent-address>", message: "<the questions>"})
79
+ Wait for the worker to respond (it resumes). Record the answers:
80
+ bin/handoff $id --record-interrogation --answer "<answers>"
81
+
82
+ If the worker cannot be reached (no longer listed), answer from artifacts:
83
+ 1. Read $worker_wt/HANDOFF.md (changed/found/assumed/next)
84
+ 2. Read the changed files in the worktree
85
+ At least one answer must be substantive.
86
+
87
+ ## STEP 2 — LIFT
88
+
89
+ Run: bin/handoff $id --from $worker_wt --lift
90
+ If any files are rejected (main drifted), merge manually: read the worker's
91
+ version and main's current version, apply the semantic diff onto main.
92
+
93
+ ## STEP 3 — TEST
94
+
95
+ Run: tests/run
96
+ All tests must pass. Fix failures caused by the lift.
97
+
98
+ ## STEP 4 — VERIFY (do NOT commit)
99
+
100
+ Run: bin/handoff $id --from $worker_wt --verified "auto-verified: tests passed"
101
+ If HANDOFF.md had found: none AND next: none, add --clean.
102
+ If it had real found: or next: values, pass --found "..." --next "..."
103
+
104
+ DO NOT stage, commit, or push. Report what you verified and stop.
105
+ The orchestrator is the last pass — they review your work and commit.
106
+ VEOF
107
+
108
+ echo
109
+ echo "--- AGENT CALL ---"
110
+ python3 -c '
111
+ import json, sys
112
+ prompt = open(sys.argv[1]).read()
113
+ call = {
114
+ "subagent_type": "general-purpose",
115
+ "name": "verify-" + sys.argv[2],
116
+ "description": "Verify and land " + sys.argv[2],
117
+ "prompt": prompt
118
+ }
119
+ print(json.dumps(call, indent=2))
120
+ ' "$prompt_file" "$id"
121
+ echo "--- END AGENT CALL ---"
122
+ echo
123
+ echo "Pass the JSON block above to the Agent tool to start verification."
package/bin/cycle ADDED
@@ -0,0 +1,29 @@
1
+ #!/bin/sh
2
+ # Review-cycle CLI — thin wrapper around lib/cycle.py.
3
+ #
4
+ # bin/cycle list every handle + status + ball
5
+ # bin/cycle resolve "<phrase>" handle → file (fuzzy)
6
+ # bin/cycle responses <handle> read the user's saved answers
7
+ # bin/cycle start <handle> mark 'in-review' (agent is on it)
8
+ # bin/cycle done <handle> [label] mark 'open' (ball back to user) + snapshot
9
+ # bin/cycle set <handle> <status> any status transition
10
+ # bin/cycle new <handle> <file> <title> [status] register a new doc
11
+ # bin/cycle history <handle> list this doc's versions
12
+ # bin/cycle sync regenerate STATE.md + INDEX.html from manifest
13
+ set -e
14
+
15
+ . "$(dirname "$0")/../lib/roots.sh"
16
+ ENTROPY_MACHINES_HOME=$(entropy_machines_home "$0")
17
+ entropy_machines_require_root cycle
18
+
19
+ CONFIG_PY="$ENTROPY_MACHINES_HOME/lib/config.py"
20
+
21
+ docs_dir=$(python3 "$CONFIG_PY" get docs.dir 2>/dev/null) || docs_dir="entropy-machines-docs"
22
+ case "$docs_dir" in
23
+ /*) docs_path="$docs_dir" ;;
24
+ *) docs_path="$ENTROPY_MACHINES_ROOT/$docs_dir" ;;
25
+ esac
26
+
27
+ export ENTROPY_MACHINES_DOCS="$docs_path"
28
+ export PYTHONPATH="$ENTROPY_MACHINES_HOME/lib${PYTHONPATH:+:$PYTHONPATH}"
29
+ exec python3 "$ENTROPY_MACHINES_HOME/lib/cycle.py" "$@"
package/bin/dispatch CHANGED
@@ -3,9 +3,13 @@
3
3
  #
4
4
  # bin/dispatch i-foo --files "src/x.ts tests/x.test.ts" --brief "one-line task"
5
5
  #
6
- # Exit 0 means SAFE TO DISPATCH; anything else names the hazard. It does not
7
- # spawn the agent your agent runner does that it refuses the mistakes
8
- # that have each burned a real run:
6
+ # Exit 0 means SAFE TO DISPATCH; anything else names the hazard. It emits
7
+ # TWO structured JSON blocks (--- AGENT CALL --- / --- END AGENT CALL ---)
8
+ # with the exact Agent tool parameters — the WORKER (implements in a
9
+ # worktree) and the VERIFIER (polls for HANDOFF.md, then runs the full
10
+ # handoff pipeline and commits). The orchestrator pastes both blocks in one
11
+ # message and walks away — nothing to remember, no manual landing. It refuses
12
+ # the mistakes that have each burned a real run:
9
13
  #
10
14
  # 1. NON-ROOT CWD. A worktree is created in whatever repo the dispatching
11
15
  # session's cwd belongs to. A session parked one directory off from the
@@ -21,7 +25,11 @@
21
25
  # none of this agent's business.
22
26
  # 3. ALREADY-LANDED WORK. If git log already mentions the issue id, the
23
27
  # task may be partly or fully done. The audit comes before the dispatch.
24
- # 4. UNRECORDED BRIEFS. The brief + file scope are appended to the tracker
28
+ # 4. RECENTLY-TOUCHED FILES. If any declared file was touched in the last
29
+ # 5 commits on any branch, that file is hot — another change may still
30
+ # be landing, or the agent's base commit may already contain the fix.
31
+ # The commits are printed and the dispatch requires --force to proceed.
32
+ # 5. UNRECORDED BRIEFS. The brief + file scope are appended to the tracker
25
33
  # notes log in the same act, so any session can see what was handed out
26
34
  # and scopes can be checked for overlap before agents collide in a file.
27
35
  #
@@ -78,19 +86,21 @@
78
86
  # with no obvious symptom until the agent can't find its own files. Check 1
79
87
  # is what closes that.
80
88
  #
81
- # --anyway skips check 3 only (the git-log hits are still printed); checks 1
82
- # and 2 have no override there is no correct dispatch from a wrong cwd or
83
- # over your own uncommitted edits.
89
+ # --anyway skips check 3 only (the git-log hits are still printed); --force
90
+ # skips check 4 only (the file-history hits are still printed); checks 1 and
91
+ # 2 have no override — there is no correct dispatch from a wrong cwd or over
92
+ # your own uncommitted edits.
84
93
  set -e
85
94
 
86
- usage() { echo "usage: bin/dispatch <issue-id> --files \"<paths>\" --brief \"<text>\" [--anyway] [--dry-run]" >&2; exit 2; }
95
+ usage() { echo "usage: bin/dispatch <issue-id> --files \"<paths>\" --brief \"<text>\" [--anyway] [--force] [--dry-run]" >&2; exit 2; }
87
96
 
88
- id=""; files=""; brief=""; anyway=0; dry=0
97
+ id=""; files=""; brief=""; anyway=0; force=0; dry=0
89
98
  while [ $# -gt 0 ]; do
90
99
  case "$1" in
91
100
  --files) files="$2"; shift 2 ;;
92
101
  --brief) brief="$2"; shift 2 ;;
93
102
  --anyway) anyway=1; shift ;;
103
+ --force) force=1; shift ;;
94
104
  # Run every check and print the brief block, but write nothing to the
95
105
  # log. Added after a smoke test of this script left a phantom DISPATCH
96
106
  # note behind — a note that can be annotated but never removed, and that
@@ -189,6 +199,29 @@ if [ -n "$hits" ]; then
189
199
  fi
190
200
  fi
191
201
 
202
+ # CHECK 4 — recently-touched files. A declared file that was touched in the
203
+ # last 5 commits on any branch is hot: the change may still be landing, the
204
+ # agent may be re-doing work that already shipped, or the base commit already
205
+ # contains the fix. The commits are printed unconditionally; the dispatch is
206
+ # refused unless --force is given.
207
+ file_history_hits=""
208
+ for f in $files; do
209
+ fhits=$(git log -5 --all --oneline -- "$f" 2>/dev/null)
210
+ if [ -n "$fhits" ]; then
211
+ file_history_hits="${file_history_hits}${file_history_hits:+
212
+ } $f:
213
+ $(echo "$fhits" | sed 's/^/ /')"
214
+ fi
215
+ done
216
+ if [ -n "$file_history_hits" ]; then
217
+ echo "dispatch: declared files were touched in the last 5 commits on some branch:" >&2
218
+ echo "$file_history_hits" >&2
219
+ if [ "$force" != "1" ]; then
220
+ echo " Review the commits above, then rerun with --force to proceed." >&2
221
+ exit 1
222
+ fi
223
+ fi
224
+
192
225
  # ---------------------------------------------------------------------------
193
226
  # THE DENYLIST — what OTHER agents are holding right now
194
227
  # ---------------------------------------------------------------------------
@@ -405,6 +438,20 @@ Whether the tracker is reachable from inside your worktree depends on this
405
438
  project's config.json worktree.linkPaths and on whether this clone's hooks
406
439
  are installed. Ask the dispatching session for the issue text if you need it."
407
440
 
441
+ # Extract the issue description from the tracker JSON for context-file
442
+ # inclusion. Falls back to empty if unavailable or the JSON has no
443
+ # description field — the section is omitted rather than printing a
444
+ # confusing "(none)".
445
+ issue_description=$(printf '%s\n' "$issue_json" | python3 -c '
446
+ import json, sys
447
+ try:
448
+ data = json.loads(sys.stdin.read())
449
+ desc = data.get("description") or ""
450
+ print(desc)
451
+ except Exception:
452
+ pass
453
+ ' 2>/dev/null || true)
454
+
408
455
  notes_rendered=$(printf '%s\n' "$notes_raw" | render_notes)
409
456
 
410
457
  # Not capped: an id with a long history is exactly the case the audit is
@@ -434,7 +481,22 @@ for s in suites:
434
481
  link_paths=$(cfg_get worktree.linkPaths)
435
482
 
436
483
  mkdir -p "$ctxdir"
484
+ # PRESERVE EXISTING CONTENT. The orchestrator may have already written a
485
+ # spec into this file before dispatching — that spec is the agent's primary
486
+ # input and must appear FIRST and intact. Read it before the redirect
487
+ # truncates the file; if present, it is emitted before the dispatch
488
+ # boilerplate under a clear separator.
489
+ existing_ctx=""
490
+ if [ -s "$ctx" ]; then
491
+ existing_ctx=$(cat "$ctx")
492
+ fi
437
493
  {
494
+ if [ -n "$existing_ctx" ]; then
495
+ printf '%s\n' "$existing_ctx"
496
+ echo
497
+ echo "---"
498
+ echo
499
+ fi
438
500
  echo "# Dispatch context — $id"
439
501
  echo
440
502
  echo "Written by bin/dispatch on $(date -u '+%Y-%m-%dT%H:%M:%SZ') from $ENTROPY_MACHINES_ROOT ($branch @ $head)."
@@ -503,6 +565,12 @@ for s in suites:
503
565
  echo "has not read it. It is recorded for the audit trail and it is NOT the"
504
566
  echo "limit of what you may write. The enforced limit is the claimed list above."
505
567
  echo
568
+ if [ -n "$issue_description" ]; then
569
+ echo "## Issue description"
570
+ echo
571
+ echo "$issue_description"
572
+ echo
573
+ fi
506
574
  echo "## Your brief"
507
575
  echo
508
576
  echo " $brief"
@@ -596,101 +664,116 @@ if [ "$dry" -eq 1 ]; then
596
664
  else
597
665
  echo "dispatch: OK — brief + scope recorded on $id."
598
666
  fi
599
- echo "Dispatch the worker defined in agents/isolated-worker.md."
600
- echo " Claude Code: Agent tool, subagent_type: isolated-worker."
601
- echo " A different runner: give it its own worktree, this brief directly (not"
602
- echo " just pointed at it), and a way for you to read back its HANDOFF.md —"
603
- echo " see the note at the top of agents/isolated-worker.md."
604
- echo " issue: $id"
605
- echo " advisory --files: $files"
606
- echo " ENFORCED denylist: $deny_field"
607
- echo " brief: $brief"
608
- echo
609
- # Pasted verbatim into the agent's brief. Four keys, one line each, and the
610
- # instruction says so twice — an agent given a free-form "write up what you
611
- # found" returns prose the lander then has to read and compress, which is
612
- # the work this was meant to remove. The file is gitignored, so the agent
613
- # cannot accidentally add it to the tree.
614
- echo "PASTE INTO THE AGENT'S BRIEF:"
615
- echo " BRIEF: $brief"
616
- echo
617
- # FIRST LINE after the brief, and the load-bearing one — see the measured
618
- # numbers in the header. This holds regardless of whether your runner
619
- # auto-loads a project-instructions file into subagents: proximity to the
620
- # prompt is what changes behaviour, not presence in context.
621
- echo " FIRST, before anything else: read this file, in full, with one Read:"
622
- echo " $ctx"
623
- echo " It is your issue text, what already landed under this id, your scope and"
624
- echo " brief, what earlier agents left you, and this project's suites. It is in"
625
- echo " the MAIN checkout, NOT in your worktree; the path above is absolute, use"
626
- echo " it as-is."
627
- if [ "$dry" -eq 1 ]; then
628
- echo " (--dry-run: that file was NOT written. Rerun without --dry-run.)"
629
- fi
630
- # The cwd check, stated to the AGENT. This script already refuses a
631
- # non-root cwd, but that runs when the brief is written and the dispatch
632
- # call happens after — a `cd` in between still misroutes the worktree, and
633
- # has once taken out three agents at once this way. Nothing in the tool
634
- # chain can catch that on the way in, so the agent checks on the way out.
667
+ # ---------------------------------------------------------------------------
668
+ # STRUCTURED AGENT CALL
669
+ # ---------------------------------------------------------------------------
670
+ # The prompt is assembled and emitted as part of a JSON block containing the
671
+ # exact Agent tool parameters. The orchestrator passes these directly — no
672
+ # manual assembly, no remembering the subagent_type. The same measured
673
+ # numbers from the header (pasted ~96%, referenced ~38%) shaped what goes
674
+ # into the prompt: every load-bearing rule is INSIDE the prompt text, not
675
+ # merely referenced.
635
676
  #
636
- # --git-common-dir answers WHICH REPO IS THIS A WORKTREE OF. From a linked
637
- # worktree it resolves to the MAIN repo's .git as an ABSOLUTE path, so it
638
- # carries the repo's own path. From a non-worktree checkout at its root it
639
- # prints a bare relative `.git`, which is why the wording calls that out as
640
- # its own reportable state rather than leaving the agent to interpret it.
641
- # --show-toplevel is the wrong check here: it prints the WORKTREE's own
642
- # path, which can never equal the main repo's path, so it can never confirm
643
- # which repo the worktree belongs to.
644
- echo " Your scope is a DENYLIST, not an allowlist. These files are claimed by"
645
- echo " another agent RIGHT NOW and you must NOT write them: $deny_field"
646
- echo " Everything else is yours if the fix needs it. If your fix needs a CLAIMED"
647
- echo " file: STOP — do not write it, and do NOT ship the half you were allowed"
648
- echo " to write. Leave the in-scope work coherent and name the exact file and"
649
- echo " what it must contain in HANDOFF.md's found: line. The context file above"
650
- echo " has the full protocol and says what does and does not enforce it."
651
- echo " SECOND, before you write anything: run \`git rev-parse --git-common-dir\`."
652
- echo " It prints the MAIN repo's .git — which repo your worktree branched from."
653
- echo " It MUST contain /$(basename "$ENTROPY_MACHINES_ROOT")/. A bare relative \`.git\` means you"
654
- echo " are not in a worktree at all: report that."
655
- echo " If either is wrong, STOP and report it. NEVER cd to make it pass and"
656
- echo " never write to the shared checkout — a cd is the failure, not the fix."
657
- echo " Do NOT use --show-toplevel here: yours is the worktree path, so it can"
658
- echo " never end in /$(basename "$ENTROPY_MACHINES_ROOT") and tells you nothing about the repo."
659
- # THE SCRATCHPAD, IN THE PASTE BLOCK RATHER THAN ONLY IN A DOC. The same
660
- # measurement that shapes the rest of this block puts a pasted rule at ~96%
661
- # and one sitting only in a doc the agent was told to go read at far less —
662
- # and a scratchpad the worker never hears about is the shared one it was
663
- # already using.
664
- echo " YOUR PRIVATE SCRATCHPAD is \`.scratch\` at the root of your worktree —"
665
- echo " mutators, probe harnesses, captured suite output go THERE, not in any"
666
- echo " scratchpad shared with the agents running beside you. Two concurrent"
667
- echo " agents have overwritten each other's throwaway files by both reaching"
668
- echo " for a shared scratch directory instead. If a tool refuses to write"
669
- echo " through .scratch, say so in HANDOFF.md — do not fall back to a shared"
670
- echo " scratchpad silently."
671
- # Earlier agents' carry-forward lines, INSIDE the paste block rather than
672
- # only in the context file above it, for the same measured reason.
673
- if [ -n "$carry" ]; then
674
- echo " WHAT EARLIER WORK ON THIS ISSUE LEFT YOU (you may not be able to look"
675
- echo " this up yourself — see the context file for how to check):"
676
- echo "$carry"
677
+ # The prompt is written to a temp file so Python can JSON-encode it with
678
+ # proper escaping. The file is cleaned up on exit.
679
+ prompt_tmp="$ctxdir/.prompt-$id.tmp"
680
+ trap 'rm -f "$prompt_tmp"' EXIT
681
+ {
682
+ echo "BRIEF: $brief"
677
683
  echo
678
- fi
679
- echo " Before you finish, write HANDOFF.md at the root of your worktree."
680
- echo " Exactly these four keys, ONE LINE each, no prose:"
681
- echo " changed: <what you changed>"
682
- echo " found: <seen but not fixed, out of scope or: none>"
683
- echo " assumed: <what you took as given — or: none>"
684
- echo " next: <what the next agent needs to know or: none>"
685
- echo " Keep it under six lines total. Do not commit it; it is gitignored."
686
- echo " AFTER YOU STOP YOU WILL BE QUESTIONED, while you are still resumable, and"
687
- echo " your work is not lifted until your answers are recorded. The questions are"
688
- echo " adversarial on purpose what you did NOT run, which of your assertions is"
689
- echo " weakest, what you took on trust, what in the brief you skipped, what you"
690
- echo " would check next. They are listed in the context file above; note the"
691
- echo " answers as you work. \"Nothing\" to every one of them is refused."
684
+ # FIRST LINE after the brief, and the load-bearing one — see the measured
685
+ # numbers in the header. This holds regardless of whether your runner
686
+ # auto-loads a project-instructions file into subagents: proximity to the
687
+ # prompt is what changes behaviour, not presence in context.
688
+ echo "FIRST, before anything else: read this file, in full, with one Read:"
689
+ echo " $ctx"
690
+ echo "It is your issue text, what already landed under this id, your scope and"
691
+ echo "brief, what earlier agents left you, and this project's suites. It is in"
692
+ echo "the MAIN checkout, NOT in your worktree; the path above is absolute, use"
693
+ echo "it as-is."
694
+ if [ "$dry" -eq 1 ]; then
695
+ echo "(--dry-run: that file was NOT written. Rerun without --dry-run.)"
696
+ fi
697
+ # The cwd check, stated to the AGENT. This script already refuses a
698
+ # non-root cwd, but that runs when the brief is written and the dispatch
699
+ # call happens after — a `cd` in between still misroutes the worktree, and
700
+ # has once taken out three agents at once this way. Nothing in the tool
701
+ # chain can catch that on the way in, so the agent checks on the way out.
702
+ #
703
+ # --git-common-dir answers WHICH REPO IS THIS A WORKTREE OF. From a linked
704
+ # worktree it resolves to the MAIN repo's .git as an ABSOLUTE path, so it
705
+ # carries the repo's own path. From a non-worktree checkout at its root it
706
+ # prints a bare relative `.git`, which is why the wording calls that out as
707
+ # its own reportable state rather than leaving the agent to interpret it.
708
+ # --show-toplevel is the wrong check here: it prints the WORKTREE's own
709
+ # path, which can never equal the main repo's path, so it can never confirm
710
+ # which repo the worktree belongs to.
711
+ echo "Your scope is a DENYLIST, not an allowlist. These files are claimed by"
712
+ echo "another agent RIGHT NOW and you must NOT write them: $deny_field"
713
+ echo "Everything else is yours if the fix needs it. If your fix needs a CLAIMED"
714
+ echo "file: STOP — do not write it, and do NOT ship the half you were allowed"
715
+ echo "to write. Leave the in-scope work coherent and name the exact file and"
716
+ echo "what it must contain in HANDOFF.md's found: line. The context file above"
717
+ echo "has the full protocol and says what does and does not enforce it."
718
+ echo "SECOND, before you write anything: run \`git rev-parse --git-common-dir\`."
719
+ echo "It prints the MAIN repo's .git — which repo your worktree branched from."
720
+ echo "It MUST contain /$(basename "$ENTROPY_MACHINES_ROOT")/. A bare relative \`.git\` means you"
721
+ echo "are not in a worktree at all: report that."
722
+ # THE SCRATCHPAD, IN THE PASTE BLOCK RATHER THAN ONLY IN A DOC. The same
723
+ # measurement that shapes the rest of this block puts a pasted rule at ~96%
724
+ # and one sitting only in a doc the agent was told to go read at far less —
725
+ # and a scratchpad the worker never hears about is the shared one it was
726
+ # already using.
727
+ echo "YOUR PRIVATE SCRATCHPAD is \`.scratch\` at the root of your worktree."
728
+ # Earlier agents' carry-forward lines, INSIDE the paste block rather than
729
+ # only in the context file above it, for the same measured reason.
730
+ if [ -n "$carry" ]; then
731
+ echo "WHAT EARLIER WORK ON THIS ISSUE LEFT YOU:"
732
+ echo "$carry"
733
+ echo
734
+ fi
735
+ echo "THIRD, write the file .dispatch-issue at the root of your worktree,"
736
+ echo "containing ONLY this line: $id"
737
+ echo "This identifies your worktree to the auto-verifier polling for it."
738
+ echo "Before you finish, write HANDOFF.md at the root of your worktree."
739
+ echo "Exactly these four keys, ONE LINE each, no prose:"
740
+ echo " changed: what you changed"
741
+ echo " found: seen but not fixed, out of scope — or: none"
742
+ echo " assumed: what you took as given — or: none"
743
+ echo " next: what the next agent needs to know — or: none"
744
+ } > "$prompt_tmp"
745
+
746
+ # Emit the structured agent call — the orchestrator passes these fields
747
+ # directly to the Agent tool. subagent_type and isolation are set here;
748
+ # the orchestrator must not override them.
749
+ echo
750
+ echo "--- AGENT CALL ---"
751
+ python3 -c '
752
+ import json, sys
753
+ prompt = open(sys.argv[1]).read()
754
+ call = {
755
+ "subagent_type": "general-purpose",
756
+ "isolation": "worktree",
757
+ "name": sys.argv[2],
758
+ "description": "Implement " + sys.argv[2],
759
+ "prompt": prompt
760
+ }
761
+ print(json.dumps(call, indent=2))
762
+ ' "$prompt_tmp" "$id"
763
+ echo "--- END AGENT CALL ---"
764
+ echo
765
+ echo "--- AUTO-VERIFY ---"
766
+ echo "bin/auto-verify $id"
767
+ echo "--- END AUTO-VERIFY ---"
768
+ echo
769
+ echo "Pass the AGENT block to the Agent tool AND run the AUTO-VERIFY command"
770
+ echo "via the Bash tool (run_in_background: true) — both in ONE message."
771
+ echo "The worker implements; bin/auto-verify polls for HANDOFF.md at zero token"
772
+ echo "cost, then emits a verifier Agent call. Paste that call when notified."
773
+ echo "The verifier runs interrogate → lift → test → verify. YOU review and commit."
774
+ echo " issue: $id | advisory --files: $files | ENFORCED denylist: $deny_field"
692
775
  echo
693
- echo "WHEN YOU LAND IT, before committing:"
776
+ echo "To skip auto-verification and land manually:"
694
777
  echo " bin/handoff $id --interrogate # the questions; send them to the LIVE agent"
695
778
  echo " bin/handoff $id --record-interrogation --answer \"...\" # what it said"
696
779
  echo " bin/handoff $id --from <agent-worktree> --lift # copies the files IN, refusing any that drifted"
@@ -312,6 +312,23 @@ function start(argv) {
312
312
  if (!fs.existsSync(serve)) {
313
313
  die([`entropy-machines: ${path.join(loc.shown, 'bin', 'serve')} not found.`]);
314
314
  }
315
+ // Agent onboarding notice — only if no agent config file mentions
316
+ // entropy-machines yet. Checks every common agent instruction file.
317
+ const agentConfigPaths = [
318
+ path.join(root, 'CLAUDE.md'),
319
+ path.join(root, '.claude', 'CLAUDE.md'),
320
+ path.join(root, '.cursorrules'),
321
+ path.join(root, '.github', 'copilot-instructions.md'),
322
+ path.join(root, '.windsurfrules'),
323
+ ];
324
+ const agentOnboarded = agentConfigPaths.some((p) => {
325
+ try { return fs.readFileSync(p, 'utf8').includes('entropy-machines'); } catch { return false; }
326
+ });
327
+ if (!agentOnboarded) {
328
+ console.log(`\n${BOLD}${YELLOW} ⚠ your coding agent doesn't know about entropy-machines yet.${RESET}`);
329
+ console.log(` Point it at ${CYAN}${path.join(loc.shown, 'docs', 'AGENT-QUICKSTART.md')}${RESET} before answering the PRD.\n`);
330
+ }
331
+
315
332
  console.log(`\n${BOLD}entropy-machines: starting serve from ${loc.shown}/${RESET}\n`);
316
333
  const r = spawnSync('sh', [serve], { cwd: loc.dir, stdio: 'inherit' });
317
334
  process.exit(r.status ?? 1);