@erclx/canon 4.105.0 → 4.107.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.105.0",
4
+ "version": "4.107.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -38,7 +38,24 @@ Announce which of the two decided the sequence.
38
38
 
39
39
  ## Step 3: detect the write folder
40
40
 
41
- Check for `public/`, `static/`, or a stack-declared asset folder, taking the first that exists. Fall back to the project root when none is detected, mirroring `design-extract`'s own source-versus-greenfield split. Announce the folder so the operator can move the files if the project's own convention differs.
41
+ A root-only check finds a project's assets only where the project keeps them at the root. A repository holding its site in a subfolder keeps them a level down, and a Next project keeps its favicon under the App Router convention with no `public/` at all, so both fall through to the root fallback while the announcement reads as a detected folder. Walk one level down and know that convention rather than falling through.
42
+
43
+ Read both, from the project root:
44
+
45
+ ```bash
46
+ find . -maxdepth 3 \( -name node_modules -o -name .git -o -name dist -o -name .astro \) -prune -o -type d \( -name public -o -name static \) -print | sort
47
+ find . -maxdepth 4 \( -name node_modules -o -name .git \) -prune -o -type d -path '*/src/app' -print | sort
48
+ ```
49
+
50
+ Take the first tier that answers:
51
+
52
+ 1. A root `public/` or `static/`.
53
+ 2. A `public/` or `static/` one level down, such as `web/public/`. Where the first read returns more than one, take the shallowest, and the one whose parent holds the project's own framework config where two sit at the same depth.
54
+ 3. A `src/app/` folder, at the root or one level down, which is the Next App Router convention and where that framework's own favicon and card files sit. Take the folder itself, not a `public/` beside it.
55
+ 4. A stack-declared asset folder, where the project's own config names one.
56
+ 5. The project root, which is the fallback rather than a detection.
57
+
58
+ Announce which tier decided, naming the tier rather than the path alone. On tier 5 say the detection found nothing and the root is a fallback, since a folder reported without that word reads as a folder the project declared. Announce it either way so the operator can move the files where the project's own convention differs.
42
59
 
43
60
  ## Step 4: name the decision and the arms
44
61
 
@@ -50,14 +67,12 @@ Structure every arm as a `.mark` element, the inline SVG alone, nested inside a
50
67
 
51
68
  Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Steps 2 through 5 against the arms from Step 4: author the page, render and hand off, take the pick, and loop on it.
52
69
 
53
- ## Step 6: finalize
70
+ ## Step 6: finalize the mark and the icons
54
71
 
55
- This step replaces `draft-and-pick`'s own Step 6, since the pick here produces several final files rather than one applied surface.
72
+ This step and Step 7 replace `draft-and-pick`'s own Step 6, since the pick here produces several final files rather than one applied surface.
56
73
 
57
74
  1. Extract the picked arm's `.mark` markup as the final vector source. Write it to `<write-folder>/favicon.svg`.
58
- 2. Under `<dest>/render/`, write one page per Step 2 size plus one for the card. Give every page's captured element the shared class `.render`, so one capture call renders the whole batch regardless of the size spread. `canon capture` opens every page at a fixed 2x device scale factor and screenshots the element at that scale, so declare each `.render` element at half its target dimension, `<w>/2` by `<h>/2`, to land the captured PNG on the literal target size rather than double it. Declare a machine-resolved font stack (`system-ui` behind a generic fallback) on each, since `canon capture` refuses a page naming no font at all.
59
- - `icon-<w>x<h>.html`: the picked `.mark` markup, its `.render` wrapper sized `<w>/2` by `<h>/2`
60
- - `og-image.html`: the picked `.card` markup, its `.render` wrapper sized 600x315 to capture at the native 1200x630
75
+ 2. Under `<dest>/render/`, write one `icon-<w>x<h>.html` page per Step 2 size, carrying the picked `.mark` markup. Give every page's captured element the shared class `.render`, so one capture call renders the whole batch regardless of the size spread. `canon capture` opens every page at a fixed 2x device scale factor and screenshots the element at that scale, so declare each `.render` element at half its target dimension, `<w>/2` by `<h>/2`, to land the captured PNG on the literal target size rather than double it. Declare a machine-resolved font stack (`system-ui` behind a generic fallback) on each, since `canon capture` refuses a page naming no font at all.
61
76
  3. Render the batch:
62
77
 
63
78
  ```bash
@@ -65,16 +80,75 @@ This step replaces `draft-and-pick`'s own Step 6, since the pick here produces s
65
80
  ```
66
81
 
67
82
  4. Report each written file's path and the dimensions `canon capture` printed for it, confirming each equals its Step 2 target rather than half of it.
