@north-light/crouter 0.3.331 → 0.3.333

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 (128) hide show
  1. package/README.md +2 -2
  2. package/dist/api/command-manifest/manifest.js +1 -1
  3. package/dist/build-root.js +1 -1
  4. package/dist/builtin-memory/crouter-concepts/INDEX.md +13 -0
  5. package/dist/builtin-memory/crouter-concepts/README.md +19 -0
  6. package/dist/builtin-memory/crouter-concepts/lifecycle-and-wakes.md +28 -0
  7. package/dist/builtin-memory/crouter-concepts/memory.md +36 -0
  8. package/dist/builtin-memory/crouter-concepts/nodes-and-the-canvas.md +30 -0
  9. package/dist/builtin-memory/crouter-concepts/profiles-kinds-and-modes.md +33 -0
  10. package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +34 -0
  11. package/dist/builtin-memory/crouter-concepts/why-a-daemon.md +31 -0
  12. package/dist/builtin-memory/crouter-plugin/README.md +10 -13
  13. package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +2 -4
  14. package/dist/builtin-memory/crouter-plugin/commands.md +4 -7
  15. package/dist/builtin-memory/crouter-plugin/deploying.md +4 -7
  16. package/dist/builtin-memory/crouter-plugin/errors.md +2 -5
  17. package/dist/builtin-memory/crouter-plugin/getting-started.md +3 -6
  18. package/dist/builtin-memory/crouter-plugin/output.md +1 -3
  19. package/dist/builtin-memory/crouter-plugin/parameters.md +3 -4
  20. package/dist/builtin-memory/crouter-sdk/README.md +3 -4
  21. package/dist/builtin-memory/crouter-sdk/bash.md +2 -3
  22. package/dist/builtin-memory/crouter-sdk/client.md +3 -4
  23. package/dist/builtin-memory/crouter-sdk/docker.md +2 -4
  24. package/dist/builtin-memory/crouter-sdk/errors.md +9 -4
  25. package/dist/builtin-memory/crouter-sdk/files.md +2 -3
  26. package/dist/builtin-memory/crouter-sdk/getting-started.md +19 -6
  27. package/dist/builtin-memory/crouter-sdk/memory.md +3 -4
  28. package/dist/builtin-memory/crouter-sdk/migration.md +2 -6
  29. package/dist/builtin-memory/crouter-sdk/nodes.md +3 -3
  30. package/dist/builtin-memory/crouter-sdk/resources.md +2 -4
  31. package/dist/builtin-memory/crouter-sdk/streaming.md +3 -4
  32. package/dist/clients/attach/input/capabilities.js +1 -1
  33. package/dist/clients/attach/overlays/help.js +1 -1
  34. package/dist/clients/attach/slash/dispatch.js +1 -1
  35. package/dist/clients/attach/viewer.js +762 -765
  36. package/dist/commands/sys/branch.js +1 -1
  37. package/dist/commands/sys/connect.js +3 -3
  38. package/dist/commands/sys/setup-core.d.ts +1 -1
  39. package/dist/commands/sys/setup-core.js +4 -4
  40. package/dist/commands/sys/tutorial/branch.d.ts +1 -0
  41. package/dist/commands/sys/tutorial/branch.js +1 -0
  42. package/dist/commands/sys/tutorial/lessons.d.ts +6 -0
  43. package/dist/commands/sys/tutorial/lessons.js +12 -0
  44. package/dist/commands/sys/tutorial/scenario.d.ts +1 -0
  45. package/dist/commands/sys/tutorial/scenario.js +2 -0
  46. package/dist/commands/sys/tutorial/tracks.d.ts +2 -0
  47. package/dist/commands/sys/tutorial/tracks.js +4 -0
  48. package/dist/commands/sys.js +1 -1
  49. package/dist/core/runtime/boot-root.d.ts +7 -0
  50. package/dist/core/runtime/boot-root.js +4 -4
  51. package/dist/core/runtime/first-run-offer.d.ts +11 -0
  52. package/dist/core/runtime/first-run-offer.js +5 -0
  53. package/dist/core/runtime/front-door.js +3 -3
  54. package/dist/core/runtime/spawn.d.ts +3 -1
  55. package/dist/core/runtime/spawn.js +2 -2
  56. package/dist/core/scopes.d.ts +4 -2
  57. package/dist/core/scopes.js +1 -1
  58. package/dist/core/secrets.d.ts +11 -0
  59. package/dist/core/secrets.js +2 -2
  60. package/dist/daemon/api/bridge.d.ts +4 -0
  61. package/dist/daemon/api/bridge.js +2 -2
  62. package/dist/daemon/api/handlers/attach.d.ts +1 -1
  63. package/dist/daemon/api/handlers/attach.js +1 -1
  64. package/dist/daemon/api/handlers/bash.js +1 -1
  65. package/dist/daemon/api/handlers/broker-ops.d.ts +1 -1
  66. package/dist/daemon/api/handlers/broker-ops.js +1 -1
  67. package/dist/daemon/api/handlers/broker-recovery.d.ts +1 -1
  68. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  69. package/dist/daemon/api/handlers/canvas.js +4 -4
  70. package/dist/daemon/api/handlers/crons.d.ts +1 -1
  71. package/dist/daemon/api/handlers/crons.js +1 -1
  72. package/dist/daemon/api/handlers/daemon.d.ts +1 -1
  73. package/dist/daemon/api/handlers/daemon.js +1 -1
  74. package/dist/daemon/api/handlers/files.js +1 -1
  75. package/dist/daemon/api/handlers/focus.d.ts +1 -1
  76. package/dist/daemon/api/handlers/focus.js +1 -1
  77. package/dist/daemon/api/handlers/human-requests.js +1 -1
  78. package/dist/daemon/api/handlers/memory.js +1 -1
  79. package/dist/daemon/api/handlers/messages.d.ts +1 -1
  80. package/dist/daemon/api/handlers/messages.js +2 -2
  81. package/dist/daemon/api/handlers/model-config.d.ts +1 -1
  82. package/dist/daemon/api/handlers/model-config.js +1 -1
  83. package/dist/daemon/api/handlers/modelauth.js +1 -1
  84. package/dist/daemon/api/handlers/nodes.js +1 -1
  85. package/dist/daemon/api/handlers/profiles.js +1 -1
  86. package/dist/daemon/api/handlers/reports.js +1 -1
  87. package/dist/daemon/api/handlers/reviews.js +1 -1
  88. package/dist/daemon/api/handlers/worktree.d.ts +1 -1
  89. package/dist/daemon/api/handlers/worktree.js +1 -1
  90. package/dist/daemon/api/router.d.ts +20 -1
  91. package/dist/daemon/api/router.js +1 -1
  92. package/dist/daemon/api/server.js +1 -11
  93. package/dist/types.js +1 -1
  94. package/docs/cli/README.md +16 -0
  95. package/docs/cli/canvas-and-dashboard.md +56 -0
  96. package/docs/cli/first-session.md +55 -0
  97. package/docs/cli/human-inbox.md +34 -0
  98. package/docs/cli/memory-and-preferences.md +50 -0
  99. package/docs/cli/the-viewer.md +79 -0
  100. package/docs/concepts/README.md +17 -0
  101. package/docs/concepts/lifecycle-and-wakes.md +26 -0
  102. package/docs/concepts/memory.md +34 -0
  103. package/docs/concepts/nodes-and-the-canvas.md +28 -0
  104. package/docs/concepts/profiles-kinds-and-modes.md +31 -0
  105. package/docs/concepts/scopes-and-trust.md +32 -0
  106. package/docs/concepts/why-a-daemon.md +29 -0
  107. package/docs/plugin/README.md +5 -0
  108. package/docs/plugin/bundles-and-memory.md +5 -0
  109. package/docs/plugin/commands.md +5 -0
  110. package/docs/plugin/deploying.md +5 -0
  111. package/docs/plugin/errors.md +5 -0
  112. package/docs/plugin/getting-started.md +5 -0
  113. package/docs/plugin/output.md +5 -0
  114. package/docs/plugin/parameters.md +5 -0
  115. package/docs/sdk/README.md +5 -0
  116. package/docs/sdk/bash.md +5 -0
  117. package/docs/sdk/client.md +6 -1
  118. package/docs/sdk/docker.md +5 -0
  119. package/docs/sdk/errors.md +12 -0
  120. package/docs/sdk/files.md +5 -0
  121. package/docs/sdk/getting-started.md +21 -1
  122. package/docs/sdk/memory.md +5 -0
  123. package/docs/sdk/migration.md +5 -0
  124. package/docs/sdk/nodes.md +6 -1
  125. package/docs/sdk/resources.md +5 -0
  126. package/docs/sdk/streaming.md +5 -0
  127. package/package.json +10 -9
  128. package/runtime.lock.json +6734 -670
package/README.md CHANGED
@@ -18,11 +18,11 @@ crtr node new "Inspect this repository and report the failing tests."
18
18
 
19
19
  ## Use crouter from an application
20
20
 
