akm-cli 0.9.5 → 0.9.7

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 (130) hide show
  1. package/CHANGELOG.md +295 -0
  2. package/dist/assets/hints/cli-hints-full.md +3 -3
  3. package/dist/assets/improve-strategies/catchup.json +40 -11
  4. package/dist/assets/improve-strategies/thorough.json +45 -7
  5. package/dist/assets/tasks/improve/akm-improve-frequent.yml +2 -2
  6. package/dist/commands/agent/contribute-cli.js +11 -0
  7. package/dist/commands/env/env-cli.js +1 -2
  8. package/dist/commands/env/secret-cli.js +1 -5
  9. package/dist/commands/feedback-cli.js +0 -4
  10. package/dist/commands/health/checks.js +0 -32
  11. package/dist/commands/health/surfaces.js +2 -2
  12. package/dist/commands/health.js +6 -15
  13. package/dist/commands/improve/autonomy-gate.js +1 -1
  14. package/dist/commands/improve/consolidate.js +25 -26
  15. package/dist/commands/improve/distill.js +2 -2
  16. package/dist/commands/improve/extract.js +8 -1
  17. package/dist/commands/improve/improve-cli.js +1 -1
  18. package/dist/commands/improve/improve-strategies.js +0 -4
  19. package/dist/commands/improve/improve.js +3 -5
  20. package/dist/commands/improve/locks.js +13 -5
  21. package/dist/commands/improve/loop-stages.js +8 -9
  22. package/dist/commands/improve/memory/memory-contradiction-detect.js +1 -19
  23. package/dist/commands/improve/memory/memory-improve.js +2 -1
  24. package/dist/commands/improve/outcome-loop.js +0 -6
  25. package/dist/commands/improve/preparation.js +1 -1
  26. package/dist/commands/improve/reflect.js +21 -10
  27. package/dist/commands/lint/base-linter.js +141 -18
  28. package/dist/commands/lint/index.js +21 -10
  29. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
  30. package/dist/commands/read/curate.js +47 -0
  31. package/dist/commands/read/search-cli.js +24 -1
  32. package/dist/commands/sources/bundle-config-ops.js +9 -4
  33. package/dist/commands/sources/info.js +14 -10
  34. package/dist/commands/tasks/tasks.js +12 -16
  35. package/dist/commands/workflow-cli.js +2 -2
  36. package/dist/core/asset/asset-placement.js +13 -2
  37. package/dist/core/asset/frontmatter.js +116 -0
  38. package/dist/core/asset/memory-archive.js +97 -0
  39. package/dist/core/common.js +61 -24
  40. package/dist/core/config/config-io.js +2 -2
  41. package/dist/core/config/config-sources.js +32 -2
  42. package/dist/core/config/config.js +1 -1
  43. package/dist/core/config/engine-semantics.js +0 -2
  44. package/dist/core/config/schema/engines.js +9 -7
  45. package/dist/core/config/schema/primitives.js +0 -5
  46. package/dist/core/config/schema/search.js +2 -1
  47. package/dist/core/file-lock.js +2 -1
  48. package/dist/core/maintenance-barrier.js +2 -14
  49. package/dist/core/paths.js +0 -3
  50. package/dist/core/redaction.js +2 -2
  51. package/dist/core/spawn-env.js +8 -12
  52. package/dist/core/state/migrations.js +1 -12
  53. package/dist/core/state-db.js +9 -27
  54. package/dist/core/write-source.js +12 -19
  55. package/dist/execution/directory-identity.js +36 -10
  56. package/dist/indexer/graph/graph-boost.js +0 -4
  57. package/dist/indexer/index-writer-lock.js +43 -24
  58. package/dist/indexer/index-written-assets.js +5 -6
  59. package/dist/indexer/indexer.js +2 -39
  60. package/dist/indexer/materialize-embeddings.js +85 -41
  61. package/dist/indexer/search/db-search.js +15 -48
  62. package/dist/indexer/search/ranking-contributors.js +0 -25
  63. package/dist/indexer/search/ranking.js +3 -13
  64. package/dist/integrations/agent/builder-shared.js +0 -25
  65. package/dist/integrations/agent/model-map.js +2 -60
  66. package/dist/integrations/harnesses/aider/agent-builder.js +1 -3
  67. package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -7
  68. package/dist/integrations/harnesses/claude/agent-builder.js +1 -4
  69. package/dist/integrations/harnesses/codex/agent-builder.js +1 -4
  70. package/dist/integrations/harnesses/copilot/agent-builder.js +2 -6
  71. package/dist/integrations/harnesses/gemini/agent-builder.js +2 -7
  72. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -4
  73. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +2 -16
  74. package/dist/integrations/harnesses/openhands/agent-builder.js +1 -3
  75. package/dist/integrations/harnesses/pi/agent-builder.js +2 -4
  76. package/dist/integrations/session-logs/index.js +0 -9
  77. package/dist/llm/client.js +75 -42
  78. package/dist/llm/embedder.js +7 -3
  79. package/dist/llm/embedders/remote.js +141 -42
  80. package/dist/registry/network.js +5 -37
  81. package/dist/runtime.js +2 -10
  82. package/dist/scripts/akm-migrate-node.js +73 -133
  83. package/dist/scripts/akm-migrate.js +73 -133
  84. package/dist/setup/engine-config.js +2 -5
  85. package/dist/setup/registry-stash-loader.js +0 -8
  86. package/dist/setup/setup.js +9 -46
  87. package/dist/setup/steps/connection-shared.js +10 -13
  88. package/dist/sources/providers/git-install.js +1 -1
  89. package/dist/sources/snapshot-fetchers/website-ingest.js +126 -0
  90. package/dist/storage/engines/sqlite-migrations.js +20 -1
  91. package/dist/storage/repositories/index-connection.js +45 -3
  92. package/dist/storage/repositories/index-entries-repository.js +0 -15
  93. package/dist/tasks/backends/cron.js +49 -9
  94. package/dist/tasks/backends/launchd.js +15 -20
  95. package/dist/tasks/backends/schtasks.js +18 -8
  96. package/dist/tasks/resolve-akm-bin.js +17 -2
  97. package/dist/tasks/run/run-native-task.js +8 -6
  98. package/dist/tasks/scheduler-invocation.js +8 -1
  99. package/dist/tasks/source/bounded-document.js +2 -4
  100. package/dist/tasks/source/parse-task-source.js +23 -9
  101. package/dist/tasks/source/task-source-v3-frozen.js +5 -7
  102. package/dist/tasks/source/task-source-v4.js +5 -10
  103. package/dist/tasks/source/task-to-v3.js +5 -10
  104. package/dist/tasks/source/task-to-v4.js +1 -4
  105. package/dist/tasks/source-v3.js +6 -6
  106. package/dist/workflows/exec/native-executor.js +21 -31
  107. package/dist/workflows/exec/run-workflow.js +5 -6
  108. package/dist/workflows/exec/scheduler.js +3 -19
  109. package/dist/workflows/exec/step-work.js +1 -4
  110. package/dist/workflows/exec/unit-dispatch.js +2 -2
  111. package/dist/workflows/exec/worktree.js +1 -13
  112. package/dist/workflows/freeze/targets/child-workflow.js +2 -10
  113. package/dist/workflows/ir/plan-hash.js +4 -6
  114. package/dist/workflows/ir/schema-v4.js +0 -12
  115. package/dist/workflows/ir/schema.js +20 -31
  116. package/dist/workflows/parser.js +11 -52
  117. package/dist/workflows/renderer.js +2 -3
  118. package/dist/workflows/resource-limits.js +11 -41
  119. package/dist/workflows/runtime/runs.js +3 -4
  120. package/dist/workflows/source-ir/schema.js +14 -30
  121. package/dist/workflows/validator.js +1 -7
  122. package/docs/reference/cli.md +5 -1
  123. package/docs/reference/configuration.md +1 -1
  124. package/package.json +1 -1
  125. package/schemas/akm-config.json +4 -23
  126. package/schemas/akm-task.json +1 -2
  127. package/schemas/akm-workflow.json +1 -13
  128. package/dist/assets/improve-strategies/frequent.json +0 -15
  129. package/dist/assets/improve-strategies/memory-focus.json +0 -15
  130. package/dist/indexer/search/semantic-status.js +0 -142