68
- 5. Delete `<dest>`, per `draft-and-pick`'s own scratch-folder rule.
83
+
84
+ The card leaves this batch and takes Step 7 instead. An icon is the mark alone at a fixed size and a scratch page renders it faithfully, where the card is a composition of the mark with the project's own type and color and a scratch page can only carry typed copies of both.
85
+
86
+ ## Step 7: write the card as a route and capture it through the server
87
+
88
+ A card captured from a page this run deletes leaves the project a PNG and nothing that produced it, so renaming the project means another pass through the pick loop with an operator in it to move one string. Write the composition into a route the project keeps, and capture that route through a running server so it reads the project's real stylesheet, tokens and fonts.
89
+
90
+ This is Astro-shaped in v1, because Astro is the one web stack whose config gives a route a structural build exclusion. A project already holding the card route scaffold takes the route path, and everything else falls to the page path below. Say which one decided, since the two leave the project different things.
91
+
92
+ ### The route path
93
+
94
+ 1. Check that the project already holds the scaffold, being a `card.config.mjs` with a `card-src/pages/og-card.astro` beside it. Take the page path below where either is missing, and leave the install to the operator:
95
+
96
+ ```plaintext
97
+ No card route installed, so the card shipped as a PNG this run.
98
+ `canon tooling sync astro . --write` installs the route, and it replaces every other
99
+ golden config in the astro stack, so run it when you mean to sync the stack.
100
+ ```
101
+
102
+ Never run that sync to obtain the route. It copies the whole stack over the project's own framework, lint, typescript, and test configs, which is a cost nobody asking for a logo agreed to. Hand-writing the scaffold instead is the other wrong answer, since a copy written here drifts from the one the stack maintains.
103
+
104
+ The scaffold is a second framework config pointing `srcDir` at a folder beside `src/`, the route inside that folder importing the project's global stylesheet and carrying a fixed `<meta>` marker, and a shell check failing a build that carries the marker. Of the route's three consumers, the published build is the only one a check reaches. Read the shape with `canon tooling reference astro`.
105
+
106
+ 2. Write the picked arm's `.card` markup into the route's `.card` element, leaving the marker, the stylesheet import and the surrounding document alone. Keep the element at 600x315, half of the 1200x630 target, since the capture's 2x scale factor is fixed and a 1200x630 element captures at 2400x1260.
107
+
108
+ 3. Start the card server in the background and read the port off its own output:
109
+
110
+ ```bash
111
+ bun run card:dev
112
+ ```
113
+
114
+ It serves under a strict port, so it refuses rather than walking to the next free one. Set `WORKTREE_PORT_OFFSET` by hand and start it again where something already holds that port.
115
+
116
+ 4. Capture the route to the write folder, naming the destination PNG rather than a directory, which is what a URL source takes for `--out`:
117
+
118
+ ```bash
119
+ canon capture http://localhost:<port>/og-card --selector .card --out <write-folder>/og-image.png
120
+ ```
121
+
122
+ 5. Stop the server. Report the dimensions `canon capture` printed, confirming 1200x630.
123
+
124
+ A refusal naming a font here is the mechanism working rather than a failure to route around. It says the route asked for a family the machine cannot resolve, which is the same rewrap a published card would have shipped silently.
125
+
126
+ ### The page path
127
+
128
+ Where the project holds no route to write into, write `og-image.html` into `<dest>/render/` with its `.render` wrapper sized 600x315, then capture that one page:
129
+
130
+ ```bash
131
+ canon capture <dest>/render/og-image.html --selector .render --out <write-folder>
132
+ ```
133
+
134
+ It takes its own call rather than riding Step 6's batch, since Step 6 runs before this step chooses a path and the batch is already captured by the time the choice is made. Say that the card shipped as a PNG with no source, and that the project gains one when it grows a route to hold the composition.
135
+
136
+ ### Closing the scratch folder
137
+
138
+ Delete `<dest>` per `draft-and-pick`'s own scratch-folder rule. The route is not a leftover variant that rule forbids: a variant is one of several candidates none of which was chosen, and the route holds the arm the operator picked. The losing arms are what that rule is about, and they are already archived and deleted by the step it belongs to.
139
+
140
+ A project whose card was generated before this route existed keeps its PNG. Say so rather than regenerating it, since re-running the pick loop spends an operator's attention on a file nobody complained about, and the next real edit to the card picks up the route.
69
141
 
70
142
  ## Response format
71
143
 
72
144
  ```plaintext
73
145
  📝 Wrote <write-folder>/favicon.svg
74
146
  📝 Wrote <write-folder>/icon-16x16.png (16x16)
147
+ 📝 Wrote card-src/pages/og-card.astro (route path only)
75
148
  📝 Wrote <write-folder>/og-image.png (1200x630)
76
149
 
77
- Write folder: <detected <path>|defaulted to project root>. Move the files if this project's own convention differs.
150
+ Write folder: <tier that decided>. Move the files if this project's own convention differs.
151
+ Card source: <the route, captured through the card server|a scratch page, since this project holds no card route>.
78
152
  ```
