@ctrl-spc/cs 0.7.0 → 0.7.1

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.
@@ -122,6 +122,16 @@ const OWNER_FILE = 'ctrl-spc-owner.json';
122
122
  export function codexHomesRoot() {
123
123
  return join(configDir(), 'codex-homes');
124
124
  }
125
+ /** Persistent isolated homes belong only to panel3 conversation owners. They
126
+ * are separate from `codexHomesRoot()` so the per-process PID sweep can keep
127
+ * its existing contract without deleting a resumable owner conversation. */
128
+ export function panel3CodexOwnerHomesRoot() {
129
+ return join(configDir(), 'panel3-codex-owner-homes');
130
+ }
131
+ export function panel3CodexOwnerHomePath(ownerRunId) {
132
+ const key = ownerRunId.replace(/[^a-zA-Z0-9-]/g, '_');
133
+ return join(panel3CodexOwnerHomesRoot(), key);
134
+ }
125
135
  /** Where this run's throwaway codex home lives. Inside the product's own config
126
136
  * dir, beside `scratch/`, so it is the user's own directory and it is obvious
127
137
  * what wrote it. Keyed by request so two concurrent runs never share one. */
@@ -197,7 +207,7 @@ export function codexRunConfigToml(server, runTodoId, runtimePlatform = process.
197
207
  * only v3's `startAgent` passes it off. Never inferred from `server`'s shape:
198
208
  * v2 and v3 both pass `{ url }` here, so the shape cannot tell them apart.
199
209
  */
200
- subagentsEnabled = true) {
210
+ subagentsEnabled = true, persistentPanelOwner = false) {
201
211
  const lines = [
202
212
  '# Written by CTRL+SPC for ONE codex run. Not the user\'s config; never read',
203
213
  '# by anything but the worker this was written for.',
@@ -207,6 +217,13 @@ subagentsEnabled = true) {
207
217
  only for that bundled runtime; other platforms keep Codex's own model
208
218
  selection. */
209
219
  ...(runtimePlatform === 'win32' ? ['model = "gpt-5.5"', ''] : []),
220
+ ...(persistentPanelOwner ? [
221
+ 'sandbox_mode = "workspace-write"',
222
+ '',
223
+ '[sandbox_workspace_write]',
224
+ 'network_access = true',
225
+ '',
226
+ ] : []),
210
227
  '[features]',
211
228
  /* The account's own connectors, which no config.toml grants and no argv
212
229
  removes. See the block comment above. */
@@ -340,6 +357,50 @@ subagentsEnabled = true, runtimePlatform = process.platform) {
340
357
  return null;
341
358
  }
342
359
  }
