nemoris 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.env.example +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +209 -0
  4. package/SECURITY.md +119 -0
  5. package/bin/nemoris +46 -0
  6. package/config/agents/agent.toml.example +28 -0
  7. package/config/agents/default.toml +22 -0
  8. package/config/agents/orchestrator.toml +18 -0
  9. package/config/delivery.toml +73 -0
  10. package/config/embeddings.toml +5 -0
  11. package/config/identity/default-purpose.md +1 -0
  12. package/config/identity/default-soul.md +3 -0
  13. package/config/identity/orchestrator-purpose.md +1 -0
  14. package/config/identity/orchestrator-soul.md +1 -0
  15. package/config/improvement-targets.toml +15 -0
  16. package/config/jobs/heartbeat-check.toml +30 -0
  17. package/config/jobs/memory-rollup.toml +46 -0
  18. package/config/jobs/workspace-health.toml +63 -0
  19. package/config/mcp.toml +16 -0
  20. package/config/output-contracts.toml +17 -0
  21. package/config/peers.toml +32 -0
  22. package/config/peers.toml.example +32 -0
  23. package/config/policies/memory-default.toml +10 -0
  24. package/config/policies/memory-heartbeat.toml +5 -0
  25. package/config/policies/memory-ops.toml +10 -0
  26. package/config/policies/tools-heartbeat-minimal.toml +8 -0
  27. package/config/policies/tools-interactive-safe.toml +8 -0
  28. package/config/policies/tools-ops-bounded.toml +8 -0
  29. package/config/policies/tools-orchestrator.toml +7 -0
  30. package/config/providers/anthropic.toml +15 -0
  31. package/config/providers/ollama.toml +5 -0
  32. package/config/providers/openai-codex.toml +9 -0
  33. package/config/providers/openrouter.toml +5 -0
  34. package/config/router.toml +22 -0
  35. package/config/runtime.toml +114 -0
  36. package/config/skills/self-improvement.toml +15 -0
  37. package/config/skills/telegram-onboarding-spec.md +240 -0
  38. package/config/skills/workspace-monitor.toml +15 -0
  39. package/config/task-router.toml +42 -0
  40. package/install.sh +50 -0
  41. package/package.json +90 -0
  42. package/src/auth/auth-profiles.js +169 -0
  43. package/src/auth/openai-codex-oauth.js +285 -0
  44. package/src/battle.js +449 -0
  45. package/src/cli/help.js +265 -0
  46. package/src/cli/output-filter.js +49 -0
  47. package/src/cli/runtime-control.js +704 -0
  48. package/src/cli-main.js +2763 -0
  49. package/src/cli.js +78 -0
  50. package/src/config/loader.js +332 -0
  51. package/src/config/schema-validator.js +214 -0
  52. package/src/config/toml-lite.js +8 -0
  53. package/src/daemon/action-handlers.js +71 -0
  54. package/src/daemon/healing-tick.js +87 -0
  55. package/src/daemon/health-probes.js +90 -0
  56. package/src/daemon/notifier.js +57 -0
  57. package/src/daemon/nurse.js +218 -0
  58. package/src/daemon/repair-log.js +106 -0
  59. package/src/daemon/rule-staging.js +90 -0
  60. package/src/daemon/rules.js +29 -0
  61. package/src/daemon/telegram-commands.js +54 -0
  62. package/src/daemon/updater.js +85 -0
  63. package/src/jobs/job-runner.js +78 -0
  64. package/src/mcp/consumer.js +129 -0
  65. package/src/memory/active-recall.js +171 -0
  66. package/src/memory/backend-manager.js +97 -0
  67. package/src/memory/backends/file-backend.js +38 -0
  68. package/src/memory/backends/qmd-backend.js +219 -0
  69. package/src/memory/embedding-guards.js +24 -0
  70. package/src/memory/embedding-index.js +118 -0
  71. package/src/memory/embedding-service.js +179 -0
  72. package/src/memory/file-index.js +177 -0
  73. package/src/memory/memory-signature.js +5 -0
  74. package/src/memory/memory-store.js +648 -0
  75. package/src/memory/retrieval-planner.js +66 -0
  76. package/src/memory/scoring.js +145 -0
  77. package/src/memory/simhash.js +78 -0
  78. package/src/memory/sqlite-active-store.js +824 -0
  79. package/src/memory/write-policy.js +36 -0
  80. package/src/onboarding/aliases.js +33 -0
  81. package/src/onboarding/auth/api-key.js +224 -0
  82. package/src/onboarding/auth/ollama-detect.js +42 -0
  83. package/src/onboarding/clack-prompter.js +77 -0
  84. package/src/onboarding/doctor.js +530 -0
  85. package/src/onboarding/lock.js +42 -0
  86. package/src/onboarding/model-catalog.js +344 -0
  87. package/src/onboarding/phases/auth.js +589 -0
  88. package/src/onboarding/phases/build.js +130 -0
  89. package/src/onboarding/phases/choose.js +82 -0
  90. package/src/onboarding/phases/detect.js +98 -0
  91. package/src/onboarding/phases/hatch.js +216 -0
  92. package/src/onboarding/phases/identity.js +79 -0
  93. package/src/onboarding/phases/ollama.js +345 -0
  94. package/src/onboarding/phases/scaffold.js +99 -0
  95. package/src/onboarding/phases/telegram.js +377 -0
  96. package/src/onboarding/phases/validate.js +204 -0
  97. package/src/onboarding/phases/verify.js +206 -0
  98. package/src/onboarding/platform.js +482 -0
  99. package/src/onboarding/status-bar.js +95 -0
  100. package/src/onboarding/templates.js +794 -0
  101. package/src/onboarding/toml-writer.js +38 -0
  102. package/src/onboarding/tui.js +250 -0
  103. package/src/onboarding/uninstall.js +153 -0
  104. package/src/onboarding/wizard.js +499 -0
  105. package/src/providers/anthropic.js +168 -0
  106. package/src/providers/base.js +247 -0
  107. package/src/providers/circuit-breaker.js +136 -0
  108. package/src/providers/ollama.js +163 -0
  109. package/src/providers/openai-codex.js +149 -0
  110. package/src/providers/openrouter.js +136 -0
  111. package/src/providers/registry.js +36 -0
  112. package/src/providers/router.js +16 -0
  113. package/src/runtime/bootstrap-cache.js +47 -0
  114. package/src/runtime/capabilities-prompt.js +25 -0
  115. package/src/runtime/completion-ping.js +99 -0
  116. package/src/runtime/config-validator.js +121 -0
  117. package/src/runtime/context-ledger.js +360 -0
  118. package/src/runtime/cutover-readiness.js +42 -0
  119. package/src/runtime/daemon.js +729 -0
  120. package/src/runtime/delivery-ack.js +195 -0
  121. package/src/runtime/delivery-adapters/local-file.js +41 -0
  122. package/src/runtime/delivery-adapters/openclaw-cli.js +94 -0
  123. package/src/runtime/delivery-adapters/openclaw-peer.js +98 -0
  124. package/src/runtime/delivery-adapters/shadow.js +13 -0
  125. package/src/runtime/delivery-adapters/standalone-http.js +98 -0
  126. package/src/runtime/delivery-adapters/telegram.js +104 -0
  127. package/src/runtime/delivery-adapters/tui.js +128 -0
  128. package/src/runtime/delivery-manager.js +807 -0
  129. package/src/runtime/delivery-store.js +168 -0
  130. package/src/runtime/dependency-health.js +118 -0
  131. package/src/runtime/envelope.js +114 -0
  132. package/src/runtime/evaluation.js +1089 -0
  133. package/src/runtime/exec-approvals.js +216 -0
  134. package/src/runtime/executor.js +500 -0
  135. package/src/runtime/failure-ping.js +67 -0
  136. package/src/runtime/flows.js +83 -0
  137. package/src/runtime/guards.js +45 -0
  138. package/src/runtime/handoff.js +51 -0
  139. package/src/runtime/identity-cache.js +28 -0
  140. package/src/runtime/improvement-engine.js +109 -0
  141. package/src/runtime/improvement-harness.js +581 -0
  142. package/src/runtime/input-sanitiser.js +72 -0
  143. package/src/runtime/interaction-contract.js +347 -0
  144. package/src/runtime/lane-readiness.js +226 -0
  145. package/src/runtime/migration.js +323 -0
  146. package/src/runtime/model-resolution.js +78 -0
  147. package/src/runtime/network.js +64 -0
  148. package/src/runtime/notification-store.js +97 -0
  149. package/src/runtime/notifier.js +256 -0
  150. package/src/runtime/orchestrator.js +53 -0
  151. package/src/runtime/orphan-reaper.js +41 -0
  152. package/src/runtime/output-contract-schema.js +139 -0
  153. package/src/runtime/output-contract-validator.js +439 -0
  154. package/src/runtime/peer-readiness.js +69 -0
  155. package/src/runtime/peer-registry.js +133 -0
  156. package/src/runtime/pilot-status.js +108 -0
  157. package/src/runtime/prompt-builder.js +261 -0
  158. package/src/runtime/provider-attempt.js +582 -0
  159. package/src/runtime/report-fallback.js +71 -0
  160. package/src/runtime/result-normalizer.js +183 -0
  161. package/src/runtime/retention.js +74 -0
  162. package/src/runtime/review.js +244 -0
  163. package/src/runtime/route-job.js +15 -0
  164. package/src/runtime/run-store.js +38 -0
  165. package/src/runtime/schedule.js +88 -0
  166. package/src/runtime/scheduler-state.js +434 -0
  167. package/src/runtime/scheduler.js +656 -0
  168. package/src/runtime/session-compactor.js +182 -0
  169. package/src/runtime/session-search.js +155 -0
  170. package/src/runtime/slack-inbound.js +249 -0
  171. package/src/runtime/ssrf.js +102 -0
  172. package/src/runtime/status-aggregator.js +330 -0
  173. package/src/runtime/task-contract.js +140 -0
  174. package/src/runtime/task-packet.js +107 -0
  175. package/src/runtime/task-router.js +140 -0
  176. package/src/runtime/telegram-inbound.js +1565 -0
  177. package/src/runtime/token-counter.js +134 -0
  178. package/src/runtime/token-estimator.js +59 -0
  179. package/src/runtime/tool-loop.js +200 -0
  180. package/src/runtime/transport-server.js +311 -0
  181. package/src/runtime/tui-server.js +411 -0
  182. package/src/runtime/ulid.js +44 -0
  183. package/src/security/ssrf-check.js +197 -0
  184. package/src/setup.js +369 -0
  185. package/src/shadow/bridge.js +303 -0
  186. package/src/skills/loader.js +84 -0
  187. package/src/tools/catalog.json +49 -0
  188. package/src/tools/cli-delegate.js +44 -0
  189. package/src/tools/mcp-client.js +106 -0
  190. package/src/tools/micro/cancel-task.js +6 -0
  191. package/src/tools/micro/complete-task.js +6 -0
  192. package/src/tools/micro/fail-task.js +6 -0
  193. package/src/tools/micro/http-fetch.js +74 -0
  194. package/src/tools/micro/index.js +36 -0
  195. package/src/tools/micro/lcm-recall.js +60 -0
  196. package/src/tools/micro/list-dir.js +17 -0
  197. package/src/tools/micro/list-skills.js +46 -0
  198. package/src/tools/micro/load-skill.js +38 -0
  199. package/src/tools/micro/memory-search.js +45 -0
  200. package/src/tools/micro/read-file.js +11 -0
  201. package/src/tools/micro/session-search.js +54 -0
  202. package/src/tools/micro/shell-exec.js +43 -0
  203. package/src/tools/micro/trigger-job.js +79 -0
  204. package/src/tools/micro/web-search.js +58 -0
  205. package/src/tools/micro/workspace-paths.js +39 -0
  206. package/src/tools/micro/write-file.js +14 -0
  207. package/src/tools/micro/write-memory.js +41 -0
  208. package/src/tools/registry.js +348 -0
  209. package/src/tools/tool-result-contract.js +36 -0
  210. package/src/tui/chat.js +835 -0
  211. package/src/tui/renderer.js +175 -0
  212. package/src/tui/socket-client.js +217 -0
  213. package/src/utils/canonical-json.js +29 -0
  214. package/src/utils/compaction.js +30 -0
  215. package/src/utils/env-loader.js +5 -0
  216. package/src/utils/errors.js +80 -0
  217. package/src/utils/fs.js +101 -0
  218. package/src/utils/ids.js +5 -0
  219. package/src/utils/model-context-limits.js +30 -0
  220. package/src/utils/token-budget.js +74 -0
  221. package/src/utils/usage-cost.js +25 -0
  222. package/src/utils/usage-metrics.js +14 -0
  223. package/vendor/smol-toml-1.5.2.tgz +0 -0
