@dpeek/codeless 0.1.3 → 0.1.5

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
@@ -100,16 +100,20 @@ Review and commit those local prompt edits in the invoking checkout, then bring
100
100
  that commit onto the configured integration branch before creating streams.
101
101
  Required prompts and directions must exist for stream creation and opening.
102
102
 
103
- The package-owned planner extension activates every planner session. Before its
104
- first project prompt, it requires the exact `<slug>-planner` Pi name and verifies
105
- the `<slug>_planner` Herdr identity, managed interactive readiness, foreground
106
- worktree, and matching native session reference from Herdr's Pi lifecycle
107
- integration. It confirms `approve_stream_change`,
108
- `dispatch_stream_implementer`, `rework_stream_implementer`, `finish_stream_implementer`, and `next_stream_change` are active. Missing or
109
- incompatible activation, identity mismatch, or inactive tools stops before
110
- `/change`. During session replacement, activation allows a brief bounded wait for
111
- an otherwise-valid Herdr identity to publish the current native Pi reference;
112
- it never waits on a wrong name, process, lifecycle source, or worktree. Global
103
+ The package-owned planner extension admits each newly launched planner using
104
+ its exact `<slug>-planner` Pi name, `<slug>_planner` Herdr identity, managed
105
+ readiness, canonical worktree, and official Pi lifecycle integration. It checks
106
+ that all five planner tools are active before sending `/change` and binds the
107
+ admitted process, pane, worktree, name, and live session ID.
108
+
109
+ After landing, replacement activation uses a one-use ticket held in that Pi
110
+ process, bound to the new conversation's session ID. It verifies the local
111
+ identity, effective model/thinking selection, and tools without querying Herdr's
112
+ asynchronous status. Saved session entries cannot replay a handoff. Codeless
113
+ requires activation acknowledgement before submitting `/change` exactly once;
114
+ failed or cancelled handoffs never retry automatically. After a failed
115
+ replacement, exit Pi and reopen the stream from another Herdr shell. Herdr's
116
+ integration still supplies monitoring and external agent control. Global
113
117
  installation of Codeless's extension is unnecessary.
114
118
  The approval tool has no arguments. Its extension derives the active
115
119
  `<slug>-planner` Pi session and passes it to the backing CLI, which requires it
@@ -131,7 +135,13 @@ replaces the Pi session in the same pane. It preserves the planner name, applies
131
135
  the planner selection read and validated after the stream fast-forwards to the
132
136
  captured integration commit, then activates and verifies the replacement before
133
137
  sending `/change` after resources reload. The previous conversation is not copied; the
134
- journal and project files carry context.
138
+ journal and project files carry context. The fresh planner reads repository guidance,
139
+ its journal, and current direction first. It stops on wholly gated work, uses the
140
+ latest numbered change only for active or ambiguous recovery, and reads older changes
141
+ only for journal-linked unresolved decisions. After selecting an ungated candidate it
142
+ reads relevant contracts and implementation; after a fast-forward it also rereads
143
+ current direction and files affected by incoming commits, without mining deleted or
144
+ historical documents for work.
135
145
 
136
146
  This uses Pi's `newSession({ setup, withSession })` command API, verified with
137
147
  Pi 0.85.1. Only the replacement context activates the selection and sends the
@@ -228,8 +238,10 @@ only when absent, and uses `herdr agent start` for named, readiness-checked Pi
228
238
  startup. It verifies the result before sending activation. An occupied or
229
239
  mismatched pane, unmanaged agent, ambiguous layout, or failed startup stops;
230
240
  Codeless never takes over an existing agent. Pi's display name is separate from
231
- Herdr's managed agent name. Activation verifies names and native session binding;
232
- it never renames an unmanaged process. There is no direct `planner` command.
241
+ Herdr's managed agent name. Launch admission verifies both names, lifecycle
242
+ authority, and worktree; subsequent conversation replacements use the local
243
+ handoff described above. Codeless never renames an unmanaged process. There is
244
+ no direct `planner` command.
233
245
 
234
246
  Dispatch validates the implementer selection before touching the planner's