21
- Install [`@north-light/crouter-sdk`](https://www.npmjs.com/package/@north-light/crouter-sdk) to create agent runs, receive typed results, stream activity, and use the daemon's other application-facing APIs. It connects to the local daemon or to a remote daemon with a bearer token. The [SDK guide](https://github.com/vallum-security/crouter/tree/main/docs/sdk) has working examples and the full API reference.
21
+ Install [`@north-light/crouter-sdk`](https://www.npmjs.com/package/@north-light/crouter-sdk) to create agent runs, receive typed results, stream activity, and use the daemon's other application-facing APIs. It connects to the local daemon or to a remote daemon with a bearer token. The [SDK guide](https://crouter-docs.vercel.app/docs/sdk) has working examples and the full API reference.
22
22
 
23
23
  ## Extend crouter
24
24
 
25
- - Build an HTTP plugin with [`@north-light/crouter-plugin`](https://www.npmjs.com/package/@north-light/crouter-plugin). Its [authoring guide](https://github.com/vallum-security/crouter/tree/main/docs/plugin) covers commands, parameters, output, errors, deployment, and plugin memory documents.
25
+ - Build an HTTP plugin with [`@north-light/crouter-plugin`](https://www.npmjs.com/package/@north-light/crouter-plugin). Its [authoring guide](https://crouter-docs.vercel.app/docs/plugin) covers commands, parameters, output, errors, deployment, and plugin memory documents.
26
26
  - Install plugins from the [crouter official marketplace](https://github.com/crouton-labs/crouter-official-marketplace). `crtr sys setup` offers to install and register it; then use `crtr pkg market browse crouter-official-marketplace` to see its plugins.
27
27
 
28
28
  ## Source and contributing
@@ -1 +1 @@
1
- var j=Object.defineProperty;var h=(e,n)=>j(e,"name",{value:n,configurable:!0});import{validateCommandNode as U}from"./schema.js";import{isRecord as y}from"../../shared/predicates.js";const g=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,E=new Set(["GET","POST","PUT","PATCH","DELETE"]);function b(e){return e===null?"null":Array.isArray(e)?"array":typeof e}h(b,"typeName");function L(e,n){const a=[],o=h((t,v,k,x,A,_)=>{a.push({code:t,message:v,received:k,expected:x,next:A,..._?{path:_}:{}})},"issue");if(!y(e))return o("command_manifest_invalid","manifest must be an object",b(e),n.extensionFragment===!0?"{ schemaVersion, transport, mounts }":n.transport==="http"?"{ schemaVersion, baseUrl?, timeouts?, mounts, helpAddenda? }":"{ schemaVersion, mounts, helpAddenda? }","Provide a valid JSON manifest object."),{issues:a};const s=Object.keys(e),r=n.extensionFragment===!0?new Set(["schemaVersion","transport","mounts"]):n.transport==="http"?new Set(["schemaVersion","baseUrl","timeouts","mounts","helpAddenda"]):new Set(["schemaVersion","mounts","helpAddenda"]),l=s.filter(t=>!r.has(t));if(l.length>0)return o("command_manifest_invalid","unknown top-level keys",l.join(", "),n.extensionFragment===!0?"only: schemaVersion, transport, mounts":n.transport==="http"?"only: schemaVersion, baseUrl, timeouts, mounts, helpAddenda":"only: schemaVersion, mounts, helpAddenda","Remove the unknown keys."),{issues:a};if(e.schemaVersion!==1)return o("command_schema_version","schemaVersion must be exactly 1",String(e.schemaVersion),"1","Update the manifest schema version to 1.","schemaVersion"),{issues:a};let d;if(n.transport==="http"&&e.baseUrl!==void 0){if(typeof e.baseUrl!="string")return o("command_manifest_invalid","baseUrl must be a string",b(e.baseUrl),"an absolute HTTP(S) URL or omitted","Provide a valid baseUrl or remove it.","baseUrl"),{issues:a};try{const t=new URL(e.baseUrl);if(!["http:","https:"].includes(t.protocol))return o("command_manifest_invalid","baseUrl must be http: or https:",t.protocol,"http: or https:","Use an HTTP(S) URL.","baseUrl"),{issues:a};d=e.baseUrl}catch{return o("command_manifest_invalid","baseUrl must be a valid absolute URL",e.baseUrl,"a valid URL","Fix the baseUrl.","baseUrl"),{issues:a}}}let m;if(n.transport==="http"&&e.timeouts!==void 0){const t=$(e.timeouts,o);if(t===null)return{issues:a};m=t}let i;if(e.helpAddenda!==void 0){const t=M(e.helpAddenda,n.coreCommandPaths,o);if(t===null)return{issues:a};Object.keys(t).length>0&&(i=t)}const c=e.mounts;if(!Array.isArray(c))return o("command_manifest_invalid","mounts must be an array",b(c),"a non-empty array of { parent, node } contributions","Provide at least one mount.","mounts"),{issues:a};if(c.length===0)return o("command_manifest_invalid","mounts must be a non-empty array","empty array","at least one contribution","Add at least one mount.","mounts"),{issues:a};const u=[];for(let t=0;t<c.length;t++){const v=R(c[t],t,n.transport,o,n.extensionFragment===!0?{topLevelRootEntry:"forbidden",allowPassthrough:!1,allowExtensible:!1}:void 0);if(v===null)return{issues:a};u.push(v)}const f=u.filter(t=>t.parent.length>0&&n.extensibleCoreBranches?.has(t.parent[0])===!0);for(const t of f){if(t.parent.length!==1)return o("command_parent_invalid","core mount parent must name an extensible core branch directly",t.parent.join("."),`one extensible core branch (one of: ${[...n.extensibleCoreBranches??[]].join(", ")})`,`Mount below one of the extensible core branches: ${[...n.extensibleCoreBranches??[]].join(", ")}.`,t.parent.join(".")),{issues:a};if(n.coreCommandPaths?.has(`${t.parent[0]} ${t.node.name}`)===!0)return o("command_collision",`contributed command "${t.node.name}" conflicts with a child the extensible core branch already owns`,t.node.name,`a name not already owned by the ${t.parent[0]} core branch`,"Rename the contributed command or remove this mount.",`${t.parent.join(".")}.${t.node.name}`),{issues:a}}const p=F(u.filter(t=>!f.includes(t)),n.reservedCoreNames,n.extensibleCoreBranches,o);return p===null?{issues:a}:a.length===0?{manifest:{schemaVersion:1,...d!==void 0?{baseUrl:d}:{},...m!==void 0?{timeouts:m}:{},...i!==void 0?{helpAddenda:i}:{},roots:p,coreMounts:f},issues:[]}:{issues:a}}h(L,"validateCommandManifest");function $(e,n){if(!y(e))return n("command_manifest_invalid","timeouts must be an object",b(e),"{ connectMs?, requestMs?, streamIdleMs? }","Fix timeouts.","timeouts"),null;const a=new Set(["connectMs","requestMs","streamIdleMs"]),o=Object.keys(e).filter(r=>!a.has(r));if(o.length>0)return n("command_manifest_invalid","unknown timeouts keys",o.join(", "),"only: connectMs, requestMs, streamIdleMs","Remove the unknown keys.","timeouts"),null;const s={};for(const r of["connectMs","requestMs","streamIdleMs"])if(e[r]!==void 0){if(typeof e[r]!="number"||!Number.isInteger(e[r])||e[r]<=0)return n("command_manifest_invalid",`${r} must be a positive integer`,String(e[r]),"a positive integer",`Fix ${r}.`,`timeouts.${r}`),null;s[r]=e[r]}return s}h($,"validateTimeouts");function M(e,n,a){if(!y(e))return a("command_manifest_invalid","helpAddenda must be an object",b(e),"a map of core command path \u2192 addendum text","Fix helpAddenda.","helpAddenda"),null;const o={};for(const[s,r]of Object.entries(e)){if(s.split(" ").some(l=>!g.test(l)))return a("command_help_addendum_invalid","helpAddenda key must be a space-separated core command path",s,'kebab tokens separated by single spaces (e.g. "cron" or "cron add")',"Fix the helpAddenda key.",`helpAddenda.${s}`),null;if(typeof r!="string"||r.length===0)return a("command_manifest_invalid","helpAddenda value must be a non-empty string",typeof r=="string"?"empty string":b(r),"non-empty addendum text","Fix the helpAddenda value.",`helpAddenda.${s}`),null;if(n!==void 0&&!n.has(s))return a("command_help_addendum_invalid","helpAddenda key names no crtr core command path",s,'an existing core command path (e.g. "cron", "cron add")',"Fix the key or remove the addendum.",`helpAddenda.${s}`),null;o[s]=r}return o}h(M,"validateHelpAddenda");function R(e,n,a,o,s){const r=`mounts[${n}]`;if(!y(e))return o("command_manifest_invalid","mount must be an object",b(e),"{ parent, node }","Fix the mount.",r),null;const l=new Set(["parent","node"]),d=Object.keys(e).filter(p=>!l.has(p));if(d.length>0)return o("command_manifest_invalid","unknown mount keys",d.join(", "),"only: parent, node","Remove the unknown keys.",r),null;const m=e.parent;if(!Array.isArray(m))return o("command_manifest_invalid","mount parent must be an array",b(m),"an array of kebab tokens, or [] for a top-level mount","Fix parent.",`${r}.parent`),null;for(let p=0;p<m.length;p++){const t=m[p];if(typeof t!="string"||!g.test(t))return o("command_manifest_invalid","parent token must be a kebab token",String(t),"a kebab-case identifier","Fix the parent path.",`${r}.parent[${p}]`),null}const i=m,c=e.node,u=i.length===0,f=U(c,[`${r}.node`],u,a,o,s);return f===null?null:{parent:i,node:f}}h(R,"validateMount");function F(e,n,a,o){const s=e.filter(i=>i.parent.length===0),r=e.filter(i=>i.parent.length>0);for(const i of s)if(n.has(i.node.name))return o("command_collision","node name conflicts with a crtr core command",i.node.name,`a name not in core (not one of: ${[...n].join(", ")})`,"Rename the node or remove this mount.",`${i.node.name}`),null;const l=s.map(i=>i.node.kind==="branch"?{...i.node,children:[...i.node.children]}:{...i.node});for(const i of r)if(n.has(i.parent[0]))return o("command_parent_invalid","parent path starts with a core branch that is not extensible",i.parent[0],`an extensible core branch (one of: ${[...a??[]].join(", ")})`,`Mount below one of the extensible core branches: ${[...a??[]].join(", ")}.`,i.parent.join(".")),null;for(const i of r)if(!S(l,i,o))return null;const d=new Set,m=h((i,c)=>{const u=c.join(".");return d.has(u)?(o("command_node_invalid","duplicate node path",u,"unique paths within the manifest","Remove the duplicate node.",u),!1):(d.add(u),i.kind==="branch"?i.children.every(f=>m(f,[...c,f.name])):!0)},"indexPaths");for(const i of l)if(!m(i,[i.name]))return null;return l}h(F,"materializeForest");function S(e,n,a){const[o,...s]=n.parent,r=e.find(d=>d.name===o);if(!r)return a("command_parent_invalid","parent path starts with unknown branch",o,"a branch this manifest contributes","Fix the parent path or add the parent branch.",n.parent.join(".")),!1;if(r.kind==="leaf")return a("command_parent_invalid","parent path resolves to a leaf",o,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;let l=r;for(const d of s){const m=l.children.find(i=>i.name===d);if(!m)return a("command_parent_invalid","parent path contains unknown branch",d,"a branch this manifest contributes","Fix the parent path or add the intermediate branch.",n.parent.join(".")),!1;if(m.kind==="leaf")return a("command_parent_invalid","parent path resolves to a leaf",d,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;l=m}return l.passthrough!==void 0?(a("command_parent_invalid","parent path resolves to a passthrough branch",n.parent.join("."),"a branch without passthrough","Remove the nested mount or remove passthrough from its parent branch.",n.parent.join(".")),!1):l.children.some(d=>d.name===n.node.name)?(a("command_node_invalid","duplicate child name",n.node.name,"unique child names within a branch","Rename the node or remove the duplicate.",`${n.parent.join(".")}.${n.node.name}`),!1):(l.children.push(n.node),!0)}h(S,"resolveAndAttachMount");export{L as validateCommandManifest};
1
+ var j=Object.defineProperty;var h=(e,n)=>j(e,"name",{value:n,configurable:!0});import{validateCommandNode as U}from"./schema.js";import{isRecord as y}from"../../shared/predicates.js";const g=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,E=new Set(["GET","POST","PUT","PATCH","DELETE"]);function b(e){return e===null?"null":Array.isArray(e)?"array":typeof e}h(b,"typeName");function L(e,n){const t=[],a=h((o,v,k,A,x,_)=>{t.push({code:o,message:v,received:k,expected:A,next:x,..._?{path:_}:{}})},"issue");if(!y(e))return a("command_manifest_invalid","manifest must be an object",b(e),n.extensionFragment===!0?"{ schemaVersion, transport, mounts }":n.transport==="http"?"{ schemaVersion, baseUrl?, timeouts?, mounts, helpAddenda? }":"{ schemaVersion, mounts, helpAddenda? }","Provide a valid JSON manifest object."),{issues:t};const s=Object.keys(e),r=n.extensionFragment===!0?new Set(["schemaVersion","transport","mounts"]):n.transport==="http"?new Set(["schemaVersion","baseUrl","timeouts","mounts","helpAddenda"]):new Set(["schemaVersion","mounts","helpAddenda"]),l=s.filter(o=>!r.has(o));if(l.length>0)return a("command_manifest_invalid","unknown top-level keys",l.join(", "),n.extensionFragment===!0?"only: schemaVersion, transport, mounts":n.transport==="http"?"only: schemaVersion, baseUrl, timeouts, mounts, helpAddenda":"only: schemaVersion, mounts, helpAddenda","Remove the unknown keys."),{issues:t};if(e.schemaVersion!==1)return a("command_schema_version","schemaVersion must be exactly 1",String(e.schemaVersion),"1","Update the manifest schema version to 1.","schemaVersion"),{issues:t};let d;if(n.transport==="http"&&e.baseUrl!==void 0){if(typeof e.baseUrl!="string")return a("command_manifest_invalid","baseUrl must be a string",b(e.baseUrl),"an absolute HTTP(S) URL or omitted","Provide a valid baseUrl or remove it.","baseUrl"),{issues:t};try{const o=new URL(e.baseUrl);if(!["http:","https:"].includes(o.protocol))return a("command_manifest_invalid","baseUrl must be http: or https:",o.protocol,"http: or https:","Use an HTTP(S) URL.","baseUrl"),{issues:t};d=e.baseUrl}catch{return a("command_manifest_invalid","baseUrl must be a valid absolute URL",e.baseUrl,"a valid URL","Fix the baseUrl.","baseUrl"),{issues:t}}}let m;if(n.transport==="http"&&e.timeouts!==void 0){const o=$(e.timeouts,a);if(o===null)return{issues:t};m=o}let i;if(e.helpAddenda!==void 0){const o=M(e.helpAddenda,n.coreCommandPaths,a);if(o===null)return{issues:t};Object.keys(o).length>0&&(i=o)}const c=e.mounts;if(!Array.isArray(c))return a("command_manifest_invalid","mounts must be an array",b(c),"a non-empty array of { parent, node } contributions","Provide at least one mount.","mounts"),{issues:t};if(c.length===0)return a("command_manifest_invalid","mounts must be a non-empty array","empty array","at least one contribution","Add at least one mount.","mounts"),{issues:t};const u=[];for(let o=0;o<c.length;o++){const v=R(c[o],o,n.transport,a,n.extensionFragment===!0?{topLevelRootEntry:"forbidden",allowPassthrough:!1,allowExtensible:!1}:void 0);if(v===null)return{issues:t};u.push(v)}const f=u.filter(o=>o.parent.length>0&&n.extensibleCoreBranches?.has(o.parent.join(" "))===!0);for(const o of f){const v=o.parent.join(" ");if(n.coreCommandPaths?.has(`${v} ${o.node.name}`)===!0)return a("command_collision",`contributed command "${o.node.name}" conflicts with a child the extensible core branch already owns`,o.node.name,`a name not already owned by the ${v} core branch`,"Rename the contributed command or remove this mount.",`${o.parent.join(".")}.${o.node.name}`),{issues:t}}const p=F(u.filter(o=>!f.includes(o)),n.reservedCoreNames,n.extensibleCoreBranches,a);return p===null?{issues:t}:t.length===0?{manifest:{schemaVersion:1,...d!==void 0?{baseUrl:d}:{},...m!==void 0?{timeouts:m}:{},...i!==void 0?{helpAddenda:i}:{},roots:p,coreMounts:f},issues:[]}:{issues:t}}h(L,"validateCommandManifest");function $(e,n){if(!y(e))return n("command_manifest_invalid","timeouts must be an object",b(e),"{ connectMs?, requestMs?, streamIdleMs? }","Fix timeouts.","timeouts"),null;const t=new Set(["connectMs","requestMs","streamIdleMs"]),a=Object.keys(e).filter(r=>!t.has(r));if(a.length>0)return n("command_manifest_invalid","unknown timeouts keys",a.join(", "),"only: connectMs, requestMs, streamIdleMs","Remove the unknown keys.","timeouts"),null;const s={};for(const r of["connectMs","requestMs","streamIdleMs"])if(e[r]!==void 0){if(typeof e[r]!="number"||!Number.isInteger(e[r])||e[r]<=0)return n("command_manifest_invalid",`${r} must be a positive integer`,String(e[r]),"a positive integer",`Fix ${r}.`,`timeouts.${r}`),null;s[r]=e[r]}return s}h($,"validateTimeouts");function M(e,n,t){if(!y(e))return t("command_manifest_invalid","helpAddenda must be an object",b(e),"a map of core command path \u2192 addendum text","Fix helpAddenda.","helpAddenda"),null;const a={};for(const[s,r]of Object.entries(e)){if(s.split(" ").some(l=>!g.test(l)))return t("command_help_addendum_invalid","helpAddenda key must be a space-separated core command path",s,'kebab tokens separated by single spaces (e.g. "cron" or "cron add")',"Fix the helpAddenda key.",`helpAddenda.${s}`),null;if(typeof r!="string"||r.length===0)return t("command_manifest_invalid","helpAddenda value must be a non-empty string",typeof r=="string"?"empty string":b(r),"non-empty addendum text","Fix the helpAddenda value.",`helpAddenda.${s}`),null;if(n!==void 0&&!n.has(s))return t("command_help_addendum_invalid","helpAddenda key names no crtr core command path",s,'an existing core command path (e.g. "cron", "cron add")',"Fix the key or remove the addendum.",`helpAddenda.${s}`),null;a[s]=r}return a}h(M,"validateHelpAddenda");function R(e,n,t,a,s){const r=`mounts[${n}]`;if(!y(e))return a("command_manifest_invalid","mount must be an object",b(e),"{ parent, node }","Fix the mount.",r),null;const l=new Set(["parent","node"]),d=Object.keys(e).filter(p=>!l.has(p));if(d.length>0)return a("command_manifest_invalid","unknown mount keys",d.join(", "),"only: parent, node","Remove the unknown keys.",r),null;const m=e.parent;if(!Array.isArray(m))return a("command_manifest_invalid","mount parent must be an array",b(m),"an array of kebab tokens, or [] for a top-level mount","Fix parent.",`${r}.parent`),null;for(let p=0;p<m.length;p++){const o=m[p];if(typeof o!="string"||!g.test(o))return a("command_manifest_invalid","parent token must be a kebab token",String(o),"a kebab-case identifier","Fix the parent path.",`${r}.parent[${p}]`),null}const i=m,c=e.node,u=i.length===0,f=U(c,[`${r}.node`],u,t,a,s);return f===null?null:{parent:i,node:f}}h(R,"validateMount");function F(e,n,t,a){const s=e.filter(i=>i.parent.length===0),r=e.filter(i=>i.parent.length>0);for(const i of s)if(n.has(i.node.name))return a("command_collision","node name conflicts with a crtr core command",i.node.name,`a name not in core (not one of: ${[...n].join(", ")})`,"Rename the node or remove this mount.",`${i.node.name}`),null;const l=s.map(i=>i.node.kind==="branch"?{...i.node,children:[...i.node.children]}:{...i.node});for(const i of r)if(n.has(i.parent[0]))return a("command_parent_invalid","parent path starts with a core branch that is not extensible",i.parent[0],`an extensible core branch (one of: ${[...t??[]].join(", ")})`,`Mount below one of the extensible core branches: ${[...t??[]].join(", ")}.`,i.parent.join(".")),null;for(const i of r)if(!S(l,i,a))return null;const d=new Set,m=h((i,c)=>{const u=c.join(".");return d.has(u)?(a("command_node_invalid","duplicate node path",u,"unique paths within the manifest","Remove the duplicate node.",u),!1):(d.add(u),i.kind==="branch"?i.children.every(f=>m(f,[...c,f.name])):!0)},"indexPaths");for(const i of l)if(!m(i,[i.name]))return null;return l}h(F,"materializeForest");function S(e,n,t){const[a,...s]=n.parent,r=e.find(d=>d.name===a);if(!r)return t("command_parent_invalid","parent path starts with unknown branch",a,"a branch this manifest contributes","Fix the parent path or add the parent branch.",n.parent.join(".")),!1;if(r.kind==="leaf")return t("command_parent_invalid","parent path resolves to a leaf",a,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;let l=r;for(const d of s){const m=l.children.find(i=>i.name===d);if(!m)return t("command_parent_invalid","parent path contains unknown branch",d,"a branch this manifest contributes","Fix the parent path or add the intermediate branch.",n.parent.join(".")),!1;if(m.kind==="leaf")return t("command_parent_invalid","parent path resolves to a leaf",d,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;l=m}return l.passthrough!==void 0?(t("command_parent_invalid","parent path resolves to a passthrough branch",n.parent.join("."),"a branch without passthrough","Remove the nested mount or remove passthrough from its parent branch.",n.parent.join(".")),!1):l.children.some(d=>d.name===n.node.name)?(t("command_node_invalid","duplicate child name",n.node.name,"unique child names within a branch","Rename the node or remove the duplicate.",`${n.parent.join(".")}.${n.node.name}`),!1):(l.children.push(n.node),!0)}h(S,"resolveAndAttachMount");export{L as validateCommandManifest};
@@ -1 +1 @@
1
- var b=Object.defineProperty;var o=(e,n)=>b(e,"name",{value:n,configurable:!0});import{defineBranch as P,defineRoot as c,GLOBAL_TOKENS as v}from"./core/command.js";import{createCoreHookCatalog as y}from"./core/command-hooks/catalog.js";import{composeCoreHooks as h}from"./core/command-hooks/compose.js";import{mark as l}from"./core/timing.js";import{envScopes as O,scopeAllowed as R}from"./shared/env.js";const m="crtr: agentic runtime.",d=[{name:"--json",desc:"stdout as the leaf's declared outputs in raw JSON instead of prose \u2014 for scripts (a cron's bash, external tooling) that must branch on canvas state; prose stays the contract for what you read yourself."}],p=Object.assign(Object.create(null),{memory:o(async()=>(await import("./commands/memory.js")).registerMemory(),"memory"),profile:o(async()=>(await import("./commands/profile.js")).registerProfile(),"profile"),pkg:o(async()=>(await import("./commands/pkg.js")).registerPkg(),"pkg"),human:o(async()=>(await import("./commands/human.js")).registerHuman(),"human"),sys:o(async()=>(await import("./commands/sys.js")).registerSys(),"sys"),node:o(async()=>(await import("./commands/node.js")).registerNode(),"node"),push:o(async()=>(await import("./commands/push.js")).registerPush(),"push"),cron:o(async()=>(await import("./commands/cron.js")).registerCron(),"cron"),canvas:o(async()=>(await import("./commands/canvas.js")).registerCanvas(),"canvas"),surface:o(async()=>(await import("./commands/surface.js")).registerSurface(),"surface")}),f=Object.freeze(Object.keys(p)),B={human:["ask"],cron:["schedule"],memory:["memory:read","memory:write"]};function w(e){const n=O();return n===null?e:e.filter(s=>{const r=B[s.name];return r===void 0||r.some(i=>R(n,i))})}o(w,"allowedSubtrees");let S;function u(){return S??=Promise.all(f.map(e=>p[e]())),S}o(u,"loadCoreSubtrees");let x;function T(){return l("cli.hooks.catalog"),x??=u().then(y),x}o(T,"coreHookCatalog");function C(){let e;return()=>(e??=(async()=>{const{compileHookRegistry:n,effectiveHookPlugins:s}=await import("./core/command-hooks/discovery.js"),r=s();return l("cli.hooks.effective_plugins",{count:r.length}),r.length===0?(l("cli.hooks.empty_registry"),n(y([]),[])):n(await T(),r)})(),e)}o(C,"hookRegistryLoader");function g(e){const n=new Set,s=o((r,i)=>{if(!(r.kind==="branch"&&r.passthrough!==void 0)&&(n.add(i),r.kind==="branch"))for(const t of r.children)s(t,`${i} ${t.name}`)},"visit");for(const r of e)s(r,r.name);return n}o(g,"commandPathsFor");async function D(){return g(await u())}o(D,"coreCommandPaths");async function G(){return new Set((await u()).filter(e=>e.extensible===!0).map(e=>e.name))}o(G,"extensibleCoreBranches");async function k(e){const n=new Set(e.filter(t=>t.extensible===!0).map(t=>t.name));if(n.size===0)return e;const[{buildExternalCommandSnapshot:s},{composeCoreBranchMounts:r}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),i=s(new Set(f),void 0,void 0,g(e),n);return e.map(t=>{const a=r(i.coreMounts,[t.name]);if(a.length===0)return t;const{listing:_,...E}=t.help;return P({name:t.name,...t.description!==void 0?{description:t.description}:{},...t.whenToUse!==void 0?{whenToUse:t.whenToUse}:{},...t.tier!==void 0?{tier:t.tier}:{},...t.rootEntry!==void 0?{rootEntry:t.rootEntry}:{},...t.extensible===!0?{extensible:!0}:{},...t.slash!==void 0?{slash:t.slash}:{},...t.passthrough!==void 0?{passthrough:t.passthrough}:{},help:E,children:[...t.children,...a]})})}o(k,"composeCorePluginMounts");function z(e){const n=e.slice(2).filter(s=>!v.has(s));return n[0]==="sys"&&n[1]==="daemon"}o(z,"isDaemonControlInvocation");async function I(){const{registerDaemonControlSys:e}=await import("./commands/sys/daemon.js");return c({tagline:m,globals:d,subtrees:[e()]})}o(I,"resolveDaemonControlRoot");async function $(e){const n=e!==void 0?p[e]:void 0;if(n!==void 0){const s=w(await k([await n()]));if(s.length>0)return c({tagline:m,globals:d,subtrees:h(s,C())})}return L()}o($,"resolveRoot");async function L(){const e=await u(),[{buildExternalCommandSnapshot:n},{composeExternalSubtrees:s}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),r=await k(e),i=n(new Set(f),void 0,void 0,g(e),new Set(r.filter(a=>a.extensible===!0).map(a=>a.name))),t=s(i);return c({tagline:m,globals:d,subtrees:[...h(w(r),C()),...t]})}o(L,"buildRoot");export{f as SUBTREE_NAMES,L as buildRoot,D as coreCommandPaths,T as coreHookCatalog,G as extensibleCoreBranches,z as isDaemonControlInvocation,I as resolveDaemonControlRoot,$ as resolveRoot};
1
+ var L=Object.defineProperty;var t=(e,n)=>L(e,"name",{value:n,configurable:!0});import{defineBranch as T,defineRoot as l,GLOBAL_TOKENS as _}from"./core/command.js";import{createCoreHookCatalog as b}from"./core/command-hooks/catalog.js";import{composeCoreHooks as C}from"./core/command-hooks/compose.js";import{mark as m}from"./core/timing.js";import{envScopes as j,scopeAllowed as A}from"./shared/env.js";const p="crtr: agentic runtime.",f=[{name:"--json",desc:"stdout as the leaf's declared outputs in raw JSON instead of prose \u2014 for scripts (a cron's bash, external tooling) that must branch on canvas state; prose stays the contract for what you read yourself."}],d=Object.assign(Object.create(null),{memory:t(async()=>(await import("./commands/memory.js")).registerMemory(),"memory"),profile:t(async()=>(await import("./commands/profile.js")).registerProfile(),"profile"),pkg:t(async()=>(await import("./commands/pkg.js")).registerPkg(),"pkg"),human:t(async()=>(await import("./commands/human.js")).registerHuman(),"human"),sys:t(async()=>(await import("./commands/sys.js")).registerSys(),"sys"),node:t(async()=>(await import("./commands/node.js")).registerNode(),"node"),push:t(async()=>(await import("./commands/push.js")).registerPush(),"push"),cron:t(async()=>(await import("./commands/cron.js")).registerCron(),"cron"),canvas:t(async()=>(await import("./commands/canvas.js")).registerCanvas(),"canvas"),surface:t(async()=>(await import("./commands/surface.js")).registerSurface(),"surface")}),g=Object.freeze(Object.keys(d)),H={human:["ask"],cron:["schedule"],memory:["memory:read","memory:write"]};function x(e){const n=j();return n===null?e:e.filter(r=>{const o=H[r.name];return o===void 0||o.some(s=>A(n,s))})}t(x,"allowedSubtrees");let E;function u(){return E??=Promise.all(g.map(e=>d[e]())),E}t(u,"loadCoreSubtrees");let P;function M(){return m("cli.hooks.catalog"),P??=u().then(b),P}t(M,"coreHookCatalog");function v(){let e;return()=>(e??=(async()=>{const{compileHookRegistry:n,effectiveHookPlugins:r}=await import("./core/command-hooks/discovery.js"),o=r();return m("cli.hooks.effective_plugins",{count:o.length}),o.length===0?(m("cli.hooks.empty_registry"),n(b([]),[])):n(await M(),o)})(),e)}t(v,"hookRegistryLoader");function h(e){const n=new Set,r=t((o,s)=>{if(!(o.kind==="branch"&&o.passthrough!==void 0)&&(n.add(s),o.kind==="branch"))for(const i of o.children)r(i,`${s} ${i.name}`)},"visit");for(const o of e)r(o,o.name);return n}t(h,"commandPathsFor");async function J(){return h(await u())}t(J,"coreCommandPaths");function N(e){const n=[],r=t((o,s)=>{o.extensible===!0&&n.push(s);for(const i of o.children)i.kind==="branch"&&r(i,[...s,i.name])},"visit");for(const o of e)r(o,[o.name]);return n}t(N,"extensibleBranchPaths");async function B(){return new Set(N(await u()).map(e=>e.join(" ")))}t(B,"extensibleCoreBranches");function U(e,n){const{listing:r,...o}=e.help;return T({name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:o,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:n})}t(U,"rebuildBranch");async function O(e,n){if(n.size===0)return e;const[{buildExternalCommandSnapshot:r},{composeCoreBranchMounts:o}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),s=r(new Set(g),void 0,void 0,h(await u()),n),i=t((a,y)=>{let w=!1;const R=a.children.map(c=>{if(c.kind!=="branch")return c;const k=i(c,[...y,c.name]);return k!==c&&(w=!0),k}),S=a.extensible===!0?o(s.coreMounts,y):[];return w||S.length>0?U(a,[...R,...S]):a},"composeBranch");return e.map(a=>i(a,[a.name]))}t(O,"composeCorePluginMounts");function K(e){const n=e.slice(2).filter(r=>!_.has(r));return n[0]==="sys"&&n[1]==="daemon"}t(K,"isDaemonControlInvocation");async function Q(){const{registerDaemonControlSys:e}=await import("./commands/sys/daemon.js");return l({tagline:p,globals:f,subtrees:[e()]})}t(Q,"resolveDaemonControlRoot");async function V(e){const n=e!==void 0?d[e]:void 0;if(n!==void 0){const r=x(await O([await n()],await B()));if(r.length>0)return l({tagline:p,globals:f,subtrees:C(r,v())})}return D()}t(V,"resolveRoot");async function D(){const e=await u(),[{buildExternalCommandSnapshot:n},{composeExternalSubtrees:r}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),o=await B(),s=await O(e,o),i=n(new Set(g),void 0,void 0,h(e),o),a=r(i);return l({tagline:p,globals:f,subtrees:[...C(x(s),v()),...a]})}t(D,"buildRoot");export{g as SUBTREE_NAMES,D as buildRoot,J as coreCommandPaths,M as coreHookCatalog,B as extensibleCoreBranches,K as isDaemonControlInvocation,Q as resolveDaemonControlRoot,V as resolveRoot};
@@ -0,0 +1,13 @@
1
+ ---
2
+ kind: knowledge
3
+ description: Why crouter is shaped the way it is — durable nodes, the canvas graph, dormancy, memory tiers, profiles, scoped authority, and one daemon
4
+ when-and-why-to-read: When you are choosing the shape of an agent application on crtr — whether work needs a durable node, whether it should finish or stay resident, where memory belongs, or why only the daemon writes canvas state — this section should be read because picking an SDK method or plugin field before understanding the runtime model produces an application that fights the runtime instead of using it.
5
+ short-form: The concepts behind the crtr runtime, read before choosing an SDK method or plugin field — nodes and the canvas, lifecycle and wakes, memory, profiles/kinds/modes, scopes and trust, and why a daemon.
6
+ surfaces:
7
+ - on: boot
8
+ at: name
9
+ ---
10
+
11
+ # `crouter-concepts/` — choose the crouter runtime shape before using the SDK
12
+
13
+ Read `crouter-concepts/README` for the section overview. These pages explain why crouter uses durable nodes, the canvas graph, free dormancy, memory tiers, profiles, scoped authority, and one daemon before an application chooses an SDK method or plugin field.
@@ -0,0 +1,19 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When building with crtr, read this section because its
4
+ pages explain which runtime parts solve each problem before you choose an SDK
5
+ method or plugin field.
6
+ ---
7
+
8
+ # Concepts
9
+
10
+ Use these pages to choose the shape of an agent application before you start wiring it. They explain the durable runtime model behind the SDK and plugin surfaces, then point to the detailed guides for each surface.
11
+
12
+ | Page | Decide |
13
+ |---|---|
14
+ | [[crouter-concepts/nodes-and-the-canvas]] | Whether work needs a durable node rather than one request and response |
15
+ | [[crouter-concepts/lifecycle-and-wakes]] | Whether a node should finish, remain resident, delegate, or wait |
16
+ | [[crouter-concepts/memory]] | What an agent should know or embody, who owns it, and where it belongs |
17
+ | [[crouter-concepts/profiles-kinds-and-modes]] | Which run-shaping dial matches an application concern |
18
+ | [[crouter-concepts/scopes-and-trust]] | What an application or node may do and why only the daemon writes canvas state |
19
+ | [[crouter-concepts/why-a-daemon]] | Why a broker survives a terminal and why viewers are clients rather than hosts |
@@ -0,0 +1,28 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When an agent must react to later work, read this because
4
+ lifecycle and wake choices let it sleep without a process while preserving its
5
+ goal and the event that should resume it.
6
+ ---
7
+
8
+ # Lifecycle and wakes
9
+
10
+ Choose a node’s lifecycle from the kind of relationship it has with work. A **terminal** node owes a final report when it is done. A **resident** node is for an ongoing conversation with a person: it can become dormant and be messaged again without first finalizing. Resident does not mean “keep a process running.” Dormant nodes have no live broker or model turn and cost no context window or compute.
11
+
12
+ Mode answers a different question. A **base** node works hands-on and may delegate a clearly separate piece. An **orchestrator** owns enough independent parallel work that decomposition, delegation, and integration are now its main job. Long or sequential work is not enough reason to orchestrate; keep one hands-on owner and give it a fresh context when needed. Lifecycle answers whether a conversation remains open; mode answers who does the work.
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ Active[Active node] -->|nothing to do now| Dormant[Dormant: no broker]
17
+ Child[Child report] --> Inbox
18
+ Message[Message or human answer] --> Inbox
19
+ Cron[Cron or deadline] --> Inbox
20
+ Inbox -->|wake| Active
21
+ Active -->|terminal final| Finished[Finished]
22
+ ```
23
+
24
+ A node wakes when work actually arrives: a subscribed child pushes a report, another node or an application sends a message, a person answers a human request, or a cron action delivers work. A deadline is a scheduled wake that races an otherwise unpushable wait. The inbox is the durable path for these triggers, so a node can stop between them without losing its goal.
25
+
26
+ Do not keep a node active to poll a child, a person, or a message. Creation automatically subscribes a parent to its child, and the runtime delivers the child’s outcome. Waiting for something the canvas can push is free: end the turn and let the node become dormant. Schedule a cron only for recurring work or an external condition that nothing can push into the canvas, such as checking a CI run. A timer added “just in case” a child does not report duplicates a runtime guarantee and hides a runtime defect.
27
+
28
+ A broker crash does not erase the node. The daemon retains the durable node row, conversation, waits, and outstanding inbox entries, then applies its recovery policy. This is what makes a resident event-driven assistant practical: it can wait for a webhook, be dormant for hours, and resume its saved work only when the webhook produces a message. Use [[crouter-sdk/nodes]] to deliver that external event, and run `crtr memory read internal/nodes-and-canvas` for lifecycle operations.
@@ -0,0 +1,36 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When shaping an agent without rewriting its prompt, read
4
+ this because memory documents separate facts to consult from behavior to
5
+ embody and let each owner control who receives them.
6
+ ---
7
+
8
+ # Memory
9
+
10
+ Memory shapes a node without adding the same instructions to every prompt. A memory document says what it contains, who owns it, and when it should enter context. The result is durable guidance that can be discovered when relevant instead of a growing startup prompt.
11
+
12
+ There are two kinds. **Knowledge** is something an agent consults: a procedure, fact, or technical reference. A **preference** is behavior the agent should embody: a standing directive or correction. This is a use-based split. A procedure and a fact are both knowledge because the agent reads either one to answer a question; a preference changes how it acts.
13
+
14
+ | Tier | Owner | Put here |
15
+ |---|---|---|
16
+ | Node | One running node | A note needed across that node’s fresh contexts |
17
+ | Project | One repository or workspace | Repository facts and procedures |
18
+ | Profile | One application identity and its purview | Application-wide conventions and knowledge |
19
+ | User | One person | Facts and preferences that follow them everywhere |
20
+ | Builtin | crouter | Runtime documentation that ships to every user |
21
+
22
+ Choose the narrowest tier that reaches the next agent who needs the document. For an application author, the profile store is the usual home for knowledge shared by that application’s nodes across repositories. [[crouter-sdk/memory]] names a target for every operation, so the daemon resolves the application’s profile rather than its own current directory. A node’s `memory:read` and `memory:write` scopes can respectively permit reading while denying changes.
23
+
24
+ ```mermaid
25
+ flowchart LR
26
+ Doc[Memory document] --> Surface[Surface entry: event, gate, rung]
27
+ Surface -->|event matches| Preview[Name, preview, or full body]
28
+ Preview --> Route[Routing line tells the agent why to read]
29
+ Route --> Read[Explicit full read when needed]
30
+ ```
31
+
32
+ A surface entry is the delivery mechanism. A document is not loaded because it sits in a particular folder or because its routing line resembles the task. Its frontmatter names an event such as boot, workspace-open, file read, memory read, command, or pre-command; that entry can also match a path or gate on the node’s shape and selects a rung: its name, a preview, or its full content. A document with no surface entry stays in its directory listing until an agent deliberately finds or reads it.
33
+
34
+ The routing line is the preview at the middle rung, not a trigger. For example, a profile document can have a file-read surface that delivers a preview when an order record is opened. Its line — “When handling a refund request, read this because the eligibility window is not in the order record” — then tells the agent why an explicit full read is useful. The event delivers the preview; the line helps the agent decide whether to read the body without pretending to replace it.
35
+
36
+ Memory therefore supports progressive disclosure. You can record knowledge freely, but it costs future contexts only when an explicit surface route delivers it. Read `crtr memory write -h` to author a document and `crtr memory read internal/memory-loading` for the routing mechanics. The SDK [[crouter-sdk/memory]] shows how an application creates and revises its profile documents.
@@ -0,0 +1,30 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When deciding whether an agent task should outlive one
4
+ request, read this because a node gives the task durable identity, context,
5
+ reports, and a path for later messages.
6
+ ---
7
+
8
+ # Nodes and the canvas
9
+
10
+ Use a node when the work may need a follow-up, a report, a child, or time without a caller holding a request open. A node is a durable unit of agent work: it has an identity, a goal, graph relationships, a context directory for artifacts, and a broker that hosts its agent engine. It is not an HTTP request with an LLM response attached.
11
+
12
+ The canvas is the durable graph of those nodes. It makes an agent's work and its relationship to other work visible after the process that created it has returned. This is why [[crouter-sdk/nodes]] returns a node you can retrieve, message, stream, or wait on instead of only returning generated text. An application can create a root node, show its progress, and later call `nodes.message` when a person or an external event has more work for it.
13
+
14
+ ```mermaid
15
+ flowchart TD
16
+ App[Application] -->|creates or messages| Daemon[crtrd]
17
+ Daemon --> Node[Durable node]
18
+ Node --> Broker[Broker and agent engine]
19
+ Node --> Context[Context directory and artifacts]
20
+ Child[Child node] -->|pushes a report| Node
21
+ Node -->|pushes a report| Parent[Subscriber]
22
+ ```
23
+
24
+ A graph edge is not just a visual parent-child line. The management relationship records who owns a child, while subscriptions carry report delivery. On normal child creation, the parent subscribes to the child, so the child’s final report wakes the parent. A node can have other subscribers too; report delivery is intentionally separate from hierarchy.
25
+
26
+ The one way work reports upward is a **push**. A push writes a durable report and puts a reference in each subscriber’s feed. Nothing is reported merely because a node stopped producing text. That makes a report an explicit claim a subscriber can inspect, rather than an inference from terminal output. The parent can then integrate the child’s result instead of repeating the work.
27
+
28
+ A node owns an outcome, not merely an artifact. It may write files and reports while working, but its terminal result is credible only when it has evidence that the requested goal was met. The canvas supports that responsibility: it preserves the goal, durable artifacts, reports, and relationships across fresh contexts and broker replacement.
29
+
30
+ Use a one-shot SDK call such as [[crouter-sdk/nodes]] when work is bounded and its only useful output is a typed result. Use `nodes.create` when the application needs a continuing conversation or must observe work while it runs. For the operational graph and report model, run `crtr memory read internal/nodes-and-canvas`.
@@ -0,0 +1,33 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When designing an agent run, read this because profiles,
4
+ kinds, modes, and memory tiers solve different problems and prevent a long
5
+ prompt from becoming an unstable substitute for an application identity.
6
+ ---
7
+
8
+ # Profiles, kinds, and modes
9
+
10
+ Shape a run by choosing the dial that owns the decision. The four dials are independent: a useful profile does not imply an orchestrator, and a specialist kind does not decide where its knowledge lives.
11
+
12
+ | Dial | It answers | Use it when |
13
+ |---|---|---|
14
+ | Profile | Which application identity, project purview, environment, and profile memory apply? | An application or body of work has stable directories and conventions |
15
+ | Kind | What standing role, model tier, tools, and expertise should the agent have? | The work matches a recurring role such as developer or reviewer |
16
+ | Mode | Does this node work hands-on or coordinate independent children? | Parallel work makes coordination the main job |
17
+ | Memory tier | Who should receive a document? | Guidance must reach one node, project, profile, user, or all crouter users |
18
+
19
+ A profile is not a label on a run. It is a stable agent identity with its own memory store and a purview of project directories. It lets an application create nodes from the same target context even if the caller runs elsewhere. Create a profile when an app has a durable set of directories, environment values, and conventions worth sharing. Do not make a profile for every repository or individual request. Use [[crouter-sdk/resources]] and the SDK’s `profile` create field to select the application’s identity.
20
+
21
+ ```mermaid
22
+ flowchart LR
23
+ Profile[Profile: purview, environment, memory] --> Node
24
+ Kind[Kind: role and tools] --> Node
25
+ Mode[Mode: base or orchestrator] --> Node
26
+ Tier[Memory tier: reach] --> Node
27
+ ```
28
+
29
+ A kind is a recurring role, not a decorative name. It carries a role-specific posture and may choose a suitable model tier and tools. A custom kind beats a long prompt when the role recurs and needs standing discipline that should survive every run: for example, an application’s compliance reviewer that always needs the same tools, expertise, and model choice. A one-off instruction belongs in the node’s prompt, where it does not create a permanent persona to maintain.
30
+
31
+ Base mode is the normal choice: the node owns and performs the work, using a child only for a separable part. Promote to orchestrator only when independent parts can proceed in parallel and the benefits outweigh coordination and integration. A terminal orchestrator still finishes normally; residency is separate and belongs to a person-facing ongoing conversation.
32
+
33
+ These choices keep the application prompt focused. Identity belongs in a profile, standing role in a kind, task-specific intent in the prompt, coordination in mode, and reusable knowledge in the narrowest memory tier. For the full selection rules, run `crtr memory read internal/agent-shaping`; [[crouter-sdk/nodes]] documents the profile, kind, and mode fields an application passes.
@@ -0,0 +1,34 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When giving an application or node less authority, read
4
+ this because a scope list is an allow-list enforced by the daemon and a bearer
5
+ token can set the maximum authority for every run it creates.
6
+ ---
7
+
8
+ # Scopes and trust
9
+
10
+ Use scopes to give a node or remote application only the authority it needs. A node carries a scope list; a scoped bearer token carries a ceiling. Omitting a node scope list gives it the inherited runtime vocabulary. Supplying one narrows that authority. A token cannot create a node with scopes outside its own ceiling.
11
+
12
+ | Scope family | It gates |
13
+ |---|---|
14
+ | `ask` | Human requests and review actions |
15
+ | `act` | Node creation on behalf of a node and node messaging |
16
+ | `schedule` | Creating scheduled work |
17
+ | `memory:read` / `memory:write` | Reading or changing memory through a node target |
18
+ | `llm`, `net`, and parameterized forms such as `files:<dir>` | Declared capability categories; some performers are recorded rather than enforced in the current beta |
19
+
20
+ This is an allow-list, not a claim that code will behave. The daemon checks the scope at the route that performs the action and rejects an unavailable one. The SDK’s [[crouter-sdk/nodes]] `scopes` field narrows a run; `client.memory` additionally checks `memory:read` or `memory:write` when you make a request on behalf of a node. A node may be allowed to consult its application’s knowledge while being unable to rewrite it.
21
+
22
+ ```mermaid
23
+ flowchart LR
24
+ Token[Bearer token ceiling] --> Request[SDK request]
25
+ Request --> Daemon[crtrd]
26
+ Daemon -->|within ceiling| Node[Node with narrowed scopes]
27
+ Daemon -->|outside ceiling| Denied[403 scope_denied]
28
+ ```
29
+
30
+ Scopes rely on a more basic trust boundary: `crtrd` is the sole writer of durable canvas state and the sole owner of broker lifecycle. SDK clients, the CLI, and viewers call its API; they do not open the canvas database or launch their own broker. One owner serializes lifecycle changes, makes the same API usable locally and remotely, and keeps a client from silently creating a second state authority.
31
+
32
+ For a browser or remote process, run `crtr sys connect`. It enables the daemon’s TCP listener when needed and returns `base_url` plus a bearer `token` to give the application. `crtr sys connect --scopes …` mints a new scoped token instead of returning the owner token. Treat either token as a credential: the remote app sends it as `Authorization: Bearer <token>`, and its scope list is the ceiling for scope-gated calls and newly created nodes.
33
+
34
+ The remote API is an explicit boundary, not permission to reach around it. Keep application code on the SDK or `/v1` contract, and let the daemon own state transitions. Run `crtr memory read internal/nodes-and-canvas` for the operational ownership model.
@@ -0,0 +1,31 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When deciding how an application should host or reconnect
4
+ to an agent, read this because the daemon keeps the durable canvas and broker
5
+ lifecycle in one place while terminals and SDK clients come and go.
6
+ ---
7
+
8
+ # Why a daemon
9
+
10
+ A terminal is not an agent host. A broker is the detached process that hosts one node’s agent engine and session; a viewer is only a terminal presentation attached to that broker. The daemon, `crtrd`, owns the durable canvas and starts, stops, and recovers brokers. This separation lets a node continue after the terminal that created or displayed it has gone away.
11
+
12
+ ```mermaid
13
+ flowchart LR
14
+ CLI[crtr CLI] --> API[/v1 API]
15
+ SDK[SDK application] --> API
16
+ Viewer[Terminal viewer] --> API
17
+ API --> Daemon[crtrd]
18
+ Daemon --> Canvas[Canvas state]
19
+ Daemon --> Broker[Detached broker]
20
+ Viewer -. live session .-> Broker
21
+ ```
22
+
23
+ The broker has one job: host a node’s agent session. The daemon has the broader job: preserve the node graph, state transitions, waits, reports, and broker lifecycle. A viewer may stream a broker’s live session, but it does not become the authority that decides whether that node is active, dormant, or revived. Closing a viewer closes a view, not the node’s durable identity.
24
+
25
+ This is why a process crash is recoverable rather than an automatic loss of work. The daemon retains the node row, session information, artifacts, waits, and inbox state, then applies recovery to the broker execution. A later wake or explicit revival can continue the node’s saved conversation. The application should create nodes through [[crouter-sdk/nodes]], not start an LLM process itself, because the daemon is the component that keeps the engine and durable canvas state coordinated.
26
+
27
+ The costs are real. A daemon must be running, and canvas state has one home instead of being scattered across terminals and application processes. Local clients use its owner-only Unix socket. Remote clients use its configured TCP listener and bearer token. A network interruption can therefore fail a mutation loudly rather than encouraging a client to replay it and risk doing the action twice.
28
+
29
+ Those costs buy a simpler model: one state owner, one broker launcher, and clients that can reconnect. A CLI command, an SDK application, and a terminal viewer all use the same API for canvas state. They can come and go without creating competing writers or losing the graph that explains what each agent is doing.
30
+
31
+ For the lower-level runtime model, run `crtr memory read internal/nodes-and-canvas`. For a complete remote application setup, start with the SDK [[crouter-sdk/getting-started]].
@@ -1,11 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: When you need Authoring crtr HTTP plugins, this knowledge
4
- should be read because `@north-light/crouter-plugin` turns one TypeScript
5
- command tree into both a Fetch handler and the archive accepted by `crtr pkg
6
- plugin install --endpoint`. Use it when an application should expose typed
7
- operations as native `crtr` commands without maintaining `commands.json` or a
8
- separate HTTP route definition.
3
+ when-and-why-to-read: When you need Plugin overview, this knowledge should be
4
+ read because Expose application operations as native crtr commands with a
5
+ typed HTTP plugin.
9
6
  ---
10
7
 
11
8
  # Authoring crtr HTTP plugins
@@ -14,12 +11,12 @@ when-and-why-to-read: When you need Authoring crtr HTTP plugins, this knowledge
14
11
 
15
12
  Start with the `node_modules/@north-light/crouter-plugin/README.md` for a complete plugin that a developer can paste into an empty file. Then read the page that matches the work at hand:
16
13
 
17
- - [[crouter-plugin/getting-started]] — the deployment and install sequence.
18
- - [[crouter-plugin/commands]] — command trees and descriptions that let an agent choose a command.
19
- - [[crouter-plugin/parameters]] — every `param.*` builder and the inferred handler input type.
20
- - [[crouter-plugin/output]] — every `field.*` builder and handler result validation.
21
- - [[crouter-plugin/errors]] — `LeafError`, HTTP errors, and NDJSON streaming leaves.
22
- - [[crouter-plugin/deploying]] — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
23
- - [[crouter-plugin/bundles-and-memory]] — generating an archive and shipping memory docs.
14
+ - [Getting started](getting-started.md) — the deployment and install sequence.
15
+ - [Commands](commands.md) — command trees and descriptions that let an agent choose a command.
16
+ - [Parameters and handler input](parameters.md) — every `param.*` builder and the inferred handler input type.
17
+ - [Output](output.md) — every `field.*` builder and handler result validation.
18
+ - [Errors and streaming](errors.md) — `LeafError`, HTTP errors, and NDJSON streaming leaves.
19
+ - [Deployment](deploying.md) — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
20
+ - [Bundles and memory docs](bundles-and-memory.md) — generating an archive and shipping memory docs.
24
21
 
25
22
  The package exports only `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. Those are the complete authoring surface.
@@ -1,10 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Bundles and memory docs, this knowledge
4
- should be read because `createFetchHandler` builds and serves the install
5
- archive automatically. Use `buildBundle` when you need to inspect or save the
6
- generated bytes during an application build. Pass the same mount path where
7
- the handler is served.
4
+ should be read because Generate install archives and include agent-facing
5
+ memory documents in a plugin.
8
6
  ---
9
7
 
10
8
  # Bundles and memory docs
@@ -1,11 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: "When you need Commands, this knowledge should be read
4
- because `definePlugin` declares the top-level crtr command. `name` must be
5
- lowercase kebab case. `description`, `whenToUse`, and `summary` are required
6
- text for the plugin, every branch, and every leaf. Write them for an agent
7
- choosing a command: describe the concrete object or action, state when the
8
- command applies, and state the short outcome."
3
+ when-and-why-to-read: When you need Commands, this knowledge should be read
4
+ because Define plugin command trees, branches, leaves, and agent-facing
5
+ descriptions.
9
6
  ---
10
7
 
11
8
  # Commands
@@ -16,6 +13,6 @@ when-and-why-to-read: "When you need Commands, this knowledge should be read
16
13
 
17
14
  Use `defineBranch` to group commands and give it `children`. Use `defineLeaf` for a request that returns one object. A leaf requires `params`, `output`, `effects`, and `handler`; `params` may be omitted when the command has no input. `effects` is a non-empty list shown to the agent. Say whether a command mutates an application, creates billable resources, or is read-only.
18
15
 
19
- `defineStreamingLeaf` has the same declaration shape, but its handler returns `AsyncIterable<object>`. It marks the generated REST mapping as streaming and is covered in [[crouter-plugin/errors]].
16
+ `defineStreamingLeaf` has the same declaration shape, but its handler returns `AsyncIterable<object>`. It marks the generated REST mapping as streaming and is covered in [Errors and streaming](errors.md).
20
17
 
21
18
  All command and parameter keys are derived into the manifest. Do not add a route path, HTTP method, REST mapping, or separate manifest to a command definition. The package always generates `POST` with all parameters in the JSON body.
@@ -1,11 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: "When you need Deployment, this knowledge should be read
4
- because `createFetchHandler(plugin, options)` returns `(request: Request) =>
5
- Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any
6
- framework route that accepts Fetch `Request` and `Response` objects. A
7
- framework with different request types needs only an adapter at its boundary;
8
- the package itself has no framework dependency."
3
+ when-and-why-to-read: When you need Deployment, this knowledge should be read
4
+ because Serve plugin Fetch handlers with authentication, mount paths, and
5
+ archive compression.
9
6
  ---
10
7
 
11
8
  # Deployment
@@ -30,4 +27,4 @@ Pass `token` to require `Authorization: Bearer <token>` on archive and command r
30
27
 
31
28
  The install response is an uncompressed tar with `Content-Type: application/x-tar`. Do not configure a proxy, CDN, or framework middleware to gzip or otherwise compress it. Crtr rejects compressed archive bytes. The handler sends an ETag and supports conditional `If-None-Match` requests automatically.
32
29
 
33
- A non-streaming successful command response is a bare JSON result object. Error responses are JSON error envelopes on non-2xx statuses. See [[crouter-plugin/errors]] for the exact error shape.
30
+ A non-streaming successful command response is a bare JSON result object. Error responses are JSON error envelopes on non-2xx statuses. See [Errors and streaming](errors.md) for the exact error shape.
@@ -1,11 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Errors and streaming, this knowledge should
4
- be read because Throw `LeafError` when an expected application error should be
5
- reported to crtr. Its `code` must be lowercase snake case and cannot be
6
- `internal`, `unknown_path`, `command_collision`, or `cli_protocol_error`.
7
- `status` defaults to `400` and must be an integer from `400` through `599`.
8
- Use `field`, `next`, and `received` when they make the fix clearer.
4
+ be read because Report expected application errors and return NDJSON streams
5
+ from command leaves.
9
6
  ---
10
7
 
11
8
  # Errors and streaming
@@ -1,11 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Getting started, this knowledge should be
4
- read because Install `@north-light/crouter-plugin`, copy the complete
5
- TypeScript file in the `node_modules/@north-light/crouter-plugin/README.md`,
6
- and deploy its default export at the URL crtr will reach. The application must
7
- provide `ACME_CRTR_TOKEN` to its handler and the machine running crtr must
8
- provide the same value.
4
+ read because Install the plugin package, deploy a Fetch handler, and install
5
+ its commands in crtr.
9
6
  ---
10
7
 
11
8
  # Getting started
@@ -25,4 +22,4 @@ The endpoint receives `GET` to provide the install archive and `POST` to run a c
25
22
 
26
23
  Run `crtr pkg plugin show acme` after installation to inspect the installed command tree, and `crtr sys doctor` to check the installed package. A plugin name collision with crtr or another installed plugin is determined during installation and cannot be predicted from an application repository alone.
27
24
 
28
- Continue with [[crouter-plugin/deploying]] before mounting the handler below the origin root or behind a proxy.
25
+ Continue with [deployment](deploying.md) before mounting the handler below the origin root or behind a proxy.
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Output fields, this knowledge should be read
4
- because Each leaf declares an `output` object. Field object keys are returned
5
- verbatim in the result object, so `appId` stays `appId`; unlike command and
6
- parameter keys, output keys are not converted to kebab case.
4
+ because Declare typed output fields and validate handler results.
7
5
  ---
8
6
 
9
7
  # Output fields
@@ -1,9 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: "When you need Parameters and handler input, this
4
- knowledge should be read because Parameter object keys become handler input
5
- keys. The generated manifest uses their kebab-case form: `appId` becomes
6
- `app-id`, while the handler receives `input.appId`."
3
+ when-and-why-to-read: When you need Parameters and handler input, this knowledge
4
+ should be read because Declare command parameters and infer their handler
5
+ input types.
7
6
  ---
8
7
 
9
8
  # Parameters and handler input
@@ -1,9 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: "When you need `@north-light/crouter-sdk`, this knowledge
4
- should be read because The ESM-only client an application installs to drive a
5
- crouter daemon: create agent runs, watch streamed events, wait for typed
6
- results, read and write memory, and reach the rest of the daemon's `/v1` API."
3
+ when-and-why-to-read: When you need SDK overview, this knowledge should be read
4
+ because Drive a crouter daemon from a Node or browser application with the
5
+ typed SDK.
7
6
  ---
8
7
 
9
8
  # `@north-light/crouter-sdk`
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: When you need `client.bash`, this knowledge should be read
4
- because `client.bash.run` executes one bounded `bash -c` command through the
5
- daemon.
3
+ when-and-why-to-read: When you need Bash, this knowledge should be read because
4
+ Execute bounded shell commands through the daemon.
6
5
  ---
7
6
 
8
7
  # `client.bash`
@@ -1,9 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Client construction, this knowledge should
4
- be read because `Crouter` is the default export and the only class an
5
- application constructs. `CrtrClient` from `@north-light/crouter-api` is an
6
- implementation detail and is not re-exported.
4
+ be read because Configure local socket and remote HTTP connections,
5
+ authentication, and request options.
7
6
  ---
8
7
 
9
8
  # Client construction
@@ -24,7 +23,7 @@ const customSocketClient = new Crouter({ socketPath: '/custom/path/crtrd.sock' }
24
23
  |---|---|---|---|
25
24
  | `baseURL` | `string` | `CRTR_BASE_URL`, else unset | `http(s)://host:port` of a daemon TCP listener. |
26
25
  | `socketPath` | `string` | `CRTR_SOCKET`, else `${CRTR_HOME}/crtrd.sock`, else `~/.crouter/canvas/crtrd.sock` | Unix socket. Node only; throws in a browser. |
27
- | `token` | `string` | `CRTRD_TOKEN` | Sent as `Authorization: Bearer <token>`. Ignored by a unix-socket daemon, which authenticates by filesystem permission. |
26
+ | `token` | `string` | `CRTRD_TOKEN` | Sent as `Authorization: Bearer <token>`. The owner token or a scoped token from `crtr sys connect`; a scoped token's ceiling is enforced per request (see [[crouter-sdk/getting-started]]). Ignored by a unix-socket daemon, which authenticates by filesystem permission. |
28
27
  | `timeout` | `number` (ms) | `30_000` | Per-request wall clock. Does not apply to a stream. |
29
28
  | `maxRetries` | `number` | `2` | Transient-failure retries. Never applied to `POST` or `PATCH` — see [[crouter-sdk/errors]]. |
30
29
  | `defaultHeaders` | `Record<string, string>` | `{}` | Merged into every request. |
@@ -1,10 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Docker environment, this knowledge should be
4
- read because `@north-light/crouter-env-docker` runs a crouter daemon in a
5
- container and tells you how to reach it. It is a separate package because
6
- container lifecycle is real work the SDK does not otherwise do — and it **has
7
- no dependencies**, including on the SDK itself.
4
+ read because Run a crouter daemon in a container with the separate Docker
5
+ environment package.
8
6
  ---
9
7
 
10
8
  # Docker environment
@@ -1,10 +1,8 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When you need Errors, this knowledge should be read
4
- because **An agent's own outcome is returned, never thrown.** A run that
5
- declined the schema, hit its deadline, or crashed comes back as a settled
6
- `NodeOutcome` from `waitForOutcome`, `createAndWait`, or `parse`. Narrow on it
7
- — see [[crouter-sdk/nodes]].
4
+ because Handle API and connection errors separately from settled agent
5
+ outcomes.
8
6
  ---
9
7
 
10
8
  # Errors
@@ -59,6 +57,13 @@ The subclasses add **no fields**. `status` and `code` on the base class already
59
57
 
60
58
  The SDK validates path-segment identifiers before it makes a request. Invalid node ids, cron ids, bash-job ids, human-request ids, inbox ticket ids, provider names, and profile names throw `TypeError` locally. Node ids apply to core node calls, lifecycle calls, and nested node resources. `nodes.events()` returns a `NodeStream` synchronously, so its invalid-id `TypeError` rejects `stream.node`, `stream.finalOutcome()`, and iteration instead. File paths are not identifiers; invalid or relative file paths reach the daemon and return its mapped API error. `nodes.outcome(id, { wait })` separately throws `RangeError` when `wait` is not an integer from 0 through 25.
61
59
 
60
+ ## Scoped tokens
61
+
62
+ | Condition | Class, status, and code |
63
+ |---|---|
64
+ | The bearer token's ceiling lacks the scope a route needs, or `nodes.create` asks for `scopes` outside it (`details.scopes` lists them) | `PermissionDeniedError`, 403 `scope_denied` |
65
+ | A scoped token reaches an owner-only route (daemon restart, attach, broker internals, canvas prune, profile pause/resume/delete, model credential install) | `PermissionDeniedError`, 403 `owner_only` |
66
+
62
67
  ## Memory requests
63
68
 
64
69
  | Condition | Class, status, and code |