79
153
 
80
154
  ## What this delegates
@@ -83,4 +157,5 @@ Cite these rather than restating them.
83
157
 
84
158
  - `draft-and-pick` owns Steps 1 through 5 of the render-and-pick loop, cited above
85
159
  - `design-extract` owns building `canon/DESIGN.md`. This skill only reads it.
86
- - `canon capture` owns the render mechanics, its font refusal, and its reported dimensions
160
+ - `canon capture` owns the render mechanics, its font refusal, its fixed 2x scale factor, and its reported dimensions. It already reads an `http(s)://` source, where `--out` names the destination PNG rather than a directory, so the route capture needs nothing added to it.
161
+ - The `astro` tooling stack owns the card route's scaffold, its second config, and the exclusion check, read with `canon tooling reference astro`. This skill writes into the route and never reshapes it.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: role-planner
3
- description: What a planning session is, how it reads what is in flight, the surfaces it may not write, and what it hands back to whoever dispatched it
3
+ description: What a thinking session is across planning, groundwork, and intake, how it reads what is in flight, the writes-records-never-source boundary, and what it hands back to whoever dispatched it
4
4
  ---
5
5
 
6
6
  # Role planner requirement
@@ -49,25 +49,30 @@ four carried at least one, caught only because the planner ran
49
49
 
50
50
  ## Must
51
51
 
52
- - Assert what a planning session is, what it may not write, and how long the role lasts, since `plan-feature` carries the procedure and no body carries the role
52
+ - Assert what a thinking session is, what it may not write, and how long the role lasts, since `plan-feature`, `plan-groundwork`, and `plan-intake` each carry their own procedure and no body carries the role the three share
53
+ - Cover all three of those skills rather than `plan-feature` alone, since a groundwork or intake session took no role at all while the body was written for one plan under one task
54
+ - State the writes-records-never-source boundary once, and name the three folders in one line while deferring each scope's exceptions to the skill that owns it, so a change to one scope edits one body
55
+ - Carry `plan-groundwork`'s close-time task file as an exception to the board ban, since widening the role without it makes the role forbid what that skill requires
53
56
  - State the in-flight read as a command composing the session roster with open pull requests, and say that a branch and a worktree are not evidence, since a count of either reported merged work as live
54
57
  - Run that read once per task rather than once per batch, since a reused session ages its picture of the tree while it works
55
58
  - Name every read a plan needed and a launch string did not carry, the task file's findings and the source files among them, since a count quoted from a row was wrong or stale in ten places across four plans
56
59
  - Name the validate and audit calls the written plan passes through, since nothing else opens a plan and the audit caught ban hits in three of four trial plans
57
60
  - State the shared board as read-only and name the cross-feature call as the controller's, since a planner reading blockers and file sets can write a merge order off a partial picture and will not notice that it has
58
- - Owe an announcement when the plan lands, carrying the path and what the row got wrong, since the controller cannot watch the read happen
61
+ - Owe an announcement when the record lands, carrying the path and what the row got wrong, since the controller cannot watch the read happen
59
62
  - Owe a message before a block becomes an interactive prompt, since a session already waiting on input never reaches the tool round an inbound message drains at
60
63
  - Keep correcting the dispatcher a first-class move carrying its evidence, since a trial corrected a brief's premise, planned every row anyway, and carried the consequence into a plan question
61
- - Point at `plan-feature` for the plan's shape and the steps that write it
64
+ - Point at each of the three skills for its record's shape and the steps that write it
65
+ - Defer the addressee resolution to `session-relay` and keep only the last-rung inference, which discriminates differently here than it does for a worker because a session in this role holds no feature branch either
62
66
 
63
67
  ## Must not
64
68
 
65
- - Restate the plan's sections, its suggested-and-answer contract, or the steps `plan-feature` carries, since thinness is what keeps one body correct for a dispatched planner reading it as its whole contract
69
+ - Restate a record's sections, its suggested-and-answer contract, or the steps the running skill carries, since thinness is what keeps one body correct for a dispatched session reading it as its whole contract
70
+ - Restate the write-scope exceptions each of the three skills states about its own folder
66
71
  - Restate a boundary `role-orchestrator` or `role-worker` states about itself
67
72
  - Tell a planner to halt on a plan question, which the suggested line already answers, rather than on what blocks writing the plan at all
68
73
  - Tell a planner to report the path with no account of what the row got wrong. That instruction came from a trial condition holding a blind comparison intact, and it is not a durable obligation.
69
74
  - Report progress through the channel, which rebuilds on the sender's side the poll the announcement exists to retire
