akm-cli 0.9.24 → 0.9.25-alpha.2

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 (84) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/health/checks.js +10 -11
  4. package/dist/commands/improve/consolidate/pair-pass.js +4 -2
  5. package/dist/commands/improve/consolidate.js +10 -4
  6. package/dist/commands/improve/execution.js +4 -11
  7. package/dist/commands/improve/extract-prompt.js +4 -4
  8. package/dist/commands/improve/extract.js +11 -13
  9. package/dist/commands/improve/improve-cli.js +65 -34
  10. package/dist/commands/improve/improve-strategies.js +49 -43
  11. package/dist/commands/improve/improve-usage-report.js +8 -17
  12. package/dist/commands/improve/loop-stages.js +3 -0
  13. package/dist/commands/improve/preparation.js +3 -1
  14. package/dist/commands/improve/reflect-noise.js +125 -0
  15. package/dist/commands/improve/reflect.js +105 -172
  16. package/dist/commands/improve/retrieval-gate.js +7 -2
  17. package/dist/commands/improve/stage.js +67 -24
  18. package/dist/commands/proposal/drain.js +11 -2
  19. package/dist/commands/proposal/proposal-cli.js +1 -5
  20. package/dist/commands/proposal/propose-cli.js +2 -2
  21. package/dist/commands/proposal/propose.js +72 -84
  22. package/dist/commands/proposal/validators/proposal-quality-validators.js +4 -2
  23. package/dist/commands/read/search-cli.js +0 -38
  24. package/dist/commands/remember.js +3 -3
  25. package/dist/commands/sources/schema-repair.js +1 -1
  26. package/dist/core/config/config-schema.js +37 -60
  27. package/dist/core/config/engine-semantics.js +15 -11
  28. package/dist/core/config/schema/improve-processes.js +18 -2
  29. package/dist/core/improve-result.js +3 -3
  30. package/dist/core/redaction.js +4 -0
  31. package/dist/core/spawn-env.js +25 -0
  32. package/dist/core/structured.js +11 -1
  33. package/dist/execution/source.js +10 -0
  34. package/dist/indexer/passes/memory-inference.js +2 -1
  35. package/dist/integrations/agent/builder-shared.js +15 -0
  36. package/dist/integrations/agent/config.js +1 -1
  37. package/dist/integrations/agent/engine-resolution.js +13 -31
  38. package/dist/integrations/agent/execution.js +48 -22
  39. package/dist/integrations/agent/index.js +1 -1
  40. package/dist/integrations/agent/profiles.js +2 -2
  41. package/dist/integrations/agent/prompts.js +55 -114
  42. package/dist/integrations/agent/request-lowering.js +21 -8
  43. package/dist/integrations/agent/runner-dispatch.js +96 -3
  44. package/dist/integrations/agent/runner.js +8 -2
  45. package/dist/integrations/harnesses/aider/agent-builder.js +10 -23
  46. package/dist/integrations/harnesses/aider/index.js +0 -5
  47. package/dist/integrations/harnesses/amazonq/agent-builder.js +9 -21
  48. package/dist/integrations/harnesses/amazonq/index.js +0 -5
  49. package/dist/integrations/harnesses/claude/agent-builder.js +47 -36
  50. package/dist/integrations/harnesses/claude/index.js +0 -14
  51. package/dist/integrations/harnesses/codex/agent-builder.js +3 -4
  52. package/dist/integrations/harnesses/codex/index.js +0 -4
  53. package/dist/integrations/harnesses/copilot/agent-builder.js +4 -13
  54. package/dist/integrations/harnesses/copilot/index.js +2 -7
  55. package/dist/integrations/harnesses/gemini/agent-builder.js +4 -13
  56. package/dist/integrations/harnesses/gemini/index.js +0 -5
  57. package/dist/integrations/harnesses/ids.js +12 -10
  58. package/dist/integrations/harnesses/opencode/agent-builder.js +41 -9
  59. package/dist/integrations/harnesses/opencode/index.js +0 -8
  60. package/dist/integrations/harnesses/opencode/model-config.js +33 -0
  61. package/dist/integrations/harnesses/opencode/model-work-agent.js +115 -0
  62. package/dist/integrations/harnesses/opencode-sdk/harness.js +2 -7
  63. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +114 -28
  64. package/dist/integrations/harnesses/openhands/agent-builder.js +7 -21
  65. package/dist/integrations/harnesses/openhands/index.js +0 -5
  66. package/dist/integrations/harnesses/pi/agent-builder.js +7 -21
  67. package/dist/integrations/harnesses/pi/index.js +0 -5
  68. package/dist/llm/client.js +5 -0
  69. package/dist/llm/feature-gate.js +12 -9
  70. package/dist/llm/index-passes.js +2 -5
  71. package/dist/llm/memory-infer.js +6 -5
  72. package/dist/llm/structured-call.js +33 -11
  73. package/dist/output/shapes/passthrough.js +1 -0
  74. package/dist/scripts/akm-migrate-node.js +298 -228
  75. package/dist/scripts/akm-migrate.js +298 -228
  76. package/dist/workflows/exec/step-work.js +6 -5
  77. package/dist/workflows/exec/unit-dispatch.js +4 -13
  78. package/dist/workflows/freeze/step-values.js +1 -1
  79. package/docs/reference/cli.md +41 -18
  80. package/docs/reference/configuration.md +165 -12
  81. package/docs/reference/data-and-telemetry.md +2 -3
  82. package/docs/reference/workflow-schema.md +10 -9
  83. package/package.json +1 -1
  84. package/schemas/akm-config.json +108 -0