360
+ /**
361
+ * Create or refresh the isolated home for one durable panel3 Level 2 owner.
362
+ * Unlike `ensureCodexRunHome`, this never removes the directory: Codex's native
363
+ * session files inside it are the state a later activation resumes. Only the
364
+ * credential copy and current sealed MCP configuration are refreshed.
365
+ */
366
+ export function ensurePanel3CodexOwnerHome(server, ownerRunId, runtimePlatform = process.platform) {
367
+ const source = join(userCodexHome(), 'auth.json');
368
+ if (!existsSync(source))
369
+ return null;
370
+ const home = panel3CodexOwnerHomePath(ownerRunId);
371
+ try {
372
+ mkdirSync(home, { recursive: true, mode: 0o700 });
373
+ chmodSync(home, 0o700);
374
+ copyFileSync(source, join(home, 'auth.json'));
375
+ writeFileSync(join(home, 'config.toml'), codexRunConfigToml(server, null, runtimePlatform, false, true), { mode: 0o600 });
376
+ return home;
377
+ }
378
+ catch {
379
+ return null;
380
+ }
381
+ }
382
+ /** Product-owned persistent state only. Windows owners use the installed Codex
383
+ * home, so this path is normally absent there and removal remains harmless. */
384
+ export function removePanel3CodexOwnerHome(ownerRunId) {
385
+ try {
386
+ rmSync(panel3CodexOwnerHomePath(ownerRunId), { recursive: true, force: true });
387
+ }
388
+ catch {
389
+ // Reconciliation retries. Cleanup must never turn a completed turn into a failure.
390
+ }
391
+ }
392
+ /** Valid directory names only; callers reconcile each id against the signed-in
393
+ * owner row before retaining it. */
394
+ export function listPanel3CodexOwnerHomeIds() {
395
+ try {
396
+ return readdirSync(panel3CodexOwnerHomesRoot(), { withFileTypes: true })
397
+ .filter((entry) => entry.isDirectory())
398
+ .map((entry) => entry.name);
399
+ }
400
+ catch {
401
+ return [];
402
+ }
403
+ }
343
404
  /**
344
405
  * Delete a run's home once the run is over.
345
406
  *
@@ -90,8 +90,7 @@
90
90
  * can look — is named beside it.
91
91
  *
92
92
  * ---------------------------------------------------------------------------
93
- * ═══ A DISPATCHED AGENT IS TOLD ITS OWN JOB AND NOTHING ABOUT THE
94
- * CONVERSATION. ═══
93
+ * ═══ A DISPATCHED WORK BRIEF HOLDS THE JOB, NOT THE CONVERSATION. ═══
95
94
  *
96
95
  * ux.md: "a fresh subagent needs its task, the interfaces it touches, and the
97
96
  * global constraints. Nothing else", with the measured cost of the alternative —
@@ -99,9 +98,10 @@
99
98
  *
100
99
  * So `workBrief` carries exactly two things written by judgement, the
101
100
  * responsibility and the boundary, and the facts the product attaches. It
102
- * carries NO messages, no earlier replies and no history: what the person said
103
- * reaches it only insofar as whoever dispatched it put it in the
104
- * responsibility, which is that agent's job to get right.
101
+ * carries NO messages, no earlier replies and no history. A level 3 worker gets
102
+ * that brief directly. A level 2 owner gets it inside `ownerActivationPrompt`,
103
+ * beside the visible conversation read from the card rather than a launcher
104
+ * retelling of it.
105
105
  *
106
106
  * ═══ AND IT IS THE SAME VOICE, WHICH IS WHY THE OUTBOUND RULES ARE SHARED. ═══
107
107
  * ux.md: "the brief is the same at every level. Nothing in a reply reveals which
@@ -142,7 +142,20 @@
142
142
  const whatYouSendBack = (produced) => [
143
143
  'WHAT YOU SEND BACK',
144
144
  'Your reply is the whole of what they read, so write it to them, about their own work.',
145
- '- Be brief. They are looking for the sentence that answers them, not for a report.',
145
+ '- By default, be very brief: use the shortest accurate reply that lets them understand the',
146
+ ' outcome, decide, or unblock the work.',
147
+ '- A routine update is absent unless it is useful, and then it is one short sentence or phrase.',
148
+ '- An ordinary completion starts with the outcome, includes only the result they need, and is one',
149
+ ' or two short sentences; never add a third.',
150
+ '- If the work cannot continue without a decision or input only the person can supply, you MUST',
151
+ ' call `ask_question`. This includes asking them to choose between options. That tool is the only',
152
+ ' person-question path: writing the question in an ordinary reply or completing the card with it',
153
+ ' is wrong. After the call, stop without repeating the question in an ordinary reply. Use only',
154
+ ' the minimum context they need to answer safely.',
155
+ '- Do not copy explanations, logs, prompts, internal reasoning, test inventories or internal',
156
+ ' orchestration into an ordinary reply.',
157
+ '- If they explicitly ask for detail, give it for that reply, then return to the brief default.',
158
+ ' Accuracy and truthful failures are never shortened away.',
146
159
  '- Name the real things: the actual items, projects and people, by the names they have.',
147
160
  ...produced,
148
161
  '- Never name the machinery. Words like agent, run, dispatch, level, card, turn, coordinator,',
@@ -196,12 +209,20 @@ const WHILE_YOU_ARE_WORKING = [
196
209
  * ═══ WHAT THE PERSON ATTACHED, AS A FACT AND NOTHING MORE. ═══
197
210
  *
198
211
  * plan-work-items.md constraint 5: "no agent is asked in prose to go and find
199
- * what a card is about." So this is one sentence saying it happened and to whom
200
- * it belongs, and nothing more: not "read these before you answer", not "use
201
- * get_work_item on each one", nothing that tells the agent what to DO. It
202
- * already holds `get_work_item` and `list_work_items`; what it cannot discover
203
- * on its own is that these specific ids are the ones the person meant, and that
204
- * is the one fact stated here.
212
+ * what a card is about." So the LIST is one sentence saying the attaching
213
+ * happened and to whom it belongs, and nothing more. What it cannot discover on
214
+ * its own is that these specific ids are the ones the person meant, and that is
215
+ * the fact the list states.
216
+ *
217
+ * ═══ EACH KIND THEN GETS ONE SENTENCE SAYING WHAT THAT WORD MEANS. ═══ Three
218
+ * of them now, and the work item's is the one added last (the
219
+ * `work-lands-on-the-item-6` slice, off ux.md's "before an agent begins working
220
+ * on it, it must read the description, and any artifacts"). An earlier draft of
221
+ * this comment said none of them may tell the agent what to DO; that was true
222
+ * when a work item was only a subject. It is not the same rule as constraint 5,
223
+ * which forbids sending an agent LOOKING for which item a card is about. The id
224
+ * is handed over here by the product; reading the item it was handed is not
225
+ * finding it.
205
226
  *
206
227
  * SHARED between `levelOnePrompt` and `workBrief` for the same reason
207
228
  * `whatYouSendBack` is: one wording for "what was attached", never two that
@@ -215,13 +236,35 @@ const whatWasAttached = (attachments) => {
215
236
  'WHAT THE PERSON ATTACHED',
216
237
  'They attached these when they sent this, so what is asked belongs against them.',
217
238
  ...personAttachments,
218
- /* ═══ THE ONE KIND THAT IS AN INSTRUCTION RATHER THAN A SUBJECT. ═══ A
219
- work item is what the work is ABOUT, and the paragraph above already
220
- says so. A skill is not: it is a method a person chose, and an agent
221
- that read it as one more thing the request belongs against would quote
222
- it back and carry on doing the work its own way. That is the fact it
223
- cannot discover from the line above, so it is the one sentence added,
224
- and the tool's own description carries the rest.
239
+ /* ═══ READ BEFORE YOU DECIDE, WHICH IS THE WHOLE OF THIS SLICE. ═══ ux.md:
240
+ "Before an agent begins working on it, it must read the description, and
241
+ any artifacts, and intelligently decide what to do next." An agent that
242
+ decides first and reads afterwards writes a second plan onto an item that
243
+ already carries one, against a description it never opened.
244
+
245
+ ═══ AND IT LIVES HERE, NOT UNDER `THE WORK ITEM THIS IS ABOUT`. ═══ That
246
+ header is the obvious home and it is the wrong one: `workItemId` comes
247
+ only from `dispatch`'s OPTIONAL `work_item_id`, which no prompt asks a
248
+ launcher to supply, and which the launcher MUST NOT supply when several
249
+ items are attached (the selection is the person's, below). So at exactly
250
+ the moment reading the items decides the answer, that header is absent.
251
+ This block is written from the person's own attachment rows, travels to
252
+ every dispatch on the card, and is shared by `levelOnePrompt` and
253
+ `workBrief`, so the sentence reaches all three levels. The tools it needs
254
+ — `get_work_item`, `list_artifacts`, `get_artifact` — are `levels: ALL`.
255
+
256
+ NO TOOL NAMES IN IT. `get_work_item` already says it returns "its
257
+ description, status, placement, and the artifacts on it", and
258
+ `list_artifacts` already ends "Use get_artifact to read one". The prompt
259
+ carries the obligation; the tool descriptions carry the mechanics. */
260
+ 'A `work_item` is what the work is about. Read its description and the artifacts already on it',
261
+ 'before you decide anything.',
262
+ /* ═══ THE KIND THAT IS A METHOD RATHER THAN A SUBJECT. ═══ A skill is not
263
+ one more thing the request belongs against: it is a method a person
264
+ chose, and an agent that read it as a subject would quote it back and
265
+ carry on doing the work its own way. That is the fact it cannot discover
266
+ from the line above, so it is the sentence added, and the tool's own
267
+ description carries the rest.
225
268
 
226
269
  ═══ SAID WHENEVER ANYTHING IS ATTACHED, WHICH IS A CHOICE AND NOT AN
227
270
  ACCIDENT. ═══ Both callers hold the rows, `kind` and all, so narrowing
@@ -249,6 +292,21 @@ const whatWasAttached = (attachments) => {
249
292
  prompt. */