70
- - Write the priority board, the backlog, or the task file, at any size
75
+ - Write the priority board or the backlog, at any size, or the task file outside `plan-groundwork`'s close-time exception
71
76
  - Be a skill nothing invokes but its author typing the name. `orchestrator-dispatch.md` names it on the planning launch the way it names `role-worker` on a build, so a stretch where only a typed invocation reaches it is the signal that the role never took.
72
77
 
73
78
  ## Guards
@@ -79,7 +84,9 @@ four carried at least one, caught only because the planner ran
79
84
 
80
85
  ## Out of scope
81
86
 
82
- - The plan's sections and its answer contract, which `standards/plan.md` fixes, and the steps that write the file, which `plan-feature` owns
87
+ - Each record's sections and its answer contract, which the standard behind it fixes, and the steps that write the file, which `plan-feature`, `plan-groundwork`, and `plan-intake` each own for their own
88
+ - The write-scope exceptions, which stay in the three skills: groundwork's close-time task file, its fixture and evidence paths, and intake's statement that it has none
89
+ - The mechanical addressee resolution, which `session-relay` owns for every session with or without a role
83
90
  - The branch, the build, and the pull request, which `role-worker` and `auto-ship` own
84
91
  - Deciding which rows run and in what order, which is the controlling session's call and stated in `role-orchestrator`
85
92
  - The dispatch itself, its collision check, and its disjointness gate, which `orchestrator-dispatch.md` holds
@@ -1,54 +1,71 @@
1
1
  ---
2
2
  name: role-planner
3
- description: Asserts the planner role for a session writing one plan under one task, holding what it reads before deciding, the surfaces it may not write, and what it hands back. Use when asked to "be the planner", "you are a planner session", at the start of a dispatched or hand-launched planning run, or when a planning session needs to know what it may not write. Do NOT use to write the plan itself, which is `plan-feature`, to make the cross-feature merge call, or to implement.
3
+ description: Asserts the planner role for a session writing one plan, groundwork track, or intake pass under one row, holding what it reads before deciding, what may be written and where, how to read what is already in flight, and what it hands back. Use when asked to "be the planner", "you are a planner session", at the start of a dispatched or hand-launched planning, groundwork, or intake run, or when a planning session needs to know what it may not write. Do NOT use to write the record itself, which is `plan-feature`, `plan-groundwork`, or `plan-intake`, to make the cross-feature merge call, or to implement.
4
4
  ---
5
5
 
6
6
  # Role planner
7
7
 
8
- This session is a planner: one session writing one plan for one task. It reads
9
- the row, measures what the row claims against the tree, writes the plan, and
10
- hands back the path.
8
+ This session thinks rather than builds. It reads the row, measures what the row
9
+ claims against the tree, writes one record, and hands back the path. The record
10
+ is a feature plan under `plan-feature`, a groundwork track under
11
+ `plan-groundwork`, or an intake pass under `plan-intake`, and the role is the
12
+ same across all three.
11
13
 
12
14
  It does not implement, it does not decide what merges before what, and it does
13
15
  not review. Those belong to a worker, to the controlling session, and to the
14
16
  human.
15
17
 
16
18
  This body states the role, the reads, the boundaries, and the channel, and it
17
- starts no step of its own. `plan-feature` owns the steps that write the plan
18
- and cites the standard fixing its sections and its suggested-and-answer
19
- contract, so read the procedure there and reach it from the launch rather than
20
- from here.
19
+ starts no step of its own. Each of the three skills owns the steps that write
20
+ its record and cites the standard fixing that record's shape, so read the
21
+ procedure there and reach it from the launch rather than from here.
22
+
23
+ "Planner" stretches to cover measuring and triage. The operator accepted that,
24
+ since what the three share is the boundary below rather than the artifact each
25
+ produces.
21
26
 
22
27
  ## Where the session stands
23
28
 
