agency-lang 0.7.6 → 0.7.8

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 (236) hide show
  1. package/dist/lib/agents/agency-agent/agent.agency +490 -39
  2. package/dist/lib/agents/agency-agent/agent.js +3583 -639
  3. package/dist/lib/agents/agency-agent/lib/attachments.js +6 -3
  4. package/dist/lib/agents/agency-agent/lib/capabilities.agency +464 -0
  5. package/dist/lib/agents/agency-agent/lib/capabilities.js +2570 -0
  6. package/dist/lib/agents/agency-agent/lib/config.agency +22 -1
  7. package/dist/lib/agents/agency-agent/lib/config.js +159 -6
  8. package/dist/lib/agents/agency-agent/lib/defaultPolicy.js +8 -4
  9. package/dist/lib/agents/agency-agent/lib/modelFilters.js +4 -2
  10. package/dist/lib/agents/agency-agent/lib/models.agency +46 -12
  11. package/dist/lib/agents/agency-agent/lib/models.js +359 -33
  12. package/dist/lib/agents/agency-agent/lib/resolution.agency +22 -7
  13. package/dist/lib/agents/agency-agent/lib/resolution.js +131 -46
  14. package/dist/lib/agents/agency-agent/lib/search.js +34 -17
  15. package/dist/lib/agents/agency-agent/lib/settings.agency +27 -4
  16. package/dist/lib/agents/agency-agent/lib/settings.js +224 -23
  17. package/dist/lib/agents/agency-agent/lib/slots.js +6 -3
  18. package/dist/lib/agents/agency-agent/lib/utils.js +16 -8
  19. package/dist/lib/agents/agency-agent/shared.agency +218 -18
  20. package/dist/lib/agents/agency-agent/shared.js +1890 -186
  21. package/dist/lib/agents/agency-agent/subagents/code.agency +45 -1
  22. package/dist/lib/agents/agency-agent/subagents/code.js +105 -11
  23. package/dist/lib/agents/agency-agent/subagents/explorer.js +4 -2
  24. package/dist/lib/agents/agency-agent/subagents/oracle.js +4 -2
  25. package/dist/lib/agents/agency-agent/subagents/research.js +4 -2
  26. package/dist/lib/agents/agency-agent/subagents/review.js +20 -10
  27. package/dist/lib/agents/agency-agent/subagents/verify.agency +103 -0
  28. package/dist/lib/agents/agency-agent/subagents/verify.js +797 -0
  29. package/dist/lib/agents/agency-agent/tests/agentDir.agency +20 -0
  30. package/dist/lib/agents/agency-agent/tests/agentDir.js +353 -0
  31. package/dist/lib/agents/agency-agent/tests/agentDir.test.json +15 -0
  32. package/dist/lib/agents/agency-agent/tests/agentTurn.agency +163 -1
  33. package/dist/lib/agents/agency-agent/tests/agentTurn.js +1433 -141
  34. package/dist/lib/agents/agency-agent/tests/agentTurn.test.json +261 -19
  35. package/dist/lib/agents/agency-agent/tests/capabilities.agency +233 -0
  36. package/dist/lib/agents/agency-agent/tests/capabilities.js +2203 -0
  37. package/dist/lib/agents/agency-agent/tests/capabilities.test.json +165 -0
  38. package/dist/lib/agents/agency-agent/tests/execPolicy.js +2 -1
  39. package/dist/lib/agents/agency-agent/tests/gitPolicy.js +2 -1
  40. package/dist/lib/agents/agency-agent/tests/memoryWiring.agency +148 -0
  41. package/dist/lib/agents/agency-agent/tests/memoryWiring.js +1334 -0
  42. package/dist/lib/agents/agency-agent/tests/memoryWiring.test.json +75 -0
  43. package/dist/lib/agents/agency-agent/tests/models.js +2 -1
  44. package/dist/lib/agents/agency-agent/tests/resolution.agency +52 -0
  45. package/dist/lib/agents/agency-agent/tests/resolution.js +377 -3
  46. package/dist/lib/agents/agency-agent/tests/resolution.test.json +110 -8
  47. package/dist/lib/agents/agency-agent/tests/search.js +14 -7
  48. package/dist/lib/agents/agency-agent/tests/settings.agency +109 -0
  49. package/dist/lib/agents/agency-agent/tests/settings.js +939 -2
  50. package/dist/lib/agents/agency-agent/tests/settings.test.json +105 -6
  51. package/dist/lib/agents/agency-agent/tests/toolWiring.js +2 -1
  52. package/dist/lib/agents/docs/cli/agent.md +16 -0
  53. package/dist/lib/agents/docs/guide/error-handling.md +21 -0
  54. package/dist/lib/agents/docs/guide/types.md +48 -0
  55. package/dist/lib/agents/policy/agent.js +26 -13
  56. package/dist/lib/backends/agencyGenerator.test.js +21 -0
  57. package/dist/lib/backends/recursiveAliases.codegen.test.d.ts +1 -0
  58. package/dist/lib/backends/recursiveAliases.codegen.test.js +229 -0
  59. package/dist/lib/backends/typescriptBuilder/nameClassifier.d.ts +6 -0
  60. package/dist/lib/backends/typescriptBuilder/nameClassifier.js +1 -1
  61. package/dist/lib/backends/typescriptBuilder.d.ts +14 -0
  62. package/dist/lib/backends/typescriptBuilder.js +74 -11
  63. package/dist/lib/backends/typescriptGenerator/recursiveSchemaJson.test.d.ts +1 -0
  64. package/dist/lib/backends/typescriptGenerator/recursiveSchemaJson.test.js +32 -0
  65. package/dist/lib/backends/typescriptGenerator/typeToZodSchema.d.ts +1 -1
  66. package/dist/lib/backends/typescriptGenerator/typeToZodSchema.js +19 -7
  67. package/dist/lib/backends/typescriptGenerator/validationDescriptor.d.ts +1 -1
  68. package/dist/lib/backends/typescriptGenerator/validationDescriptor.js +30 -20
  69. package/dist/lib/backends/valueParamCycle.d.ts +22 -0
  70. package/dist/lib/backends/valueParamCycle.js +41 -0
  71. package/dist/lib/cli/commands.js +6 -2
  72. package/dist/lib/cli/runBundledAgent.d.ts +10 -0
  73. package/dist/lib/cli/runBundledAgent.js +47 -18
  74. package/dist/lib/cli/runBundledAgent.test.js +37 -1
  75. package/dist/lib/cli/test.js +9 -0
  76. package/dist/lib/cli/util.js +14 -0
  77. package/dist/lib/compiler/buildManifest.d.ts +40 -0
  78. package/dist/lib/compiler/buildManifest.js +210 -0
  79. package/dist/lib/compiler/buildManifest.test.d.ts +1 -0
  80. package/dist/lib/compiler/buildManifest.test.js +179 -0
  81. package/dist/lib/compiler/buildSession.d.ts +29 -12
  82. package/dist/lib/compiler/buildSession.js +177 -32
  83. package/dist/lib/compiler/buildSession.test.js +206 -22
  84. package/dist/lib/compiler/compileClosure.d.ts +10 -0
  85. package/dist/lib/compiler/compileClosure.js +18 -1
  86. package/dist/lib/compiler/compileClosure.test.js +24 -2
  87. package/dist/lib/compiler/manifestTracker.d.ts +33 -0
  88. package/dist/lib/compiler/manifestTracker.js +112 -0
  89. package/dist/lib/compiler/manifestTracker.test.d.ts +1 -0
  90. package/dist/lib/compiler/manifestTracker.test.js +98 -0
  91. package/dist/lib/config.d.ts +11 -0
  92. package/dist/lib/config.js +1 -0
  93. package/dist/lib/logsViewer/summary.js +19 -0
  94. package/dist/lib/logsViewer/summary.test.js +61 -0
  95. package/dist/lib/logsViewer/tree.d.ts +10 -0
  96. package/dist/lib/logsViewer/tree.js +58 -4
  97. package/dist/lib/logsViewer/tree.test.js +107 -0
  98. package/dist/lib/optimize/constraint.test.js +9 -7
  99. package/dist/lib/runtime/agencyFunction.d.ts +12 -0
  100. package/dist/lib/runtime/agencyFunction.js +18 -0
  101. package/dist/lib/runtime/call.js +17 -0
  102. package/dist/lib/runtime/configOverrides.d.ts +2 -0
  103. package/dist/lib/runtime/configOverrides.js +5 -0
  104. package/dist/lib/runtime/configOverrides.test.js +24 -0
  105. package/dist/lib/runtime/failurePropagation.d.ts +65 -0
  106. package/dist/lib/runtime/failurePropagation.js +205 -0
  107. package/dist/lib/runtime/failurePropagation.test.d.ts +1 -0
  108. package/dist/lib/runtime/failurePropagation.test.js +381 -0
  109. package/dist/lib/runtime/index.d.ts +2 -1
  110. package/dist/lib/runtime/index.js +1 -0
  111. package/dist/lib/runtime/ipc.js +1 -6
  112. package/dist/lib/runtime/llmRetry.d.ts +10 -0
  113. package/dist/lib/runtime/llmRetry.js +25 -0
  114. package/dist/lib/runtime/llmRetry.test.js +22 -1
  115. package/dist/lib/runtime/prompt.d.ts +13 -0
  116. package/dist/lib/runtime/prompt.js +57 -6
  117. package/dist/lib/runtime/prompt.test.js +49 -0
  118. package/dist/lib/runtime/result.d.ts +14 -0
  119. package/dist/lib/runtime/result.js +13 -0
  120. package/dist/lib/runtime/result.test.js +2 -0
  121. package/dist/lib/runtime/runner.js +37 -30
  122. package/dist/lib/runtime/state/context.d.ts +2 -0
  123. package/dist/lib/runtime/state/context.js +7 -0
  124. package/dist/lib/runtime/threadEndHooksEvents.d.ts +28 -0
  125. package/dist/lib/runtime/threadEndHooksEvents.js +49 -0
  126. package/dist/lib/runtime/threadEndHooksEvents.test.d.ts +1 -0
  127. package/dist/lib/runtime/threadEndHooksEvents.test.js +71 -0
  128. package/dist/lib/runtime/truncate.d.ts +6 -0
  129. package/dist/lib/runtime/truncate.js +26 -0
  130. package/dist/lib/runtime/validateChain.d.ts +12 -0
  131. package/dist/lib/runtime/validateChain.js +23 -1
  132. package/dist/lib/runtime/validateChain.test.js +77 -0
  133. package/dist/lib/statelogClient.d.ts +61 -1
  134. package/dist/lib/statelogClient.js +73 -0
  135. package/dist/lib/statelogClient.test.js +66 -0
  136. package/dist/lib/stdlib/builtins.js +16 -0
  137. package/dist/lib/stdlib/http.d.ts +6 -6
  138. package/dist/lib/stdlib/http.js +52 -26
  139. package/dist/lib/stdlib/shell.js +41 -12
  140. package/dist/lib/stdlib/version.d.ts +1 -1
  141. package/dist/lib/stdlib/version.js +1 -1
  142. package/dist/lib/typeChecker/assignability.js +63 -68
  143. package/dist/lib/typeChecker/builtinGenerics.d.ts +53 -0
  144. package/dist/lib/typeChecker/builtinGenerics.js +226 -0
  145. package/dist/lib/typeChecker/builtinGenerics.test.d.ts +1 -0
  146. package/dist/lib/typeChecker/builtinGenerics.test.js +540 -0
  147. package/dist/lib/typeChecker/flow.d.ts +8 -10
  148. package/dist/lib/typeChecker/flow.js +14 -12
  149. package/dist/lib/typeChecker/handlerParamTyping.js +7 -0
  150. package/dist/lib/typeChecker/handlerParamTyping.test.js +7 -0
  151. package/dist/lib/typeChecker/index.js +33 -27
  152. package/dist/lib/typeChecker/matchExprTypes.js +8 -0
  153. package/dist/lib/typeChecker/matchExpression.test.js +7 -0
  154. package/dist/lib/typeChecker/nullLiteralSynth.test.d.ts +1 -0
  155. package/dist/lib/typeChecker/nullLiteralSynth.test.js +36 -0
  156. package/dist/lib/typeChecker/paramAcceptsFailure.test.d.ts +1 -0
  157. package/dist/lib/typeChecker/paramAcceptsFailure.test.js +35 -0
  158. package/dist/lib/typeChecker/recursiveAssignability.test.d.ts +1 -0
  159. package/dist/lib/typeChecker/recursiveAssignability.test.js +94 -0
  160. package/dist/lib/typeChecker/resultUnion.js +16 -0
  161. package/dist/lib/typeChecker/strictMemberAccess.test.js +34 -0
  162. package/dist/lib/typeChecker/synthesizer.js +28 -6
  163. package/dist/lib/typeChecker/typeKey.d.ts +24 -0
  164. package/dist/lib/typeChecker/typeKey.js +83 -0
  165. package/dist/lib/typeChecker/typeKey.test.d.ts +1 -0
  166. package/dist/lib/typeChecker/typeKey.test.js +165 -0
  167. package/dist/lib/typeChecker/utils.d.ts +16 -0
  168. package/dist/lib/typeChecker/utils.js +40 -0
  169. package/dist/lib/typeChecker/validate.js +3 -6
  170. package/dist/scripts/agency.js +5 -1
  171. package/dist/scripts/stage-agents.test.d.ts +1 -0
  172. package/dist/scripts/stage-agents.test.js +50 -0
  173. package/dist/scripts/stdlib-stamp.d.ts +1 -0
  174. package/dist/scripts/stdlib-stamp.js +31 -0
  175. package/package.json +1 -1
  176. package/stdlib/agency/eval.js +64 -32
  177. package/stdlib/agency/local.js +24 -12
  178. package/stdlib/agency.js +84 -42
  179. package/stdlib/agent.js +44 -22
  180. package/stdlib/args.js +2 -1
  181. package/stdlib/array.js +54 -27
  182. package/stdlib/auth/keyring.js +14 -7
  183. package/stdlib/auth/oauth.js +22 -11
  184. package/stdlib/calendar.js +44 -22
  185. package/stdlib/capabilities.agency +1 -1
  186. package/stdlib/capabilities.js +1 -1
  187. package/stdlib/clipboard.js +2 -1
  188. package/stdlib/concurrency.js +8 -4
  189. package/stdlib/data/finance/dbnomics.js +16 -8
  190. package/stdlib/data/finance/edgar.js +40 -20
  191. package/stdlib/data/finance/fred.js +52 -26
  192. package/stdlib/data/finance/gdelt.js +16 -8
  193. package/stdlib/data/people/littlesis.js +52 -26
  194. package/stdlib/data/tech/hackernews.js +72 -36
  195. package/stdlib/data/tech/yc.js +32 -16
  196. package/stdlib/data/usaspending.agency +263 -0
  197. package/stdlib/data/usaspending.js +2304 -0
  198. package/stdlib/data/wikidata.js +46 -23
  199. package/stdlib/date.js +56 -28
  200. package/stdlib/fs.js +40 -20
  201. package/stdlib/git.js +96 -48
  202. package/stdlib/http.agency +31 -15
  203. package/stdlib/http.js +138 -37
  204. package/stdlib/image.js +16 -8
  205. package/stdlib/index.agency +1 -1
  206. package/stdlib/index.js +113 -57
  207. package/stdlib/llm.js +18 -9
  208. package/stdlib/markdown.js +10 -5
  209. package/stdlib/math.js +20 -10
  210. package/stdlib/memory.agency +1 -1
  211. package/stdlib/memory.js +15 -8
  212. package/stdlib/messaging/email.js +70 -35
  213. package/stdlib/messaging/imessage.js +8 -4
  214. package/stdlib/messaging/sms.js +14 -7
  215. package/stdlib/object.js +26 -13
  216. package/stdlib/path.js +18 -9
  217. package/stdlib/policy.js +56 -28
  218. package/stdlib/shell.js +84 -42
  219. package/stdlib/skills.js +26 -13
  220. package/stdlib/speech.js +22 -11
  221. package/stdlib/statelog.js +22 -11
  222. package/stdlib/strategy.js +28 -14
  223. package/stdlib/syntax.js +46 -23
  224. package/stdlib/system.js +28 -14
  225. package/stdlib/tag.js +20 -10
  226. package/stdlib/thread.js +44 -22
  227. package/stdlib/ui/chart.js +36 -18
  228. package/stdlib/ui/cli.js +18 -9
  229. package/stdlib/ui/layout.js +186 -93
  230. package/stdlib/ui/table.js +54 -27
  231. package/stdlib/ui.js +414 -207
  232. package/stdlib/validation.js +32 -16
  233. package/stdlib/weather.js +8 -4
  234. package/stdlib/web/browser.js +14 -7
  235. package/stdlib/web/search.js +24 -12
  236. package/stdlib/wikipedia.js +8 -4
