@north-light/crouter 0.3.230 → 0.3.232

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.
Files changed (173) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +4 -0
  3. package/dist/api/dto/canvas.d.ts +10 -0
  4. package/dist/api/dto/common.d.ts +1 -1
  5. package/dist/api/dto/health.d.ts +2 -1
  6. package/dist/api/dto/lifecycle.d.ts +3 -4
  7. package/dist/api/dto/messages.d.ts +5 -4
  8. package/dist/api/dto/nodes.d.ts +2 -0
  9. package/dist/api/dto/profiles.d.ts +5 -0
  10. package/dist/api/routes.d.ts +1 -0
  11. package/dist/api/routes.js +1 -0
  12. package/dist/builtin-memory/00-runtime-base/00-authoring.md +8 -0
  13. package/dist/builtin-memory/00-runtime-base/01-escalation.md +1 -1
  14. package/dist/builtin-memory/01-spine/00-has-manager.md +1 -1
  15. package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +5 -0
  16. package/dist/builtin-memory/02-turn-lifecycle/02-resident.md +5 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +2 -2
  18. package/dist/builtin-memory/insights/capture.md +3 -2
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +6 -1
  20. package/dist/clients/attach/render/diagram.js +13 -5
  21. package/dist/clients/attach/render/page-block.d.ts +0 -1
  22. package/dist/clients/attach/render/page-block.js +4 -57
  23. package/dist/clients/attach/viewer.js +570 -563
  24. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +9 -0
  25. package/dist/clients/inbox/__tests__/integration/mount-panel.test.js +62 -1
  26. package/dist/clients/inbox/controller.d.ts +10 -0
  27. package/dist/clients/inbox/controller.js +56 -12
  28. package/dist/clients/inbox/tui/input.js +38 -10
  29. package/dist/clients/inbox/tui/page-body.d.ts +10 -0
  30. package/dist/clients/inbox/tui/page-body.js +66 -0
  31. package/dist/clients/inbox/tui/panel.js +6 -4
  32. package/dist/clients/inbox/tui/render.js +89 -17
  33. package/dist/clients/inbox/tui/types.d.ts +4 -4
  34. package/dist/commands/__tests__/human.test.js +18 -3
  35. package/dist/commands/__tests__/node-message.test.js +3 -3
  36. package/dist/commands/api-client.js +1 -7
  37. package/dist/commands/canvas-config.js +6 -14
  38. package/dist/commands/canvas-use.js +4 -6
  39. package/dist/commands/cron.js +16 -22
  40. package/dist/commands/human/prompts.d.ts +1 -1
  41. package/dist/commands/human/prompts.js +118 -112
  42. package/dist/commands/human/request.js +13 -14
  43. package/dist/commands/human/review.js +3 -4
  44. package/dist/commands/human/shared.d.ts +6 -0
  45. package/dist/commands/human/shared.js +43 -5
  46. package/dist/commands/human.js +1 -1
  47. package/dist/commands/memory/delete.js +4 -6
  48. package/dist/commands/memory/edit.js +0 -4
  49. package/dist/commands/memory/move.js +3 -5
  50. package/dist/commands/memory/shared.d.ts +1 -1
  51. package/dist/commands/memory/shared.js +9 -5
  52. package/dist/commands/memory/write.js +60 -29
  53. package/dist/commands/memory.js +1 -1
  54. package/dist/commands/node/bash.js +6 -9
  55. package/dist/commands/node/create.js +89 -22
  56. package/dist/commands/node/inspect.js +3 -3
  57. package/dist/commands/node/lifecycle.js +30 -27
  58. package/dist/commands/node/message.js +24 -45
  59. package/dist/commands/node/subscription.js +6 -15
  60. package/dist/commands/node/wait.js +2 -3
  61. package/dist/commands/node-lifecycle-revive.js +1 -12
  62. package/dist/commands/pkg/browse/actions.js +2 -3
  63. package/dist/commands/pkg/market-manage.js +2 -5
  64. package/dist/commands/pkg/plugin-manage.js +7 -8
  65. package/dist/commands/profile/default.js +5 -5
  66. package/dist/commands/profile/delete.js +1 -1
  67. package/dist/commands/profile/env.js +9 -15
  68. package/dist/commands/profile/kind.js +3 -7
  69. package/dist/commands/profile/meta.js +3 -5
  70. package/dist/commands/profile/new.js +0 -6
  71. package/dist/commands/profile/pause.js +4 -8
  72. package/dist/commands/profile/project.js +5 -9
  73. package/dist/commands/profile/rename.js +3 -7
  74. package/dist/commands/profile/show.js +3 -3
  75. package/dist/commands/profile.js +4 -3
  76. package/dist/commands/surface-tmux-spread.js +1 -3
  77. package/dist/commands/sys/config.js +3 -4
  78. package/dist/commands/sys/support/prepare.js +8 -4
  79. package/dist/commands/sys/support/submit.js +2 -3
  80. package/dist/commands/sys/sync-deps.js +1 -9
  81. package/dist/commands/sys/sync-project-guidance.js +1 -7
  82. package/dist/commands/sys/sync-skills.js +1 -11
  83. package/dist/core/__tests__/cron-node-sink-parked-root.test.d.ts +1 -0
  84. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +147 -0
  85. package/dist/core/__tests__/history-inbox.test.js +11 -1
  86. package/dist/core/__tests__/human-deliver.test.js +2 -1
  87. package/dist/core/__tests__/integration/command-plugins.test.js +0 -1
  88. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -1
  89. package/dist/core/__tests__/lifecycle.test.js +30 -2
  90. package/dist/core/__tests__/revive-parked-fresh.test.d.ts +1 -0
  91. package/dist/core/__tests__/revive-parked-fresh.test.js +109 -0
  92. package/dist/core/__tests__/seam/dormancy-release.test.js +32 -5
  93. package/dist/core/canvas/attention.d.ts +2 -0
  94. package/dist/core/canvas/attention.js +25 -18
  95. package/dist/core/canvas/extensions.d.ts +1 -1
  96. package/dist/core/canvas/extensions.js +7 -1
  97. package/dist/core/canvas/history.js +20 -2
  98. package/dist/core/canvas/types.d.ts +1 -1
  99. package/dist/core/command.js +33 -8
  100. package/dist/core/help.d.ts +28 -2
  101. package/dist/core/help.js +46 -10
  102. package/dist/core/human/__tests__/page-html-markdown.test.d.ts +1 -0
  103. package/dist/core/human/__tests__/page-html-markdown.test.js +48 -0
  104. package/dist/core/human/component-docs.js +4 -4
  105. package/dist/core/human/page-html-markdown.d.ts +8 -0
  106. package/dist/core/human/page-html-markdown.js +260 -0
  107. package/dist/core/memory/lint.d.ts +15 -0
  108. package/dist/core/memory/lint.js +150 -90
  109. package/dist/core/profiles/__tests__/fuzzy-match.test.d.ts +1 -0
  110. package/dist/core/profiles/__tests__/fuzzy-match.test.js +51 -0
  111. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  112. package/dist/core/profiles/fuzzy-match.js +92 -0
  113. package/dist/core/profiles/manifest.d.ts +14 -7
  114. package/dist/core/profiles/manifest.js +62 -12
  115. package/dist/core/profiles/select.d.ts +3 -1
  116. package/dist/core/profiles/select.js +5 -3
  117. package/dist/core/profiles/state-block.js +4 -3
  118. package/dist/core/runtime/boot-root.d.ts +3 -2
  119. package/dist/core/runtime/canvas-extensions.d.ts +7 -1
  120. package/dist/core/runtime/canvas-extensions.js +8 -1
  121. package/dist/core/runtime/lifecycle.d.ts +11 -2
  122. package/dist/core/runtime/lifecycle.js +15 -2
  123. package/dist/core/runtime/model-selection.d.ts +4 -0
  124. package/dist/core/runtime/model-selection.js +5 -0
  125. package/dist/core/runtime/nodes.js +5 -0
  126. package/dist/core/runtime/reopen.d.ts +6 -0
  127. package/dist/core/runtime/reopen.js +12 -1
  128. package/dist/core/runtime/revive.d.ts +6 -0
  129. package/dist/core/runtime/revive.js +22 -2
  130. package/dist/core/runtime/spawn.d.ts +5 -2
  131. package/dist/core/runtime/spawn.js +18 -32
  132. package/dist/core/runtime/structured-output.d.ts +6 -0
  133. package/dist/core/runtime/structured-output.js +6 -0
  134. package/dist/core/substrate/__tests__/surface-match-command.test.d.ts +1 -0
  135. package/dist/core/substrate/__tests__/surface-match-command.test.js +89 -0
  136. package/dist/core/substrate/on-read.js +2 -1
  137. package/dist/core/substrate/surface-match.js +164 -12
  138. package/dist/core/termrender/version.d.ts +1 -1
  139. package/dist/core/termrender/version.js +1 -1
  140. package/dist/core/user-settings.js +1 -1
  141. package/dist/daemon/api/__tests__/broker-settle-park.test.d.ts +1 -0
  142. package/dist/daemon/api/__tests__/broker-settle-park.test.js +102 -0
  143. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.d.ts +1 -0
  144. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +188 -0
  145. package/dist/daemon/api/__tests__/node-create-description.test.d.ts +1 -0
  146. package/dist/daemon/api/__tests__/node-create-description.test.js +83 -0
  147. package/dist/daemon/api/__tests__/profile-metadata-route.test.d.ts +1 -0
  148. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +92 -0
  149. package/dist/daemon/api/__tests__/reopen-delivery.test.d.ts +1 -0
  150. package/dist/daemon/api/__tests__/reopen-delivery.test.js +173 -0
  151. package/dist/daemon/api/handlers/broker-ops.js +21 -0
  152. package/dist/daemon/api/handlers/canvas.js +10 -0
  153. package/dist/daemon/api/handlers/messages.js +25 -16
  154. package/dist/daemon/api/handlers/nodes.js +5 -0
  155. package/dist/daemon/api/handlers/profiles.js +22 -1
  156. package/dist/daemon/cron-run.js +19 -1
  157. package/dist/daemon/manage.d.ts +16 -1
  158. package/dist/daemon/manage.js +20 -1
  159. package/dist/daemon/park-pending.d.ts +13 -0
  160. package/dist/daemon/park-pending.js +42 -0
  161. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -0
  162. package/dist/daemon/reconcilers/broker-supervision.js +139 -21
  163. package/dist/daemon/reconcilers/live-obligation.d.ts +10 -4
  164. package/dist/daemon/reconcilers/live-obligation.js +7 -3
  165. package/dist/daemon/reconcilers/storage-maintenance.d.ts +0 -4
  166. package/dist/daemon/reconcilers/storage-maintenance.js +1 -33
  167. package/dist/pi-extensions/canvas-prompt-scrub.d.ts +13 -0
  168. package/dist/pi-extensions/canvas-prompt-scrub.js +53 -0
  169. package/dist/shared/generated-context.d.ts +7 -0
  170. package/dist/shared/generated-context.js +11 -0
  171. package/package.json +4 -4
  172. package/runtime.lock.json +2 -2
  173. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +0 -47