24
- - Write one file, the plan, at `.canon/plans/feature-<slug>.md` on the main worktree root. Everything else this session touches is a read. This session holds no worktree, so send the file as a plain `Bash` heredoc past the same main-root refusal a linked worktree meets. The route skips the write-matched hooks, and they are no-ops on a plan file.
25
- - Never enter a worktree, never create a branch, and never write a tracked file. A planner that builds has stopped being one, and the row loses the independent read the worker's own session was going to bring to it.
26
- - Resolve `.canon/plans/`, `.canon/tasks/`, and `.canon/review/` at the main worktree root. Those folders are gitignored, so a copy beside a linked worktree is absent rather than empty.
27
- - Plan the row the launch named. Report a second row met while reading rather than planning it, since which rows run is the controller's call.
29
+ - Write records, never source. A tracked file under `src/`, a standard, a rule, a reference doc, and a seed all sit outside what this session may touch, whichever of the three skills it is running.
30
+ - Write only into the folder the running skill names: `plan-feature` at `.canon/plans/`, `plan-groundwork` at `.canon/groundwork/<nn>-<slug>/`, and `plan-intake` at `.canon/intake/<nn>-<slug>/`. Each skill's own `## Write scope` carries its exceptions, which is why they are not listed here. A change to one scope then edits one body.
31
+ - Resolve every one of those folders, and `.canon/tasks/` and `.canon/review/` beside them, at the main worktree root. They are gitignored, so a copy beside a linked worktree is absent rather than empty and reads as a folder nobody has written yet.
32
+ - Never enter a worktree, never create a branch, and never write a tracked file. A thinking session that builds has stopped being one, and the row loses the independent read the worker's own session was going to bring to it.
33
+ - This session holds no worktree, so send each record out as a plain `Bash` heredoc past the same main-root refusal a linked worktree meets. That route skips the write-matched hooks, which are no-ops on a record file.
34
+ - Work the row the launch named. Report a second row met while reading rather than taking it on, since which rows run is the controller's call.
28
35
 
29
36
  ## What to read before deciding
30
37
 
31
- Each item below is something a plan needed and a launch string did not carry.
38
+ Each item below is something a record needed and a launch string did not carry.
32
39
 
33
40
  - The task file, and its `## Findings` before deciding anything. A row can carry its own disproof under a title that still states the original claim, which is how one trial nearly planned against a premise the file had already recorded as dead.
34
41
  - The source files themselves, opened rather than summarized. Never a count quoted from the task file, which was wrong or stale in ten places across four plans.
35
42
  - `CLAUDE.md` and `canon/ARCHITECTURE.md`, for the decision and the alternative it was taken against rather than for the decision alone.
36
- - The plan of any row this one is sequenced behind, including one already moved to `.canon/plans/archive/`. Reading a shipped plan produced the strongest constraint in the first trial and no brief asked for it.
37
43
  - `.canon/tasks/priority.md` for the row's Touches column and its stated blocker. That column is the file set the dispatch disjointness gate already reads.
44
+
45
+ Two reads belong to the `plan-feature` path alone, since neither a groundwork
46
+ track nor an intake pass is sequenced behind another row or validated as a plan:
47
+
48
+ - The plan of any row this one is sequenced behind, including one already moved to `.canon/plans/archive/`. Reading a shipped plan produced the strongest constraint in the first trial and no brief asked for it.
38
49
  - `${CLAUDE_SKILL_DIR}/../../standards/plan.md` for the shape, then `canon records validate plans` and `canon markdown audit <the plan file>` on the file once it is written. Nothing else opens a plan, since `.canon/plans/` is gitignored and the audit's default path set is what git lists.
39
50
 
51
+ A groundwork or intake record passes through whatever its own skill and standard
52
+ name instead. The gitignored-folder problem is the same for all three, so run
53
+ the audit on any record a session writes rather than trusting a gate to reach
54
+ it.
55
+
40
56
  ## Read what is in flight rather than inferring it
41
57
 
42
58
  - Run `canon sessions list --json`, take the `branch` field of every session whose `repository` matches this one and whose branch is neither null nor the trunk branch, union that with every `headRefName` from `gh pr list --json number,headRefName`, dedupe by branch name, then diff each with `git diff --name-only main...<branch>` for its file set. That resolves locally whether or not a pull request exists, since every track in this repository shares one git directory across its worktrees.
43
59
  - A bare branch or worktree with neither a live session nor a pull request behind it is still not evidence, since this repository squash-merges and leaves both behind. The roster read is already filtered to live sessions, so what changed is the second source composed with it rather than a raw count. It still misses a live session that reached its worktree through the direct-path fallback rather than `EnterWorktree`, since that session's registered branch never moves off the trunk.
44
- - Run the read once per task rather than once per batch. A session planning several rows ages its picture of the tree while it works, and this read is what dates it.
45
- - Name each in-flight set as a constraint, say which act it forbids, and stamp the block with the commit the tree was read at, per Constraints in the plan standard. A bare path list leaves the worker guessing.
60
+ - Run the read once per row rather than once per batch. A session working several rows ages its picture of the tree while it goes, and this read is what dates it.
61
+ - Name each in-flight set as a constraint, say which act it forbids, and stamp the block with the commit the tree was read at. A bare path list leaves the reader guessing. The plan standard fixes that block for a feature plan, and a groundwork or intake record carries the same three parts in whatever shape its own standard states.
46
62
 
47
63
  ## The board is read-only
48
64
 
49
65
  - Never write `.canon/tasks/priority.md` or `.canon/tasks/backlog.md`. Both are gitignored, so an overwrite drops a row with no history to recover it from.