@@ -0,0 +1,794 @@
1
+ /**
2
+ * Bundled TOML and markdown templates for the onboarding scaffold phase.
3
+ * Each function accepts injected values and returns file content as a string.
4
+ */
5
+
6
+ import path from "node:path";
7
+ import { writeFile as writeFileAsync, mkdir as mkdirAsync, access as accessAsync } from "node:fs/promises";
8
+
9
+ // ── Agent manifest ───────────────────────────────────────────────────────────
10
+
11
+ /**
12
+ * Generates config/agents/{agentId}.toml
13
+ * Must satisfy AGENT_SCHEMA: id, primaryLane, memoryPolicy, toolPolicy
14
+ */
15
+ export function agentTemplate({ agentId, installDir = "", workspaceRoot = "" }) {
16
+ // Normalise to forward slashes — backslashes break TOML string parsing (\U etc.)
17
+ const safeInstallDir = installDir.replace(/\\/g, "/");
18
+ const identityBase = safeInstallDir
19
+ ? `${safeInstallDir}/config/identity`
20
+ : `config/identity`;
21
+ const wsRoot = (workspaceRoot || (safeInstallDir ? `${safeInstallDir}/workspace` : "workspace")).replace(/\\/g, "/");
22
+
23
+ return `id = "${agentId}"
24
+ primary_lane = "interactive_primary"
25
+ memory_policy = "default"
26
+ tool_policy = "interactive_safe"
27
+ soul_ref = "${identityBase}/${agentId}-soul.md"
28
+ purpose_ref = "${identityBase}/${agentId}-purpose.md"
29
+ workspace_root = "${wsRoot}"
30
+ workspace_context_files = ["MEMORY.md", "USER.md", "AGENTS.md"]
31
+ workspace_context_cap = 8000
32
+ checkpoint_policy = "compact"
33
+
34
+ [limits]
35
+ max_tokens_per_turn = 16000
36
+ max_tool_calls_per_turn = 6
37
+ max_runtime_seconds = 120
38
+
39
+ [access]
40
+ workspace = "rw"
41
+ network = "restricted"
42
+ `;
43
+ }
44
+
45
+ // ── Identity files ───────────────────────────────────────────────────────────
46
+
47
+ /**
48
+ * Generates config/identity/{agentId}-soul.md
49
+ */
50
+ export function soulTemplate({ agentName, userName, date }) {
51
+ return `# Soul — ${agentName}
52
+
53
+ Created: ${date}
54
+ Operator: ${userName}
55
+
56
+ You are a pragmatic technical coworker assisting ${userName}.
57
+
58
+ Values:
59
+
60
+ - clarity over performance theater
61
+ - durable systems over clever hacks
62
+ - truthful reporting over optimistic guessing
63
+ - calm execution under ambiguity
64
+
65
+ Behavior:
66
+
67
+ - speak directly
68
+ - keep context lean
69
+ - document what matters
70
+ - protect ${userName}'s trust and data
71
+ `;
72
+ }
73
+
74
+ /**
75
+ * Generates config/identity/{agentId}-purpose.md
76
+ */
77
+ export function purposeTemplate({ agentName, userName, userGoal, date }) {
78
+ return `# Purpose — ${agentName}
79
+
80
+ Created: ${date}
81
+ Operator: ${userName}
82
+
83
+ Help ${userName} ${userGoal}.
84
+
85
+ Primary responsibilities:
86
+
87
+ - drive implementation forward
88
+ - preserve continuity across sessions
89
+ - surface risk early
90
+ - convert messy state into clear action
91
+ `;
92
+ }
93
+
94
+ // ── Router ───────────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Generates config/router.toml with lanes based on available providers.
98
+ * Must satisfy ROUTER_SCHEMA: minLanes = 1
99
+ */
100
+ export function routerTemplate({ anthropic = false, openrouter = false, openai = false, ollama = false, selectedModels = {} }) {
101
+ const pick = (provider, defaults = []) => {
102
+ const chosen = Array.isArray(selectedModels?.[provider]) && selectedModels[provider].length > 0
103
+ ? selectedModels[provider]
104
+ : defaults;
105
+ return {
106
+ primary: chosen[0] || defaults[0],
107
+ secondary: chosen[1] || defaults[1] || chosen[0] || defaults[0],
108
+ tertiary: chosen[2] || defaults[2] || chosen[1] || defaults[1] || chosen[0] || defaults[0],
109
+ };
110
+ };
111
+
112
+ const anthropicModels = pick("anthropic", [
113
+ "anthropic/claude-haiku-4-5",
114
+ "anthropic/claude-sonnet-4-6",
115
+ "anthropic/claude-opus-4-6",
116
+ ]);
117
+ const openrouterModels = pick("openrouter", [
118
+ "openrouter/anthropic/claude-haiku-4-5",
119
+ "openrouter/anthropic/claude-sonnet-4-6",
120
+ "openrouter/anthropic/claude-opus-4-6",
121
+ ]);
122
+ const openaiModels = pick("openai", [
123
+ "openai-codex/gpt-4.1",
124
+ "openai-codex/gpt-4o",
125
+ "openai-codex/o4-mini",
126
+ ]);
127
+ const ollamaModels = pick("ollama", [
128
+ "ollama/qwen2.5:0.5b",
129
+ "ollama/qwen3:8b",
130
+ "ollama/qwen3:14b",
131
+ ]);
132
+
133
+ const sections = [];
134
+
135
+ // interactive_primary lane — must always exist
136
+ if (openrouter && anthropic) {
137
+ sections.push(`[lanes.interactive_primary]
138
+ primary = "${openrouterModels.primary}"
139
+ fallback = "${anthropicModels.primary}"
140
+ manual_bump = "${openrouterModels.secondary}"`);
141
+ } else if (openrouter) {
142
+ sections.push(`[lanes.interactive_primary]
143
+ primary = "${openrouterModels.primary}"
144
+ manual_bump = "${openrouterModels.secondary}"`);
145
+ } else if (anthropic) {
146
+ sections.push(`[lanes.interactive_primary]
147
+ primary = "${anthropicModels.primary}"
148
+ manual_bump = "${anthropicModels.secondary}"`);
149
+ } else if (openai) {
150
+ sections.push(`[lanes.interactive_primary]
151
+ primary = "${openaiModels.primary}"
152
+ manual_bump = "${openaiModels.secondary}"`);
153
+ } else if (ollama) {
154
+ // ollama-only fallback for interactive
155
+ sections.push(`[lanes.interactive_primary]
156
+ primary = "${ollamaModels.secondary}"`);
157
+ } else {
158
+ // No providers at all — emit a placeholder lane so router.toml satisfies minLanes = 1
159
+ sections.push(`# No providers configured — add one with: nemoris setup
160
+ [lanes.interactive_primary]
161
+ primary = "none"`);
162
+ }
163
+
164
+ // local lanes — only if ollama is available
165
+ if (ollama) {
166
+ sections.push(`[lanes.local_cheap]
167
+ primary = "${ollamaModels.primary}"`);
168
+
169
+ if (openrouter) {
170
+ sections.push(`[lanes.local_report]
171
+ primary = "${ollamaModels.secondary}"
172
+ fallback = "${openrouterModels.primary}"
173
+ manual_bump = "${ollamaModels.tertiary}"`);
174
+ } else if (openai) {
175
+ sections.push(`[lanes.local_report]
176
+ primary = "${ollamaModels.secondary}"
177
+ fallback = "${openaiModels.primary}"
178
+ manual_bump = "${ollamaModels.tertiary}"`);
179
+ } else {
180
+ sections.push(`[lanes.local_report]
181
+ primary = "${ollamaModels.secondary}"
182
+ manual_bump = "${ollamaModels.tertiary}"`);
183
+ }
184
+ }
185
+
186
+ // report_fallback_lowcost — only if remote providers are available
187
+ if (openrouter) {
188
+ sections.push(`[lanes.report_fallback_lowcost]
189
+ primary = "${openrouterModels.primary}"`);
190
+ } else if (openai) {
191
+ sections.push(`[lanes.report_fallback_lowcost]
192
+ primary = "${openaiModels.primary}"`);
193
+ }
194
+
195
+ // job_heavy — only if cloud providers available
196
+ if (openrouter && anthropic) {
197
+ sections.push(`[lanes.job_heavy]
198
+ primary = "${openrouterModels.secondary}"
199
+ fallback = "${anthropicModels.secondary}"`);
200
+ } else if (openrouter) {
201
+ sections.push(`[lanes.job_heavy]
202
+ primary = "${openrouterModels.secondary}"`);
203
+ } else if (anthropic) {
204
+ sections.push(`[lanes.job_heavy]
205
+ primary = "${anthropicModels.secondary}"`);
206
+ } else if (openai) {
207
+ sections.push(`[lanes.job_heavy]
208
+ primary = "${openaiModels.secondary}"`);
209
+ }
210
+
211
+ return sections.join("\n\n") + "\n";
212
+ }
213
+
214
+ // ── Runtime ──────────────────────────────────────────────────────────────────
215
+
216
+ /**
217
+ * Generates config/runtime.toml with shipped defaults.
218
+ * Must satisfy RUNTIME_SCHEMA: safety.contextTokens
219
+ */
220
+ export function runtimeTemplate() {
221
+ return `[safety]
222
+ context_tokens = 32768
223
+ context_pressure_soft_ratio = 0.72
224
+ context_pressure_hard_ratio = 0.9
225
+ fresh_session_on_high_pressure = true
226
+ snapshot_before_compaction = true
227
+
228
+ [concurrency]
229
+ max_concurrent_jobs = 2
230
+ max_concurrent_subagents = 2
231
+
232
+ [retention.runs]
233
+ ttl_days = 30
234
+ max_files_per_bucket = 2000
235
+
236
+ [retention.notifications]
237
+ ttl_days = 14
238
+ max_files_per_bucket = 1000
239
+
240
+ [retention.deliveries]
241
+ ttl_days = 14
242
+ max_files_per_bucket = 1000
243
+
244
+ [retention.transport_inbox]
245
+ ttl_days = 7
246
+ max_files_per_bucket = 1000
247
+
248
+ [retrieval]
249
+ lexical_weight = 0.36
250
+ embedding_weight = 0.3
251
+ recency_weight = 0.14
252
+ salience_weight = 0.14
253
+ type_weight = 0.06
254
+ semantic_rescue_bonus = 0.06
255
+ shadow_snapshot_penalty = 0.12
256
+
257
+ [memory_locks]
258
+ ttl_ms = 15000
259
+ retry_delay_ms = 25
260
+ max_retries = 40
261
+
262
+ [network]
263
+ dns_result_order = "system"
264
+ connect_timeout_ms = 20000
265
+ read_timeout_ms = 60000
266
+ retry_budget = 1
267
+ circuit_breaker_threshold = 3
268
+
269
+ [bootstrap_cache]
270
+ enabled = true
271
+ identity_ttl_ms = 300000
272
+
273
+ [extensions]
274
+ implicit_workspace_autoload = false
275
+ require_explicit_trust = true
276
+ trusted_roots = []
277
+
278
+ [shutdown]
279
+ drain_timeout_ms = 15000
280
+ transport_shutdown_timeout_ms = 5000
281
+
282
+ [maintenance]
283
+ wal_checkpoint_threshold_bytes = 67108864
284
+ prune_on_tick = true
285
+ sweep_pending_handoffs_on_tick = true
286
+ sweep_pending_followups_on_tick = true
287
+
288
+ [delivery]
289
+ prevent_resend_on_uncertain = true
290
+ retry_on_failure = false
291
+ notify_on_failure = true
292
+
293
+ [yields]
294
+ enabled = true
295
+ default_target_surface = "operator_review"
296
+ `;
297
+ }
298
+
299
+ // ── Delivery ─────────────────────────────────────────────────────────────────
300
+
301
+ /**
302
+ * Generates a minimal config/delivery.toml with standalone profiles.
303
+ */
304
+ export function deliveryTemplate() {
305
+ return `default_interactive_profile_standalone = "standalone_operator"
306
+ default_peer_profile_standalone = "standalone_peer"
307
+
308
+ [profiles.shadow_scheduler]
309
+ adapter = "shadow"
310
+ enabled = true
311
+ target = "scheduler_log"
312
+
313
+ [profiles.standalone_operator]
314
+ adapter = "local_file"
315
+ enabled = true
316
+ target = "operator"
317
+
318
+ [profiles.standalone_peer]
319
+ adapter = "local_file"
320
+ enabled = true
321
+ target = "peer_queue"
322
+ `;
323
+ }
324
+
325
+ // ── Peers ────────────────────────────────────────────────────────────────────
326
+
327
+ /**
328
+ * Generates an empty config/peers.toml scaffold.
329
+ */
330
+ export function peersTemplate() {
331
+ return `# Peer agent registry
332
+ # Add peers here as you connect additional agents.
333
+ # See config/peers.toml in the reference installation for the full format.
334
+ `;
335
+ }
336
+
337
+ // ── Output contracts ─────────────────────────────────────────────────────────
338
+
339
+ /**
340
+ * Generates config/output-contracts.toml with shipped profiles.
341
+ */
342
+ export function outputContractsTemplate() {
343
+ return `[profiles.default]
344
+ require_status = true
345
+ section_style = "freeform"
346
+ require_section_items = false
347
+ template_lines = ["Status: <one-line status>", "<response body>"]
348
+
349
+ [profiles.bulleted_briefing]
350
+ require_status = true
351
+ section_style = "bullets"
352
+ require_section_items = true
353
+ template_lines = ["Status: <one-line status>", "- Calendar: <brief update or None>", "- Issues: <brief update or None>", "- Weather: <brief update or None>"]
354
+
355
+ [profiles.structured_rollup]
356
+ require_status = false
357
+ section_style = "headings"
358
+ require_section_items = true
359
+ template_lines = ["## Inbox", "- <brief update or None>", "", "## Projects", "- <brief update or None>", "", "## Backlog", "- <brief update or None>", "", "## Update", "- <brief update or None>"]
360
+ `;
361
+ }
362
+
363
+ // ── Embeddings ───────────────────────────────────────────────────────────────
364
+
365
+ /**
366
+ * Generates config/embeddings.toml with embeddings disabled by default.
367
+ */
368
+ export function embeddingsTemplate() {
369
+ return `enabled = true
370
+ provider = "ollama"
371
+ model = "ollama/nomic-embed-text"
372
+ dimensions = 128
373
+ index_on_write = true
374
+ `;
375
+ }
376
+
377
+ // ── Improvement targets ──────────────────────────────────────────────────────
378
+
379
+ /**
380
+ * Generates an empty config/improvement-targets.toml scaffold.
381
+ */
382
+ export function improvementTargetsTemplate() {
383
+ return `# Improvement targets — add entries here to enable the improvement harness.
384
+ # See config/improvement-targets.toml in the reference installation for format.
385
+ `;
386
+ }
387
+
388
+ // ── Provider ─────────────────────────────────────────────────────────────────
389
+
390
+ /**
391
+ * Generates config/providers/{id}.toml
392
+ * Must satisfy PROVIDER_SCHEMA: id, and at least one of adapter/type
393
+ */
394
+ export function providerTemplate(providerId, config = {}) {
395
+ const {
396
+ adapter = providerId,
397
+ authEnv = `${providerId.toUpperCase()}_API_KEY`,
398
+ authRef = authEnv ? `env:${authEnv}` : "",
399
+ baseUrl = "",
400
+ healthcheck = "",
401
+ timeoutMs = 45000,
402
+ lanes = [],
403
+ models = []
404
+ } = config;
405
+
406
+ let lines = [`id = "${providerId}"`, `adapter = "${adapter}"`];
407
+
408
+ if (authRef) {
409
+ lines.push(`auth_ref = "${authRef}"`);
410
+ }
411
+ if (baseUrl) {
412
+ lines.push(`base_url = "${baseUrl}"`);
413
+ }
414
+ if (healthcheck) {
415
+ lines.push(`healthcheck = "${healthcheck}"`);
416
+ }
417
+ lines.push(`default_timeout_ms = ${timeoutMs}`);
418
+ if (lanes.length > 0) {
419
+ lines.push(`lanes = [${lanes.map((l) => `"${l}"`).join(", ")}]`);
420
+ }
421
+
422
+ const body = lines.join("\n");
423
+
424
+ const modelSections = models
425
+ .map(({ key, id, role }) => `\n[models.${key}]\nid = "${id}"\nrole = "${role}"`)
426
+ .join("\n");
427
+
428
+ return body + "\n" + modelSections + (modelSections ? "\n" : "");
429
+ }
430
+
431
+ // ── Job ──────────────────────────────────────────────────────────────────────
432
+
433
+ /**
434
+ * Generates config/jobs/{id}.toml — workspace-health default.
435
+ * Must satisfy JOB_SCHEMA: id, modelLane, and at least one of schedule/trigger
436
+ */
437
+ export function jobTemplate(jobId, { agentId = "nemo" } = {}) {
438
+ return `id = "${jobId}"
439
+ agent_id = "${agentId}"
440
+ trigger = "hourly"
441
+ task_type = "workspace_health"
442
+ model_lane = "local_report"
443
+ output_target = "state/health"
444
+ idempotency_key = "${jobId}:hour"
445
+ memory_backends = ["file"]
446
+ memory_limit = 6
447
+
448
+ [budget]
449
+ max_tokens = 4000
450
+ max_runtime_seconds = 120
451
+
452
+ [retry]
453
+ max_attempts = 1
454
+
455
+ [stop]
456
+ halt_on_policy_error = true
457
+ halt_on_budget_exceeded = true
458
+ `;
459
+ }
460
+
461
+ // ── Policy files ─────────────────────────────────────────────────────────────
462
+
463
+ /**
464
+ * Generates default policy file content keyed by filename stem.
465
+ * Returns an object: { [filename]: tomlString }
466
+ */
467
+ export function policyTemplates() {
468
+ return {
469
+ "memory-default": `id = "default"
470
+ allow_durable_writes = true
471
+ allow_identity_updates = false
472
+ require_source_reference = true
473
+ require_write_reason = true
474
+ max_writes_per_run = 5
475
+
476
+ [categories]
477
+ allowed = ["decision", "preference", "workflow_rule", "artifact_summary"]
478
+ blocked = ["ephemeral_chatter", "raw_tool_output", "unverified_external_claim"]
479
+ `,
480
+ "tools-interactive-safe": `id = "interactive_safe"
481
+ default = "deny"
482
+ allowed = ["read_file", "search_file", "list_dir", "apply_patch", "run_tests"]
483
+ blocked = ["send_email", "post_web", "delete_file", "reset_repo"]
484
+
485
+ [limits]
486
+ max_parallel = 3
487
+ require_approval_for_network = true
488
+ `,
489
+ "tools-ops-bounded": `id = "ops_bounded"
490
+ default = "deny"
491
+ allowed = ["read_file", "search_file", "list_dir", "apply_patch", "check_status", "run_tests"]
492
+ blocked = ["send_email", "post_web", "delete_file", "reset_repo"]
493
+
494
+ [limits]
495
+ max_parallel = 3
496
+ require_approval_for_network = true
497
+ `
498
+ };
499
+ }
500
+
501
+ // ── Env file ─────────────────────────────────────────────────────────────────
502
+
503
+ /**
504
+ * Generates .env content from a key-value pairs object.
505
+ * @param {Record<string, string>} keys
506
+ */
507
+ export function envTemplate(keys = {}) {
508
+ const header = `# nemoris environment variables\n# Generated by onboarding wizard\n`;
509
+ const lines = Object.entries(keys)
510
+ .map(([k, v]) => `${k}=${v}`)
511
+ .join("\n");
512
+ return header + (lines ? lines + "\n" : "");
513
+ }
514
+
515
+ // ── Execution layer scaffold ──────────────────────────────────────────────────
516
+
517
+ /**
518
+ * Creates config/tools, config/skills, config/agents, config/policies
519
+ * directories and writes default skill manifests and orchestrator config.
520
+ * Safe to run multiple times (directories use recursive: true).
521
+ *
522
+ * @param {string} installDir Absolute path to the nemoris installation root.
523
+ */
524
+ export async function scaffoldToolsAndSkills(installDir) {
525
+ const toolsDir = path.join(installDir, "config", "tools");
526
+ const skillsDir = path.join(installDir, "config", "skills");
527
+ const agentsDir = path.join(installDir, "config", "agents");
528
+ const policiesDir = path.join(installDir, "config", "policies");
529
+
530
+ await mkdirAsync(toolsDir, { recursive: true });
531
+ await mkdirAsync(skillsDir, { recursive: true });
532
+ await mkdirAsync(agentsDir, { recursive: true });
533
+ await mkdirAsync(policiesDir, { recursive: true });
534
+
535
+ // Default skills
536
+ await writeFileAsync(path.join(skillsDir, "workspace-monitor.toml"), `[skill]
537
+ id = "workspace_monitor"
538
+ description = "Monitor workspace directory for changes and summarise"
539
+ agent_scope = ["ops", "main"]
540
+
541
+ [skill.context]
542
+ prompt = "You are monitoring a workspace directory for changes. Compare current state against last known checkpoint. Report: new files, modified files, deleted files, key content changes. Keep summary under 200 words."
543
+
544
+ [skill.tools]
545
+ required = ["read_file", "list_dir"]
546
+ optional = ["shell_exec"]
547
+
548
+ [skill.budget]
549
+ max_tokens = 4096
550
+ max_tool_calls = 10
551
+ `);
552
+
553
+ await writeFileAsync(path.join(skillsDir, "self-improvement.toml"), `[skill]
554
+ id = "self_improvement"
555
+ description = "Analyse run artifacts and apply tuning adjustments"
556
+ agent_scope = ["ops", "main"]
557
+
558
+ [skill.context]
559
+ prompt = "You are reviewing a run artifact that scored below threshold. Read the run artifact and current tunings. Identify the root cause. Apply ONE of: prompt refinement, budget adjustment, tool policy change, lane escalation. Write the tuning to state/tunings/<jobId>/ as a JSON file. Explain what you changed and why in under 100 words."
560
+
561
+ [skill.tools]
562
+ required = ["read_file", "write_file", "list_dir"]
563
+ optional = []
564
+
565
+ [skill.budget]
566
+ max_tokens = 4096
567
+ max_tool_calls = 8
568
+ `);
569
+
570
+ // Orchestrator agent
571
+ await writeFileAsync(path.join(agentsDir, "orchestrator.toml"), `id = "orchestrator"
572
+ soul_ref = "config/identity/orchestrator-soul.md"
573
+ purpose_ref = "config/identity/orchestrator-purpose.md"
574
+ primary_lane = "local_cheap"
575
+ fallback_lane = "interactive_fallback"
576
+ tool_policy = "orchestrator"
577
+ memory_policy = "orchestrator"
578
+
579
+ [limits]
580
+ max_tokens_per_turn = 2700
581
+
582
+ [routing.static]
583
+ "heartbeat-check" = "heartbeat"
584
+
585
+ [routing.dynamic]
586
+ enabled = true
587
+ model_lane = "local_cheap"
588
+ max_routing_tokens = 500
589
+ `);
590
+
591
+ // Orchestrator identity files
592
+ const identityDir = path.join(installDir, "config", "identity");
593
+ await mkdirAsync(identityDir, { recursive: true });
594
+ const orchSoulPath = path.join(identityDir, "orchestrator-soul.md");
595
+ const orchPurposePath = path.join(identityDir, "orchestrator-purpose.md");
596
+ // Only write if missing (don't overwrite user edits)
597
+ try { await accessAsync(orchSoulPath); } catch {
598
+ await writeFileAsync(orchSoulPath, `You are the orchestrator — a routing and delegation agent.\nYou do not execute tasks directly. You inspect incoming work, select the best agent, and delegate.\nBe concise. Favour the cheapest capable lane. Escalate only when the task demands it.\n`);
599
+ }
600
+ try { await accessAsync(orchPurposePath); } catch {
601
+ await writeFileAsync(orchPurposePath, `Route incoming jobs to the correct agent and model lane.\nMinimise cost. Maximise task-agent fit. Never execute work yourself.\n`);
602
+ }
603
+
604
+ // Orchestrator tool policy
605
+ await writeFileAsync(path.join(policiesDir, "tools-orchestrator.toml"), `id = "orchestrator"
606
+ default = "deny"
607
+ allowed = ["delegate_agent", "delegate_cli"]
608
+ blocked = ["read_file", "write_file", "shell_exec"]
609
+
610
+ [limits]
611
+ require_approval_for_network = false
612
+ `);
613
+ }
614
+
615
+ // ── Workspace context files ───────────────────────────────────────────────────
616
+
617
+ /**
618
+ * Generates workspace/SOUL.md — who the agent is.
619
+ * Based on the OpenClaw SOUL.md template pattern.
620
+ */
621
+ export function workspaceSoulTemplate({ agentName, userName }) {
622
+ return `# SOUL.md - Who You Are
623
+
624
+ *You're not a chatbot. You're becoming someone.*
625
+
626
+ ## Core Identity
627
+
628
+ - **Name:** ${agentName}
629
+ - **Role:** Personal AI assistant for ${userName}
630
+ - **Workspace:** This folder is home.
631
+
632
+ ## Core Truths
633
+
634
+ **Be genuinely helpful, not performatively helpful.** Skip the filler — just help.
635
+
636
+ **Have opinions.** You're allowed to disagree, prefer things, find things interesting or boring.
637
+
638
+ **Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Then ask if you're stuck.
639
+
640
+ **Earn trust through competence.** Be careful with external actions. Be bold with internal ones.
641
+
642
+ ## Boundaries
643
+
644
+ - Private things stay private.
645
+ - Ask before acting externally.
646
+ - Never send half-baked replies.
647
+
648
+ ## Continuity
649
+
650
+ Each session, you wake up fresh. These workspace files are your memory. Read them. Update them.
651
+
652
+ *Update this file as you learn who you are.*
653
+ `;
654
+ }
655
+
656
+ /**
657
+ * Generates workspace/USER.md — about the human.
658
+ */
659
+ export function workspaceUserTemplate({ userName }) {
660
+ return `# USER.md - About ${userName}
661
+
662
+ *Learn about the person you're helping. Update this as you go.*
663
+
664
+ - **Name:** ${userName}
665
+ - **What to call them:** ${userName}
666
+ - **Pronouns:**
667
+ - **Timezone:**
668
+ - **Notes:**
669
+
670
+ ## Context
671
+
672
+ *(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)*
673
+
674
+ ---
675
+
676
+ The more you know, the better you can help.
677
+ `;
678
+ }
679
+
680
+ /**
681
+ * Generates workspace/MEMORY.md — long-term curated memory.
682
+ */
683
+ export function workspaceMemoryTemplate({ agentName }) {
684
+ return `# MEMORY.md — ${agentName}'s Long-Term Memory
685
+
686
+ *Curated durable memory. Not raw logs — those live in memory/YYYY-MM-DD.md*
687
+
688
+ ## Identity
689
+
690
+ - (Fill in who you are and what you're here for)
691
+
692
+ ## Durable Facts
693
+
694
+ - (Key things you've learned about the user)
695
+
696
+ ## Active Projects
697
+
698
+ - (Running list of what's in flight)
699
+
700
+ ## Decisions
701
+
702
+ - (Recurring decisions and standing rules worth remembering)
703
+ `;
704
+ }
705
+
706
+ /**
707
+ * Generates workspace/AGENTS.md — operating manual.
708
+ */
709
+ export function workspaceAgentsTemplate({ agentName }) {
710
+ return `# AGENTS.md - ${agentName}'s Operating Manual
711
+
712
+ This workspace is home.
713
+
714
+ ## Session Startup
715
+
716
+ Before doing anything else:
717
+
718
+ 1. Read \`SOUL.md\` — this is who you are
719
+ 2. Read \`USER.md\` — this is who you're helping
720
+ 3. Check \`memory/YYYY-MM-DD.md\` (today + yesterday) for recent context
721
+ 4. In main sessions: also read \`MEMORY.md\` for long-term context
722
+
723
+ Don't ask permission. Just do it.
724
+
725
+ ## Memory
726
+
727
+ - **Daily notes:** \`memory/YYYY-MM-DD.md\` — raw logs of what happened
728
+ - **Long-term:** \`MEMORY.md\` — curated memories and durable facts
729
+
730
+ Write things down. If you want to remember something, write it to a file.
731
+
732
+ ## Hard Rules
733
+
734
+ - Don't exfiltrate private data.
735
+ - Don't run destructive commands without asking.
736
+ - Ask before any external action (emails, posts, anything public).
737
+
738
+ ## Make It Yours
739
+
740
+ This is a starting point. Add your own conventions as you figure out what works.
741
+ `;
742
+ }
743
+
744
+ /**
745
+ * Generates workspace/TOOLS.md — local notes about the setup.
746
+ */
747
+ export function workspaceToolsTemplate() {
748
+ return `# TOOLS.md - Local Notes
749
+
750
+ *This file is for setup-specific notes — things unique to your environment.*
751
+
752
+ ## What Goes Here
753
+
754
+ - API endpoints and service URLs
755
+ - SSH hosts and aliases
756
+ - Device names and nicknames
757
+ - Preferred models or voices
758
+ - Anything environment-specific that doesn't belong in config
759
+
760
+ ---
761
+
762
+ Add whatever helps you do your job. This is your cheat sheet.
763
+ `;
764
+ }
765
+
766
+ /**
767
+ * Writes all workspace context files to workspaceRoot.
768
+ * Skips files that already exist (writeIfMissing pattern).
769
+ */
770
+ import { existsSync, writeFileSync, mkdirSync } from "node:fs";
771
+ export function writeWorkspaceContextFiles({ workspaceRoot, agentName, userName, agentId }) {
772
+ mkdirSync(workspaceRoot, { recursive: true });
773
+ mkdirSync(path.join(workspaceRoot, "memory"), { recursive: true });
774
+
775
+ const files = [
776
+ { name: "SOUL.md", content: workspaceSoulTemplate({ agentName, userName }) },
777
+ { name: "USER.md", content: workspaceUserTemplate({ userName }) },
778
+ { name: "MEMORY.md", content: workspaceMemoryTemplate({ agentName }) },
779
+ { name: "AGENTS.md", content: workspaceAgentsTemplate({ agentName }) },
780
+ { name: "TOOLS.md", content: workspaceToolsTemplate() },
781
+ ];
782
+
783
+ const results = [];
784
+ for (const { name, content } of files) {
785
+ const filePath = path.join(workspaceRoot, name);
786
+ if (!existsSync(filePath)) {
787
+ writeFileSync(filePath, content, "utf8");
788
+ results.push({ file: name, status: "created" });
789
+ } else {
790
+ results.push({ file: name, status: "exists" });
791
+ }
792
+ }
793
+ return results;
794
+ }