@@ -31,7 +31,7 @@ import { UsageError } from "../../../core/errors.js";
31
31
  import { GuardedExecutionSourceCollector } from "../../../execution/guarded-source.js";
32
32
  import { workflowParamContract } from "../../ir/params.js";
33
33
  import { canonicalJson, canonicalPlanJson } from "../../ir/plan-hash.js";
34
- import { utf8Bytes, WORKFLOW_MAX_COMPOSITION_DEPTH, WORKFLOW_MAX_EMBEDDED_CHILD_PLAN_BYTES, } from "../../resource-limits.js";
34
+ import { utf8Bytes, WORKFLOW_MAX_EMBEDDED_CHILD_PLAN_BYTES } from "../../resource-limits.js";
35
35
  import { loadWorkflowAsset } from "../../runtime/workflow-asset-loader.js";
36
36
  import { resolveOwnedAsset } from "../environment.js";
37
37
  import { declaredParamNames, earlierStepIds } from "../step-values.js";
@@ -91,13 +91,6 @@ function assertNoCompositionCycle(stepId, childRef, refPath) {
91
91
  `Path: ${compositionPath(refPath, childRef)}.`, "COMPOSITION_INVALID", "Break the cycle: remove or redirect one of the compositions in the path above so no workflow ends up " +
92
92
  "composing itself, directly or through intermediates.");
93
93
  }
94
- function assertCompositionDepthAllowed(stepId, childRef, childDepth, refPath) {
95
- if (childDepth <= WORKFLOW_MAX_COMPOSITION_DEPTH)
96
- return;
97
- throw new UsageError(`Workflow step ${stepId} cannot compose ${childRef}: workflow composition is limited to ` +
98
- `${WORKFLOW_MAX_COMPOSITION_DEPTH} levels. Path: ${compositionPath(refPath, childRef)}.`, "COMPOSITION_INVALID", `Flatten the composition chain to ${WORKFLOW_MAX_COMPOSITION_DEPTH} levels or fewer — inline one of the ` +
99
- "intermediate workflows, or restructure the chain so fewer child compositions are nested.");
100
- }
101
94
  /**
102
95
  * Add the child's embedded plan bytes to the shared, tree-wide budget
103
96
  * (A-N6) and fail before publication if the AGGREGATE crosses the cap.
@@ -126,10 +119,9 @@ export async function childWorkflowDispatch(input) {
126
119
  // path to reach the child run and must reject, not vanish (see
127
120
  // assertNoStepEnvironment's doc comment).
128
121
  assertNoStepEnvironment(source.id, childRef, source);
129
- // §4.2 steps 2-3: the composition bounds, before any child compilation.
122
+ // §4.2 step 2: the composition cycle check, before any child compilation.
130
123
  assertNoCompositionCycle(source.id, childRef, context.composition.refPath);
131
124
  const childDepth = context.composition.depth + 1;
132
- assertCompositionDepthAllowed(source.id, childRef, childDepth, context.composition.refPath);
133
125
  // §4.2 step 4: freeze the child COMPLETELY (compile -> validate -> freeze),
134
126
  // with its OWN fresh collector (A-N7) so its plan is a pure function of its
135
127
  // own source, and the SAME mutable budget object so the aggregate bound
@@ -13,7 +13,7 @@
13
13
  * Pure module: no IO beyond node:crypto, no engine imports.
14
14
  */
15
15
  import { createHash } from "node:crypto";