@@ -39,13 +39,11 @@
39
39
  * - **schema** — the matrix places Aider in the "via prompt+validate" tier
40
40
  * with *no* structured output mode at all (plan §"Structured-output
41
41
  * normalization", tier "none"): there is no schema flag and no JSON output
42
- * flag, so the JSON Schema is injected into the message payload using the
43
- * exact directive wording of the engine's prompt assembly
44
- * (`step-work.ts` `buildUnitPrompt`) and the pi builder, so all
45
- * dispatch paths speak one dialect. Downstream, embedded-JSON extraction +
46
- * the engine's shared retry-until-valid loop supply the validation Aider
47
- * lacks. No temp schema file is written — that is Codex's native-schema
48
- * mechanism (`--output-schema`), which Aider does not have.
42
+ * flag, so the JSON Schema reaches it only as the instruction the shared
43
+ * request lowering appends to the prompt. Downstream, embedded-JSON
44
+ * extraction + the engine's shared retry-until-valid loop supply the
45
+ * validation Aider lacks. No temp schema file is written — that is Codex's
46
+ * native-schema mechanism (`--output-schema`), which Aider does not have.
49
47
  * - **tools** — deliberately unconsumed. Aider has no per-tool allowlist
50
48
  * flag; tool-ish behaviour is governed by its own switches (`--yes-always`,
51
49
  * git integration, shell-command confirmation). A restrictive policy is
@@ -57,41 +55,31 @@
57
55
  * durable source of truth; resume works even against a harness with no
58
56
  * session model (plan §"Session, MCP, and identity across harnesses" —
59
57
  * Aider is the plan's named example).
60
- * - **effort** — stays unconsumed (reserved; the shared request contract's
61
- * "no builder consumes it yet" note stays true).
58
+ * - **inference** — not translated: the shared lowering reports each field of
59
+ * the request's inference as untranslated.
62
60
  *
63
61
  * Registered: `aiderBuilder` is `AiderHarness.agentBuilder` (`./index.ts`),
64
62
  * one of the ten harnesses `HARNESS_REGISTRY` constructs
65
63
  * (`harnesses/index.ts`); `agent/builders.ts` derives `BUILTIN_BUILDERS` from
66
64
  * that registry, so this builder is reachable under the `"aider"` platform
67
- * name without any further wiring. The registry entry also declares
68
- * `structuredOutput: "none"` alongside it (`./index.ts`).
65
+ * name without any further wiring.
69
66
  */
70
67
  import { resolveDispatchModel } from "../../agent/builder-shared.js";
71
68
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
72
69
  /** Canonical harness/platform id used for model-alias resolution. */
73
70
  export const AIDER_PLATFORM = "aider";
74
- /**
75
- * Assemble the `--message` payload: optional system text, the task prompt,
76
- * and — when a schema is requested — the same schema directive the workflow
77
- * engine's prompt assembly uses (Aider has no native structured output, so
78
- * the prompt is the only channel; plan §"Structured-output normalization",
79
- * tier "none").
80
- */
71
+ /** Assemble the `--message` payload: optional system text, then the task prompt. */
81
72
  function buildMessagePayload(req) {
82
73
  const sections = [];
83
74
  if (req.systemPrompt)
84
75
  sections.push(req.systemPrompt);
85
76
  sections.push(req.prompt);
86
- if (req.schema) {
87
- sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
88
- }
89
77
  return sections.join("\n\n");
90
78
  }
91
79
  /**
92
80
  * Aider builder.
93
81
  * Command shape:
94
- * aider [--model <m>] --yes-always --no-pretty --message=<[system\n\n]prompt[\n\nschema directive]>
82
+ * aider [--model <m>] --yes-always --no-pretty --message=<[system\n\n]prompt>
95
83
  */
96
84
  export const aiderBuilder = {
97
85
  platform: AIDER_PLATFORM,
@@ -100,7 +88,6 @@ export const aiderBuilder = {
100
88
  adapter: AIDER_PLATFORM,
101
89
  personaChannel: "prompt",
102
90
  tools: "none",
103
- outputSchema: true,
104
91
  }),
105
92
  build(profile, req) {
106
93
  const args = [...profile.args];
@@ -29,11 +29,6 @@ export class AiderHarness extends BaseHarness {
29
29
  agentBuilder = aiderBuilder;
30
30
  resultExtractor = aiderResultExtractor;
31
31
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
32
- // akm spawns the `aider` CLI locally per unit ⇒ local-runner.
33
- pattern = "local-runner";
34
- // No structured-output mode at all (the matrix's "none — parse output"):
35
- // akm injects the schema into the prompt and extracts embedded JSON.
36
- structuredOutput = "none";
37
32
  // No flag-shaped resume: Aider persists context in chat-history files
38
33
  // (`.aider.chat.history.md`), not session ids — the plan's named example of
39
34
  // a harness with no session model. akm's `workflow_run_units` remains the
@@ -35,9 +35,8 @@
35
35
  * blank line.
36
36
  * - **schema** — the matrix places Q in the NO-structured-output tier
37
37
  * ("via prompt+validate": *(none documented)* — there is no `--json` or
38
- * `--output-format` to ask for). The JSON Schema is therefore passed
39
- * through the prompt: a directive matching the engine's wording
40
- * (`step-work.ts` `buildUnitPrompt`) is appended to the payload.
38
+ * `--output-format` to ask for). The JSON Schema therefore reaches it only
39
+ * as the instruction the shared request lowering appends to the prompt.
41
40
  * Stdout stays plain text; `./result-extractor.ts` strips terminal framing
42
41
  * and the engine's shared embedded-JSON parse + retry-until-valid loop does
43
42
  * the rest. No schema temp file is written — that seam is codex-only
@@ -51,16 +50,14 @@
51
50
  * falling back to `--trust-all-tools` (never silently widen a restriction)
52
51
  * — Q then refuses untrusted tool actions in non-interactive mode, which is
53
52
  * the conservative failure mode.
54
- * - **effort** — stays unconsumed (reserved; the shared request contract's
55
- * "no builder consumes it yet" note stays true).
53
+ * - **inference** — not translated: the shared lowering reports each field of
54
+ * the request's inference as untranslated.
56
55
  *
57
56
  * Registered: `amazonqBuilder` is `AmazonqHarness.agentBuilder`
58
57
  * (`./index.ts`), one of the ten harnesses `HARNESS_REGISTRY` constructs
59
58
  * (`harnesses/index.ts`); `agent/builders.ts` derives `BUILTIN_BUILDERS` from
60
59
  * that registry, so this builder is reachable under the `"amazonq"` platform
61
- * name without any further wiring. The registry-side capability entry —
62
- * pattern `local-runner`, structuredOutput `none` — is declared alongside it
63
- * (`./index.ts`).
60
+ * name without any further wiring.
64
61
  */
65
62
  import { resolveDispatchModel } from "../../agent/builder-shared.js";
66
63
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
@@ -84,27 +81,19 @@ function toolPolicyEntries(tools) {
84
81
  }
85
82
  return undefined;
86
83
  }
87
- /**
88
- * Assemble the positional prompt payload: optional system prompt, the task
89
- * prompt, and — when a schema is requested — the same schema directive the
90
- * workflow engine's prompt assembly uses, so both dispatch paths speak one
91
- * dialect.
92
- */
84
+ /** Assemble the positional prompt payload: optional system prompt, then the task prompt. */
93
85
  function buildPromptPayload(req) {
94
86
  const sections = [];
95
87
  if (req.systemPrompt)
96
88
  sections.push(req.systemPrompt);
97
89
  sections.push(req.prompt);
98
- if (req.schema) {
99
- sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
100
- }
101
90
  return sections.join("\n\n");
102
91
  }
103
92
  /**
104
93
  * Amazon Q Developer CLI builder.
105
94
  * Command shape:
106
95
  * q chat --no-interactive (--trust-all-tools | --trust-tools=<t1,t2>)
107
- * [--model <m>] -- "<systemPrompt?\n\nprompt\n\nschema directive?>"
96
+ * [--model <m>] -- "<systemPrompt?\n\nprompt>"
108
97
  */
109
98
  export const amazonqBuilder = {
110
99
  platform: AMAZONQ_PLATFORM,
@@ -113,7 +102,6 @@ export const amazonqBuilder = {
113
102
  adapter: AMAZONQ_PLATFORM,
114
103
  personaChannel: "prompt",
115
104
  tools: "flat",
116
- outputSchema: true,
117
105
  }),
118
106
  build(profile, req) {
119
107
  // Built-in q profiles would ship `args: []`; headless dispatch is the
@@ -141,8 +129,8 @@ export const amazonqBuilder = {
141
129
  const resolved = resolveDispatchModel(req, profile, AMAZONQ_PLATFORM);
142
130
  args.push("--model", resolved);
143
131
  }
144
- // No system-prompt / schema flags exist on `q chat` — both travel in the
145
- // positional payload, after the end-of-options separator.
132
+ // No system-prompt flag exists on `q chat` — it travels in the positional
133
+ // payload, after the end-of-options separator.
146
134
  args.push("--");
147
135
  args.push(buildPromptPayload(req));
148
136
  return { argv: [profile.bin, ...args] };
@@ -30,11 +30,6 @@ export class AmazonqHarness extends BaseHarness {
30
30
  agentBuilder = amazonqBuilder;
31
31
  resultExtractor = amazonqResultExtractor;
32
32
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
33
- // akm spawns `q chat` locally per unit ⇒ local-runner.
34
- pattern = "local-runner";
35
- // No documented structured output: akm injects the schema into the prompt
36
- // and extracts embedded JSON from plain-text stdout.
37
- structuredOutput = "none";
38
33
  // No `identityEnv`: the matrix lists Q's identity markers as uncertain, and
39
34
  // Q stamps no session var onto child processes.
40
35
  capabilities = caps({
@@ -11,51 +11,57 @@
11
11
  * in `agent/builders.ts`, which imports this builder back into
12
12
  * `BUILTIN_BUILDERS`.
13
13
  *
14
- * ## Structured output (Codex round-3 finding A)
14
+ * ## Structured output
15
15
  *
16
- * The headless `claude -p` (`--print`) CLI has NO native output-SCHEMA flag
17
- * (unlike Codex's `--output-schema <file>`). Its documented structured path is
18
- * `--output-format json`, which wraps the run in a RESULT ENVELOPE
19
- * (`{"type":"result","result":"<final answer>","session_id":"…", …}`) — the
20
- * "native-json" tier, NOT "native-schema". (The registry's earlier
21
- * `native-schema` claim described Claude Code's IN-HARNESS `Workflow`/`agent()`
22
- * tool-input-schema path, which is a different execution surface than the
23
- * agentBuilder dispatch akm's local-runner uses; the descriptor is aligned to
24
- * `native-json` to match this builder honestly.)
16
+ * For a schema-bearing request this builder emits `--output-format json`,
17
+ * which wraps the run in a RESULT ENVELOPE
18
+ * (`{"type":"result","result":"<final answer>","session_id":"…", …}`); the
19
+ * shared request lowering has already appended the schema instruction to the
20
+ * prompt. The envelope is unwrapped by `./result-extractor.ts`, and the
21
+ * engine's shared `runStructured` retry-until-valid loop validates the
22
+ * extracted text against the schema (hinted output is trusted but verified).
23
+ * Without a schema the argv carries no output flag.
25
24
  *
26
- * So for a schema-bearing unit this builder emits `--output-format json` and
27
- * appends the SAME schema directive the engine's prompt assembly uses
28
- * (`step-work.ts` `buildUnitPrompt`) so a direct (non-workflow) dispatch is
29
- * self-sufficient — matching the copilot/gemini native-json builders. The
30
- * result envelope is unwrapped by `./result-extractor.ts`, and the engine's
31
- * shared `runStructured` retry-until-valid loop still validates the extracted
32
- * text against the node schema (constrained/hinted output is trusted but
33
- * verified). Without a schema the argv is byte-identical to the pre-fix shape.
25
+ * Claude Code 2.1.283 also has `--json-schema <schema>` (JSON Schema for
26
+ * structured output validation, with `--print`). akm does not pass it; the
27
+ * instruction and the validation loop above are what enforce a schema.
34
28
  *
35
29
  * The builder's `platform` stays `'claude'` (the canonical harness id).
36
30
  */
37
- import { normalizeTools, resolveDispatchModel, } from "../../agent/builder-shared.js";
31
+ import { modelFromArgs, normalizeTools, resolveDispatchModel, } from "../../agent/builder-shared.js";
38
32
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
39
- /**
40
- * Assemble the positional prompt: the task prompt and — when a schema is
41
- * requested — the same schema directive the workflow engine's prompt assembly
42
- * uses (`step-work.ts` `buildUnitPrompt`), so both dispatch paths speak one
43
- * dialect. Claude Code takes the system prompt as a `--system-prompt` FLAG (it
44
- * has one, unlike copilot/gemini), so only the schema directive is folded in
45
- * here.
46
- */
47
- function buildPromptPayload(req) {
48
- if (!req.schema)
49
- return req.prompt;
50
- return `${req.prompt}\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`;
51
- }
33
+ /** The model-work tool policy on Claude Code: read, edit in the working directory, `akm search`, `akm show`. */
34
+ export const MODEL_WORK_CLAUDE_FLAGS = Object.freeze([
35
+ "--restricted",
36
+ "--strict-mcp-config",
37
+ "--tools",
38
+ "Read,Edit,Bash",
39
+ "--allowedTools",
40
+ "Read,Edit,Bash(akm search *),Bash(akm show *)",
41
+ "--permission-mode",
42
+ "dontAsk",
43
+ ]);
52
44
  /**
53
45
  * Claude Code builder.
54
46
  * Command shape:
55
47
  * claude [--agent <name>] [--system-prompt "..."] [--model <m>] [--allowedTools <t>]
56
- * [--output-format json] --print -- "<prompt (+ schema directive)>"
48
+ * [--output-format json] --print -- "<prompt>"
57
49
  *
58
50
  * --print switches Claude Code to non-interactive captured output mode.
51
+ *
52
+ * The model-work tool policy lowers to {@link MODEL_WORK_CLAUDE_FLAGS} in place
53
+ * of the engine's `args` and `--allowedTools`, verified against Claude Code
54
+ * 2.1.283 and a local stub:
55
+ * - `--restricted` ignores the user, project and local settings files (whose
56
+ * allow rules would otherwise pre-approve any command or path) and confines
57
+ * the file tools to the working directory;
58
+ * - `--strict-mcp-config` starts no MCP server, and `--tools` offers only
59
+ * Read, Edit and Bash;
60
+ * - `--allowedTools` pre-approves Read, Edit, `akm search` and `akm show`,
61
+ * and `--permission-mode dontAsk` denies everything else instead of
62
+ * prompting, including a compound, redirected or substituted command.
63
+ * The engine's `args` are left out because `--add-dir`, `--settings` or a
64
+ * second `--allowedTools` would widen that. Only the model they name is kept.
59
65
  */
60
66
  export const claudeBuilder = {
61
67
  platform: "claude",
@@ -65,10 +71,10 @@ export const claudeBuilder = {
65
71
  personaChannel: "native",
66
72
  nativeAgentSelector: true,
67
73
  tools: "all",
68
- outputSchema: true,
69
74
  }),
70
75
  build(profile, req) {
71
- const args = [...profile.args];
76
+ const modelWork = req.modelWork === true;
77
+ const args = modelWork ? [...MODEL_WORK_CLAUDE_FLAGS] : [...profile.args];
72
78
  if (req.agent) {
73
79
  args.push("--agent", req.agent);
74
80
  }
@@ -79,6 +85,11 @@ export const claudeBuilder = {
79
85
  const resolved = resolveDispatchModel(req, profile, "claude");
80
86
  args.push("--model", resolved);
81
87
  }
88
+ else if (modelWork) {
89
+ const model = modelFromArgs(profile.args);
90
+ if (model)
91
+ args.push("--model", model);
92
+ }
82
93
  if (req.tools) {
83
94
  args.push("--allowedTools", normalizeTools(req.tools));
84
95
  }
@@ -90,7 +101,7 @@ export const claudeBuilder = {
90
101
  // --print = non-interactive, outputs to stdout — required for captured mode
91
102
  args.push("--print");
92
103
  args.push("--");
93
- args.push(buildPromptPayload(req));
104
+ args.push(req.prompt);
94
105
  return { argv: [profile.bin, ...args] };
95
106
  },
96
107
  };
@@ -24,20 +24,6 @@ export class ClaudeHarness extends BaseHarness {
24
24
  agentBuilder = claudeBuilder;
25
25
  resultExtractor = claudeResultExtractor;
26
26
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
27
- // Claude Code is the in-harness pattern: the orchestrating session itself
28
- // drives units via the `akm workflow` gate spine (`claude -p` headless
29
- // dispatch also exists via `agentBuilder`, but the pattern classification
30
- // follows the matrix row).
31
- pattern = "in-harness";
32
- // Structured output tier for the AGENT-DISPATCH (`claude -p`) path akm's
33
- // local runner uses (Codex round-3 finding A). The headless CLI has NO
34
- // output-schema flag — its documented structured path is `--output-format
35
- // json`, a RESULT ENVELOPE akm parses (`./result-extractor.ts`) and then
36
- // validates against the node schema ⇒ the "native-json" tier. (Claude Code's
37
- // in-harness `Workflow`/`agent()` tool-input-schema path IS native-schema,
38
- // but that is a different surface than the dispatch builder — the descriptor
39
- // is aligned to what the builder honestly does.)
40
- structuredOutput = "native-json";
41
27
  // Session-id env marker: presence of a concrete session id (not the bare
42
28
  // "running under Claude Code" flag) attributes a run to this harness.
43
29
  identityEnv = ["CLAUDE_SESSION_ID"];
@@ -42,9 +42,9 @@
42
42
  * not expressible through `AgentDispatchRequest` (which has no session
43
43
  * field yet); {@link codexResumeArgs} exposes the argv prefix for the
44
44
  * integration task that wires session-id reuse from `workflow_run_units`.
45
- * - `req.effort` stays unconsumed (reserved; codex would take it as
46
- * `-c model_reasoning_effort=<v>` — left to the integration task so the
47
- * shared request contract's "no builder consumes it yet" note stays true).
45
+ * - The request's inference is not translated: the shared lowering reports
46
+ * each field as untranslated. codex would take `reasoningEffort` as
47
+ * `-c model_reasoning_effort=<v>`, which is left to the integration task.
48
48
  *
49
49
  * Registered: `codexBuilder` is `CodexHarness.agentBuilder` (`./index.ts`),
50
50
  * one of the ten harnesses `HARNESS_REGISTRY` constructs
@@ -110,7 +110,6 @@ export const codexBuilder = {
110
110
  adapter: "codex",
111
111
  personaChannel: "prompt",
112
112
  tools: "none",
113
- outputSchema: true,
114
113
  }),
115
114
  build(profile, req) {
116
115
  // Built-in codex profiles ship `args: []`; headless dispatch is the `exec`
@@ -29,10 +29,6 @@ export class CodexHarness extends BaseHarness {
29
29
  agentBuilder = codexBuilder;
30
30
  resultExtractor = codexResultExtractor;
31
31
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
32
- // akm spawns `codex exec` locally per unit ⇒ local-runner.
33
- pattern = "local-runner";
34
- // `--output-schema <file>` enforces a caller-supplied JSON schema natively.
35
- structuredOutput = "native-schema";
36
32
  // No flag-shaped resume: codex resume is the `exec resume <id>` SUBCOMMAND
37
33
  // chain (see `codexResumeArgs` in ./agent-builder.ts).
38
34
  // Presence flag: CODEX_SANDBOX is stamped only on processes codex itself
@@ -24,9 +24,8 @@
24
24
  * blank line.
25
25
  * - **schema** — the matrix places Copilot in the "via prompt+validate" tier
26
26
  * (no native `--output-schema` equivalent, unlike Codex), so the JSON
27
- * Schema is passed through the prompt: a directive matching the engine's
28
- * wording (`step-work.ts` `buildUnitPrompt`) is appended to the `-p`
29
- * payload, and `--output-format json` is emitted so stdout is the
27
+ * Schema reaches it as the instruction the shared request lowering appends
28
+ * to the prompt, and `--output-format json` is emitted so stdout is the
30
29
  * documented JSON envelope the copilot result extractor normalizes. The
31
30
  * engine's shared retry-until-valid loop performs the actual validation.
32
31
  * - **tools** — a string/array tool policy maps to repeated
@@ -66,26 +65,19 @@ function toolPolicyEntries(tools) {
66
65
  }
67
66
  return undefined;
68
67
  }
69
- /**
70
- * Assemble the `-p` payload: optional system prompt, the task prompt, and —
71
- * when a schema is requested — the same schema directive the workflow
72
- * engine's prompt assembly uses, so both dispatch paths speak one dialect.
73
- */
68
+ /** Assemble the `-p` payload: optional system prompt, then the task prompt. */
74
69
  function buildPromptPayload(req) {
75
70
  const sections = [];
76
71
  if (req.systemPrompt)
77
72
  sections.push(req.systemPrompt);
78
73
  sections.push(req.prompt);
79
- if (req.schema) {
80
- sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
81
- }
82
74
  return sections.join("\n\n");
83
75
  }
84
76
  /**
85
77
  * GitHub Copilot CLI builder.
86
78
  * Command shape:
87
79
  * copilot [--model <m>] (--allow-all-tools | --allow-tool <t> ...)
88
- * [--output-format json] -p "<systemPrompt?\n\nprompt\n\nschema?>"
80
+ * [--output-format json] -p "<systemPrompt?\n\nprompt>"
89
81
  */
90
82
  export const copilotBuilder = {
91
83
  platform: COPILOT_PLATFORM,
@@ -94,7 +86,6 @@ export const copilotBuilder = {
94
86
  adapter: COPILOT_PLATFORM,
95
87
  personaChannel: "prompt",
96
88
  tools: "flat",
97
- outputSchema: true,
98
89
  }),
99
90
  build(profile, req) {
100
91
  const args = [...profile.args];
@@ -10,8 +10,8 @@
10
10
  *
11
11
  * It also defines {@link CopilotHarness}, the {@link AkmHarness} descriptor
12
12
  * that `HARNESS_REGISTRY` registers. This is the LOCAL Copilot CLI
13
- * (`copilot -p …`); the cloud "Copilot coding agent" is the plan's
14
- * cloud-delegate pattern and is a separate, future descriptor.
13
+ * (`copilot -p …`); the cloud "Copilot coding agent" is a separate, future
14
+ * descriptor.
15
15
  */
16
16
  import { caps } from "../shared.js";
17
17
  import { BaseHarness } from "../types.js";
@@ -30,11 +30,6 @@ export class CopilotHarness extends BaseHarness {
30
30
  agentBuilder = copilotBuilder;
31
31
  resultExtractor = copilotResultExtractor;
32
32
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
33
- // akm spawns the `copilot` CLI locally per unit ⇒ local-runner.
34
- pattern = "local-runner";
35
- // `--output-format json` emits a documented JSON envelope akm parses, then
36
- // validates against the node schema ⇒ native-json tier.
37
- structuredOutput = "native-json";
38
33
  // Session-id env marker only. The matrix's other candidates (GH_TOKEN,
39
34
  // bare COPILOT_* presence vars) are credential/presence flags that would
40
35
  // stamp identity onto manual runs, so they are deliberately NOT registered
@@ -26,9 +26,8 @@
26
26
  * prompt, separated by a blank line.
27
27
  * - **schema** — the matrix places Gemini in the "via prompt+validate" tier
28
28
  * (no native `--output-schema` equivalent, unlike Codex — so no temp-file
29
- * plumbing here), so the JSON Schema is passed through the prompt: a
30
- * directive matching the engine's wording (`step-work.ts`
31
- * `buildUnitPrompt`) is appended to the `-p` payload, and
29
+ * plumbing here), so the JSON Schema reaches it as the instruction the
30
+ * shared request lowering appends to the prompt, and
32
31
  * `--output-format json` is emitted so stdout is the documented JSON
33
32
  * envelope the gemini result extractor normalizes. The engine's shared
34
33
  * retry-until-valid loop performs the actual validation.
@@ -69,26 +68,19 @@ function toolPolicyEntries(tools) {
69
68
  }
70
69
  return undefined;
71
70
  }
72
- /**
73
- * Assemble the `-p` payload: optional system prompt, the task prompt, and —
74
- * when a schema is requested — the same schema directive the workflow
75
- * engine's prompt assembly uses, so both dispatch paths speak one dialect.
76
- */
71
+ /** Assemble the `-p` payload: optional system prompt, then the task prompt. */
77
72
  function buildPromptPayload(req) {
78
73
  const sections = [];
79
74
  if (req.systemPrompt)
80
75
  sections.push(req.systemPrompt);
81
76
  sections.push(req.prompt);
82
- if (req.schema) {
83
- sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
84
- }
85
77
  return sections.join("\n\n");
86
78
  }
87
79
  /**
88
80
  * Gemini CLI builder.
89
81
  * Command shape:
90
82
  * gemini [--model <m>] [--allowed-tools <t> ...]
91
- * [--output-format json] -p "<systemPrompt?\n\nprompt\n\nschema?>"
83
+ * [--output-format json] -p "<systemPrompt?\n\nprompt>"
92
84
  */
93
85
  export const geminiBuilder = {
94
86
  platform: GEMINI_PLATFORM,
@@ -97,7 +89,6 @@ export const geminiBuilder = {
97
89
  adapter: GEMINI_PLATFORM,
98
90
  personaChannel: "prompt",
99
91
  tools: "flat",
100
- outputSchema: true,
101
92
  }),
102
93
  build(profile, req) {
103
94
  const args = [...profile.args];
@@ -29,11 +29,6 @@ export class GeminiHarness extends BaseHarness {
29
29
  agentBuilder = geminiBuilder;
30
30
  resultExtractor = geminiResultExtractor;
31
31
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
32
- // akm spawns the `gemini` CLI locally per unit ⇒ local-runner.
33
- pattern = "local-runner";
34
- // `--output-format json` emits a documented JSON envelope akm parses, then
35
- // validates against the node schema ⇒ native-json tier.
36
- structuredOutput = "native-json";
37
32
  // The matrix's identity marker: Gemini CLI stamps GEMINI_CLI=1 only on
38
33
  // processes it spawns, so it genuinely means "running under gemini" (it is
39
34
  // not a user-profile config var) — but its VALUE ("1") is a bare flag, not
@@ -2,16 +2,16 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  export const HARNESS_ID_TABLE = [
5
- { id: "opencode", agentDispatch: true },
6
- { id: "claude", agentDispatch: true },
7
- { id: "opencode-sdk", agentDispatch: true },
8
- { id: "codex", agentDispatch: true },
9
- { id: "copilot", agentDispatch: true },
10
- { id: "pi", agentDispatch: true },
11
- { id: "gemini", agentDispatch: true },
12
- { id: "aider", agentDispatch: true },
13
- { id: "amazonq", agentDispatch: true },
14
- { id: "openhands", agentDispatch: true },
5
+ { id: "opencode", agentDispatch: true, enforcesModelWorkTools: true },
6
+ { id: "claude", agentDispatch: true, enforcesModelWorkTools: true },
7
+ { id: "opencode-sdk", agentDispatch: true, enforcesModelWorkTools: true },
8
+ { id: "codex", agentDispatch: true, enforcesModelWorkTools: false },
9
+ { id: "copilot", agentDispatch: true, enforcesModelWorkTools: false },
10
+ { id: "pi", agentDispatch: true, enforcesModelWorkTools: false },
11
+ { id: "gemini", agentDispatch: true, enforcesModelWorkTools: false },
12
+ { id: "aider", agentDispatch: true, enforcesModelWorkTools: false },
13
+ { id: "amazonq", agentDispatch: true, enforcesModelWorkTools: false },
14
+ { id: "openhands", agentDispatch: true, enforcesModelWorkTools: false },
15
15
  ];
16
16
  /**
17
17
  * Canonical, ordered list of valid harness / platform ids — the
@@ -22,3 +22,5 @@ export const HARNESS_ID_TABLE = [
22
22
  export const VALID_HARNESS_IDS = Object.freeze(HARNESS_ID_TABLE.map((h) => h.id));
23
23
  /** Harness ids whose `capabilities.agentDispatch` is `true`. */
24
24
  export const HARNESS_AGENT_DISPATCH_IDS = new Set(HARNESS_ID_TABLE.filter((h) => h.agentDispatch).map((h) => h.id));
25
+ /** Harness ids that confine the model-work tool policy, so unattended model work may run on them. */
26
+ export const HARNESS_MODEL_WORK_IDS = new Set(HARNESS_ID_TABLE.filter((h) => h.enforcesModelWorkTools).map((h) => h.id));
@@ -14,26 +14,61 @@
14
14
  * pre-migration `opencodeBuilder`. The builder's `platform` stays `'opencode'`
15
15
  * (the canonical harness id).
16
16
  */
17
- import { resolveDispatchModel } from "../../agent/builder-shared.js";
17
+ import { modelFromArgs, resolveDispatchModel } from "../../agent/builder-shared.js";
18
18
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
19
+ import { MODEL_WORK_AGENT_INFERENCE, opencodeInferenceConfig } from "./model-config.js";
20
+ import { MODEL_WORK_OPENCODE_AGENT, modelWorkOpencodeConfig, modelWorkPluginEnv } from "./model-work-agent.js";
19
21
  /**
20
22
  * OpenCode builder.
21
- * Command shape: opencode run [--system-prompt "..."] [--agent <name>] [--model <m>] "<prompt>"
23
+ * Command shape: opencode run [--agent <name>] [--model <m>] -- "<prompt>"
24
+ *
25
+ * `opencode run` has no system-prompt option (1.18.25 prints its usage and
26
+ * exits 1 on `--system-prompt`), so the shared lowerer composes a persona
27
+ * into the prompt.
22
28
  *
23
29
  * Tool policy is omitted — opencode manages tool access through its own agent
24
- * config files, not via CLI flags.
30
+ * config files, not via CLI flags. The one exception is the model-work tool
31
+ * policy: the builder injects its confined agent through
32
+ * `OPENCODE_CONFIG_CONTENT` and selects it with `--agent`. That command is
33
+ * akm's own (`opencode run --agent akm-model-work`): the engine's `args` are
34
+ * left out, because one such as `--attach` or `--dir` would move the run out
35
+ * of the injected config or the scratch working directory. Only the model they
36
+ * name is kept.
37
+ *
38
+ * Model work's agent carries the request's inference options
39
+ * (`./model-config.ts`). Any other dispatch injects nothing and carries none, so
40
+ * the model's own opencode config applies: set inference there.
25
41
  */
26
42
  export const opencodeBuilder = {
27
43
  platform: "opencode",
28
- personaChannel: "native",
44
+ personaChannel: "prompt",
29
45
  lower: createAgentRequestLowerer({
30
46
  adapter: "opencode",
31
- personaChannel: "native",
47
+ personaChannel: "prompt",
32
48
  nativeAgentSelector: true,
33
49
  tools: "none",
34
- outputSchema: false,
50
+ inference: MODEL_WORK_AGENT_INFERENCE,
35
51
  }),
36
52
  build(profile, req) {
53
+ if (req.modelWork) {
54
+ const model = req.model ?? modelFromArgs(profile.args);
55
+ const { agentOptions } = opencodeInferenceConfig(req.inference, true);
56
+ return {
57
+ argv: [
58
+ profile.bin,
59
+ "run",
60
+ "--agent",
61
+ MODEL_WORK_OPENCODE_AGENT,
62
+ ...(model ? ["--model", model] : []),
63
+ "--",
64
+ req.prompt,
65
+ ],
66
+ env: {
67
+ ...modelWorkPluginEnv(),
68
+ OPENCODE_CONFIG_CONTENT: JSON.stringify(modelWorkOpencodeConfig(agentOptions)),
69
+ },
70
+ };
71
+ }
37
72
  const args = req.model ? [] : [...profile.args];
38
73
  if (req.model) {
39
74
  for (let index = 0; index < profile.args.length; index += 1) {
@@ -48,9 +83,6 @@ export const opencodeBuilder = {
48
83
  }
49
84
  }
50
85
  }
51
- if (req.systemPrompt) {
52
- args.push("--system-prompt", req.systemPrompt);
53
- }
54
86
  if (req.agent) {
55
87
  args.push("--agent", req.agent);
56
88
  }
@@ -21,14 +21,6 @@ export class OpencodeHarness extends BaseHarness {
21
21
  setupDetectionDir = ".config/opencode";
22
22
  agentBuilder = opencodeBuilder;
23
23
  // ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
24
- // This entry is the CLI spawn path (`opencode run …`): akm launches the
25
- // harness locally per unit ⇒ local-runner. (The SDK path is the separate
26
- // `opencode-sdk` harness.)
27
- pattern = "local-runner";
28
- // The CLI path emits plain text — no JSON stream akm consumes — so the
29
- // engine uses the prompt-injected schema + embedded-JSON extraction tier
30
- // (the matrix's "via prompt+validate"). The SDK entry is native-json.
31
- structuredOutput = "none";
32
24
  // Session-id env marker for run attribution.
33
25
  identityEnv = ["OPENCODE_SESSION_ID"];
34
26
  sessionLogProvider = () => new OpenCodeProvider();