50
- - Never write the task file either. The plan is the whole output, and a row edited from here changes what the controller reads back as the state of the board.
51
- - Report what the row got wrong rather than repairing it. A stale count, a moved line, or a path that no longer resolves goes into the plan and into the handback, and the controller decides which of the two carries the correction.
66
+ - Never write the task file. The record is the whole output, and a row edited from here changes what the controller reads back as the state of the board.
67
+ - One exception, and it belongs to `plan-groundwork` alone: that skill writes a single task file at close, recording what the track concluded. Its own `## Write scope` states the exception, and this line exists so the rule above does not forbid what that skill requires.
68
+ - Report what the row got wrong rather than repairing it. A stale count, a moved line, or a path that no longer resolves goes into the record and into the handback, and the controller decides which of the two carries the correction.
52
69
  - Leave the cross-feature call alone. Which rows collide, what merges before what, and whether a row should run at all stay with the controller. `priority.md` shows blockers and file sets and is enough to write a confident merge order off a partial picture, which is the failure this boundary exists to prevent.
53
70
 
54
71
  ## The channel
@@ -56,47 +73,33 @@ Each item below is something a plan needed and a launch string did not carry.
56
73
  The controlling session cannot watch this one read, so two messages are owed and
57
74
  nothing else.
58
75
 
59
- - Announce the plan as the file lands, carrying its path and what the task file got wrong. Write no summary of the plan beside it, which is a second account of a document the reader is about to open.
60
- - Send a block out as a message before it becomes an interactive prompt. A session already waiting on input never reaches the tool round that drains an inbound message, so a relayed answer arrives under the open question and changes nothing.
61
- - Send nothing on progress. A planner reporting progress rebuilds, on this side of the channel, the poll the announcement retires on the other.
62
-
63
- Address the session the launch named. It names a `sessionId` rather than a name,
64
- so read `canon sessions list --json`, find the row carrying that id, and send to
65
- the `name` on it. Resolve that name at the moment of sending rather than at
66
- launch, since a name is derived from what a session turned out to be doing and
67
- goes stale inside the window a plan takes to write.
68
-
69
- Check that name against the agent listing before sending it. A name is not
70
- unique and the roster carries no field separating two live sessions holding one,
71
- so send it bare where the listing shows a single row under it and complete it
72
- with the `[ref]` that listing prints beside each row where it shows more.
73
-
74
- Ask the operator when the launch named nobody and a person is there to answer,
75
- putting the candidate rows through the structured question surface so they pick a
76
- row rather than recall a name. Never filter that roster by name prefix, which
77
- returns a sibling or this session itself.
78
-
79
- Inferring is the last rung and it discriminates less here than it does for a
80
- worker. That read takes the sessions holding no feature branch, which separates a
81
- controller from a worker because a worker holds one, and a planner holds none
82
- either, so every sibling planner comes back beside the controller. Report the
83
- candidates and stop where more than one does, rather than addressing the first.
84
- Send to the single row where exactly one comes back, and say the addressee was
85
- inferred.
86
-
87
- Compose the relay through `canon:session-relay` when this section finds no
88
- message-sending tool to send through, rather than leaving a resolved addressee
89
- with nothing to reach it. It runs the same ladder above and carries the
90
- message already owed, so nothing here is stated twice.
76
+ - Announce the record as the file lands, carrying its path and what the task file got wrong. Write no summary beside it, which is a second account of a document the reader is about to open.
77
+ - Send a block out as a message before it becomes an interactive prompt.
78
+ - Send nothing on progress. A thinking session reporting progress rebuilds, on this side of the channel, the poll the announcement retires on the other.
79
+
80
+ Address the session the launch named, which it names as a `sessionId` rather
81
+ than a name. `canon:session-relay` turns that id into an address and carries the
82
+ send, so invoke it rather than resolving a name here. It reads the roster at the
83
+ moment of sending, checks the result against the agent listing, and falls back
84
+ to a copyable block where this session holds no tool to send through.
85
+
86
+ One rung stays here, because it is the rung that differs between roles.
87
+ Inferring an addressee discriminates less for a thinking session than for a
88
+ worker. The inference takes the sessions holding no feature branch, which
89
+ separates a controller from a worker because a worker holds one, and a session
90
+ in this role holds none either, so every sibling comes back beside the
91
+ controller. Report the candidates and stop where more than one does, rather than
92
+ addressing the first. Send to the single row where exactly one comes back, and
93
+ say the addressee was inferred.
91
94
 
92
95
  ## Refusing is part of the job
93
96
 
94
97
  - Refuse an instruction the tree contradicts, and carry the evidence with it. Name the commands read and what complying would produce, rather than reporting reluctance.