@@ -9,7 +9,7 @@ import type { FocusDTO, RegisterFocusRequest, SetFocusPaneRequest } from './dto/
9
9
  import { type ArmCronRequest, type CancelCronQuery, type CronDTO, type CronRunDTO, type CronScopeQuery, type CronShowDTO, type ListCronsQuery, type PokeCronsResult } from './dto/crons.js';
10
10
  import type { NodeConfigPatch } from './dto/config.js';
11
11
  import type { AttachEnsureRequest, AttachEnsureResultDTO } from './dto/attach.js';
12
- import type { DeleteProfileRequest, DeleteProfileResultDTO, EnsureProfileRequest, ProfileDTO } from './dto/profiles.js';
12
+ import type { DeleteProfileRequest, DeleteProfileResultDTO, EnsureProfileRequest, ProfileDTO, UpdateProfileMetadataRequest } from './dto/profiles.js';
13
13
  import type { FilePeekDTO } from './dto/files.js';
14
14
  import type { MemoryDocRefDTO } from './dto/memory.js';
15
15
  import type { ChatInventoryDTO, ProspectiveChatInventoryDTO, ProspectiveChatInventoryQuery } from './dto/chat-inventory.js';
@@ -174,6 +174,8 @@ export declare class CrtrClient {
174
174
  ensureProfile(name: string, req?: EnsureProfileRequest): Promise<ProfileDTO>;
175
175
  listProfiles(): Promise<ProfileDTO[]>;
176
176
  getProfile(name: string): Promise<ProfileDTO>;
177
+ /** Merge and remove entries in a profile's metadata map. */
178
+ updateProfileMetadata(name: string, req: UpdateProfileMetadataRequest): Promise<ProfileDTO>;
177
179
  /** Force-delete or detach one profile by exact id or unique name. */
178
180
  deleteProfile(name: string, req: DeleteProfileRequest): Promise<DeleteProfileResultDTO>;
179
181
  listModelAuth(): Promise<ModelAuthListDTO>;
@@ -309,6 +309,10 @@ export class CrtrClient {
309
309
  getProfile(name) {
310
310
  return this.request('GET', routes.profile(name));
311
311
  }
312
+ /** Merge and remove entries in a profile's metadata map. */
313
+ updateProfileMetadata(name, req) {
314
+ return this.request('PATCH', routes.profileMetadata(name), req);
315
+ }
312
316
  /** Force-delete or detach one profile by exact id or unique name. */
313
317
  deleteProfile(name, req) {
314
318
  return this.request('DELETE', routes.profile(name), req);
@@ -19,6 +19,8 @@ export interface AttentionItemDTO {
19
19
  cwd: string;
20
20
  /** Number of open tickets on this node. */
21
21
  count: number;
22
+ /** Open ticket titles for this node, newest first — one per counted ticket. */
23
+ subjects: string[];
22
24
  }
23
25
  /** `GET /v1/canvas/attention` result. */
24
26
  export interface AttentionDTO {
@@ -142,6 +144,10 @@ export interface HistoryReadResultDTO {
142
144
  export interface SnapshotNodeDTO {
143
145
  node_id: NodeIdDTO;
144
146
  name: string;
147
+ /** Caller-supplied node title, kept separate from the enriched full label. */
148
+ title: string;
149
+ /** Caller-supplied node brief, or null when absent. */
150
+ description: string | null;
145
151
  kind: string;
146
152
  mode: string;
147
153
  lifecycle?: string;
@@ -161,6 +167,10 @@ export interface SnapshotNodeDTO {
161
167
  last_activity?: IsoTime;
162
168
  ctx_tokens?: number;
163
169
  streaming: boolean;
170
+ /** Number of active background bash jobs, reported only for a live broker. */
171
+ jobs_active: number;
172
+ /** Controller node this node is durably waiting for, or null. */
173
+ waiting_for: string | null;
164
174
  /** The node's active fault projection (structurally a `Fault`), or null. Kept
165
175
  * as a loose object to keep this DTO free of a `core` type import. */
166
176
  hanging: Record<string, unknown> | null;
@@ -18,7 +18,7 @@ export type LifecycleDTO = 'terminal' | 'resident';
18
18
  /** Execution mode of a node (mirrors the runtime `Mode` union). */
19
19
  export type ModeDTO = 'base' | 'orchestrator';
20
20
  /** Why a node last stopped (mirrors the runtime `ExitIntent` union). */
21
- export type ExitIntentDTO = 'done' | 'refresh' | 'idle-release' | null;
21
+ export type ExitIntentDTO = 'done' | 'refresh' | 'idle-release' | 'parked' | null;
22
22
  /** Inbox urgency tier for a delivered message (mirrors feed/inbox `InboxTier`). */
23
23
  export type InboxTierDTO = 'critical' | 'urgent' | 'normal' | 'deferred';
24
24
  /** Whether a node id is a safe single filesystem path segment. Re-declared here
@@ -8,7 +8,8 @@ export interface HealthDTO {
8
8
  brokers_reconciled: boolean;
9
9
  }
10
10
  /** `GET /v1/status` — daemon status snapshot. Backs `crtr sys daemon status`
11
- * state and the `crtr canvas dashboard` header. Versions are informational. */
11
+ * state and the `crtr canvas dashboard` header. `runtime_version` is also
12
+ * checked by Core's minimum-runtime capability gate. */
12
13
  export interface StatusDTO {
13
14
  daemon_pid: number;
14
15
  tick_interval_ms: number;
@@ -4,10 +4,9 @@ export interface ReviveRequest {
4
4
  /** Resume the saved conversation (true) vs a fresh launch (false). */
5
5
  resume?: boolean;
6
6
  /** Clear the finalization latch before reviving (the `--reopen` gate). The
7
- * latch clear is a canvas write, so the gate is enforced server-side: with
8
- * `reopen:false` (default) a finalized node is REJECTED (`node_finalized`);
9
- * with `reopen:true` a node that is NOT finalized is rejected
10
- * (`not_finalized`). */
7
+ * revive doorway accepts it only for a finalized node (`not_finalized`
8
+ * otherwise); without it, a finalized node is rejected (`node_finalized`).
9
+ * Message delivery has its own wider reopen behavior. */
11
10
  reopen?: boolean;
12
11
  /** Wake provenance: the cron whose run drove this revive (`CRTR_CRON_ID`,
13
12
  * set by the daemon in every cron run's environment and forwarded by the
@@ -19,8 +19,9 @@ export interface SendMessageRequest {
19
19
  from?: NodeIdDTO | null;
20
20
  /** Revive with no inbox entry (`--fresh` → `reviveNode({ resume: false })`). */
21
21
  fresh?: boolean;
22
- /** Clear a latched target's finalization latch before an immediate delivery
23
- * or `--fresh` revive (`--reopen`). Immediate only. */
22
+ /** Before delivery, make the target resident and clear its finalization latch
23
+ * when present (`--reopen`). Accepted for both durable and interactive
24
+ * delivery; `--fresh` retains its separate finalized-only gate. */
24
25
  reopen?: boolean;
25
26
  /** Hidden ambient context upserted onto the target's sidecar as a
26
27
  * `situational` card, never visible chat (`--situational-context`).
@@ -42,8 +43,8 @@ export interface SendMessageRequest {
42
43
  * on its one serialized frame loop — the same ordering a tmux viewer gets)
43
44
  * instead of the durable inbox; a dormant or mid-revive target falls back to
44
45
  * the durable inbox + revive (watcher delivers post-boot). Plain immediate
45
- * body only — rejected with fresh/reopen/situational_context/
46
- * output_schema or tier 'deferred'. Runtime cards ARE accepted: a
46
+ * body only — rejected with fresh/situational_context/output_schema or tier
47
+ * 'deferred'. Runtime cards and reopen ARE accepted: a
47
48
  * card-bearing send is an ordinary human send that happens to carry context,
48
49
  * and the live deliver frame places the cards ahead of the body in one turn.
49
50
  * Absent → durable inbox (unchanged). */
@@ -33,6 +33,8 @@ export interface CreateNodeRequest {
33
33
  pin_cwd?: string;
34
34
  /** Display name (tmux window + resume picker). Defaults to the kind. */
35
35
  name?: string;
36
+ /** Caller-supplied short description, preserved against automatic naming. */
37
+ description?: string;
36
38
  parent?: NodeIdDTO | null;
37
39
  root?: boolean;
38
40
  /** Lifecycle of an independent root. Defaults to `resident`; use `terminal`
@@ -23,6 +23,11 @@ export interface EnsureProfileRequest {
23
23
  * launched under the profile as `CRTR_PROFILE_META_<KEY>` env. */
24
24
  metadata?: Record<string, string>;
25
25
  }
26
+ /** `PATCH /v1/profiles/{name}/metadata` body. Merges `set` and removes `unset`. */
27
+ export interface UpdateProfileMetadataRequest {
28
+ set?: Record<string, string>;
29
+ unset?: string[];
30
+ }
26
31
  /** `DELETE /v1/profiles/{name}` body. Destructive deletion is never implicit. */
27
32
  export interface DeleteProfileRequest {
28
33
  force: boolean;
@@ -93,6 +93,7 @@ export declare const routes: {
93
93
  readonly humanRequestCancel: (requestId: string) => string;
94
94
  readonly profiles: () => string;
95
95
  readonly profile: (name: string) => string;
96
+ readonly profileMetadata: (name: string) => string;
96
97
  readonly modelAuths: () => string;
97
98
  readonly modelAuth: (provider: string) => string;
98
99
  readonly filePeek: () => string;
@@ -124,6 +124,7 @@ export const routes = {
124
124
  // Profiles (deletion is daemon-owned because it crosses canvas state)
125
125
  profiles: () => `${V}/profiles`,
126
126
  profile: (name) => `${V}/profiles/${name}`,
127
+ profileMetadata: (name) => `${V}/profiles/${name}/metadata`,
127
128
  // Model auth
128
129
  modelAuths: () => `${V}/model-auth`,
129
130
  modelAuth: (provider) => `${V}/model-auth/${provider}`,
@@ -6,6 +6,8 @@ rationale: >-
6
6
 
7
7
  "Say what actually happens" exists because an approval request called a root a person had created an "attended root" — an invented category with no referent in the product, which forced Silas to halt the decision and ask what the term meant (2026-07-28). Agents coin taxonomies to compress a distinction; the reader pays by decoding a word that names nothing real.
8
8
 
9
+ "Ground what you report" and "Acting" exist because the Fable 5 model guidance (adopted 2026-08-22, Silas) showed that an explicit audit-against-tool-results instruction nearly eliminates fabricated status reports, and that un-steered models take unrequested actions and over-deliberate; both sections track the official wording closely on purpose — its density and operational triggers are the quality bar.
10
+
9
11
  The Mermaid line exists because the viewer's inline diagram affordance is otherwise invisible to an agent working from ordinary Markdown defaults.
10
12
 
11
13
  An "Identity" section is deliberately absent, and the artifacts section carries no paths. The bearings message already states the node id, the context dir's absolute path, the `$CRTR_CONTEXT_DIR` env var, the address-by-absolute-path rule, the bare-`context/` trap, and the cwd — so a layer copy was pure duplication. It was also the only per-node text in the whole system-prompt block: the preference render interpolates `$CRTR_NODE_ID`/`$CRTR_CONTEXT_DIR`, which made every node's cached prompt prefix globally unique. Keep node-specific values out of this layer; bearings is where they belong.
@@ -24,6 +26,12 @@ An artifact you write to your context dir is shared by pointer: whatever carries
24
26
  ## Living documents
25
27
  Every doc you keep — artifact, plan, findings, memory — is a living statement of what is true *now*, never a log of how it got that way. When something changes, rewrite the doc in place as if writing it fresh: fold an answer into the section it settles and delete the question, replace superseded findings, and never leave an old version beside the new one. Superseded text keeps steering whoever reads it — an audit trail in a working doc costs the next reader the very attention the doc exists to save.
26
28
 
29
+ ## Ground what you report
30
+ Before reporting progress, audit each claim against a tool result from this session. Report only work you can point to evidence for; if something is not yet verified, say so explicitly. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.
31
+
32
+ ## Acting
33
+ When you have enough information to act, act — do not re-derive facts already established, re-litigate a decision already made, or narrate options you will not pursue; when weighing a choice, give a recommendation, not a survey. When whoever tasked you is describing a problem or thinking out loud rather than requesting a change, the deliverable is your assessment — report your findings and stop. Before a command that changes system state (a restart, a delete, a config edit), check that the evidence supports that specific action — a signal that pattern-matches a known failure may have a different cause.
34
+
27
35
  ## Say what actually happens
28
36
  Everything you write — replies, reports, approval requests, artifacts, memory docs, comments — describes systems in concrete, existing product terms: the real command, the real event, the actual cause. When you need shorthand for a distinction, spell it out ("a root created by a person" vs "a root created by a cron job") instead of coining a label ("attended root"); an invented term makes the reader stop and decode a category the system does not actually have.
29
37
 
@@ -8,7 +8,7 @@ surfaces:
8
8
  ---
9
9
 
10
10
  ## When blocked, want feedback, or need the user
11
- Don't guess at a decision a person should make. Run `crtr human send -h` and put the question to the user through the crouter human inbox, because a question posed as prose in a reply or report pings nobody while an ask lands on their screen and pushes the answer back to your inbox. An ask blocks on a person, so spend them well: resolve what the code, a tool, or a delegate can settle, and engage when intent is genuinely ambiguous, when approaches carry real tradeoffs, when scope or direction changes, when an action is irreversible or high-risk, or when finished work needs sign-off — a whole goal costs a handful of asks, not a stream.
11
+ Ask the user only when the work genuinely requires them: intent that stays ambiguous after you have dug, a tradeoff that is genuinely theirs, a real scope or direction change, a destructive or irreversible action, or sign-off on finished work. Settle everything the code, a tool, or a delegate can settle first — a whole goal costs a handful of asks, not a stream. Ask through the human inbox (`crtr human send -h`), then end your turn: a question posed as prose in a reply or report pings nobody, while an ask lands on their screen and its answer wakes you.
12
12
 
13
13
  ## When crtr itself misbehaves
14
14
  A `crtr` command that errors unexpectedly, hangs, churns, double-spawns, or contradicts its own `-h` is a harness bug — don't silently work around it. Run `crtr sys feedback` to report it (`-h` for how), then continue.
@@ -8,7 +8,7 @@ surfaces:
8
8
  ---
9
9
 
10
10
  ## Reporting up (the feed)
11
- You report to whoever subscribes to you (usually your parent). They see your output ONLY through explicit pushes (`crtr push -h`) — nothing is sent automatically when you stop, so narrating progress in your turn reaches no one.
11
+ You report to whoever subscribes to you (usually your parent). They see your output ONLY through explicit pushes (`crtr push -h`) — nothing is sent automatically when you stop, so narrating progress in your turn reaches no one. Lead a push with the outcome — what happened or what you found — then the detail and artifact paths.
12
12
 
13
13
  ## Escalating
14
14
  If the work is bigger or different than your task implies, say so in a push to your managers rather than silently expanding scope.
@@ -4,6 +4,8 @@ when-and-why-to-read: When a node has no immediate action because it awaits an e
4
4
  rationale: >-
5
5
  "Waiting is a way to end a turn" lived in its own ungated all-node doc until 2026-07-28. Same gate, same audience, never independently readable — so the split bought no routing and cost a stub cross-reference in runtime-base pointing at a section spliced a few hundred tokens later. It now has its own sort position as the turn-lifecycle preamble; split it again only when a section needs its own sort position, incident rationale, or independent deletion/re-gate decision.
6
6
 
7
+ "Never end on a promise" exists because deep into long sessions models occasionally end a turn on a text-only statement of intent without the corresponding tool call; the check-your-last-paragraph form is the operational trigger the Fable 5 guidance validated (adopted 2026-08-22, Silas).
8
+
7
9
  Yield applies to every node regardless of mode; promotion does not, because only the mode layers own that boundary — 04-base-worker for when a base node should promote, the kernel for how an orchestrator uses promotion — so restating it in the universal layer duplicated the base-worker text for an audience that includes nodes it does not apply to.
8
10
  lint-ignore: length
9
11
  surfaces:
@@ -19,6 +21,9 @@ When your goal is sound but your next step is blocked on something that has not
19
21
  - **For waits the runtime already knows — a child's report or the reply to your own human page — just stop.** Go dormant; the runtime wakes you when it lands. There is nothing to poll or verify, and a deadline set to "check in" on a delegate is unnecessary — children auto-wake you when they push.
20
22
  - **Schedule a wake yourself only when nothing can push to you** — recurring or scheduled standing work, or polling an external the spine can't deliver (CI, a deploy, a clock). Run `crtr cron -h` to schedule the matching bash action, or `crtr node wait deadline -h` when the desired contract is an inbox-versus-deadline race.
21
23
 
24
+ ## Never end on a promise
25
+ Before you stop, check your last paragraph. If it is a plan, a list of next steps, or a promise about work you have not done ("I'll now…"), that is not an ending — do that work now with tool calls. End your turn only when the work is complete, you are waiting, or you are blocked on input only a person can provide.
26
+
22
27
  ## Yield for a fresh window
23
28
  When your context is filling but the mandate isn't done, yield: you revive fresh as the same node with the same mandate, carrying a note to your future self.
24
29
 
@@ -8,9 +8,11 @@ surfaces:
8
8
  ---
9
9
 
10
10
  ## Communicating with the user
11
- - Respond to what was actually said—don’t invent questions, concerns, or agreement.
12
- - Lead with substance—skip praise, validation, and conversational throat-clearing.
13
- - Use plain, proportionate language—cut clichés, metaphors, faux urgency, and repetition.
11
+ - Respond to what was actually said — don't invent questions, concerns, or agreement.
12
+ - Lead with the outcome: your first sentence answers "what happened" or "what did you find" — the thing they would ask for if they said "just give me the TLDR." Supporting detail and reasoning come after.
13
+ - Keep replies short by being selective about what you include — drop details that don't change what the reader would do next — not by compressing into fragments, arrow chains, or coined shorthand. Readable beats short.
14
+ - After a long working stretch, your reply is their first look at any of it: write it as a re-grounding, not a continuation of your working thread, and leave the vocabulary you built up while working behind unless you re-introduce it.
15
+ - Use plain, proportionate language — cut praise, clichés, faux urgency, and repetition.
14
16
 
15
17
  ## How you end
16
18
  You are **resident** and interactable: you are never forced to submit a final result. Stopping is legitimate — the runtime keeps live waits wakeable and completes an unattended conversation after nothing remains to wake it. Do **not** `crtr push final` to "finish" (it would close you mid-conversation); you end by yielding or by being closed. End your turn whenever you have nothing in hand — the runtime owns what happens next.
@@ -5,7 +5,7 @@ gate: {mode: orchestrator}
5
5
  rationale: >-
6
6
  Two observed orchestration failures set this kernel's stopping rules. A sole-writer feature lane produced a 5-deep 1:1 developer/orchestrator chain by repeatedly delegating the whole assignment; separately, the kernel's “idle capacity,” “maximum agents,” and “when in doubt, more rigor” objective helped produce review-only subtrees as large as 87 nodes and five levels deep. Coordination must optimize new evidence toward the goal rather than node count or process length.
7
7
 
8
- Waiting guidance is deliberately absent: 02-turn-lifecycle/00-ending-a-turn owns waiting for every node, including the auto-wake on a child's report, so a kernel copy only duplicated it. Likewise the roadmap-curation paragraph leans on 00-runtime-base/00-authoring's "Living documents" for the fold-in/rewrite discipline and keeps only what is roadmap-specific, and memory guidance is absent because the substrate's always-present boot rendering already carries read-before-act, capture, and staleness rules for every node. Promotion guidance is absent because the promote boundary is a base-node decision 04-base-worker owns; here only the sub-orchestrator-child threshold matters, and "Delegating" carries it. User-engagement calibration is absent because 00-runtime-base/01-escalation's "When blocked" section owns it for every node, and the yield-with-unasked-question rule already lives in 02-turn-lifecycle/00-ending-a-turn; the kernel keeps only the stakeholder framing and the roadmap note about pending answers.
8
+ Prompt-writing guidance (outcome framing, intent context, artifact paths, report shape) is deliberately absent because `node new -h` owns it — the forced read at the spawn moment for every spawner, base and orchestrator alike; the kernel keeps only decomposition and cross-child routing. Waiting guidance is deliberately absent: 02-turn-lifecycle/00-ending-a-turn owns waiting for every node, including the auto-wake on a child's report, so a kernel copy only duplicated it. Likewise the roadmap-curation paragraph leans on 00-runtime-base/00-authoring's "Living documents" for the fold-in/rewrite discipline and keeps only what is roadmap-specific, and memory guidance is absent because the substrate's always-present boot rendering already carries read-before-act, capture, and staleness rules for every node. Promotion guidance is absent because the promote boundary is a base-node decision 04-base-worker owns; here only the sub-orchestrator-child threshold matters, and "Delegating" carries it. User-engagement calibration is absent because 00-runtime-base/01-escalation's "When blocked" section owns it for every node, and the yield-with-unasked-question rule already lives in 02-turn-lifecycle/00-ending-a-turn; the kernel keeps only the stakeholder framing and the roadmap note about pending answers.
9
9
  lint-ignore: length
10
10
  surfaces:
11
11
  - on: boot
@@ -62,7 +62,7 @@ Then advance. Reshape the phases themselves only when reality invalidates the pl
62
62
 
63
63
  ## Delegating
64
64
 
65
- Delegate **outcomes, not implementations** — define what needs to happen and why, give the child the context and the constraints, and let it choose how. You are the relay point for everything your children report up: when a child's task depends on an explore report, design, or spec an earlier child produced, name that file by path in the task — a child inherits only the files you point it at, so findings you hold but don't reference are lost to it. Break the goal into units each small enough for one child to finish well in one window. When a bounded unit itself contains enough independent work for worthwhile parallelism, create it directly as a sub-orchestrator (`crtr node new --kind <kind> --mode orchestrator`); when it is sequential, assign it to a base child that can yield across windows rather than relying on promotion.
65
+ How to write a child's prompt lives on `crtr node new -h`, read at the moment you spawn; this kernel owns the split and the management. Break the goal into units each small enough for one child to finish well in one window, and route findings across children: you are the relay point for everything your children report up, so when a child's task depends on an explore report, design, or spec an earlier child produced, hand that artifact into the new task. When a bounded unit itself contains enough independent work for worthwhile parallelism, create it directly as a sub-orchestrator (`crtr node new --kind <kind> --mode orchestrator`); when it is sequential, assign it to a base child that can yield across windows rather than relying on promotion.
66
66
 
67
67
  Every child's task must be a proper subset of your goal. Handing your whole remaining assignment to one child is recursion, not delegation — it recreates you one window poorer, and repeated once per window it builds a chain of managers with no worker. When a unit cannot be split and cannot run in parallel — a sole-writer implementation, one tightly interlocked surface — it is yours: work it hands-on and yield to continue across windows; hands-on work on an unsplittable unit is orchestration done right, not a lost plot.
68
68
 
@@ -26,7 +26,8 @@ Write a small Markdown artifact under this node's absolute context directory and
26
26
  ```markdown
27
27
  # Proposed insight: <short principle name>
28
28
 
29
- - **destination:** <canonical name (scope) — existing doc to rewrite, new flat doc, or link into a listener or router>
29
+ - **destination:** <canonical name (scope) — existing doc to rewrite, new flat doc, or link into a listener or router — with the neighbouring listing, so the placement is judgeable without a lookup>
30
+ - **visibility:** <the gate and surfaces the doc will carry, or none, and why that reach is right>
30
31
  - **source:** <the episode in one line, quoting the user's pivotal words>
31
32
 
32
33
  ## Proposed durable truth
@@ -35,7 +36,7 @@ Write a small Markdown artifact under this node's absolute context directory and
35
36
  - <Another plausible formulation only when the deeper principle is uncertain.>
36
37
  ```
37
38
 
38
- Quote only enough of the user's words in `source` for them to judge the inference. Candidate bullets are hypotheses, not polished prose: several alternatives are allowed when the deeper principle is uncertain; do not pad, explain every bullet, or hide uncertainty in a long essay.
39
+ `destination` and `visibility` are proposals to approve, never questions to ask: decide both and state the reason. Quote only enough of the user's words in `source` for them to judge the inference. Candidate bullets are hypotheses, not polished prose: several alternatives are allowed when the deeper principle is uncertain; do not pad, explain every bullet, or hide uncertainty in a long essay.
39
40
 
40
41
  The review is asynchronous. Continue unrelated work or wait dormant, but do not finish while this review is outstanding. An approval settles only this candidate; do not recapture it as a new insight unless the user introduces a genuinely separate principle.
41
42
 
@@ -38,7 +38,12 @@ directory's `node_modules/`.
38
38
  | `sysprompt-window.ts` | Registers `/sysprompt`, which runs `crtr sys sysprompt --window` without injecting the prompt into context. |
39
39
  | `frontmatter-rules/` | Injects `.pi/rules/*.md` whose `when:` frontmatter matches a read markdown file. `.claude/rules` are migrated into substrate docs with read-glob `surfaces` entries via `crtr sys sync project-guidance`. Needs the `yaml` dep. See the `pi-frontmatter-rules` skill. |
40
40
  | `statusline.ts` | Custom status line. |
41
- | `strip-skills-docs.ts` | Trims skill docs from context. |
41
+
42
+ Removing pi's self-referential prompt text (the harness identity sentence and
43
+ the pi documentation block) is NOT done here. It runs as a canvas extension, so
44
+ it is ordered ahead of every installed package — including the Claude
45
+ subscription adapter, which otherwise cuts the node's own persona out of the
46
+ system prompt along with the docs block.
42
47
 
43
48
  ## Notes
44
49
 
@@ -6,11 +6,19 @@
6
6
  // viewer shows the process rather than source syntax.
7
7
  //
8
8
  // How it stays simple:
9
- // • termrender renders mermaid to MONOCHROME box-drawing lines with zero ANSI
10
- // escapes (verified: `--color on` output for a pure mermaid fence is
11
- // byte-identical to `--color off`). So the rendered lines are safe to splice
12
- // back into markdown inside a plain ``` code fence — pi's Markdown then shows
13
- // them verbatim in monospace, no escape-code leakage, no re-wrapping.
9
+ // • The rendered lines are spliced back into markdown inside a plain ``` code
10
+ // fence, and pi's Markdown shows a fence body VERBATIM — no re-wrapping, and
11
+ // ANSI escapes pass through to the terminal unchanged. That pass-through is
12
+ // what the splice depends on, because mermaid output is NOT escape-free:
13
+ // since termrender 4.12.9 an emphasis tag in a node or edge label (`<b>`,
14
+ // `<strong>`, `<i>`, `<em>`) renders as a real ANSI run. Verified on screen
15
+ // — an emphasized label arrives bold, with its box borders still aligned.
16
+ // • So treat these lines as STYLED text, not plain text. termrender already
17
+ // sized every box off the visible glyphs, so anything here that measures,
18
+ // truncates, or pads a rendered line must be escape-aware or it will
19
+ // silently reintroduce the misalignment termrender just removed. The
20
+ // trailing-whitespace trim below is safe only because termrender closes a
21
+ // run before the cell padding that follows it.
14
22
  // • `renderMarkdown` (crouter's sole org-wide termrender binding) is
15
23
  // synchronous and memoized, so this is a plain string→string transform with
16
24
  // no async swap or component lifecycle.
@@ -29,6 +29,5 @@ export declare class PageBlockComponent implements Component {
29
29
  private diskStamp;
30
30
  private reload;
31
31
  private compose;
32
- private bodyLines;
33
32
  private footerLine;
34
33
  }
@@ -14,11 +14,11 @@
14
14
  import { readFileSync, statSync } from 'node:fs';
15
15
  import { pageAwaitsResponse } from '../../../core/human/page-schema.js';
16
16
  import { projectPageDisplayMarkdown } from '../../../core/human/page-markdown.js';
17
+ import { projectHtmlDocumentMarkdown } from '../../../core/human/page-html-markdown.js';
17
18
  import { parsePage } from '../../../core/human/page.js';
18
19
  import { pageManifestPath, pagePath, responsePath } from '../../../core/human/convention.js';
19
20
  import { pageTicketState, readTicketResult } from '../../../core/human/tickets.js';
20
- import { renderMarkdownBlockAwareLines } from '../../../core/termrender/termrender.js';
21
- import { describeSlot, tableMarkdown, terminalAnswerable } from '../../inbox/tui/slots.js';
21
+ import { pageBodyLines } from '../../inbox/tui/page-body.js';
22
22
  import { BOLD, CYAN, DIM, GREEN, RESET, YELLOW, sanitize, singleLine, truncate, wrap } from '../../inbox/tui/ansi.js';
23
23
  /** Rows of page body shown while the block is folded; Ctrl+O expands it. */
24
24
  const FOLDED_BODY_LINES = 16;
@@ -113,7 +113,7 @@ export class PageBlockComponent {
113
113
  const manifest = parsePage(this.dir);
114
114
  const displayMarkdown = manifest.dialect === 'jsx'
115
115
  ? projectPageDisplayMarkdown(readFileSync(pagePath(this.dir, 'jsx'), 'utf8'), manifest)
116
- : '';
116
+ : projectHtmlDocumentMarkdown(readFileSync(pagePath(this.dir, 'html'), 'utf8'));
117
117
  const state = pageTicketState(this.dir);
118
118
  const digest = state === 'resolved' ? readTicketResult(this.dir) : null;
119
119
  this.snapshot = {
@@ -140,7 +140,7 @@ export class PageBlockComponent {
140
140
  for (const line of wrap(sanitize(snapshot.manifest.subtitle), maxW))
141
141
  out.push(` ${DIM}${line}${RESET}`);
142
142
  }
143
- const body = this.bodyLines(snapshot.manifest, snapshot.displayMarkdown, maxW, Math.max(20, width - 4));
143
+ const body = pageBodyLines(snapshot.manifest, snapshot.displayMarkdown, maxW, Math.max(20, width - 4));
144
144
  if (body.length > 0) {
145
145
  out.push('');
146
146
  const shown = this.expanded || body.length <= FOLDED_BODY_LINES ? body : body.slice(0, FOLDED_BODY_LINES);
@@ -155,59 +155,6 @@ export class PageBlockComponent {
155
155
  out.push('');
156
156
  return out;
157
157
  }
158
- bodyLines(manifest, displayMarkdown, maxW, paneW) {
159
- const md = (text) => renderMarkdownBlockAwareLines(text, maxW, paneW);
160
- if (manifest.dialect === 'html')
161
- return [`${DIM}HTML page — opens in a browser surface${RESET}`];
162
- const lines = displayMarkdown === '' ? [] : md(displayMarkdown);
163
- let priorStep = -1;
164
- for (const slot of manifest.slots) {
165
- if (manifest.steps > 1 && slot.step !== priorStep) {
166
- if (lines.length > 0)
167
- lines.push('');
168
- lines.push(`${BOLD}Step ${slot.step + 1}/${manifest.steps}${RESET}`);
169
- priorStep = slot.step;
170
- }
171
- if (lines.length > 0)
172
- lines.push('');
173
- lines.push(`${BOLD}${truncate(sanitize(singleLine(describeSlot(slot))), maxW)}${RESET}`);
174
- const body = slot.config.body;
175
- if (typeof body === 'string' && body !== '')
176
- lines.push(...md(body));
177
- if (!terminalAnswerable(slot)) {
178
- lines.push(`${DIM}${sanitize(slot.kind)} — open in Northlight to answer this${RESET}`);
179
- continue;
180
- }
181
- if (slot.kind === 'text') {
182
- lines.push(...md(slot.config.initialText));
183
- }
184
- else if (slot.kind === 'table') {
185
- lines.push(...md(tableMarkdown(slot.config)));
186
- }
187
- else if (slot.kind === 'cards') {
188
- for (const card of slot.config.cards ?? []) {
189
- lines.push(` • ${truncate(sanitize(singleLine(card.subtitle === undefined ? card.title : `${card.title} — ${card.subtitle}`)), Math.max(10, maxW - 4))}`);
190
- if (card.body !== undefined)
191
- for (const line of wrap(sanitize(card.body), Math.max(10, maxW - 6)))
192
- lines.push(` ${DIM}${line}${RESET}`);
193
- }
194
- }
195
- else if (slot.kind === 'options') {
196
- for (const option of slot.config.options) {
197
- lines.push(` • ${truncate(sanitize(singleLine(option.label)), Math.max(10, maxW - 4))}`);
198
- if (option.description !== undefined)
199
- for (const line of wrap(sanitize(option.description), Math.max(10, maxW - 6)))
200
- lines.push(` ${DIM}${line}${RESET}`);
201
- }
202
- }
203
- else if (slot.kind === 'chart') {
204
- const config = slot.config;
205
- const title = config.title ?? `${config.x ?? ''} vs ${(config.y ?? []).map((y) => y.label ?? y.field).join(', ')}`;
206
- lines.push(`${DIM}chart — ${sanitize(config.kind ?? 'chart')}: ${sanitize(title)}${RESET}`);
207
- }
208
- }
209
- return lines;
210
- }
211
158
  footerLine(snapshot, maxW) {
212
159
  switch (snapshot.state) {
213
160
  case 'pending':