235
247
  right-hand pane, starts a fresh ephemeral implementer with Codeless's reporting
@@ -276,10 +288,11 @@ the integration checkout. Success releases the lock. Other branches and
276
288
  checkouts are untouched; no push is performed.
277
289
 
278
290
  Another lock owner causes a stop, without queuing or polling. Rebase conflicts
279
- or failed checks retain ownership. Resolve the existing failure, then rerun
280
- `land`; it verifies the recorded integration commit has not changed. To abandon
281
- a landing, inspect the owner/base and Git state before manually removing the
282
- lock. There is no automatic stale-lock removal or retry.
291
+ or failed checks retain ownership. During conflict resolution, run focused checks
292
+ when useful; rerun `land` for the configured full check, which it alone owns.
293
+ It verifies the recorded integration commit has not changed. To abandon a landing,
294
+ inspect the owner/base and Git state before manually removing the lock. There is
295
+ no automatic stale-lock removal or retry.
283
296
 
284
297
  `next` is the session handoff's preparation command. It requires the stream's
285
298
  own clean worktree and latest numbered change, a full commit hash present in its
@@ -1,11 +1,10 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { resolve } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
3
4
  import { validAttempt } from "../src/attempt.ts";
4
5
 
5
6
  const codeless = fileURLToPath(new URL("../bin/codeless", import.meta.url));
6
7
  const thinkingLevels = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