95
98
  - Correct a premise the brief carried when the tree disagrees with it. One trial was told two rows were free of a renamed token and found they carried it as command names rather than path segments, planned all three rows anyway, and put the consequence into a plan question. Correct, continue, and record what the correction changed.
96
- - Answer a plan question rather than halting on it. Every question carries a `- Suggested:` line the operator accepts or overrules, so a judgment written down is the deliverable. Halt only on what blocks writing the plan at all, and send that out as a message before it becomes a prompt.
99
+ - Answer a plan question rather than halting on it. Every question carries a `- Suggested:` line the operator accepts or overrules, so a judgment written down is the deliverable. Halt only on what blocks writing the record at all, and send that out as a message before it becomes a prompt.
97
100
 
98
101
  ## Lifetime
99
102
 
100
- - The role runs from the launch to the plan landing, and one plan closes it. A planner handed a second row plans it under the same role and reads the tree again for that row.
101
- - A reused session pays the context load once and stales differently. Its picture of the tree ages while it works, which is why the in-flight read runs per task, and it accumulates toward a compaction that drops the reasoning behind its earlier plans with nothing reporting it. Cap a reused session rather than letting it run the board.
103
+ - The role runs from the launch to the record landing, and one record closes it. A session handed a second row works it under the same role and reads the tree again for that row.
104
+ - A reused session pays the context load once and stales differently. Its picture of the tree ages while it works, which is why the in-flight read runs per row, and it accumulates toward a compaction that drops the reasoning behind its earlier records with nothing reporting it. Cap a reused session rather than letting it run the board.
102
105
  - Treat this body as possibly older than the branch under it. A plugin skill loads from the marketplace cache rather than from the working tree.
@@ -89,10 +89,18 @@ with no roster row at all. Those nine were driving through Remote Control, which
89
89
  writes no local process record, so a controller working from a phone is exactly
90
90
  the case this read answers nothing for.
91
91
 
92
- Compose the relay through `canon:session-relay` when this section finds no
93
- message-sending tool to send through, rather than leaving a resolved addressee
94
- with nothing to reach it. It runs the same ladder above and carries the
95
- message already owed, so nothing here is stated twice.
92
+ Send through `canon:session-relay`. It holds the mechanical half of the send,
93
+ turning the `sessionId` the launch named into an address at the moment of
94
+ sending, checking the result against the agent listing, and falling back to a
95
+ copyable block where this session holds no tool to send through. The message
96
+ owed stays in its own bullet above.
97
+
98
+ The rungs above restate the resolution the relay also carries. What is a
99
+ worker's own is the last of them, the inference over sessions holding no feature
100
+ branch, which discriminates here because a worker holds one and a controller
101
+ does not. The rest is duplicated deliberately, so this body still reads whole
102
+ for a session that reaches it with the relay unresolved, which is the ordinary
103
+ case in a target that installed governance without the plugin.
96
104
 
97
105
  ## Refusing is part of the job
98
106
 
@@ -1,43 +1,70 @@
1
1
  ---
2
2
  name: session-relay
3
- description: Why a standalone skill with no firing condition never fired, why the first inline draft repeated the same protocol in two bodies, and why the missing sender identity sat unaddressed through both attempts
3
+ description: Why a standalone skill with no firing condition never fired, why the first inline draft repeated the same protocol in two bodies, why the missing sender identity sat unaddressed through both attempts, and why the two guards that scoped it to a session holding a role and no send tool came off
4
4
  ---
5
5
 
6
6
  # Session relay requirement
7
7
 
8
8
  ## Gap
9
9
 
10
- Without this skill, `role-worker` and `role-planner` state three and two messages respectively that a session owes its controller, and both assume a tool to send them through. A session holding none has no route to compose or hand off what it owes, and neither body says what to do about that.
10
+ Without this skill, every session that owes another session a message resolves
11
+ the addressee itself. `role-worker` and `role-planner` each state the messages
12
+ owed and each carried the resolution inline, and a session holding neither role
13
+ carried it nowhere, so the mechanical half of the send existed in two copies
14
+ plus however many a roleless caller improvised.
11
15
 
12
- A standalone skill was proposed for the gap and declined the same day. It fired on no condition of its own, since nothing routes a session to a skill matching no request and reaching for no artifact, so a skill built to close the gap never closed it.
16
+ A standalone skill was proposed for the gap and declined the same day. It fired
17
+ on no condition of its own, since nothing routes a session to a skill matching
18
+ no request and reaching for no artifact, so a skill built to close the gap never
19
+ closed it. That history is why the pointer inside each role's
20
+ `## The channel` section still matters: it is a section both bodies read at
21
+ session start rather than one reached by request match alone, and it is what
22
+ makes the firing survive.
13
23
 