@@ -57,7 +57,8 @@ import {
57
57
  BUILTIN_POLICIES,
58
58
  } from "./lib/defaultPolicy.agency"
59
59
  import { filterHostedModels, ModelFilters } from "./lib/modelFilters.agency"
60
- import { parseModelFlag, knownProviders, Resolved } from "./lib/models.agency"
60
+ import { parseModelFlag, parseModelSpec, knownProviders, embeddingSpecHasProvider, missingProviderEnvVar, Resolved } from "./lib/models.agency"
61
+ import { SLOTS } from "./lib/slots.agency"
61
62
  import {
62
63
  buildLayers,
63
64
  resolveAll,
@@ -71,7 +72,19 @@ import {
71
72
  getSearchBackend,
72
73
  availableBackendItems,
73
74
  } from "./lib/search.agency"
74
- import { getModelSettings, loadSettings } from "./lib/settings.agency"
75
+ import {
76
+ CAPABILITY_FIELDS,
77
+ CapabilityPatch,
78
+ mergeCapabilityOverride,
79
+ removeCapabilityField,
80
+ providerLacksEmbeddings,
81
+ } from "./lib/capabilities.agency"
82
+ import {
83
+ getCapabilitySettings,
84
+ getModelSettings,
85
+ loadSettings,
86
+ saveSettings,
87
+ } from "./lib/settings.agency"
75
88
  import { truncate, formatArgs, formatToolResponse } from "./lib/utils.agency"
76
89
  import {
77
90
  configureModels,
@@ -82,6 +95,17 @@ import {
82
95
  promoteAll,
83
96
  currentProvider,
84
97
  resolveMaxToolCallRounds,
98
+ getCapabilities,
99
+ getCapabilitiesResolved,
100
+ getCapabilityModelKey,
101
+ getCapabilityProvider,
102
+ refreshCapabilities,
103
+ embeddingKeyStatus,
104
+ memoryEnablePlan,
105
+ overrideFromResolved,
106
+ embeddingsOverride,
107
+ providerEnvMissing,
108
+ demoteProvider,
85
109
  } from "./shared.agency"
86
110
  import { codeAgent, setCodeOneShot } from "./subagents/code.agency"
87
111
  import { explorerAgent } from "./subagents/explorer.agency"
@@ -314,6 +338,7 @@ def builtinPalette(): Record<string, string> {
314
338
  "/models": "List / filter the hosted model catalog",
315
339
  "/local": "Switch to a local model",
316
340
  "/search": "Choose the web search backend (hosted / Tavily / Brave / off)",
341
+ "/settings": "View and change capability settings for the current model",
317
342
  "/paste": "Multi-line paste mode (Ctrl+D submits, Ctrl+C cancels)",
318
343
  "/help": "Show available slash commands"
319
344
  }
@@ -361,6 +386,15 @@ export def switchModel(
361
386
  ctx: Ctx,
362
387
  before: Record<string, Resolved>,
363
388
  ): { session: Session; resolved: Record<string, Resolved>; changes: SlotChange[] } {
389
+ // The caller builds ctx from currentProvider(), which returns the
390
+ // PROMOTED name on openai sessions. priceDefault and the built-in
391
+ // tables key by public provider names, so demote before resolving —
392
+ // otherwise a switch that lets any slot fall through to the built-in
393
+ // defaults fails with "openai-responses has no built-in defaults".
394
+ const ctx2: Ctx = {
395
+ provider: demoteProvider(ctx.provider),
396
+ price: ctx.price
397
+ }
364
398
  let nextSlots: Record<string, string> = {}
365
399
  for (key in Object.keys(prior.slots)) {
366
400
  nextSlots[key] = prior.slots[key]
@@ -370,9 +404,23 @@ export def switchModel(
370
404
  slots: nextSlots
371
405
  }
372
406
  const parsed = parseModelFlag(spec)
373
- if (parsed.slot != "" && parsed.slot != "main" && parsed.slot != "reasoning") {
407
+ if (parsed.slot != "" && !SLOTS.includes(parsed.slot)) {
408
+ pushMessage(
409
+ color.red("Unknown model slot '${parsed.slot}'. Valid slots: ${SLOTS.join(", ")}."),
410
+ )
411
+ return {
412
+ session: prior,
413
+ resolved: before,
414
+ changes: []
415
+ }
416
+ }
417
+ if (parsed.slot == "embedding" && !embeddingSpecHasProvider(parsed.model)) {
418
+ // The embedding slot exists to point AWAY from the chat provider, so
419
+ // the provider can never be inferred. Require the explicit form.
374
420
  pushMessage(
375
- color.red("Unknown model slot '${parsed.slot}'. Valid slots: main, reasoning."),
421
+ color.red(
422
+ "Embedding models need the provider/model form, e.g. embedding=openai/text-embedding-3-small.",
423
+ ),
376
424
  )
377
425
  return {
378
426
  session: prior,
@@ -399,7 +447,7 @@ export def switchModel(
399
447
  knownProviders(),
400
448
  providerEstablished,
401
449
  )
402
- const all = resolveAll(layers, ctx)
450
+ const all = resolveAll(layers, ctx2)
403
451
  if (isFailure(all)) {
404
452
  pushMessage(color.red("Could not switch model: ${all.error}"))
405
453
  return {
@@ -423,6 +471,13 @@ export def switchModel(
423
471
  returns when a selective toggle lands.) Returns true if it emitted the notice. */
424
472
  export def reactToSlotChange(change: SlotChange): boolean {
425
473
  if (change.slot == "main" && change.before.provider != change.after.provider) {
474
+ // Skip the pause only when startup actually enabled memory WITH the
475
+ // override. A set-but-keyless slot took the fallback path, so memory
476
+ // runs on derived embeddings and the provider change still shifts
477
+ // the embedding space behind recall.
478
+ if (_memoryEmbeddingsPinned) {
479
+ return false
480
+ }
426
481
  pushMessage(
427
482
  color.dim(
428
483
  "Warning: provider changed - memory is paused until restart (recall would be unreliable).",
@@ -433,6 +488,28 @@ export def reactToSlotChange(change: SlotChange): boolean {
433
488
  disableMemory() with approve
434
489
  return true
435
490
  }
491
+ if (change.slot == "embedding") {
492
+ const override = overrideFromResolved(change.after)
493
+ if (override != null) {
494
+ const missing = missingProviderEnvVar(override.provider)
495
+ if (missing != "") {
496
+ pushMessage(
497
+ color.yellow(
498
+ "Warning: the embedding slot needs ${missing}, which is not set. The assignment takes effect at next agent start, and memory falls back to the default embedding behavior until it is exported.",
499
+ ),
500
+ )
501
+ return true
502
+ }
503
+ // The healthy path must not be silent: enableMemory already ran,
504
+ // so the user would otherwise believe embeddings switched now.
505
+ pushMessage(
506
+ color.dim(
507
+ "Embedding slot set: memory embeddings use ${override.provider}/${override.model} at next agent start.",
508
+ ),
509
+ )
510
+ return true
511
+ }
512
+ }
436
513
  return false
437
514
  }
438
515
 
@@ -467,6 +544,290 @@ export def parseModelsFilters(cmd: string): ModelFilters {
467
544
  }
468
545
  }
469
546
 
547
+ /** Human label for a capability source layer. Interprets the source
548
+ strings resolveCapabilities emits (capabilities.agency). */
549
+ def capabilitySourceLabel(source: string): string {
550
+ if (source == "default") {
551
+ return "built-in default"
552
+ }
553
+ if (source == "provider") {
554
+ return "provider default: ${getCapabilityProvider()}"
555
+ }
556
+ if (source == "model") {
557
+ return "built-in for ${getCapabilityModelKey()}"
558
+ }
559
+ if (source == "user-global") {
560
+ return "your global override"
561
+ }
562
+ if (source == "user-provider") {
563
+ return "your override for ${getCapabilityProvider()}"
564
+ }
565
+ return "your override for ${getCapabilityModelKey()}"
566
+ }
567
+
568
+ /** One "value (source)" cell. The typed Capabilities shape cannot be
569
+ indexed by a dynamic key, so this is the renderer's per-field switch. */
570
+ def capabilityRow(fieldKey: string): string {
571
+ const resolved = getCapabilitiesResolved()
572
+ const values = resolved.values
573
+ const sources = resolved.sources
574
+ if (fieldKey == "prompt") {
575
+ return "${values.prompt} (${capabilitySourceLabel(sources.prompt)})"
576
+ }
577
+ if (fieldKey == "summarize") {
578
+ return "${values.summarize} (${capabilitySourceLabel(sources.summarize)})"
579
+ }
580
+ if (fieldKey == "memory") {
581
+ return "${values.memory} (${capabilitySourceLabel(sources.memory)})"
582
+ }
583
+ let cap = "none"
584
+ if (values.maxTokens != null) {
585
+ cap = "${values.maxTokens}"
586
+ }
587
+ return "${cap} (${capabilitySourceLabel(sources.maxTokens)})"
588
+ }
589
+
590
+ /** Render the current profile with per-field provenance. */
591
+ def showCapabilities() {
592
+ pushMessage("Capabilities for ${getCapabilityModelKey()} (${getCapabilityProvider()}):")
593
+ for (field in CAPABILITY_FIELDS) {
594
+ pushMessage(" ${field.label} ${capabilityRow(field.key)}")
595
+ }
596
+ }
597
+
598
+ /** The settings.json key a scope writes under. Global's key is unused. */
599
+ def scopeTargetName(scopeKey: string): string {
600
+ if (scopeKey == "provider") {
601
+ return getCapabilityProvider()
602
+ }
603
+ return getCapabilityModelKey()
604
+ }
605
+
606
+ /** Persist one override patch at a scope, then re-resolve. */
607
+ def saveCapabilityOverride(scopeKey: string, patch: CapabilityPatch) {
608
+ let settings = loadSettings()
609
+ settings.capabilities = mergeCapabilityOverride(
610
+ getCapabilitySettings(settings),
611
+ scopeKey,
612
+ scopeTargetName(scopeKey),
613
+ patch,
614
+ )
615
+ saveSettings(settings)
616
+ refreshCapabilities(getCapabilityModelKey(), getCapabilityProvider())
617
+ }
618
+
619
+ /** Remove one field override at a scope, then re-resolve. */
620
+ def removeCapabilityOverride(scopeKey: string, fieldKey: string) {
621
+ let settings = loadSettings()
622
+ settings.capabilities = removeCapabilityField(
623
+ getCapabilitySettings(settings),
624
+ scopeKey,
625
+ scopeTargetName(scopeKey),
626
+ fieldKey,
627
+ )
628
+ saveSettings(settings)
629
+ refreshCapabilities(getCapabilityModelKey(), getCapabilityProvider())
630
+ }
631
+
632
+ /** Ask which scope an override applies to. Returns "" on cancel. */
633
+ def pickOverrideScope(): string {
634
+ let scopeItems: ChoiceItem[] = []
635
+ scopeItems.push({
636
+ key: "model",
637
+ label: "this model (${getCapabilityModelKey()})"
638
+ })
639
+ scopeItems.push({
640
+ key: "provider",
641
+ label: "this provider (${getCapabilityProvider()})"
642
+ })
643
+ scopeItems.push({
644
+ key: "global",
645
+ label: "all models"
646
+ })
647
+ const scopeKey = chooseOption("Scope", "Where should this apply?", scopeItems, allowCancel: true)
648
+ if (scopeKey == null) {
649
+ return ""
650
+ }
651
+ return scopeKey
652
+ }
653
+
654
+ /** Re-apply the resolved cap as the branch default. After a reset the
655
+ resolved value falls back to a built-in layer, which is always numeric
656
+ for known providers, so live-apply covers removal too. */
657
+ def applyLiveMaxTokens() {
658
+ const capValue = getCapabilities().maxTokens
659
+ if (capValue != null) {
660
+ setLlmOptions({ maxTokens: capValue })
661
+ }
662
+ }
663
+
664
+ /** Positive-integer parse. parseInt and isNaN appear nowhere in the
665
+ repo's .agency code, so this uses a digits check instead. Returns -1
666
+ for anything that is not all digits. */
667
+ def parsePositiveInt(text: string): number {
668
+ // Bound to a const because the parser rejects method calls on string
669
+ // LITERALS ("0123456789".includes(...) does not parse; a variable does).
670
+ const digits = "0123456789"
671
+ const trimmed = text.trim()
672
+ if (trimmed == "") {
673
+ return -1
674
+ }
675
+ for (ch in trimmed.split("")) {
676
+ if (!digits.includes(ch)) {
677
+ return -1
678
+ }
679
+ }
680
+ return Number(trimmed)
681
+ }
682
+
683
+ /** Interactive edit: field → value → scope → save → live-apply. */
684
+ def editCapabilities() {
685
+ let fieldItems: ChoiceItem[] = []
686
+ for (field in CAPABILITY_FIELDS) {
687
+ fieldItems.push({
688
+ key: field.key,
689
+ label: "${field.label} — ${field.description}"
690
+ })
691
+ }
692
+ const fieldKey = chooseOption(
693
+ "Settings",
694
+ "Pick a setting to change (empty to exit).",
695
+ fieldItems,
696
+ allowCancel: true,
697
+ )
698
+ if (fieldKey == "" || fieldKey == null) {
699
+ return
700
+ }
701
+
702
+ let patch: CapabilityPatch = {}
703
+ if (fieldKey == "prompt") {
704
+ let promptItems: ChoiceItem[] = []
705
+ promptItems.push({
706
+ key: "large",
707
+ label: "large — full coordinator prompt"
708
+ })
709
+ promptItems.push({
710
+ key: "small",
711
+ label: "small — compact prompt for small-context models"
712
+ })
713
+ const picked = chooseOption("prompt", "System prompt size.", promptItems, allowCancel: true)
714
+ if (picked == "" || picked == null) {
715
+ return
716
+ }
717
+ patch = {
718
+ prompt: picked
719
+ }
720
+ }
721
+ if (fieldKey == "summarize" || fieldKey == "memory") {
722
+ let onOffItems: ChoiceItem[] = []
723
+ onOffItems.push({
724
+ key: "on",
725
+ label: "on"
726
+ })
727
+ onOffItems.push({
728
+ key: "off",
729
+ label: "off"
730
+ })
731
+ const picked = chooseOption(fieldKey, "Enable ${fieldKey}?", onOffItems, allowCancel: true)
732
+ if (picked == "" || picked == null) {
733
+ return
734
+ }
735
+ if (fieldKey == "summarize") {
736
+ patch = {
737
+ summarize: picked == "on"
738
+ }
739
+ } else {
740
+ patch = {
741
+ memory: picked == "on"
742
+ }
743
+ if (picked == "on") {
744
+ const status = embeddingKeyStatus(getResolvedSlots())
745
+ if (status.state == "key-missing") {
746
+ pushMessage(
747
+ color.red(
748
+ "Cannot enable memory: the embedding slot needs ${status.varName}, which is not set. Export it, or point the embedding slot elsewhere via /model.",
749
+ ),
750
+ )
751
+ return
752
+ }
753
+ if (status.state == "no-override" && providerLacksEmbeddings(getCapabilityProvider())) {
754
+ pushMessage(
755
+ color.dim(
756
+ "Note: ${getCapabilityProvider()} has no embeddings endpoint, so Tier-2 semantic recall stays off. Point the embedding slot at a provider that has one, e.g. /model embedding=openai/text-embedding-3-small.",
757
+ ),
758
+ )
759
+ }
760
+ }
761
+ }
762
+ }
763
+ if (fieldKey == "maxTokens") {
764
+ let capItems: ChoiceItem[] = []
765
+ capItems.push({
766
+ key: "reset",
767
+ label: "reset — remove my override, use the default"
768
+ })
769
+ const picked = chooseOption(
770
+ "maxTokens",
771
+ "Default output-token cap. Type a number, or pick reset.",
772
+ capItems,
773
+ allowFreeText: true,
774
+ allowCancel: true,
775
+ )
776
+ if (picked == "" || picked == null) {
777
+ return
778
+ }
779
+ if (picked == "reset") {
780
+ const scopeKey = pickOverrideScope()
781
+ if (scopeKey == "") {
782
+ return
783
+ }
784
+ removeCapabilityOverride(scopeKey, "maxTokens")
785
+ applyLiveMaxTokens()
786
+ pushMessage("Override removed.")
787
+ showCapabilities()
788
+ return
789
+ }
790
+ const parsed = parsePositiveInt(picked)
791
+ if (parsed <= 0) {
792
+ pushMessage(color.red("Not a positive number: ${picked}"))
793
+ return
794
+ }
795
+ patch = {
796
+ maxTokens: parsed
797
+ }
798
+ }
799
+
800
+ const scopeKey = pickOverrideScope()
801
+ if (scopeKey == "") {
802
+ return
803
+ }
804
+ saveCapabilityOverride(scopeKey, patch)
805
+
806
+ // Live-apply what can apply now; explain what can't.
807
+ if (fieldKey == "memory") {
808
+ if (getCapabilities().memory) {
809
+ // The ON direction cannot live-apply. enableAgentMemory's
810
+ // `with approve` cannot override the outer policy handler that
811
+ // startInteractive installed, so the memory-dir fs interrupts
812
+ // would surface as policy prompts mid-flow, or get denied and
813
+ // half-apply the toggle. The OFF direction stays live below:
814
+ // disableMemory raises no fs interrupts and already runs in-REPL
815
+ // from reactToSlotChange.
816
+ pushMessage(color.dim("Memory turns on at next agent start (enabling mid-session would raise policy prompts)."))
817
+ } else {
818
+ disableMemory() with approve
819
+ }
820
+ }
821
+ if (fieldKey == "maxTokens") {
822
+ applyLiveMaxTokens()
823
+ }
824
+ if (fieldKey == "prompt") {
825
+ pushMessage(color.dim("Prompt change applies on next agent start (this session's system message is already sent)."))
826
+ }
827
+ pushMessage("Saved.")
828
+ showCapabilities()
829
+ }
830
+
470
831
  // Slash-command + user-message dispatcher invoked by `repl()` for
471
832
  // every Enter keystroke. Return `false` to terminate the loop.
472
833
  def _runTurn(msg: string): boolean {
@@ -493,10 +854,15 @@ def _runTurn(msg: string): boolean {
493
854
  }
494
855
  if (trimmed == "/help") {
495
856
  pushMessage(
496
- "Commands: /exit, /clear, /clear-history, /cost, /model, /models, /local, /search, /paste, /help",
857
+ "Commands: /exit, /clear, /clear-history, /cost, /model, /models, /local, /search, /settings, /paste, /help",
497
858
  )
498
859
  return true
499
860
  }
861
+ if (trimmed == "/settings") {
862
+ showCapabilities()
863
+ editCapabilities()
864
+ return true
865
+ }
500
866
  if (trimmed == "/search") {
501
867
  const opts = availableBackendItems()
502
868
  const choice = chooseOption(
@@ -661,7 +1027,7 @@ let first = true
661
1027
  // subagent. Used to validate the flag and to dispatch in `agentReplyVia`.
662
1028
  static const START_AGENTS = ["main", "code", "research", "oracle", "explorer", "review"]
663
1029
 
664
- static const mainAgentSystemPrompt = """
1030
+ static const MAIN_PROMPT_LARGE = """
665
1031
  You are the top-level coordinator of an Agency-language assistant. You
666
1032
  receive every user message and decide how to respond.
667
1033
 
@@ -830,6 +1196,41 @@ unless the user has clearly asked for an action ("do X", "fix Y",
830
1196
  with them — don't sprint to implementation.
831
1197
  """
832
1198
 
1199
+ // Compact coordinator prompt for small-context models, ~350 tokens
1200
+ // instead of ~3,400. Selected when the capability profile says
1201
+ // prompt: "small". An 8K-context model loses 40% of its window to the
1202
+ // large prompt before the user types anything. The phrase "compact
1203
+ // coordinator" is a deliberate marker that verification greps for in
1204
+ // statelogs.
1205
+ static const MAIN_PROMPT_SMALL = """
1206
+ You are the compact coordinator of an Agency-language assistant. Decide
1207
+ how to answer each user message.
1208
+
1209
+ Tools (each runs in its own context; pass a self-contained message):
1210
+ - `codeAgent(userMsg)` — anything touching code or files: read, write,
1211
+ edit, run, typecheck. Also Agency syntax and CLI questions.
1212
+ - `researchAgent(userMsg)` — web search, URL fetches, external facts.
1213
+ - `reviewAgent(userMsg)` — check non-trivial new Agency code for
1214
+ syntax and type errors; pass the code to review.
1215
+ - `oracleAgent(userMsg)` — deep reasoning on a hard question; include
1216
+ all needed context in the message.
1217
+ - `explorerAgent(userMsg)` — broad read-only codebase/docs questions.
1218
+ - `generateImageFile(prompt, path, size, images)` — create or edit an
1219
+ image; do not route image work to codeAgent.
1220
+
1221
+ Routing rules:
1222
+ - Simple chat, greetings, quick factual answers: reply directly, no
1223
+ tools.
1224
+ - Anything code- or file-related: codeAgent. Current/external info:
1225
+ researchAgent. Broad "summarize/tour/how does X work" questions:
1226
+ explorerAgent.
1227
+ - Surface tool results to the user concisely; do not re-run a tool the
1228
+ user did not ask to re-run.
1229
+
1230
+ Style: plain, direct answers in Markdown. No preamble. Keep replies
1231
+ short unless the task demands detail.
1232
+ """
1233
+
833
1234
  // Exported for tests: agent-cwd resolution of edit inputs is a spec
834
1235
  // invariant with no other observable (the deterministic image client
835
1236
  // ignores its images argument). generateImage accepts path / URL /
@@ -902,14 +1303,56 @@ static const mainAgentTools = [
902
1303
  generateImageFile,
903
1304
  ]
904
1305
 
1306
+ /** Enable memory when the capability profile allows it. When the user
1307
+ pointed the embedding slot at a provider, embeddings run there. A
1308
+ missing API key degrades to the default embedding behavior with one
1309
+ notice. It never blocks startup. */
1310
+ // Whether startup actually enabled memory WITH the embedding override.
1311
+ // The pause guard in reactToSlotChange consults this instead of
1312
+ // re-deriving from the slots: a keyless override took the fallback path,
1313
+ // so memory runs on DERIVED embeddings and a main-provider change still
1314
+ // shifts the embedding space. Exported mutator so tests can drive the
1315
+ // guard directly.
1316
+ let _memoryEmbeddingsPinned = false
1317
+
1318
+ export def markMemoryEmbeddingsPinned(pinned: boolean) {
1319
+ _memoryEmbeddingsPinned = pinned
1320
+ }
1321
+
1322
+ def maybeEnableMemory() {
1323
+ const status = embeddingKeyStatus(getResolvedSlots())
1324
+ const plan = memoryEnablePlan(getCapabilities().memory, status)
1325
+ markMemoryEmbeddingsPinned(plan == "enable-override")
1326
+ if (plan == "skip") {
1327
+ return
1328
+ }
1329
+ if (plan == "enable-override") {
1330
+ enableAgentMemory(status.override)
1331
+ return
1332
+ }
1333
+ if (plan == "enable-fallback") {
1334
+ print(
1335
+ color.dim(
1336
+ "Memory: the embedding slot needs ${status.varName}, which is not set. Falling back to the default embedding behavior.",
1337
+ ),
1338
+ )
1339
+ }
1340
+ enableAgentMemory()
1341
+ }
1342
+
905
1343
  def mainAgent(prompt: string | (string | Attachment)[]): string {
906
- thread(label: "main", summarize: true, session: "main") {
1344
+ const doSummarize = getCapabilities().summarize
1345
+ thread(label: "main", summarize: doSummarize, session: "main") {
907
1346
  setMemoryId("main")
908
1347
  if (first) {
909
1348
  // `_context` carries the per-run grounding (date, cwd, AGENTS.md),
910
1349
  // set by `setupSession`. Appending it to the system prompt is what
911
1350
  // actually gets that context to the LLM.
912
- systemMessage("${mainAgentSystemPrompt}${_context}")
1351
+ let sysPrompt = MAIN_PROMPT_LARGE
1352
+ if (getCapabilities().prompt == "small") {
1353
+ sysPrompt = MAIN_PROMPT_SMALL
1354
+ }
1355
+ systemMessage("${sysPrompt}${_context}")
913
1356
  first = false
914
1357
  }
915
1358
  const result = llm(prompt, {
@@ -1259,6 +1702,10 @@ node main() {
1259
1702
  optional: true,
1260
1703
  description: "Approval policy for this run: a built-in name (minimal, recommended, with-writes, approve-all) or a path to a policy JSON file. with-writes auto-approves file writes and git changes scoped to the current directory and its children; approve-all approves EVERY interrupt with no scoping (sandbox use only). Overrides the saved policy without persisting it. Pass --policy with no value to list the built-in policies."
1261
1704
  },
1705
+ "agent-home": {
1706
+ type: "string",
1707
+ description: "Directory to use instead of ~/.agency-agent for settings, policy, and history (equivalent to the AGENCY_AGENT_HOME env var; the flag wins when both are set)"
1708
+ },
1262
1709
  local: {
1263
1710
  type: "string",
1264
1711
  optional: true,
@@ -1290,11 +1737,14 @@ node main() {
1290
1737
  },
1291
1738
  )
1292
1739
 
1293
- // NOTE: --trace / --log-file are declared here only for --help and so
1294
- // parseArgs accepts them. runBundledAgent extracts them from the forwarded
1295
- // args and passes them to this process as the AGENCY_CONFIG_OVERRIDES env
1296
- // var, read when the RuntimeContext is constructed before this main() runs.
1297
- // Do not re-handle them here.
1740
+ // NOTE: --trace / --log-file / --agent-home are declared here only for
1741
+ // --help and so parseArgs accepts them. runBundledAgent extracts them from
1742
+ // the forwarded args and passes them to this process via env vars
1743
+ // (AGENCY_CONFIG_OVERRIDES for the first two, AGENCY_AGENT_HOME for the
1744
+ // last), read before this main() runs — the RuntimeContext consumes the
1745
+ // config overrides at construction, and lib/config.agency derives the
1746
+ // settings/policy/history paths from the agent home in its static-const
1747
+ // initializers. Do not re-handle them here.
1298
1748
 
1299
1749
  if (args.flags.debug) {
1300
1750
  AGENT_DEBUG = true
@@ -1359,9 +1809,6 @@ node main() {
1359
1809
  // fall through into the session on the chosen local model
1360
1810
  } else if (localVal != null) {
1361
1811
  configureLocalModel(localVal)
1362
- // Intentionally do NOT enable memory for a local model: the llama-cpp
1363
- // provider has no embedding endpoint, so memory would only emit a
1364
- // "Tier-2 disabled" notice. std::memory calls no-op while it's off.
1365
1812
  configureSearch(true)
1366
1813
  } else {
1367
1814
  // Hosted path. --model accepts `slot=model` (per-slot) or a bare model
@@ -1370,6 +1817,7 @@ node main() {
1370
1817
  let modelGlobal = ""
1371
1818
  let fastFlag = ""
1372
1819
  let slowFlag = ""
1820
+ let embeddingFlag = ""
1373
1821
  if (modelArg != "") {
1374
1822
  if (parsed.slot == "") {
1375
1823
  modelGlobal = parsed.model
@@ -1377,10 +1825,20 @@ node main() {
1377
1825
  fastFlag = parsed.model
1378
1826
  } else if (parsed.slot == "reasoning") {
1379
1827
  slowFlag = parsed.model
1828
+ } else if (parsed.slot == "embedding") {
1829
+ if (!embeddingSpecHasProvider(parsed.model)) {
1830
+ print(
1831
+ color.red(
1832
+ "Embedding models need the provider/model form, e.g. --model embedding=openai/text-embedding-3-small.",
1833
+ ),
1834
+ )
1835
+ process.exit(1)
1836
+ }
1837
+ embeddingFlag = parsed.model
1380
1838
  } else {
1381
1839
  print(
1382
1840
  color.red(
1383
- "Unknown model slot '${parsed.slot}' in --model. Valid slots: main, reasoning.",
1841
+ "Unknown model slot '${parsed.slot}' in --model. Valid slots: ${SLOTS.join(", ")}.",
1384
1842
  ),
1385
1843
  )
1386
1844
  process.exit(1)
@@ -1394,16 +1852,14 @@ node main() {
1394
1852
  if (sm != "") {
1395
1853
  slowFlag = sm
1396
1854
  }
1397
- configureModels(modelGlobal, fastFlag, slowFlag, args.flags.provider ?? "")
1398
- // Persistent knowledge graph — enabled for hosted providers, skipped for
1399
- // local models. (Some hosted providers, e.g. anthropic, still lack an
1400
- // embedding endpoint and just disable Tier-2 recall with a one-time
1401
- // notice; structured recall works regardless.) Must run before any
1402
- // handler/REPL is installed.
1403
- enableAgentMemory()
1855
+ configureModels(modelGlobal, fastFlag, slowFlag, embeddingFlag, args.flags.provider ?? "")
1404
1856
  configureSearch(false)
1405
1857
  }
1406
1858
 
1859
+ // Persistent knowledge graph, gated on the capability profile. Must run
1860
+ // before any handler/REPL is installed.
1861
+ maybeEnableMemory()
1862
+
1407
1863
  // Positional args (everything after flags) are joined with spaces to
1408
1864
  // form the starting prompt. `--` ends flag parsing if a positional
1409
1865
  // would otherwise look like a flag.
@@ -1434,20 +1890,15 @@ node main() {
1434
1890
  setLlmOptions({ maxToolResultChars: maxResultChars })
1435
1891
  }
1436
1892
 
1437
- // TEMPORARY: raise the per-turn output cap above smoltalk's 4096 default.
1438
- // With adaptive thinking on, a hard reasoning turn can spend the entire
1439
- // 4096-token budget thinking and return an empty reply (no text, no tool
1440
- // call) see the empty-output failures on regex-log / protein-assembly.
1441
- // Capped at 20000: these llm() calls are non-streaming, and the Anthropic
1442
- // SDK refuses a non-streaming request whose estimated time exceeds 10 min,
1443
- // which is maxTokens > 21333 (60min * maxTokens / 128000 > 10min). 20000
1444
- // stays under that and is well within every modern-Claude output cap
1445
- // (Sonnet 5 = 64K, Opus 4.8 = 128K). Branch-scoped so every subagent
1446
- // inherits it; per-call `llm(..., { maxTokens })` (e.g. the summarizer's
1447
- // cap) still wins. Superseded by the declarative capabilities table in
1448
- // docs/superpowers/specs/2026-07-09-model-capabilities-design.md (§2,
1449
- // maxTokens applied via setLlmOptions at startup) — remove it then.
1450
- setLlmOptions({ maxTokens: 20000 })
1893
+ // Per-model default output cap from the capability profile. The hosted
1894
+ // default of 20000 carries the adaptive-thinking empty-reply fix that
1895
+ // used to live here as a TEMPORARY block. Local profiles cap lower.
1896
+ // Branch-scoped, so every subagent inherits it. A per-call
1897
+ // llm(..., { maxTokens }) still wins, e.g. the summarizer's 256.
1898
+ const capsMaxTokens = getCapabilities().maxTokens
1899
+ if (capsMaxTokens != null) {
1900
+ setLlmOptions({ maxTokens: capsMaxTokens })
1901
+ }
1451
1902
 
1452
1903
  // One-shot autonomy: tell the code subagent there is no human to answer
1453
1904
  // questions and no next turn, so it must drive to a finished artifact.