7
- const nativeSessionAttempts = 20;
8
- const nativeSessionRetryDelayMs = 50;
9
8
  const requiredTools = [
10
9
  "approve_stream_change",
11
10
  "dispatch_stream_implementer",
@@ -30,32 +29,66 @@ function selection(value) {
30
29
  return { provider: value.provider, model: value.model, thinking: value.thinking };
31
30
  }
32
31
 
32
+ // Pi reloads extension modules when replacing a session. Keep only in-flight
33
+ // tickets on the process global; neither persisted entries nor a module cache
34
+ // can establish handoff provenance. Every ticket is removed on consume/finally.
35
+ const handoffKey = Symbol.for("@dpeek/codeless/planner-handoffs");
36
+ const handoffs = (globalThis[handoffKey] ??= new Map());
37
+
38
+ function worktree(cwd) {
39
+ try {
40
+ return realpathSync(cwd);
41
+ } catch {
42
+ throw new Error("Codeless could not resolve the planner worktree");
43
+ }
44
+ }
45
+
33
46
  export default function plannerExtension(pi) {
34
47
  let registered = false;
35
48
  let pending;
49
+ let admitted;
36
50
 
37
51
  pi.registerCommand("streams-activate", {
38
52
  description: "Verify this planner session before starting its project prompt",
39
53
  handler: async (args, ctx) => {
40
- let prompt;
54
+ let input;
41
55
  try {
42
- prompt = JSON.parse(args);
56
+ input = JSON.parse(args);
43
57
  } catch {
44
- throw new Error("Codeless activation requires one JSON-quoted project prompt");
58
+ throw new Error("Codeless activation requires a JSON prompt or handoff ticket");
45
59
  }
46
- if (typeof prompt !== "string" || !prompt.startsWith("/change ")) {
60
+ const replacement = typeof input === "object" && input !== null;
61
+ const ticket = replacement ? handoffs.get(input.handoff) : undefined;
62
+ if (replacement) {
63
+ handoffs.delete(input.handoff);
64
+ if (!ticket) throw new Error("Codeless handoff is missing, expired, or already consumed");
65
+ } else if (typeof input !== "string" || !input.startsWith("/change ")) {
47
66
  throw new Error("Codeless activation requires a /change project prompt");
48
67
  }
49
- const activation = ctx.sessionManager
50
- .getEntries()
51
- .findLast(
52
- (entry) =>
53
- entry.type === "custom" &&
54
- entry.customType === "streams-role-selection" &&
55
- entry.data?.role === "planner",
56
- );
57
- if (activation) {
58
- const requested = selection(activation.data.selection);
68
+ if (admitted) throw new Error("Codeless planner session is already activated");
69
+ const sessionName = pi.getSessionName();
70
+ const match = /^([a-z][a-z0-9-]{0,23})-planner$/.exec(sessionName ?? "");
71
+ if (!match)
72
+ throw new Error("Codeless activation requires an exact <slug>-planner Pi session name");
73
+ const expectedPlanner = `${match[1].replaceAll("-", "_")}_planner`;
74
+ const pane = process.env.HERDR_PANE_ID;
75
+ if (!pane) throw new Error("Codeless activation requires a Herdr-managed planner pane");
76
+ const cwd = worktree(ctx.cwd);
77
+ const sessionId = ctx.sessionManager.getSessionId();
78
+ if (replacement) {
79
+ if (
80
+ ticket.binding.pid !== process.pid ||
81
+ ticket.binding.pane !== pane ||
82
+ ticket.binding.cwd !== cwd ||
83
+ ticket.binding.sessionName !== sessionName ||
84
+ ticket.sessionId !== sessionId ||
85
+ ticket.binding.sessionId === sessionId
86
+ ) {
87
+ throw new Error(
88
+ "Codeless handoff does not match this planner process, pane, worktree, or session",
89
+ );
90
+ }
91
+ const requested = ticket.selection;
59
92
  const reference = `${requested.provider}/${requested.model}`;
60
93
  const model = ctx.modelRegistry.find(requested.provider, requested.model);
61
94
  if (!model) throw new Error(`Planner requested ${reference}, but Pi could not find it`);
@@ -68,36 +101,26 @@ export default function plannerExtension(pi) {
68
101
  ctx.model.id !== requested.model ||
69
102
  pi.getThinkingLevel() !== requested.thinking
70
103
  ) {
71
- const effective = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "no model";
72
104
  throw new Error(
73
- `Planner requested ${reference} at thinking level ${requested.thinking}, but Pi applied ${effective} at ${pi.getThinkingLevel()}`,
105
+ `Codeless planner could not apply ${reference} at thinking level ${requested.thinking}`,
74
106
  );
75
107
  }
76
- }
77
- const sessionName = pi.getSessionName();
78
- const match = /^([a-z][a-z0-9-]{0,23})-planner$/.exec(sessionName ?? "");
79
- if (!match)
80
- throw new Error("Codeless activation requires an exact <slug>-planner Pi session name");
81
- const expectedPlanner = `${match[1].replaceAll("-", "_")}_planner`;
82
- const pane = process.env.HERDR_PANE_ID;
83
- if (!pane) throw new Error("Codeless activation requires a Herdr-managed planner pane");
84
- const plannerIdentity = async () => {
85
- const identity = await pi.exec("herdr", ["agent", "get", pane], { timeout: 30_000 });
86
- if (identity.code !== 0) {
87
- throw new Error(
88
- identity.stderr.trim() ||
89
- identity.stdout.trim() ||
90
- "Codeless could not verify Herdr planner identity",
91
- );
92
- }
93
- try {
94
- return JSON.parse(identity.stdout).result?.agent;
95
- } catch {
96
- throw new Error("Herdr returned an invalid planner identity response");
97
- }
98
- };
99
- const attempts = activation ? nativeSessionAttempts : 1;
100
- for (let attempt = 0; attempt < attempts; attempt += 1) {
108
+ } else {
109
+ const plannerIdentity = async () => {
110
+ const identity = await pi.exec("herdr", ["agent", "get", pane], { timeout: 30_000 });
111
+ if (identity.code !== 0) {
112
+ throw new Error(
113
+ identity.stderr.trim() ||
114
+ identity.stdout.trim() ||
115
+ "Codeless could not verify Herdr planner identity",
116
+ );
117
+ }
118
+ try {
119
+ return JSON.parse(identity.stdout).result?.agent;
120
+ } catch {
121
+ throw new Error("Herdr returned an invalid planner identity response");
122
+ }
123
+ };
101
124
  const agent = await plannerIdentity();
102
125
  if (agent?.name !== expectedPlanner) {
103
126
  throw new Error(
@@ -107,36 +130,17 @@ export default function plannerExtension(pi) {
107
130
  if (agent.agent !== "pi" || agent.interactive_ready !== true) {
108
131
  throw new Error("Codeless requires a Herdr-managed Pi planner started by codeless open");
109
132
  }
110
- if (
111
- typeof agent.foreground_cwd !== "string" ||
112
- resolve(agent.foreground_cwd) !== resolve(ctx.cwd)
113
- ) {
133
+ if (typeof agent.foreground_cwd !== "string" || worktree(agent.foreground_cwd) !== cwd) {
114
134
  throw new Error("Codeless planner worktree does not match Herdr's foreground cwd");
115
135
  }
116
136
  const session = agent.agent_session;
117
- const expectedSession =
118
- session?.kind === "path"
119
- ? ctx.sessionManager.getSessionFile()
120
- : session?.kind === "id"
121
- ? ctx.sessionManager.getSessionId()
122
- : undefined;
123
137
  if (
124
138
  agent.screen_detection_skipped !== true ||
125
139
  session?.source !== "herdr:pi" ||
126
- session.agent !== "pi" ||
127
- !expectedSession
140
+ session.agent !== "pi"
128
141
  ) {
129
- throw new Error(
130
- "Codeless planner native session does not match Herdr's Pi lifecycle integration",
131
- );
142
+ throw new Error("Codeless planner requires Herdr's Pi lifecycle integration");
132
143
  }
133
- if (session.value === expectedSession) break;
134
- if (attempt === attempts - 1) {
135
- throw new Error(
136
- "Codeless planner native session does not match Herdr's Pi lifecycle integration",
137
- );
138
- }
139
- await new Promise((resolveDelay) => setTimeout(resolveDelay, nativeSessionRetryDelayMs));
140
144
  }
141
145
  const activeTools = ctx.getSystemPromptOptions().selectedTools ?? [];
142
146
  const missing = requiredTools.filter((tool) => !activeTools.includes(tool));
@@ -145,8 +149,10 @@ export default function plannerExtension(pi) {
145
149
  `Codeless planner activation is missing required tools: ${missing.join(", ")}`,
146
150
  );
147
151
  }
152
+ admitted = { pid: process.pid, pane, cwd, sessionName, sessionId };
148
153
  ctx.ui.notify(`Planner activated: ${sessionName} / ${expectedPlanner}`, "info");
149
- pi.sendUserMessage(prompt, { expandPromptTemplates: true });
154
+ if (ticket) ticket.activated = true;
155
+ else pi.sendUserMessage(input, { expandPromptTemplates: true });
150
156
  },
151
157
  });
152
158
 
@@ -156,8 +162,18 @@ export default function plannerExtension(pi) {
156
162
  if (!pending || pending.running) throw new Error("No pending Codeless handoff");
157
163
  const request = pending;
158
164
  request.running = true;
165
+ let token;
159
166
  try {
160
167
  await ctx.waitForIdle();
168
+ if (
169
+ !admitted ||
170
+ admitted.pid !== process.pid ||
171
+ admitted.pane !== process.env.HERDR_PANE_ID ||
172
+ admitted.cwd !== worktree(ctx.cwd) ||
173
+ admitted.sessionId !== ctx.sessionManager.getSessionId() ||
174
+ admitted.sessionName !== pi.getSessionName()
175
+ )
176
+ throw new Error("Codeless handoff requires the activated planner session");
161
177
  const execution = await pi.exec(
162
178
  "bun",
163
179
  [codeless, "next", request.changePath, request.landedCommit],
@@ -181,23 +197,39 @@ export default function plannerExtension(pi) {
181
197
  throw new Error("Codeless returned an invalid planner handoff");
182
198
  }
183
199
  const requested = selection(requestedValue);
200
+ token = randomUUID();
201
+ const ticket = {
202
+ binding: admitted,
203
+ selection: requested,
204
+ sessionId: undefined,
205
+ activated: false,
206
+ };
207
+ handoffs.set(token, ticket);
184
208
  const result = await ctx.newSession({
185
209
  setup: async (sm) => {
186
210
  sm.appendSessionInfo(sessionName);
187
- sm.appendCustomEntry("streams-role-selection", {
188
- role: "planner",
189
- selection: requested,
190
- });
211
+ ticket.sessionId = sm.getSessionId();
191
212
  },
192
213
  withSession: async (replacement) => {
193
- await replacement.sendUserMessage(`/streams-activate ${JSON.stringify(prompt)}`, {
194
- expandPromptTemplates: true,
195
- });
214
+ await replacement.sendUserMessage(
215
+ `/streams-activate ${JSON.stringify({ handoff: token })}`,
216
+ {
217
+ expandPromptTemplates: true,
218
+ },
219
+ );
220
+ // Pi displays command errors instead of rejecting sendUserMessage.
221
+ // Require an explicit receipt before submitting the project prompt.
222
+ if (!ticket.activated)
223
+ throw new Error(
224
+ "Codeless replacement activation failed; exit Pi and reopen the stream",
225
+ );
226
+ await replacement.sendUserMessage(prompt, { expandPromptTemplates: true });
196
227
  },
197
228
  });
198
229
  if (result.cancelled)
199
230
  throw new Error("Codeless handoff cancelled; the current session was retained");
200
231
  } finally {
232
+ if (token) handoffs.delete(token);
201
233
  pending = undefined;
202
234
  }
203
235
  },
@@ -233,6 +265,7 @@ export default function plannerExtension(pi) {
233
265
  additionalProperties: false,
234
266
  },
235
267
  async execute(_toolCallId, params) {
268
+ if (!admitted) throw new Error("Codeless handoff requires an activated planner");
236
269
  if (pending) throw new Error("A Codeless handoff is already pending");
237
270
  pending = {
238
271
  changePath: params.changePath.replace(/^@/, ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dpeek/codeless",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "An attended planner and implementer workflow for parallel capability development",
5
5
  "homepage": "https://github.com/dpeek/codeless#readme",
6
6
  "bugs": {
package/prompts/change.md CHANGED
@@ -5,11 +5,11 @@ argument-hint: "<stream-directory> <direction-file>"
5
5
 
6
6
  You are the planner for the stream at `$1`. The current working directory is the stream's repository worktree.
7
7
 
8
- Read the repository guidance, `.codeless/config.json`, `$1/planner.md`, every numbered Markdown file in `$1/changes/`, `$2`, its related contracts, and the current implementation relevant to the stream. Resolve the integration branch from that configuration.
8
+ First read the repository guidance, `$1/planner.md`, and `$2`. Read `.codeless/config.json` to resolve the integration branch. Current direction is authoritative for candidate discovery: do not mine deleted or historical documents for work when it is clear. If `planner.md` shows no active work and current direction has no ungated worthwhile candidate, stop rather than reading more context.
9
9
 
10
- Before proposing, inspect the branch, recent commits, and worktree. If changes are not explained as an active approved change in `planner.md`, stop and show the operator the evidence. Never discard work automatically.
10
+ Before proposing, inspect the branch, recent commits, and worktree. If changes are not explained as an active approved change in `planner.md`, stop and show the operator the evidence. Never discard work automatically. Read the latest numbered change only when active or ambiguous work needs recovery. Read an older numbered change only when `planner.md` identifies its unresolved decision as still relevant.
11
11
 
12
- If the latest numbered change is approved but uncommitted, resume it. If it is committed but unlanded on the configured integration branch, resume review or landing. Otherwise require a clean worktree with no commits outside the configured integration branch, fast-forward to that branch, and reread the direction and affected contracts. Stop on divergence. Once planning begins, keep that stream commit as the proposal's base; do not resynchronize merely because integration advances while the proposal awaits approval. Locked landing owns the later rebase.
12
+ If the latest numbered change is approved but uncommitted, resume it. If it is committed but unlanded on the configured integration branch, resume review or landing. Otherwise select an ungated candidate from current direction, require a clean worktree with no commits outside the configured integration branch, and fast-forward to that branch. After fast-forwarding, reread `$2`, every file affected by incoming commits, and the affected contracts and implementation; do not mine deleted or historical documents. Stop on divergence. Once planning begins, keep that stream commit as the proposal's base; do not resynchronize merely because integration advances while the proposal awaits approval. Locked landing owns the later rebase.
13
13
 
14
14
  Propose exactly one small, complete change and write it to `$1/change.md`:
15
15
 
package/prompts/commit.md CHANGED
@@ -32,6 +32,6 @@ The next planner proposes one change and waits for the operator's `go`; this han
32
32
  There are two expected landing stops:
33
33
 
34
34
  - If another stream owns the integration slot, leave this stream committed where it is, report the owner, and wait. Do not poll, queue, or retry automatically.
35
- - If this stream owns the slot and the rebase conflicts, keep the slot. Resolve the conflicts in this worktree so both the current configured integration branch and the approved change are preserved, stage the resolutions, and continue the rebase with `GIT_EDITOR=true git rebase --continue`. Repeat until the rebase completes, run the relevant checks, then run `codeless land <slug>` again to finish. The slot prevents another automated landing from moving the integration branch while you resolve it.
35
+ - If this stream owns the slot and the rebase conflicts, keep the slot. Resolve the conflicts in this worktree so both the current configured integration branch and the approved change are preserved, stage the resolutions, and continue the rebase with `GIT_EDITOR=true git rebase --continue`. Repeat until the rebase completes. Run focused checks when useful to validate a resolution, then run `codeless land <slug>` again to finish; `land` alone runs the configured full check. The slot prevents another automated landing from moving the integration branch while you resolve it.
36
36
 
37
37
  For any other failure while this stream owns the slot, report the exact state and wait for the operator. The slot remains held for deliberate recovery; never remove the workspace's landing lock automatically or on guesswork.
package/spec/workflow.md CHANGED
@@ -137,26 +137,39 @@ the stream worktree; occupied, mismatched, or ambiguous layouts stop unchanged.
137
137
  The operator must invoke opening from outside those target panes. Reopening an
138
138
  existing managed planner focuses it without installation or another prompt.
139
139
 
140
- The package-owned extension activates creation, reopening, and post-landing
141
- replacement. Before the first project prompt it requires the exact
142
- `<slug>-planner` Pi name, `<slug-with-hyphens-replaced>_planner` Herdr name,
143
- managed interactive readiness, matching foreground worktree, and the current
144
- native Pi session ID/file reported by Herdr's official Pi lifecycle integration.
145
- It verifies `approve_stream_change`, `dispatch_stream_implementer`,
146
- `rework_stream_implementer`, `finish_stream_implementer`, and `next_stream_change`
147
- are active. During replacement, an otherwise-valid previous native session
148
- reference receives a brief bounded synchronization wait; a wrong name, process,
149
- lifecycle source, or worktree fails immediately. Any binding that remains missing
150
- or incompatible stops visibly before `/change`. Activation never repairs names.
151
- The direct `planner` command is removed;
152
- recovery exits Pi deliberately and reopens from another Herdr shell.
153
-
154
- Pi session replacement keeps the managed process and Herdr name while changing
155
- its native conversation reference. Codeless revalidates that new binding before
156
- prompting the replacement. Implementers use the corresponding `_impl` and
157
- `-impl` names. Codeless loads its own extension explicitly; Herdr's official Pi
158
- integration supplies lifecycle and native-session reporting. This boundary was
159
- verified against Herdr 0.8.2 and Pi 0.85.1.
140
+ The package-owned extension admits a newly launched planner after checking its
141
+ exact `<slug>-planner` Pi name, `<slug-with-hyphens-replaced>_planner` Herdr
142
+ name, managed interactive readiness, canonical foreground worktree, official Pi
143
+ lifecycle authority and reporter, and active planner tools. Admission binds the
144
+ process ID, pane, canonical worktree, planner name, and native Pi session ID.
145
+ Activation never repairs names or submits the initial prompt twice. The direct
146
+ `planner` command is removed; recovery exits Pi deliberately and reopens from
147
+ another Herdr shell.
148
+
149
+ Post-landing conversation replacement uses that admission, without querying
150
+ Herdr. After idle and workflow validation, Codeless creates a one-use in-memory
151
+ handoff ticket for the admitted parent and requested model/thinking selection.
152
+ Pi's `newSession` setup binds it to the newly allocated native session ID;
153
+ `withSession` invokes activation in the fresh extension. Activation consumes the
154
+ ticket before checking the same process, pane, canonical worktree and planner
155
+ name, a distinct matching replacement session, effective selection, and active
156
+ tools. Saved session entries cannot authorize a handoff. The ticket registry
157
+ survives Pi's extension module reload within the process, but no ticket survives
158
+ consumption, cancellation, failure, or process exit.
159
+
160
+ Pi displays extension-command errors without rejecting the command submission.
161
+ Codeless therefore requires an explicit activation acknowledgement before
162
+ `withSession` submits the project prompt exactly once through the replacement
163
+ context. Missing acknowledgement stops visibly. Neither old Pi contexts nor
164
+ Herdr's asynchronous lifecycle or native-session snapshots participate in
165
+ replacement activation. Delayed, missing, or rejected lifecycle reports cannot
166
+ block this local handoff. Cancellation retains the old conversation; failed
167
+ replacement requires deliberate exit and reopen, with no automatic retry.
168
+
169
+ Herdr's official Pi integration remains responsible for lifecycle monitoring,
170
+ external agent control, and native-session restore reporting. Implementers use
171
+ the corresponding `_impl` and `-impl` names. Codeless loads its own extension
172
+ explicitly. This boundary was checked against Herdr 0.8.2 and Pi 0.85.1.
160
173
 
161
174
  ## Dispatch and review
162
175
 
@@ -213,7 +226,8 @@ integration commit.
213
226
  If the integration branch advanced, landing rebases the single stream commit. It then
214
227
  rereads and runs the configured project check in the stream worktree, requires checks to
215
228
  leave the worktree clean, and fast-forwards the dedicated integration checkout. Only
216
- successful completion releases the lock.
229
+ successful completion releases the lock. Conflict recovery may run focused checks to
230
+ validate resolutions, but only `land` runs the configured full check after rebase.
217
231
 
218
232
  A lock owned by another stream stops landing without polling. A rebase conflict, failed
219
233
  check, or other error after acquisition retains this stream's lock for deliberate
@@ -234,7 +248,13 @@ validates the updated direction, prompts, and planner selection, and returns the
234
248
  session name and `/change` prompt. The extension replaces the Pi session in the same
235
249
  pane, preserves its name, activates the validated selection and planner identity, and
236
250
  only then sends the project prompt. Conversation history is not copied; the journal and
237
- project files carry durable context.
251
+ project files carry durable context. The prompt first reads repository guidance, the
252
+ journal, and current direction; it stops when work is wholly gated. It reads the latest
253
+ numbered change only for active or ambiguous recovery and older changes only for
254
+ journal-identified unresolved decisions. Current direction selects candidates before
255
+ relevant contracts and implementation are inspected. After a baseline fast-forward, it
256
+ rereads current direction plus files affected by incoming commits, affected contracts,
257
+ and code, without mining deleted or historical documents for work.
238
258
 
239
259
  A cancelled or failed replacement stops for operator attention. Landing remains
240
260
  complete, and any successful preparation fast-forward remains applied. There is no
@@ -267,10 +287,8 @@ an approval source, or a recovery mechanism.
267
287
  ## Limits
268
288
 
269
289
  Codeless is attended and intentionally has no supervisor, project registry, queue,
270
- automatic landing retry, stale-lock recovery, or unattended approval. Planner startup
271
- reads every numbered change, and conflict recovery currently causes the configured
272
- landing check to run twice. Because the default state is ignored, `git clean -fdx` can
273
- delete it.
290
+ automatic landing retry, stale-lock recovery, or unattended approval. Because the
291
+ default state is ignored, `git clean -fdx` can delete it.
274
292
 
275
293
  The single-active-change rule and the requirement to dispatch only approved input still
276
294
  partly depend on planner instructions. Approval reconciles records and hashes but does