16
- import { utf8Bytes, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_PLAN_BYTES } from "../resource-limits.js";
16
+ import { utf8Bytes, WORKFLOW_MAX_PLAN_BYTES } from "../resource-limits.js";
17
17
  import { decodeWorkflowPlanV4, WORKFLOW_IR_V5_VERSION } from "./schema-v4.js";
18
18
  /** sha256 hex of the canonical (recursively sorted-keys) JSON of the plan. */
19
19
  export function computePlanHash(plan) {
@@ -53,9 +53,7 @@ export function decodeCanonicalPlan(runId, planJson, planHash, expectedVersion)
53
53
  throw new Error(`Workflow run ${runId} has noncanonical frozen plan JSON.`);
54
54
  return plan;
55
55
  }
56
- function sortKeys(value, depth = 0) {
57
- if (depth > WORKFLOW_MAX_JSON_DEPTH)
58
- throw new TypeError("JSON value exceeds maximum depth");
56
+ function sortKeys(value) {
59
57
  if (value === null || typeof value === "string" || typeof value === "boolean")
60
58
  return value;
61
59
  if (typeof value === "number") {
@@ -64,11 +62,11 @@ function sortKeys(value, depth = 0) {
64
62
  return value;
65
63
  }
66
64
  if (Array.isArray(value))
67
- return value.map((item) => sortKeys(item, depth + 1));
65
+ return value.map((item) => sortKeys(item));
68
66
  if (value && typeof value === "object") {
69
67
  return Object.fromEntries(Object.entries(value)
70
68
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
71
- .map(([k, v]) => [k, sortKeys(v, depth + 1)]));
69
+ .map(([k, v]) => [k, sortKeys(v)]));
72
70
  }
73
71
  throw new TypeError("JSON value contains a non-JSON value");
74
72
  }
@@ -182,15 +182,6 @@ function decodeFrozenTarget(value, unit, environment, requiredSources, depth, bu
182
182
  return decodeChildWorkflowTarget(target, unit, depth, budget);
183
183
  fail(`unit ${unit.id} frozenTarget has unsupported kind ${String(target.kind)}`);
184
184
  }
185
- /**
186
- * Composition-depth corruption bound (spec §3.6 step 6, §4.5, row A-23).
187
- * Mirrors `WORKFLOW_MAX_COMPOSITION_DEPTH` (= 8), which Lane B adds to
188
- * `src/workflows/resource-limits.ts` in a later commit as the freeze-time
189
- * `COMPOSITION_INVALID` gate. Reproduced here — the decode-time corruption
190
- * gate — rather than imported across the lane boundary, matching
191
- * `tests/workflows/plan-v5-schema.test.ts`'s own header comment.
192
- */
193
- const CHILD_WORKFLOW_DECODE_MAX_DEPTH = 8;
194
185
  /**
195
186
  * Decode a `kind: "child-workflow"` frozen target (§3.5): the embedded
196
187
  * COMPLETE child plan, re-verified against its own `planHash` and this
@@ -241,9 +232,6 @@ function decodeChildWorkflowTarget(target, unit, depth, budget) {
241
232
  fail(`unit ${unit.id} child workflow contentHash does not match its frozen dispatch`);
242
233
  }
243
234
  const childDepth = depth + 1;
244
- if (childDepth > CHILD_WORKFLOW_DECODE_MAX_DEPTH) {
245
- fail(`unit ${unit.id} child workflow ${target.ref} exceeds the max composition depth of ${CHILD_WORKFLOW_DECODE_MAX_DEPTH}`);
246
- }
247
235
  const frozenPlan = decodeWorkflowPlanV4(target.frozenPlan, {}, childDepth, budget);
248
236
  const embeddedPlanJson = canonicalJsonLocal(frozenPlan);
249
237
  const actualPlanHash = sha256(embeddedPlanJson);
@@ -5,12 +5,11 @@ import { isContainedRelativePath } from "../../core/common.js";
5
5
  import { UsageError } from "../../core/errors.js";
6
6
  import { parseReference } from "../program/expressions.js";
7
7
  import { PROGRAM_PARAM_NAME_PATTERN, PROGRAM_RETRY_REASONS, PROGRAM_STEP_ID_PATTERN } from "../program/schema.js";
8
- import { jsonBytes, utf8Bytes, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_INSTRUCTION_BYTES, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_PLAN_BYTES, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_STEPS, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
8
+ import { jsonBytes, utf8Bytes, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_INSTRUCTION_BYTES, WORKFLOW_MAX_PLAN_BYTES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
9
9
  // Shared dispatch-significant bounds now live in `../resource-limits` so the
10
10
  // parser, the published JSON Schema, and this decoder enforce identical
11
11
  // values. Re-exported here for existing importers (e.g. `commands/workflow-cli.ts`).
12
- export { WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_TIMEOUT_MS };
13
- export const WORKFLOW_MAX_UNITS = WORKFLOW_MAX_MAP_EXPANSION;
12
+ export { WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_TIMEOUT_MS };
14
13
  const MAX_LIST_ITEMS = 1024;
15
14
  const MAX_STRING_LENGTH = 1_000_000;
16
15
  /** Validate the current durable execution graph. */
@@ -33,8 +32,8 @@ export function validateWorkflowPlanStructure(input, options, hooks = {}) {
33
32
  fail(`execution.maxConcurrency must be an integer from 1 through ${WORKFLOW_MAX_CONCURRENCY}`);
34
33
  }
35
34
  assertKeys(plan.execution, ["maxConcurrency"], "execution");
36
- if (!Array.isArray(plan.steps) || plan.steps.length === 0 || plan.steps.length > WORKFLOW_MAX_STEPS)
37
- fail("steps must contain 1 through 256 entries");
35
+ if (!Array.isArray(plan.steps) || plan.steps.length === 0)
36
+ fail("steps must be a non-empty array");
38
37
  const stepIds = new Set();
39
38
  const nodeIds = new Set();
40
39
  for (let index = 0; index < plan.steps.length; index++) {
@@ -126,7 +125,7 @@ function validateNode(node, stepId, nodeIds, unitExtraKeys) {
126
125
  validateSchema(node.schema, `unit ${node.id} schema`);
127
126
  validateRetry(node.retry, node.id);
128
127
  validateStringArray(node.env, `unit ${node.id} env`, MAX_LIST_ITEMS, true);
129
- validateStringArray(node.inputs, `unit ${node.id} inputs`, WORKFLOW_MAX_INPUTS, true);
128
+ validateStringArray(node.inputs, `unit ${node.id} inputs`, Infinity, true);
130
129
  validateSource(node.source, `unit ${node.id} source`);
131
130
  }
132
131
  /**
@@ -143,15 +142,11 @@ function validateExecSpec(value, label) {
143
142
  const command = value.command;
144
143
  if (!Array.isArray(command) ||
145
144
  command.length === 0 ||
146
- command.length > WORKFLOW_MAX_EXEC_ARGV ||
147
- !command.every((arg) => typeof arg === "string" && arg.length > 0 && utf8Bytes(arg) <= WORKFLOW_MAX_EXEC_ARG_BYTES)) {
148
- fail(`${label}.command must be an argv array of 1 through ${WORKFLOW_MAX_EXEC_ARGV} bounded non-empty strings`);
145
+ !command.every((arg) => typeof arg === "string" && arg.length > 0)) {
146
+ fail(`${label}.command must be an argv array of non-empty strings`);
149
147
  }
150
148
  if (value.cwd !== undefined) {
151
- if (typeof value.cwd !== "string" ||
152
- value.cwd.length === 0 ||
153
- value.cwd.length > WORKFLOW_MAX_EXEC_CWD_LENGTH ||
154
- !isContainedRelativePath(value.cwd)) {
149
+ if (typeof value.cwd !== "string" || value.cwd.length === 0 || !isContainedRelativePath(value.cwd)) {
155
150
  fail(`${label}.cwd must be a relative path contained in the unit working directory`);
156
151
  }
157
152
  }
@@ -183,11 +178,10 @@ function validateExecEnvScope(value, label) {
183
178
  const passEnv = value.passEnv;
184
179
  if (!Array.isArray(passEnv) ||
185
180
  passEnv.length === 0 ||
186
- passEnv.length > WORKFLOW_MAX_EXEC_PASS_ENV ||
187
181
  !passEnv.every((name) => typeof name === "string" && WORKFLOW_ENV_VAR_NAME_PATTERN.test(name)) ||
188
182
  new Set(passEnv).size !== passEnv.length) {
189
- fail(`${label}.passEnv must be 1 through ${WORKFLOW_MAX_EXEC_PASS_ENV} distinct environment variable names ` +
190
- `matching ${WORKFLOW_ENV_VAR_NAME_PATTERN.source}`);
183
+ fail(`${label}.passEnv must be a non-empty list of distinct environment variable names matching ` +
184
+ `${WORKFLOW_ENV_VAR_NAME_PATTERN.source}`);
191
185
  }
192
186
  }
193
187
  function validateGate(gate, stepId, nodeIds, gateExtraKeys) {
@@ -199,8 +193,7 @@ function validateGate(gate, stepId, nodeIds, gateExtraKeys) {
199
193
  gate.criteria.length > MAX_LIST_ITEMS ||
200
194
  !gate.criteria.every((x) => typeof x === "string" && x.length > 0 && x.length <= MAX_STRING_LENGTH) ||
201
195
  !Number.isInteger(gate.maxLoops) ||
202
- gate.maxLoops < 1 ||
203
- gate.maxLoops > WORKFLOW_MAX_GATE_LOOPS)
196
+ gate.maxLoops < 1)
204
197
  fail(`gate for step ${stepId} is invalid`);
205
198
  if (nodeIds.has(gate.id))
206
199
  fail(`gate id ${gate.id} collides with a node`);
@@ -213,7 +206,6 @@ function validateRoute(route, stepId) {
213
206
  !route.input ||
214
207
  !isRecord(route.when) ||
215
208
  Object.keys(route.when).length === 0 ||
216
- Object.keys(route.when).length > WORKFLOW_MAX_ROUTE_BRANCHES ||
217
209
  !Object.keys(route.when).every((match) => match.length > 0 && match.length <= MAX_STRING_LENGTH) ||
218
210
  !Object.values(route.when).every((target) => typeof target === "string" && target))
219
211
  fail(`route for step ${stepId} is invalid`);
@@ -226,9 +218,7 @@ function assertKeys(value, allowed, label) {
226
218
  if (!allowed.includes(key))
227
219
  fail(`${label} contains unknown key ${key}`);
228
220
  }
229
- function assertJson(value, depth = 0) {
230
- if (depth > WORKFLOW_MAX_JSON_DEPTH)
231
- fail("plan exceeds JSON depth limit of 64");
221
+ function assertJson(value) {
232
222
  if (value === null || typeof value === "boolean")
233
223
  return;
234
224
  if (typeof value === "string") {
@@ -243,23 +233,23 @@ function assertJson(value, depth = 0) {
243
233
  }
244
234
  if (Array.isArray(value)) {
245
235
  for (const item of value)
246
- assertJson(item, depth + 1);
236
+ assertJson(item);
247
237
  return;
248
238
  }
249
239
  if (isRecord(value)) {
250
240
  for (const item of Object.values(value))
251
- assertJson(item, depth + 1);
241
+ assertJson(item);
252
242
  return;
253
243
  }
254
244
  fail("plan contains a non-JSON value");
255
245
  }
256
246
  function validateParams(params, schemas) {
257
- validateStringArray(params, "params", WORKFLOW_MAX_PARAMS, true);
247
+ validateStringArray(params, "params", Infinity, true);
258
248
  if (params?.some((name) => !PROGRAM_PARAM_NAME_PATTERN.test(name)))
259
249
  fail("params contains an invalid name");
260
250
  if (schemas !== undefined) {
261
- if (!isRecord(schemas) || Object.keys(schemas).length > WORKFLOW_MAX_PARAMS)
262
- fail("paramSchemas must be a bounded object");
251
+ if (!isRecord(schemas))
252
+ fail("paramSchemas must be an object");
263
253
  for (const [name, schema] of Object.entries(schemas)) {
264
254
  if (!PROGRAM_PARAM_NAME_PATTERN.test(name))
265
255
  fail(`paramSchemas.${name} is invalid`);
@@ -284,9 +274,8 @@ function validateBudget(budget) {
284
274
  fail("budget must declare a ceiling");
285
275
  validateOptionalPositiveInteger(budget.maxTokens, "budget.maxTokens");
286
276
  const maxUnits = budget.maxUnits;
287
- if (maxUnits !== undefined &&
288
- (!Number.isSafeInteger(maxUnits) || maxUnits < 1 || maxUnits > WORKFLOW_MAX_UNITS))
289
- fail(`budget.maxUnits must be an integer from 1 through ${WORKFLOW_MAX_UNITS}`);
277
+ if (maxUnits !== undefined && (!Number.isSafeInteger(maxUnits) || maxUnits < 1))
278
+ fail("budget.maxUnits must be a positive integer");
290
279
  }
291
280
  function validateRetry(retry, nodeId) {
292
281
  if (retry === undefined)
@@ -294,7 +283,7 @@ function validateRetry(retry, nodeId) {
294
283
  if (!isRecord(retry))
295
284
  fail(`unit ${nodeId} retry must be an object`);
296
285
  assertKeys(retry, ["max", "on"], `unit ${nodeId} retry`);
297
- if (!Number.isSafeInteger(retry.max) || retry.max < 0 || retry.max > WORKFLOW_MAX_RETRIES)
286
+ if (!Number.isSafeInteger(retry.max) || retry.max < 0)
298
287
  fail(`unit ${nodeId} retry.max is invalid`);
299
288
  validateStringArray(retry.on, `unit ${nodeId} retry.on`, PROGRAM_RETRY_REASONS.length, true);
300
289
  if (retry.on === undefined ||
@@ -38,7 +38,7 @@ import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-param
38
38
  import { checkJsonSchemaDefinition, JSON_SCHEMA_SUBSET_SUPPORTED_KEYWORDS } from "../core/json-schema.js";
39
39
  import { parseReference } from "./program/expressions.js";
40
40
  import { PROGRAM_ISOLATION_KINDS, PROGRAM_ON_ERROR, PROGRAM_PARAM_NAME_PATTERN, PROGRAM_REDUCERS, PROGRAM_RETRY_REASONS, PROGRAM_STEP_ID_PATTERN, } from "./program/schema.js";
41
- import { jsonBytes, utf8Bytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_OUTPUTS, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_SOURCE_BYTES, WORKFLOW_MAX_STEPS, WORKFLOW_MAX_TIMEOUT_MS, } from "./resource-limits.js";
41
+ import { jsonBytes, utf8Bytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_SOURCE_BYTES, WORKFLOW_MAX_TIMEOUT_MS, } from "./resource-limits.js";
42
42
  import { WORKFLOW_SCHEMA_VERSION, } from "./schema.js";
43
43
  import { runSemanticChecks } from "./validator.js";
44
44
  /** Envelope keys every AKM markdown asset carries ($ref'd from schemas/akm-asset-envelope.json). */
@@ -406,9 +406,6 @@ function parseParams(ctx, raw) {
406
406
  ctx.err(["params"], `"params" must be a mapping of param name to a JSON Schema object (e.g. changed_files: { type: array }).`);
407
407
  return undefined;
408
408
  }
409
- if (Object.keys(raw).length > WORKFLOW_MAX_PARAMS) {
410
- ctx.err(["params"], `"params" must contain at most ${WORKFLOW_MAX_PARAMS} entries.`);
411
- }
412
409
  const params = {};
413
410
  for (const [paramName, value] of Object.entries(raw)) {
414
411
  if (!PROGRAM_PARAM_NAME_PATTERN.test(paramName)) {
@@ -448,9 +445,6 @@ function parseOutputs(ctx, raw) {
448
445
  ctx.err(["outputs"], `"outputs" must be a mapping of output name to { from, schema? } (e.g. report: { from: steps.summarize.output }).`);
449
446
  return undefined;
450
447
  }
451
- if (Object.keys(raw).length > WORKFLOW_MAX_OUTPUTS) {
452
- ctx.err(["outputs"], `"outputs" must contain at most ${WORKFLOW_MAX_OUTPUTS} entries.`);
453
- }
454
448
  const outputs = {};
455
449
  for (const [outputName, value] of Object.entries(raw)) {
456
450
  const path = ["outputs", outputName];
@@ -546,14 +540,11 @@ function parseBudget(ctx, raw) {
546
540
  }
547
541
  }
548
542
  if (raw.max_units !== undefined) {
549
- if (typeof raw.max_units === "number" &&
550
- Number.isInteger(raw.max_units) &&
551
- raw.max_units >= 1 &&
552
- raw.max_units <= WORKFLOW_MAX_MAP_EXPANSION) {
543
+ if (typeof raw.max_units === "number" && Number.isInteger(raw.max_units) && raw.max_units >= 1) {
553
544
  budget.maxUnits = raw.max_units;
554
545
  }
555
546
  else {
556
- ctx.err([...path, "max_units"], `"budget.max_units" must be an integer from 1 through ${WORKFLOW_MAX_MAP_EXPANSION}.`);
547
+ ctx.err([...path, "max_units"], `"budget.max_units" must be an integer >= 1.`);
557
548
  }
558
549
  }
559
550
  return Object.keys(budget).length > 0 ? budget : undefined;
@@ -563,9 +554,6 @@ function parseSteps(ctx, raw) {
563
554
  ctx.err(["steps"], `"steps" is required and must be a list with at least one step.`);
564
555
  return [];
565
556
  }
566
- if (raw.length > WORKFLOW_MAX_STEPS) {
567
- ctx.err(["steps"], `"steps" must contain at most ${WORKFLOW_MAX_STEPS} entries.`);
568
- }
569
557
  // First pass: collect ids so route targets can be checked against ALL steps
570
558
  // (including ones that fail their own validation).
571
559
  const idIndex = new Map();
@@ -775,11 +763,6 @@ function parseExecPassEnv(ctx, raw, path, stepLabel) {
775
763
  `akm's own environment, e.g. pass_env: [CARGO_HOME]. Values never appear here — use "env:" bindings for those.`);
776
764
  return undefined;
777
765
  }
778
- if (raw.length > WORKFLOW_MAX_EXEC_PASS_ENV) {
779
- ctx.err(path, `${stepLabel} "exec.pass_env" must have at most ${WORKFLOW_MAX_EXEC_PASS_ENV} entries. A command needing ` +
780
- `more than that should use explicit named "env:" bindings instead.`);
781
- return undefined;
782
- }
783
766
  const names = [];
784
767
  for (const [index, entry] of raw.entries()) {
785
768
  if (typeof entry !== "string" || !WORKFLOW_ENV_VAR_NAME_PATTERN.test(entry)) {
@@ -795,17 +778,13 @@ function parseExecPassEnv(ctx, raw, path, stepLabel) {
795
778
  }
796
779
  return names;
797
780
  }
798
- /** The argv array itself: 1..WORKFLOW_MAX_EXEC_ARGV bounded non-empty strings. */
781
+ /** The argv array itself: a non-empty list of non-empty strings. */
799
782
  function parseExecCommand(ctx, raw, path, stepLabel) {
800
783
  if (!Array.isArray(raw) || raw.length === 0) {
801
784
  ctx.err(path, `${stepLabel} "exec" requires "command": a non-empty argv list, e.g. command: ["bun", "run", "test:unit"]. ` +
802
785
  `A single shell string is not accepted — the command is spawned directly, never through a shell.`);
803
786
  return undefined;
804
787
  }
805
- if (raw.length > WORKFLOW_MAX_EXEC_ARGV) {
806
- ctx.err(path, `${stepLabel} "exec.command" must have at most ${WORKFLOW_MAX_EXEC_ARGV} entries.`);
807
- return undefined;
808
- }
809
788
  const argv = [];
810
789
  for (const [index, entry] of raw.entries()) {
811
790
  if (typeof entry !== "string" || entry === "") {
@@ -816,10 +795,6 @@ function parseExecCommand(ctx, raw, path, stepLabel) {
816
795
  ctx.err([...path, index], `${stepLabel} "exec.command[${index}]" may not contain NUL bytes.`);
817
796
  return undefined;
818
797
  }
819
- if (utf8Bytes(entry) > WORKFLOW_MAX_EXEC_ARG_BYTES) {
820
- ctx.err(path, `${stepLabel} "exec.command[${index}]" exceeds ${WORKFLOW_MAX_EXEC_ARG_BYTES} bytes.`);
821
- return undefined;
822
- }
823
798
  argv.push(entry);
824
799
  }
825
800
  return argv;
@@ -837,10 +812,6 @@ function parseExecCwd(ctx, raw, path, stepLabel) {
837
812
  return undefined;
838
813
  }
839
814
  const value = raw.trim();
840
- if (value.length > WORKFLOW_MAX_EXEC_CWD_LENGTH) {
841
- ctx.err(path, `${stepLabel} "exec.cwd" exceeds ${WORKFLOW_MAX_EXEC_CWD_LENGTH} characters.`);
842
- return undefined;
843
- }
844
815
  if (!isContainedRelativePath(value)) {
845
816
  ctx.err(path, `${stepLabel} "exec.cwd" (${JSON.stringify(value)}) must be a RELATIVE path inside the unit's working ` +
846
817
  `directory — absolute paths, Windows drive letters, "~", and ".." segments are rejected.`);
@@ -876,14 +847,11 @@ function parseMap(ctx, raw, path, stepLabel) {
876
847
  }
877
848
  let concurrency;
878
849
  if (raw.concurrency !== undefined) {
879
- if (typeof raw.concurrency === "number" &&
880
- Number.isInteger(raw.concurrency) &&
881
- raw.concurrency > 0 &&
882
- raw.concurrency <= WORKFLOW_MAX_CONCURRENCY) {
883
- concurrency = raw.concurrency;
850
+ if (typeof raw.concurrency === "number" && Number.isInteger(raw.concurrency) && raw.concurrency > 0) {
851
+ concurrency = Math.min(raw.concurrency, WORKFLOW_MAX_CONCURRENCY);
884
852
  }
885
853
  else {
886
- ctx.err([...path, "concurrency"], `${stepLabel} "concurrency" must be an integer from 1 through ${WORKFLOW_MAX_CONCURRENCY}.`);
854
+ ctx.err([...path, "concurrency"], `${stepLabel} "concurrency" must be a positive integer.`);
887
855
  }
888
856
  }
889
857
  const reducer = parseEnumField(ctx, raw.reducer, [...path, "reducer"], `${stepLabel} "reducer"`, PROGRAM_REDUCERS);
@@ -917,9 +885,6 @@ function parseRoute(ctx, raw, path, stepLabel, stepIndex, routeChecks) {
917
885
  ctx.err(whenPath, `${stepLabel} "route" requires "when": a non-empty list of { match, step } branches (e.g. when: [{ match: pass, step: ship }]).`);
918
886
  }
919
887
  else {
920
- if (raw.when.length > WORKFLOW_MAX_ROUTE_BRANCHES) {
921
- ctx.err(whenPath, `${stepLabel} "when" must contain at most ${WORKFLOW_MAX_ROUTE_BRANCHES} branches.`);
922
- }
923
888
  const seenMatches = new Map();
924
889
  raw.when.forEach((branch, i) => {
925
890
  const branchPath = [...whenPath, i];
@@ -980,9 +945,6 @@ function parseInputs(ctx, raw, path, stepLabel) {
980
945
  ctx.err(path, `${stepLabel} "inputs" must be a non-empty list of reference strings.`);
981
946
  return undefined;
982
947
  }
983
- if (raw.length > WORKFLOW_MAX_INPUTS) {
984
- ctx.err(path, `${stepLabel} "inputs" must contain at most ${WORKFLOW_MAX_INPUTS} entries.`);
985
- }
986
948
  const out = [];
987
949
  // The frozen-plan decoder requires uniqueness (validateStringArray(..., true)),
988
950
  // so duplicates parsed and linted clean and then failed the run with an
@@ -1012,14 +974,11 @@ function parseGate(ctx, raw, path, stepLabel) {
1012
974
  checkUnknownKeys(ctx, raw, path, GATE_KEYS, `${stepLabel} "gate"`);
1013
975
  const gate = {};
1014
976
  if (raw.max_loops !== undefined) {
1015
- if (typeof raw.max_loops === "number" &&
1016
- Number.isInteger(raw.max_loops) &&
1017
- raw.max_loops >= 1 &&
1018
- raw.max_loops <= WORKFLOW_MAX_GATE_LOOPS) {
977
+ if (typeof raw.max_loops === "number" && Number.isInteger(raw.max_loops) && raw.max_loops >= 1) {
1019
978
  gate.maxLoops = raw.max_loops;
1020
979
  }
1021
980
  else {
1022
- ctx.err([...path, "max_loops"], `${stepLabel} "gate.max_loops" must be an integer from 1 through ${WORKFLOW_MAX_GATE_LOOPS}.`);
981
+ ctx.err([...path, "max_loops"], `${stepLabel} "gate.max_loops" must be an integer of at least 1.`);
1023
982
  }
1024
983
  }
1025
984
  return gate;
@@ -1056,8 +1015,8 @@ function parseRetry(ctx, raw, path, stepLabel) {
1056
1015
  }
1057
1016
  checkUnknownKeys(ctx, raw, path, RETRY_KEYS, `${stepLabel} "retry"`);
1058
1017
  let ok = true;
1059
- if (!(typeof raw.max === "number" && Number.isInteger(raw.max) && raw.max >= 0 && raw.max <= WORKFLOW_MAX_RETRIES)) {
1060
- ctx.err([...path, "max"], `${stepLabel} "retry.max" is required and must be an integer from 0 through ${WORKFLOW_MAX_RETRIES}.`);
1018
+ if (!(typeof raw.max === "number" && Number.isInteger(raw.max) && raw.max >= 0)) {
1019
+ ctx.err([...path, "max"], `${stepLabel} "retry.max" is required and must be a non-negative integer.`);
1061
1020
  ok = false;
1062
1021
  }
1063
1022
  const on = [];
@@ -87,9 +87,8 @@ function projectParameters(sourceIr) {
87
87
  * visible in the workflow file `show` is rendering, and (b) pointless to clip:
88
88
  * the whole finding this projection answers is `show` describing something
89
89
  * other than what runs, and a truncated argv is that same bug in miniature.
90
- * The parser bounds it anyway (`WORKFLOW_MAX_EXEC_ARGV` entries of
91
- * `WORKFLOW_MAX_EXEC_ARG_BYTES`), and it is far smaller than the step
92
- * `instructions` this same projection already carries whole.
90
+ * It is far smaller than the step `instructions` this same projection
91
+ * already carries whole.
93
92
  */
94
93
  function summarizeStepOrchestration(step, defaults) {
95
94
  const unit = step.unit;
@@ -4,22 +4,12 @@
4
4
  import { EXECUTION_MAX_TIMEOUT_MS } from "../execution/limits.js";
5
5
  export const WORKFLOW_MAX_PLAN_BYTES = 2 * 1024 * 1024;
6
6
  export const WORKFLOW_MAX_SOURCE_BYTES = 1024 * 1024;
7
- export const WORKFLOW_MAX_STEPS = 256;
8
- export const WORKFLOW_MAX_ENGINES = 64;
9
- export const WORKFLOW_MAX_PARAMS = 128;
10
- export const WORKFLOW_MAX_ROUTE_BRANCHES = 256;
11
7
  export const WORKFLOW_MAX_INSTRUCTION_BYTES = 256 * 1024;
12
8
  export const WORKFLOW_MAX_SCHEMA_BYTES = 256 * 1024;
13
9
  export const WORKFLOW_MAX_EXTRA_PARAMS_BYTES = 64 * 1024;
14
- export const WORKFLOW_MAX_JSON_DEPTH = 64;
15
- export const WORKFLOW_MAX_MAP_EXPANSION = 10_000;
16
- /** Max declared `inputs:` reference strings on one unit/map step. */
17
- export const WORKFLOW_MAX_INPUTS = 64;
18
- /** Max declared `outputs:` entries on one workflow document (P3b, spec §4.1). */
19
- export const WORKFLOW_MAX_OUTPUTS = 64;
20
10
  // ── Dispatch-significant bounds shared across validation layers ──────────────
21
11
  //
22
- // Defined ONCE here so the three enforcement layers cannot drift:
12
+ // Defined ONCE here so the enforcement layers cannot drift:
23
13
  // 1. the parser (`../parser.ts`) — line-anchored authoring-time errors,
24
14
  // 2. the published JSON Schema (`schemas/akm-workflow.json`) — mirrored
25
15
  // `maximum`/`pattern`/`maxLength` values, pinned against these constants
@@ -31,33 +21,12 @@ export const WORKFLOW_MAX_OUTPUTS = 64;
31
21
  // `workflow create` already said the document was fine.
32
22
  /** Max per-step map fan-out concurrency (also the run-level concurrency ceiling). */
33
23
  export const WORKFLOW_MAX_CONCURRENCY = 64;
34
- /** Max evaluator-optimizer gate loops per step. */
35
- export const WORKFLOW_MAX_GATE_LOOPS = 100;
36
- /** Max retry attempts per unit. */
37
- export const WORKFLOW_MAX_RETRIES = 100;
38
24
  /** Max timeout in milliseconds (setTimeout's 32-bit signed ceiling: 2^31-1, ~24.8 days). */
39
25
  export const WORKFLOW_MAX_TIMEOUT_MS = EXECUTION_MAX_TIMEOUT_MS;
40
26
  /** Engine names: lowercase dash-separated runs of letters/digits, starting with a letter. */
41
27
  export const WORKFLOW_ENGINE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
42
28
  export const WORKFLOW_MAX_ENGINE_NAME_LENGTH = 63;
43
29
  // ── exec (shell) unit bounds ─────────────────────────────────────────────────
44
- /**
45
- * Max entries in an exec unit's `command:` argv array. Generous for a real
46
- * command line, small enough that a corrupted plan cannot ask the OS to spawn
47
- * a megabyte of arguments.
48
- */
49
- export const WORKFLOW_MAX_EXEC_ARGV = 64;
50
- /** Max UTF-8 bytes of ONE argv entry (well under every platform's ARG_MAX per-arg limit). */
51
- export const WORKFLOW_MAX_EXEC_ARG_BYTES = 4096;
52
- /** Max characters of an exec unit's relative `cwd:`. */
53
- export const WORKFLOW_MAX_EXEC_CWD_LENGTH = 1024;
54
- /**
55
- * Max entries in an exec unit's `pass_env:` list.
56
- *
57
- * `pass_env` is bounded; use explicit named `env:` bindings for a larger set.
58
- * New v4 starts do not offer whole-process inheritance.
59
- */
60
- export const WORKFLOW_MAX_EXEC_PASS_ENV = 32;
61
30
  /**
62
31
  * Grammar for an env var NAME in `pass_env:`. Matches the frozen-plan
63
32
  * `envPassthrough` grammar in `ir/schema.ts` so both allowlist surfaces accept
@@ -86,8 +55,7 @@ export const DEFAULT_EXEC_TIMEOUT_MS = 600_000;
86
55
  * ten minutes to do so. What the cap buys is a BOUND where there was none: the
87
56
  * retained prefix is promoted into the unit's outcome text and every outcome is
88
57
  * held until the step reduces, so the worst case is (units in the STEP × this
89
- * cap) rather than unbounded — the step's width, not the in-flight width, is
90
- * what sizes it, up to {@link WORKFLOW_MAX_MAP_EXPANSION}.
58
+ * cap) — the step's width, not the in-flight width, is what sizes it.
91
59
  *
92
60
  * On reaching the cap the reader switches to DRAIN-AND-DISCARD: it keeps
93
61
  * pulling from the pipe (so the child never blocks on backpressure) and stops
@@ -180,11 +148,10 @@ export function clip(text, max) {
180
148
  *
181
149
  * The promoted step artifact (`evidence.output`) is deliberately NOT clipped
182
150
  * when it is built — gates judge the full artifact and downstream
183
- * `steps.<id>.output` references need it intact — but a `collect`
184
- * reducer over a fan-out bounded only by {@link WORKFLOW_MAX_MAP_EXPANSION}
185
- * (10 000 units, each contributing up to a full unit result) would otherwise
186
- * write an unbounded blob into a single SQLite row. Persistence is therefore
187
- * bounded here, at the write boundary, by
151
+ * `steps.<id>.output` references need it intact — but a `collect` reducer
152
+ * over an unbounded fan-out (each unit contributing up to a full unit
153
+ * result) would otherwise write an unbounded blob into a single SQLite row.
154
+ * Persistence is therefore bounded here, at the write boundary, by
188
155
  * `clipStepEvidenceForPersistence` (`runtime/runs.ts`), which replaces
189
156
  * oversized values with an explicitly-marked truncation envelope rather than
190
157
  * silently shortening them.
@@ -213,8 +180,11 @@ export function jsonBytes(value) {
213
180
  // (`src/workflows/ir/schema-v4.ts`'s recursive `decodeChildWorkflowTarget`).
214
181
  // Full design history, including the rejected alternative for the byte cap:
215
182
  // docs/architecture/decisions/0007-workflow-composition-bounds.md.
216
- /** Max workflow composition depth (root = depth 0; a 9th descendant level fails). */
217
- export const WORKFLOW_MAX_COMPOSITION_DEPTH = 8;
183
+ //
184
+ // Composition DEPTH is unbounded — `assertNoCompositionCycle` (freeze/targets/
185
+ // child-workflow.ts) already makes infinite composition mathematically
186
+ // impossible, so a depth ceiling on top of it only bounded how many
187
+ // legitimately distinct workflows an author could nest.
218
188
  /**
219
189
  * Max AGGREGATE canonical-JSON bytes of every embedded child plan in ONE root
220
190
  * freeze (the sum across the whole composition tree, not per child).
@@ -396,10 +396,9 @@ export function isTruncatedEvidence(value) {
396
396
  * `buildEvidence` (exec/step-work.ts) promotes `evidence.output` UNCLIPPED by
397
397
  * design: gates judge the full promoted artifact and the in-memory
398
398
  * {@link StepExecutionResult} carries it to the caller intact. Nothing bounded
399
- * the PERSISTED form, though — a `collect` reducer over a fan-out capped only by
400
- * `WORKFLOW_MAX_MAP_EXPANSION` (10 000 units) can serialize to hundreds of
401
- * megabytes. This is the write boundary, so the bound lives here rather than in
402
- * the shared step-semantics module.
399
+ * the PERSISTED form, though — a `collect` reducer over an unbounded fan-out
400
+ * can serialize to hundreds of megabytes. This is the write boundary, so the
401
+ * bound lives here rather than in the shared step-semantics module.
403
402
  *
404
403
  * Over-cap values are REPLACED (largest top-level entry BY UTF-8 BYTES first —
405
404
  * the unit the cap is measured in — until the row fits) with a