250
293
  'A `credential` is a secret the person authorised for this work. Its name is above; the value',
251
294
  'is readable only by the agents doing the work, with `get_credential`.',
295
+ /* ═══ THE KIND THAT IS A SEQUENCE RATHER THAN A METHOD OR A SUBJECT. ═══ A
296
+ workflow says what happens in what order, and the fact an agent cannot
297
+ discover from the line above is that ONE of the three levels is the one
298
+ that reads it. So this names the ROLE rather than instructing anybody,
299
+ following the credential sentence's shape of naming a tool the reader may
300
+ not hold: `get_workflow` is level 2's alone, and `levelOnePrompt` already
301
+ calls the level 2 agent "the owner", so the word lands at every level this
302
+ one sentence reaches.
303
+
304
+ AND IT SAYS NOTHING ABOUT WORKING STAGES ONE AT A TIME. That rule
305
+ overrides a default written in `workBrief`'s own IF YOU SPLIT IT block, so
306
+ it lives inside that block and nowhere else. A second copy here would be a
307
+ second place for it to drift. */
308
+ 'A `workflow` is the process this work follows: stages, in an order it gives. The owner of this',
309
+ 'conversation is the one who reads it, with `get_workflow`.',
252
310
  ];
253
311
  return [
254
312
  ...(codebases.length === 0 ? [] : [
@@ -316,8 +374,9 @@ const projectCodebases = (codebases) => {
316
374
  */
317
375
  export function levelOnePrompt(cardTitle, messages, produced, attachments = [], codebases = null) {
318
376
  return [
319
- 'You are the one place a person talks to in CTRL+SPC. You talk to them, and you coordinate',
320
- 'the work. You do not do the work yourself.',
377
+ 'You are the short-lived launcher for one CTRL+SPC conversation. You do not do the work and',
378
+ 'you do not answer the person. Your whole job is to clarify the destination once if you truly',
379
+ 'cannot choose it, or launch exactly one owner and then exit.',
321
380
  '',
322
381
  'WHAT YOU CAN SEE',
323
382
  'Your tools read the record. Read as much of it as the question needs.',
@@ -331,11 +390,12 @@ export function levelOnePrompt(cardTitle, messages, produced, attachments = [],
331
390
  'makes every true one worthless.',
332
391
  '',
333
392
  'WHAT YOU CAN SEND SOMEBODY TO DO',
334
- 'Anything that means reading or changing code, you send somebody to do. They work in a copy of',
335
- 'the codebase and they write to this person themselves when they are done, in their own words,',
336
- 'which nobody edits on the way. So do not wait for them, do not offer to come back with what',
337
- 'they find, and do not describe what they are going to find. Say what is being looked at, and',
338
- 'let the answer arrive from whoever finds it.',
393
+ 'Launch one owner for the conversation. Give it the complete responsibility and boundary. If',
394
+ 'the request belongs to a registered codebase, name it. If it is record-only work, launch it',
395
+ 'without a codebase. The owner talks to the person and may send workers of its own.',
396
+ 'Your only question is a destination or codebase you truly cannot choose. NEVER ask the person',
397
+ 'to make a work or product decision. When one registered codebase clearly fits, launch its owner',
398
+ 'even if the work itself needs the person to choose between options; the owner asks that question.',
339
399
  ...projectCodebases(codebases),
340
400
  '',
341
401
  ...WHILE_YOU_ARE_WORKING,
@@ -355,22 +415,17 @@ export function levelOnePrompt(cardTitle, messages, produced, attachments = [],
355
415
  '',
356
416
  ...messages.map((body, i) => `${i + 1}. ${body}`),
357
417
  '',
358
- ...whatYouSendBack([
359
- '- What you make goes IN the thing you make, and not in here. Anything you create is already in',
360
- ' front of them as something they can open, so name what you made and stop there. Writing its',
361
- ' contents out again leaves them holding two copies of the same words, and the one they can edit',
362
- ' is not this one.',
363
- ]),
418
+ 'AFTER YOU LAUNCH THE OWNER, EXIT IMMEDIATELY. Do not write a reply, wait for it, summarize what',
419
+ 'it will do, or ask another question. CTRL+SPC publishes no stdout from this launcher.',
364
420
  ].join('\n');
365
421
  }
366
422
  /**
367
423
  * What an agent sent to do a piece of work is told: its own job, the facts of
368
424
  * where it is, and nothing about the conversation it came out of.
369
425
  *
370
- * ═══ THIS IS BOTH THE PROMPT AND THE BRIEF. ═══ It is what the process is
371
- * started with AND what `panel3_runs.brief` stores, exactly as level 1's prompt
372
- * is, so what `cs3 show` prints is the thing the agent was actually sent rather
373
- * than a description of it.
426
+ * ═══ THIS IS THE IMMUTABLE BRIEF. ═══ `panel3_runs.brief` stores it exactly.
427
+ * A level 3 process is started with it directly; a level 2 owner's activation
428
+ * prompt carries it verbatim with the live conversation around it.
374
429
  *
375
430
  * ═══ AND IT CARRIES NO PATH, WHICH IS CONSTRAINT 6. ═══ The working copy is
376
431
  * the directory the process is started in, so it never has to be named — and
@@ -388,28 +443,121 @@ export function levelOnePrompt(cardTitle, messages, produced, attachments = [],
388
443
  * from nothing but a responsibility sentence somebody else wrote down.
389
444
  */
390
445
  export function workBrief(level, responsibility, boundary, workItemId, attachments = [], codebase) {
446
+ /* ═══ ONLY WHEN A WORKFLOW IS ACTUALLY ON THE CARD. ═══ An owner is not
447
+ instructed about a process that is not there, which is the rule
448
+ `personAttachmentLines` already keeps by rendering nothing when nothing is
449
+ attached. The same prefix `whatWasAttached` partitions on, since both read
450
+ the one line `attachmentLine` writes. */
451
+ const hasWorkflow = attachments.some((attachment) => attachment.startsWith('workflow '));
391
452
  return [
392
453
  ...(level === 2
393
454
  ? [
394
- 'You see one thing a person asked for through to done. You may do the whole of it yourself,',
395
- 'or split it and send others to do the parts.',
455
+ 'You see one thing a person asked for through to done. Keep the whole picture and stay',
456
+ 'available to the person while the work moves.',
457
+ '',
458
+ /* ═══ THE FIRST THING, EVERY TIME, AND IT IS NOT THE REPLY. ═══ Nothing
459
+ an owner writes reaches the person until its process exits, and a turn
460
+ that exits by asking writes no message at all. So on the walk of
461
+ 2026-08-24 "Write a plan for this item" was silent for two and a half
462
+ minutes and then said a question, twice over, and Lane's requirement
463
+ is that every message he sends is acknowledged. `say` is the only
464
+ thing that can do that, because an acknowledgement cannot be an exit.
465
+ WRITTEN AS THE FIRST INSTRUCTION IN THE BRANCH because it is the first
466
+ act, and a rule about what to do first that arrives after four
467
+ paragraphs about the work is read after the work has started. */
468
+ 'FIRST, SAY WHAT YOU ARE GOING TO DO',
469
+ 'They have seen their own message and nothing else. Before you start, call `say` with one or',
470
+ 'two sentences: what you are about to do about what they asked, and what they will get back.',
471
+ 'Do this every time you are started with something new from them, including the very first',
472
+ 'time, and before any reading that takes a while. It does not end your turn and it is not',
473
+ 'your reply; you carry on straight afterwards.',
396
474
  '',
397
- 'WHICH OF THE TWO, DECIDED BEFORE YOU START READING',
398
- 'Name the parts of this job first: pieces that do not need each other\'s answers. More than',
399
- 'one such part means send one person per part, now, rather than working through them',
400
- 'yourself. Several files to read, several places to check, several questions asked at once',
401
- 'are all that case, and reading them one after another leaves the person waiting for the',
402
- 'whole queue when they could have waited for the longest one.',
403
- 'Do it yourself when it is genuinely one thread: one file, one question, or parts that each',
404
- 'need the last one\'s answer.',
475
+ 'TRACEABILITY BEFORE A CODEBASE FILE CHANGES',
476
+ 'A codebase-file change means ANY file change: source, config, docs, tests, migrations,',
477
+ 'generated inputs, and assets all count. "Non-code" means no codebase file changes at all.',
478
+ 'When this work has a Work Item and will change a codebase file, you may investigate',
479
+ 'read-only first. Any investigation you send must explicitly forbid file edits. Create one',
480
+ 'useful `analysis` or `plan` artifact on the exact Work Item, carrying the document the work',
481
+ 'is missing: a spec, a PRD, user stories or a repro is its CONTENT, not a second artifact.',
482
+ 'For a bug: what reproduces it, the behaviour it has now, the behaviour it should have, the',
483
+ 'cause and the correction. For something new: what a person will be able to do, and how you',
484
+ 'would know it works.',
485
+ 'If several Work Items were supplied, one is selected only when the person identifies it by',
486
+ 'name, id, or an unambiguous reference in a message or answer. That selection stays in force',
487
+ 'unless the person changes it. An attachment label, its order, similarity, or which seems most',
488
+ 'likely is NEVER a selection by itself; the person explicitly referring to that label IS a',
489
+ 'selection.',
490
+ 'Until one is selected, do not',
491
+ 'call `update_work_item`, create or update an artifact, dispatch work, or change any file.',
492
+ 'Call `ask_question` once with no `work_item_id`, `answer_mode: single_select`, and the exact',
493
+ 'attached Work Item names as options, and then stop.',
494
+ 'Present the artifact with `ask_question`: name both `work_item_id` and',
495
+ '`related_artifact_id`, use `answer_mode: single_select` with `approve` and',
496
+ '`request changes`, and stop. Do not edit any file OR dispatch implementation until the',
497
+ 'artifact revision that question presented is approved. If changes are requested, use',
498
+ '`update_artifact` on the SAME artifact, present its new revision in a new approval question,',
499
+ 'and apply that same no-edit, no-implementation-dispatch fence until it is approved.',
500
+ 'The only bypass is the person explicitly asking you to change codebase files WITHOUT a',
501
+ 'Work Item artifact. Urgency, smallness, silence, or a generic "go ahead" is not a bypass.',
502
+ 'When no codebase file will change, this gate does not apply.',
405
503
  '',
406
- 'READING CANNOT COLLIDE, so a job that is only reading has nothing to be careful about: send',
407
- 'as many as there are parts. The care below is about CHANGING files, and only that.',
504
+ 'WHO DOES THE WORK IS YOUR JUDGEMENT',
505
+ 'Substantial implementation, investigation, testing, and other substantial work normally',
506
+ 'goes to the people you send. Before you start substantial multi-part work, send at least',
507
+ 'one meaningful independent implementation, investigation, or test part. Multiple independent',
508
+ 'file, module, or test parts belong with the people you send by default; do not keep all of',
509
+ 'them just because you could complete every piece yourself.',
510
+ 'Skip sending only when the whole turn is very small, or when sending any meaningful',
511
+ 'independent part would genuinely make the result slower or less accurate. You keep the full',
512
+ 'picture, answer the person, and put the results together.',
513
+ /* ═══ SENDING COMES BEFORE READING, AND THAT IS ABOUT THE PERSON RATHER
514
+ THAN ABOUT THROUGHPUT. ═══ The person reads nothing until this process
515
+ exits, so every minute the owner spends reading the codebase before it
516
+ sends anybody is a minute of pulse and no words. Measured on
517
+ 2026-08-24: 101 seconds to the first message, of which about 80 were
518
+ the owner reading first and sending second.
519
+ IT IS STILL NOT A GATE. `ux.md` deleted the manager that never touches
520
+ code because that shape "required the coordinator to predict, at
521
+ dispatch time, whether a request was big. It cannot", and Issue 3 made
522
+ this judgement "never a mechanical gate". So this is which order to do
523
+ two things the owner was already going to do, not a rule about
524
+ whether. The paragraph above still decides whether. */
525
+ '',
526
+ 'WHEN YOU ARE GOING TO SEND SOMEBODY, SEND THEM FIRST',
527
+ 'Read what you need to write their piece, and no more; the rest of the reading is theirs to',
528
+ 'do. Nothing you write reaches the person until you stop, so every minute you spend reading',
529
+ 'before you send anybody is a minute they watch a line with no answer coming.',
408
530
  '',
409
531
  'IF YOU SPLIT IT',
410
532
  'Everybody you send works in the SAME copy of the codebase as everybody else, at the same',
411
533
  'time. Nothing keeps them out of each other\'s way, so anybody CHANGING files needs a piece',
412
534
  'that touches nobody else\'s files, or has to be sent on their own.',
535
+ /* ═══ AND A WORKFLOW OVERRIDES THAT DEFAULT, INSIDE THE BLOCK THAT SETS
536
+ IT. ═══ Six lines above, the owner is told the opposite: multiple
537
+ independent parts belong with the people it sends, all working at the
538
+ same time. A workflow's stages ARE multiple parts, so without a
539
+ precedence rule the owner sends them all at once, the read-back
540
+ barrier restarts it ONCE, and the order the person chose the workflow
541
+ for is gone. Nothing in the record refuses a second dispatch before
542
+ the first has ended — the barrier re-arms an owner when EVERY child
543
+ has finished, which sequences stages only because the owner sends one
544
+ at a time. So the rule is here, in the immutable brief that is
545
+ replayed verbatim on every restart, and therefore in front of the
546
+ owner at every stage boundary rather than only the first.
547
+
548
+ THE SECOND SENTENCE IS NOT DECORATION. A dispatched worker gets a
549
+ working copy and the responsibility and boundary AND NOTHING ELSE, and
550
+ `get_workflow` is this level's alone. Without it, the product takes
551
+ deliberate trouble to hand the owner an untruncated stage document and
552
+ the agent that performs the stage receives a paraphrase of it. */
553
+ ...(hasWorkflow
554
+ ? [
555
+ 'A WORKFLOW IS ATTACHED, AND ITS STAGES GO OUT ONE AT A TIME. Send somebody for the',
556
+ 'first stage only, and do not start the next stage until the one before it has come',
557
+ 'back. When you send somebody for a stage, put that stage\'s own document into what you',
558
+ 'give them, in full: they cannot read the workflow themselves.',
559
+ ]
560
+ : []),
413
561
  'What you have already sent somebody to do is written down, and you are handed that list',
414
562
  'rather than having to remember it. Anything on it has been sent. Nothing on it is sent again.',
415
563
  'When every one of them has finished you are started again, with what each of them wrote,',
@@ -417,13 +565,15 @@ export function workBrief(level, responsibility, boundary, workItemId, attachmen
417
565
  'through you, so the reply that covers all of it is yours to write and nobody else\'s.',
418
566
  'WHICH MEANS THE REPLY YOU WRITE NOW IS NOT THAT ANSWER. If you have sent anybody, you are',
419
567
  'stopping in the middle, and what you write as you stop goes to the person as a message from',
420
- 'you. So make it one short sentence at most, or nothing worth reading twice: the real answer',
421
- 'is the one you write when you come back. What is happening meanwhile belongs on the line',
422
- 'below, which is where they are already watching it.',
568
+ 'you. Write it. One short sentence, naming what has gone out and what you are waiting on:',
569
+ 'the real answer is the one you write when you come back. Never stop silently. What you said',
570
+ 'with `say` was about to happen; this is what has happened and what is still to come.',
423
571
  ]
424
572
  : [
425
- 'You change the code. One piece of the work, the one below, and nothing else. Whoever sent',
426
- 'you is doing the rest.',
573
+ 'You own one piece of the work, the one below, and nothing else. Whoever sent you is doing',
574
+ 'the rest. If your responsibility is investigation-only, read and report without changing',
575
+ 'any file. The conversation owner handles the Work Item artifact and approval; do not',
576
+ 'duplicate that record. If you were given approved implementation work, make that change.',
427
577
  '',
428
578
  'Somebody else may be working in this same copy of the codebase at the same time as you, on',
429
579
  'a different piece. Stay inside what you were given and leave the rest of it alone.',
@@ -443,15 +593,33 @@ export function workBrief(level, responsibility, boundary, workItemId, attachmen
443
593
  ] : []),
444
594
  '',
445
595
  'WHERE YOU ARE',
446
- 'The directory you are in is a working copy of the codebase. Read it, run things in it, and',
447
- 'change it where that is what you were asked for. Name a file by its path inside this copy,',
448
- 'never by the full path from the root of this machine: the full one means nothing to the person',
449
- 'reading and is not ours to hand around.',
596
+ ...(codebase
597
+ ? [
598
+ 'The directory you are in is a working copy of the codebase. Read it, run things in it, and',
599
+ 'change it where that is what you were asked for. Name a file by its path inside this copy,',
600
+ 'never by the full path from the root of this machine: the full one means nothing to the person',
601
+ 'reading and is not ours to hand around.',
602
+ ]
603
+ : [
604
+ 'The directory you are in is an empty private workspace. No codebase checkout was attached to',
605
+ 'this conversation. Work from the record and the tools you have; do not claim to have read or',
606
+ 'changed project files.',
607
+ ]),
450
608
  '',
451
609
  'WHAT YOU WERE AND WERE NOT GIVEN',
452
- 'What is above is the whole of it. You have not seen what anybody said, and you are not meant',
453
- 'to: what matters was written into what you own. If something you genuinely need is missing,',
454
- 'say so plainly in your reply rather than guessing at it.',
610
+ ...(level === 2
611
+ ? [
612
+ 'This brief is your immutable responsibility and boundary. The visible conversation is',
613
+ 'handed to you separately, in chronological order, whenever you are started. Read it as the',
614
+ 'person wrote it; the launcher does not stand in for it. If something you genuinely need is',
615
+ 'missing and only the person can supply it, you MUST call `ask_question` and stop instead',
616
+ 'of saying it in your reply. Otherwise say what is missing plainly rather than guessing.',
617
+ ]
618
+ : [
619
+ 'What is above is the whole of it. You have not seen what anybody said, and you are not meant',
620
+ 'to: what matters was written into what you own. If something you genuinely need is missing,',
621
+ 'say so plainly in your reply rather than guessing at it.',
622
+ ]),
455
623
  '',
456
624
  'WHAT CANNOT BE DECIDED HERE',
457
625
  'Some things are not yours to settle, and guessing at one of those quietly is worse than any',
@@ -478,6 +646,105 @@ export function workBrief(level, responsibility, boundary, workItemId, attachmen
478
646
  : whatAWorkerSendsBack()),
479
647
  ].join('\n');
480
648
  }
649
+ export const presentedArtifactAnswerContext = (artifact) => artifact === null
650
+ ? []
651
+ : artifact.answer === 'request changes'
652
+ ? [
653
+ '',
654
+ 'ARTIFACT REVISION THIS ANSWER SENT BACK FOR CHANGES',
655
+ `Artifact ${artifact.id}, revision ${artifact.revision}.`,
656
+ 'Do not edit any codebase file or dispatch implementation. Revise this SAME artifact as the',
657
+ 'person requested, present its new revision in a new approval question, and stop.',
658
+ ]
659
+ : [
660
+ '',
661
+ 'ARTIFACT REVISION THIS ANSWER APPROVED',
662
+ `Artifact ${artifact.id}, revision ${artifact.revision}.`,
663
+ 'Before editing any file or dispatching implementation, call `get_artifact` and compare its',
664
+ 'current revision with this one. If they differ, this answer does not approve the current body:',
665
+ 'present the current revision in a new approval question and stop.',
666
+ ];
667
+ /**
668
+ * ═══ WHAT A RUN IS TOLD WHEN A PROCESS OF ITS OWN ENDED BEFORE IT FINISHED.
669
+ * ═══
670
+ *
671
+ * Named once here rather than written inline, because `respawnPrompt` says both
672
+ * of these too, in its own longer wording (`WHAT YOU ALREADY SENT SOMEBODY TO
673
+ * DO`, and `CARRY ON FROM WHERE THAT LEAVES YOU`). **The two must move
674
+ * together.** They are not folded into one string because doing that would
675
+ * rewrite prose that is already shipped and proven on the retry and recovery
676
+ * paths, which is not this slice's assignment; naming them is what makes the
677
+ * pairing findable by the next person to edit either.
678
+ *
679
+ * ═══ THEY SAY WHAT TO DO, AND NEVER WHY IT HAPPENED. ═══ Three things end a
680
+ * process early and they are not the same event: a harness that crashed, a
681
+ * machine that went down, and a person who said the work was going the wrong
682
+ * way. `resumePrompt` and `retryPrompt` exist as two functions precisely
683
+ * because telling an agent an untrue reason for its restart is what this file
684
+ * exists to prevent, so a sentence shared by all three may only say the part
685
+ * that is true of all three.
686
+ */
687
+ const DO_NOT_SEND_SOMEBODY_TWICE = 'Do not send somebody to do a piece of work that is already in this list. Anything of theirs '
688
+ + 'still going is still going, and asking again for what they are already doing costs the person twice.';
689
+ const LOOK_AT_THE_WORK_ITSELF = 'A process of yours ended before it finished, so what you last wrote down is what you had time to '
690
+ + 'write, not everything that happened. Where your notes are silent, look at the work itself rather '
691
+ + 'than starting it over.';
692
+ /**
693
+ * The complete one-shot context for the durable conversational owner.
694
+ *
695
+ * Visible history is supplied as one ordered list. A newly leased message is
696
+ * marked in place rather than appended, and a question plus its canonical
697
+ * answer is one event, so neither can occur twice in the prompt.
698
+ */
699
+ export function ownerActivationPrompt(brief, report, events, children, childQuestion, currentArtifactAnswer = null,
700
+ /** A process of this run's own ended before it finished, so the working copy
701
+ * may hold work no report mentions and the worker list may hold one it
702
+ * dispatched a moment before. Never says WHICH ending it was: see
703
+ * `LOOK_AT_THE_WORK_ITSELF`. */
704
+ afterAProcessEnded = false) {
705
+ const history = events.map((event) => {
706
+ if (event.kind === 'question') {
707
+ return [
708
+ `CTRL+SPC QUESTION ${event.body}`,
709
+ event.answer ? `PERSON ANSWER ${event.answer}` : 'PERSON ANSWER waiting',
710
+ ...(event.relatedArtifactId && event.relatedArtifactRevision
711
+ ? [`PRESENTED ARTIFACT ${event.relatedArtifactId}, revision ${event.relatedArtifactRevision}`]
712
+ : []),
713
+ ...presentedArtifactAnswerContext(currentArtifactAnswer?.questionId === event.id
714
+ ? currentArtifactAnswer
715
+ : null),
716
+ ].join('\n');
717
+ }
718
+ const who = event.kind === 'user' ? 'PERSON' : 'CTRL+SPC';
719
+ return `${who}${event.needsReply ? ` — REPLY TO THIS NOW (turn ${event.id})` : ''}\n${event.body}`;
720
+ });
721
+ return [
722
+ 'YOU OWN THIS CTRL+SPC CONVERSATION.',
723
+ 'You are this conversation\'s one owner, started for one serialized activation. Speak directly to the',
724
+ 'person as CTRL+SPC. The launcher is finished and cannot answer or receive an escalation.',
725
+ '',
726
+ 'VISIBLE CONVERSATION, OLDEST FIRST',
727
+ ...(history.length === 0 ? ['Nothing has been said yet.'] : history),
728
+ ...(childQuestion === null ? [] : [
729
+ '',
730
+ 'A WORKER NEEDS YOUR DECISION',
731
+ `Question ${childQuestion.id}: ${childQuestion.question}`,
732
+ 'Answer it with answer_escalation if you can. If the person must decide, ask them directly.',
733
+ ]),
734
+ '',
735
+ 'WHAT YOU LAST WROTE DOWN',
736
+ report ?? 'Nothing yet.',
737
+ '',
738
+ 'PRIVATE WORKER SNAPSHOT',
739
+ ...(children.length === 0 ? ['You have sent nobody.'] : children),
740
+ ...(afterAProcessEnded ? ['', LOOK_AT_THE_WORK_ITSELF, DO_NOT_SEND_SOMEBODY_TWICE] : []),
741
+ '',
742
+ // THE BRIEF IS LAST, and these go before it: the brief ends with how to
743
+ // write the reply, and anything after it pushes that ending into the middle.
744
+ 'YOUR IMMUTABLE BRIEF',
745
+ brief,
746
+ ].join('\n');
747
+ }
481
748
  /**
482
749
  * What an agent that was stopped is started again with: the same brief, its own
483
750
  * notes, and what it already sent others to do.
@@ -559,7 +826,7 @@ export function retryPrompt(brief, report, children) {
559
826
  * something the agent is better placed to see." So it is named as a judgement
560
827
  * the agent makes, in one sentence, rather than a rule about when to ask.
561
828
  */
562
- export function answerPrompt(brief, report, children, question, answer) {
829
+ export function answerPrompt(brief, report, children, question, answer, relatedArtifact = null) {
563
830
  return respawnPrompt([
564
831
  'YOU STOPPED BECAUSE YOU COULD NOT GO ON WITHOUT KNOWING SOMETHING. HERE IT IS.',
565
832
  'You are the same agent, started again, and everything you did before you asked is still there.',
@@ -569,6 +836,7 @@ export function answerPrompt(brief, report, children, question, answer) {
569
836
  '',
570
837
  'WHAT CAME BACK',
571
838
  answer,
839
+ ...presentedArtifactAnswerContext(relatedArtifact),
572
840
  '',
573
841
  'If this answers less than you asked, or turns out to change more than the piece you were sent',
574
842
  'to do, ask again rather than deciding it yourself. Carrying on from a guess about something this',