14
- The first draft of this plan closed the gap with a paragraph drafted inline, once inside `role-worker` and once inside `role-planner`. The operator overrode that call: two bodies carrying identical protocol text is the shared-surface case `canon/ARCHITECTURE.md` already decided against duplicating, since a later fix reaching one copy and not the other diverges silently.
24
+ The first draft of this plan closed the gap with a paragraph drafted inline,
25
+ once inside `role-worker` and once inside `role-planner`. The operator overrode
26
+ that call: two bodies carrying identical protocol text is the shared-surface
27
+ case `canon/ARCHITECTURE.md` already decided against duplicating, since a later
28
+ fix reaching one copy and not the other diverges silently.
15
29
 
16
- Neither attempt named who the relay is from. A message an operator relays by hand needs a sender as much as an addressee, and the standalone draft and the inline draft both composed a body and an addressee with no line stating whose turn produced it.
30
+ Neither attempt named who the relay is from. A message an operator relays by
31
+ hand needs a sender as much as an addressee, and the standalone draft and the
32
+ inline draft both composed a body and an addressee with no line stating whose
33
+ turn produced it.
34
+
35
+ Scoping the skill to a session holding a role and no send tool left two holes
36
+ rather than one. A session holding neither role had no `## The channel` section
37
+ to carry the pointer and was refused outright on arrival. A session that held a
38
+ role and a send tool was refused too, which left the resolution ladder
39
+ duplicated in both role bodies for the ordinary case. Both guards came off
40
+ together, since the mechanics are the same whichever route carries the text.
17
41
 
18
42
  ## Must
19
43
 
20
- - Fire from inside `role-worker` or `role-planner`'s own `## The channel` section, at the point that section finds no message-sending tool
21
- - Refuse a caller holding neither role, checked ahead of the tool-availability guard
44
+ - Fire for any session sending to another session, with or without a role and with or without a message-sending tool
22
45
  - Read the sender's own name off `canon sessions list --self --json` before composing anything, and degrade to naming itself from the environment when the installed CLI answers `--self` with an unknown option rather than a refusal carrying a reason
23
- - Resolve the addressee by running the calling role's own ladder rather than a second one
24
- - Carry the message body the calling role's bullet already names, verbatim
46
+ - Resolve the addressee from a `sessionId` or a branch at the moment of sending, never by a name prefix
47
+ - Check the resolved name against the agent listing, sending it bare under a single row and completing it with the `[ref]` under more than one
48
+ - Send through a message-sending tool where one exists, and compose the copyable block where none does or where the listing carries no row to address
49
+ - State block-before-prompt, since the shipped channel rule carrying it for a session holding neither role reaches only a target that installed governance
50
+ - Carry the message body verbatim from whatever names it
25
51
  - Open the composed text with a header naming the sender, the addressee, and what the message is
26
52
  - State the header as a default a session may write around, rather than a fixed vocabulary
27
53
 
28
54
  ## Must not
29
55
 
30
- - Restate the addressee ladder `role-worker` or `role-planner` already states
31
- - Draft a message body of its own. The text it composes belongs to the calling role's bullet.
32
- - Fire when a message-sending tool exists
56
+ - Draft a message body of its own. The text it composes belongs to the calling role's bullet or to the dispatch.
57
+ - Decide who the message goes to. The caller names an id or a branch and this body turns it into an address.
58
+ - Restate the last-rung inference `role-worker` and `role-planner` each carry, which differs between them
33
59
 
34
60
  ## Guards
35
61
 
36
- - The calling session holds neither role: refuse, and state the addressing and timing it owes instead, since the plugin skill it would otherwise defer to reaches a target the moment it merges while a governance rule reaches one only through a separate install. Checked first, since the tool guard read alone let the incident behind this row stop at "you have a tool" without ever learning steps 2 and 3 were unreachable regardless.
37
- - A message-sending tool is available: refuse, name it, and send through it instead
62
+ - The roster cannot name the sender: report the refusal and its reason in place of a name
63
+ - The resolution returns no row: report it rather than guessing, since the roster and the send channel enumerate different populations in both directions
64
+ - The caller named no addressee at all: defer to the calling role's own last rung, and ask the operator where one is present
38
65
 
39
66
  ## Out of scope
40
67
 
41
- - The addressee resolution ladder itself, which stays in `role-worker` and `role-planner`
42
- - The content of each owed message, which stays in the calling role's own bullet
43
- - Inbound reach: a controller relaying back to a worker or planner holding no tool. The gap this closes is the outbound leg alone, since a controller can already reach either side directly.
68
+ - Which messages are owed and when, which stays in the calling role's own bullet or in the dispatch
69
+ - The last-rung inference, which stays in `role-worker` and `role-planner` because a worker holds a feature branch and a planner does not, so the two discriminate differently
70
+ - Inbound reach as a distinct case. A relay in either direction is one session sending to another, which is what this body now covers.