@rse/ase 2.0.6 → 2.0.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 (41) hide show
  1. package/dst/ase-agent-definition-api.d.ts +2 -0
  2. package/dst/ase-agent-definition-core.js +24 -3
  3. package/dst/ase-agent-fleet.js +2 -1
  4. package/dst/ase-agent-runtime-engine.js +22 -4
  5. package/dst/ase-agent-runtime-tool.js +10 -8
  6. package/dst/ase-agent-runtime.js +8 -4
  7. package/dst/ase-man-catalog.js +1 -1
  8. package/dst/ase-task-store-plugin-gitea.js +3 -3
  9. package/dst/man/ase-agent-adhoc.md +25 -6
  10. package/dst/man/ase-agent-condenser.md +7 -0
  11. package/dst/man/ase-agent-editor.md +7 -0
  12. package/dst/man/ase-agent-estimator.md +7 -0
  13. package/dst/man/ase-agent-fleet.md +7 -4
  14. package/dst/man/ase-agent-implementer.md +7 -0
  15. package/dst/man/ase-agent-integrator.md +7 -0
  16. package/dst/man/ase-agent-qualifier.md +7 -0
  17. package/dst/man/ase-agent-rebooter.md +7 -0
  18. package/dst/man/ase-agent-researcher.md +7 -0
  19. package/package.json +5 -5
  20. package/plugin/.claude-plugin/plugin.json +1 -1
  21. package/plugin/.codex-plugin/plugin.json +1 -1
  22. package/plugin/.github/plugin/plugin.json +1 -1
  23. package/plugin/meta/ase-common-task.md +9 -1
  24. package/plugin/meta/ase-control.md +14 -0
  25. package/plugin/meta/ase-format-specbook.yaml +9 -9
  26. package/plugin/meta/ase-skill.md +7 -0
  27. package/plugin/mods/ase-mods-help-hint.ts +1 -1
  28. package/plugin/skills/ase-code-edit/SKILL.md +53 -16
  29. package/plugin/skills/ase-code-edit/help.md +26 -13
  30. package/plugin/skills/ase-code-lint/SKILL.md +7 -3
  31. package/plugin/skills/ase-help-intent/data.md +170 -28
  32. package/plugin/skills/ase-help-skill/catalog.md +1 -0
  33. package/plugin/skills/ase-meta-workflow/workflow.txt +2 -0
  34. package/plugin/skills/ase-repo-changelog/SKILL.md +29 -10
  35. package/plugin/skills/ase-repo-changelog/help.md +5 -1
  36. package/plugin/skills/ase-repo-commit/SKILL.md +4 -1
  37. package/plugin/skills/ase-repo-dissect/help.md +2 -1
  38. package/plugin/skills/ase-repo-stage/SKILL.md +459 -0
  39. package/plugin/skills/ase-repo-stage/help.md +120 -0
  40. package/plugin/skills/ase-spec-edit/SKILL.md +20 -12
  41. package/plugin/skills/ase-spec-edit/help.md +22 -14
@@ -18,6 +18,7 @@ export interface AgentDefinition {
18
18
  lifecycle?: Many<string>;
19
19
  serial?: boolean;
20
20
  unclaimed?: boolean;
21
+ model?: string;
21
22
  state?: {
22
23
  incoming?: Many<string>;
23
24
  activity?: string;
@@ -175,6 +176,7 @@ export interface AgentHooks {
175
176
  ready?: (task: AgentHookTask, ctx: AgentHookContext) => Async<boolean | string>;
176
177
  prepare?: (task: AgentHookTask, job: AgentHookJob, ctx: AgentHookContext) => Async<void>;
177
178
  prompt?: (task: AgentHookTask, prompt: AgentHookPrompt, job: AgentHookJob, ctx: AgentHookContext) => Async<AgentHookPrompt | string | undefined>;
179
+ model?: (task: AgentHookTask, model: string, job: AgentHookJob, ctx: AgentHookContext) => Async<string | undefined>;
178
180
  verdict?: (task: AgentHookTask, result: AgentHookResult, ctx: AgentHookContext) => Async<string | undefined>;
179
181
  success?: (task: AgentHookTask, result: AgentHookResult, ctx: AgentHookContext) => Async<boolean | string>;
180
182
  settle?: (task: AgentHookTask, outcome: AgentHookOutcome, ctx: AgentHookContext) => Async<AgentHookSettlement | undefined>;
@@ -92,6 +92,9 @@ export const definitionOptions = [
92
92
  opt("unclaimed", "flag", "", "process the tasks of the incoming states (each adapted onto the " +
93
93
  "task lifecycle model) without claiming them, independent of their assignee and predecessors, " +
94
94
  "and without ever changing their status and assignee (for \"phase\" only)"),
95
+ opt("model", "value", "model", "model of the agent tool: a model for all tasks, or comma-separated " +
96
+ "\"<size>=<model>\" pairs per \"Effort:\" size (\"XS\", \"S\", \"M\", \"L\", \"XL\", or \"*\" for all other tasks) " +
97
+ "(default: the default model of the agent tool)"),
95
98
  opt("params", "list", "name=value", "value of a parameter declared by the definition (repeatable)"),
96
99
  opt("state-incoming", "list", "state", "state of the tasks to claim (mandatory for \"phase\", " +
97
100
  "repeatable with \"--unclaimed\" or \"--state-activity\" only)"),
@@ -272,7 +275,7 @@ export const flattenDefinition = (data, where) => {
272
275
  return flat;
273
276
  };
274
277
  /* the names of the hooks of a definition (see "AgentHooks") */
275
- const hookNames = ["start", "order", "passes", "ready", "prepare", "prompt", "verdict", "success", "settle", "finish", "stop"];
278
+ const hookNames = ["start", "order", "passes", "ready", "prepare", "prompt", "model", "verdict", "success", "settle", "finish", "stop"];
276
279
  /* normalize the parameter declarations <spec> of a definition (see "AgentParam"),
277
280
  where <where> names the definition in errors */
278
281
  const definitionParams = (spec, where) => {
@@ -741,6 +744,24 @@ export const resolveDefinition = async (opts, name) => {
741
744
  if (loaded.hooks.passes !== undefined)
742
745
  fail("definition", "hook \"passes\" is not for \"--unclaimed\" (as an unclaimed task never waits for its predecessors)");
743
746
  }
747
+ /* the model of the agent tool: a plain model (for all tasks, as size "*"),
748
+ or the models per "Effort:" size, where none means the default of the tool */
749
+ const modelSpec = str("model").trim();
750
+ const sizes = {};
751
+ if (modelSpec !== "" && !modelSpec.includes("="))
752
+ sizes["*"] = modelSpec;
753
+ else if (modelSpec !== "") {
754
+ for (const pair of modelSpec.split(",").map((item) => item.trim())) {
755
+ const m = /^(XS|S|M|L|XL|\*)=(\S+)$/.exec(pair);
756
+ if (m === null)
757
+ fail("model", `invalid pair "${pair}" (expected "<size>=<model>" with size "XS", "S", "M", "L", "XL", or "*")`);
758
+ else if (Object.hasOwn(sizes, m[1]))
759
+ fail("model", `size "${m[1]}" given twice`);
760
+ else
761
+ sizes[m[1]] = m[2];
762
+ }
763
+ }
764
+ const model = { spec: modelSpec, sizes };
744
765
  /* whether a verdict is determined (by "--verdict-label" or the hook "verdict") */
745
766
  const verdicting = has("verdict-label") || loaded.hooks.verdict !== undefined;
746
767
  /* the states (see "resolveStates") */
@@ -821,10 +842,10 @@ export const resolveDefinition = async (opts, name) => {
821
842
  hence the prompt of the request), resp. uses them optionally by "{prompt}" used only
822
843
  within the conditional "{?prompt:...}", else ignores them */
823
844
  const consumes = type === "request" && (template.required.has("prompt")
824
- || ["prepare", "prompt", "finish"].some((hook) => loaded.hooks[hook] !== undefined)) ? "required" :
845
+ || ["prepare", "prompt", "model", "finish"].some((hook) => loaded.hooks[hook] !== undefined)) ? "required" :
825
846
  type === "request" && template.names.has("prompt") ? "optional" : "ignored";
826
847
  return {
827
- name, title, description, type, workdir, lifecycles, unclaimed,
848
+ name, title, description, type, workdir, lifecycles, unclaimed, model,
828
849
  serial: flag("serial"),
829
850
  state: { incoming, activity, success, failure, phase, passed, verdict: verdicts },
830
851
  tag: {
@@ -45,7 +45,7 @@ const logLevelRe = /^([^|]*\| [^:]+: \[[^\]]+\]: )\[(ERROR|WARNING|INFO|DEBUG)\]
45
45
  agents, and applied to an agent only if it supports them) */
46
46
  const fleetKeys = ["foreground"];
47
47
  const relayKeys = ["address", "port", "token", "cors", "tls-cert", "tls-key"];
48
- const defaultKeys = ["tool", "parallel", "timeout", "repo", "tempdir", "agent-relay"];
48
+ const defaultKeys = ["tool", "model", "parallel", "timeout", "repo", "tempdir", "agent-relay"];
49
49
  /* the options not allowed in the section of an agent, as the fleet decides on them */
50
50
  const deniedKeys = ["name", "one-shot", "foreground", "help"];
51
51
  /* the state of the planned tasks per task lifecycle model, as estimated by the estimator */
@@ -76,6 +76,7 @@ const initConfig = (tool, lifecycle) => [
76
76
  # (each applied only to the agents supporting it, where an empty value omits it)
77
77
  default:
78
78
  tool: ${tool.padEnd(16)}# agent tool ("claude", "copilot", or "codex")
79
+ #model: <model> # model of the agent tool (or "<size>=<model>" pairs per "Effort:" size)
79
80
  parallel: 1 # maximum number of tasks processed in parallel
80
81
  timeout: 60m # maximum duration of processing a task (<number><unit> with unit "s", "m", or "h")
81
82
  #repo: <url> # Git repository to clone temporarily for the tasks without "worktree"
@@ -45,6 +45,15 @@ const promptOf = (value, configured) => {
45
45
  }
46
46
  throw new Error("invalid prompt (expected a string, \"{ text }\", or \"{ skill, args }\")");
47
47
  };
48
+ /* the model returned by the hook "model", validated, where undefined
49
+ keeps the configured model <configured> */
50
+ const modelOf = (value, configured) => {
51
+ if (value === undefined)
52
+ return configured;
53
+ else if (typeof value !== "string")
54
+ throw new Error("invalid model (expected a string)");
55
+ return value.trim();
56
+ };
48
57
  /* whether a record of an agent of a definition succeeded (where a missing one counts as failure) */
49
58
  const recordSuccess = (record) => record !== undefined && record.fixed === "" && record.checks.every((check) => check.ok);
50
59
  /* escape a text for its literal use in a regular expression */
@@ -331,7 +340,7 @@ class DefinedCore {
331
340
  }
332
341
  /* prepare the job of a task in its work <work>: render the prompt (with the request
333
342
  prompt <prompt> of a request agent), run the preparing commands, and run the hooks
334
- "prepare" and "prompt", where the work is cleaned up again on a failure, and where
343
+ "prepare", "prompt", and "model", where the work is cleaned up again on a failure, and where
335
344
  <changed> decides preliminarily whether the plan content of a request agent changed */
336
345
  async prepare(task, log, work, prompt, changed) {
337
346
  try {
@@ -374,7 +383,7 @@ class DefinedCore {
374
383
  throw err;
375
384
  }
376
385
  }
377
- /* run the hooks "prepare" and "prompt", where a failed hook fails the processing */
386
+ /* run the hooks "prepare", "prompt", and "model", where a failed hook fails the processing */
378
387
  const job = Object.freeze({
379
388
  dir: work.cwd,
380
389
  dirs: Object.freeze([...work.dirs]),
@@ -392,7 +401,7 @@ class DefinedCore {
392
401
  throw new Error(`agent: hook "${name}" failed: ${reason(err)}`, { cause: err });
393
402
  }
394
403
  };
395
- const { prepare: prepareHook, prompt: promptHook } = this.def.hooks;
404
+ const { prepare: prepareHook, prompt: promptHook, model: modelHook } = this.def.hooks;
396
405
  if (prepareHook !== undefined)
397
406
  await hooked("prepare", async () => prepareHook(this.task(task), job, ctx));
398
407
  let spec = this.def.prompt.skill !== "" ?
@@ -401,10 +410,17 @@ class DefinedCore {
401
410
  const configured = Object.freeze(spec);
402
411
  spec = await hooked("prompt", async () => promptOf(await promptHook(this.task(task), configured, job, ctx), configured));
403
412
  }
413
+ const { sizes } = this.def.model;
414
+ let model = sizes[TaskFormat.parseEffort(task.keys.get("Effort") ?? "")?.size ?? "*"] ?? sizes["*"] ?? "";
415
+ if (modelHook !== undefined) {
416
+ const configured = model;
417
+ model = await hooked("model", async () => modelOf(await modelHook(this.task(task), configured, job, ctx), configured));
418
+ }
404
419
  return {
405
420
  ...("skill" in spec ?
406
421
  { cmd: `${spec.skill}${spec.args !== "" ? ` ${spec.args}` : ""}` } :
407
422
  { prompt: spec.text }),
423
+ model,
408
424
  cwd: work.cwd,
409
425
  dirs: work.dirs,
410
426
  finish: (log, result) => this.finish(task, log, result, work, job, env, before, changed),
@@ -714,6 +730,7 @@ class DefinedPhaseAgent extends PhaseAgent {
714
730
  };
715
731
  this.presenceTitle = def.title;
716
732
  this.presenceDesc = def.description;
733
+ this.modelSpec = def.model.spec;
717
734
  this.assigned = def.assign.required;
718
735
  this.finishing = def.finish.commit || def.finish.success !== "" || def.workdir === "target"
719
736
  || def.hooks.finish !== undefined;
@@ -858,6 +875,7 @@ class DefinedRequestAgent extends RequestAgent {
858
875
  this.presencePrompt = def.request.prompt;
859
876
  this.presenceTitle = def.title;
860
877
  this.presenceDesc = def.description;
878
+ this.modelSpec = def.model.spec;
861
879
  this.serial = def.serial;
862
880
  this.worktree = def.workdir !== "project";
863
881
  this.lifecycles = def.lifecycles;
@@ -950,7 +968,7 @@ export class DefinedAgent extends Agent {
950
968
  for (const option of definitionOptions) {
951
969
  if (option.key === "definition" && !definition)
952
970
  continue;
953
- const flags = (option.key === "definition" ? "-D, " : "") + `--${option.key}` +
971
+ const flags = (option.key === "definition" ? "-D, " : option.key === "model" ? "-M, " : "") + `--${option.key}` +
954
972
  (option.kind !== "flag" ? ` <${option.arg}>` : "");
955
973
  if (option.kind === "list")
956
974
  cmd.option(flags, option.desc, (value, prev) => [...(prev ?? []), value]);
@@ -8,20 +8,22 @@ import { stripVTControlCharacters } from "node:util";
8
8
  of the task (as an agent message can be huge, e.g. the rendering of the whole task plan,
9
9
  which would otherwise be fed back into the next rendering of the task plan) */
10
10
  const TEXT_MAX = 20;
11
+ /* the "--model" option of all tools for the model <model> (none for the default model) */
12
+ const modelArgs = (model) => model !== "" ? ["--model", model] : [];
11
13
  /* the per-tool headless invocation of the rendered prompt <prompt> with access to the
12
- additional directories <add> (the "--add-dir" options), streaming JSONL events,
13
- where risky actions are denied or automatically reviewed instead of being prompted for
14
+ additional directories <add> (the "--add-dir" options) and the model <model>, streaming JSONL
15
+ events, where risky actions are denied or automatically reviewed instead of being prompted for
14
16
  (the variadic "--add-dir" of Claude has to precede the other options to not consume the prompt) */
15
17
  export const toolArgs = {
16
- "claude": (prompt, add) => [
17
- ...add, "-p", "--output-format", "stream-json", "--verbose", "--disallowedTools", "AskUserQuestion",
18
+ "claude": (prompt, add, model) => [
19
+ ...add, ...modelArgs(model), "-p", "--output-format", "stream-json", "--verbose", "--disallowedTools", "AskUserQuestion",
18
20
  "--permission-mode", "auto", "--permission-prompts", "none", prompt
19
21
  ],
20
- "copilot": (prompt, add) => [
21
- ...add, "--allow-all-tools", "--no-ask-user", "--no-color", "--output-format", "json", "-p", prompt
22
+ "copilot": (prompt, add, model) => [
23
+ ...add, ...modelArgs(model), "--allow-all-tools", "--no-ask-user", "--no-color", "--output-format", "json", "-p", prompt
22
24
  ],
23
- "codex": (prompt, add) => [
24
- "exec", ...add, "--json", "--approve-for-me", "--color", "never", prompt
25
+ "codex": (prompt, add, model) => [
26
+ "exec", ...add, ...modelArgs(model), "--json", "--approve-for-me", "--color", "never", prompt
25
27
  ]
26
28
  };
27
29
  /* the per-tool rendering of the invocation of the skill <skill> with the arguments <args>:
@@ -145,6 +145,8 @@ export class Agent {
145
145
  /* the title and the description of the operation of the agent, as announced in its presence (default: none) */
146
146
  presenceTitle = "";
147
147
  presenceDesc = "";
148
+ /* the configured model of the agent tool, as shown in the start log line (default: none) */
149
+ modelSpec = "";
148
150
  /* whether tasks are processed serially only (clamping "--parallel" to 1, and
149
151
  refusing multiple agents of the kind in a fleet, see "ase agent fleet") */
150
152
  serial = false;
@@ -840,7 +842,8 @@ export class Agent {
840
842
  const cli = toolSpecs[tool].cli;
841
843
  const cmd = job.cmd ?? "";
842
844
  const i = cmd.indexOf(" ");
843
- const args = toolArgs[tool](job.prompt ?? skillPrompt(tool, i < 0 ? cmd : cmd.slice(0, i), i < 0 ? "" : cmd.slice(i + 1)), [...new Set(job.dirs ?? [])].filter((dir) => dir !== cwd).flatMap((dir) => ["--add-dir", dir]));
845
+ const model = job.model ?? "";
846
+ const args = toolArgs[tool](job.prompt ?? skillPrompt(tool, i < 0 ? cmd : cmd.slice(0, i), i < 0 ? "" : cmd.slice(i + 1)), [...new Set(job.dirs ?? [])].filter((dir) => dir !== cwd).flatMap((dir) => ["--add-dir", dir]), model);
844
847
  await log("info", `running: ${cli} ${args.map((arg) => arg.includes(" ") ? `"${arg}"` : arg).join(" ")}`);
845
848
  const started = Date.now();
846
849
  const costs = { costToken: null, costUSD: null, cread: 0, cwrite: 0, input: 0, apiMs: null };
@@ -881,7 +884,7 @@ export class Agent {
881
884
  this.presence?.used(usage);
882
885
  /* book the usage onto the task, if the AI usage accounting is enabled ("telemetry.enabled"),
883
886
  (the AI request time falling back to the wall time and an unreported cost to the price
884
- table entry of the tool, as its model is unknown), without any human time and costs,
887
+ table entry of the given model, else of the tool), without any human time and costs,
885
888
  as the headless agent tool runs unattended; the times are booked in rounded minutes,
886
889
  with the (signed) rounding remainder carried over to the next booking onto the task,
887
890
  where a failed booking is only reported */
@@ -894,7 +897,8 @@ export class Agent {
894
897
  if (cost === null) {
895
898
  const cfg = new Config("config", configSchema, this.log);
896
899
  cfg.read();
897
- cost = tokenCost(modelPrice(parsePrices(String(cfg.get("telemetry.prices.ai") ?? "")), tool), tokens);
900
+ const prices = parsePrices(String(cfg.get("telemetry.prices.ai") ?? ""));
901
+ cost = tokenCost(modelPrice(prices, model !== "" && prices.has(model) ? model : tool), tokens);
898
902
  }
899
903
  const carry = this.unbooked.get(taskId) ?? { aiMs: 0, realMs: 0 };
900
904
  const aiMs = carry.aiMs + (costs.apiMs ?? walltimeMs);
@@ -1250,7 +1254,7 @@ export class Agent {
1250
1254
  if (this.repo !== "")
1251
1255
  await this.cloneRepo();
1252
1256
  await this.write("info", this.name, `observing ${this.observing()} ` +
1253
- `(tool: ${tool}, parallel: ${parallel}, timeout: ${this.timeoutSpec}${this.oneShot ? ", one-shot" : ""})`);
1257
+ `(tool: ${tool}${this.modelSpec !== "" ? `, model: ${this.modelSpec}` : ""}, parallel: ${parallel}, timeout: ${this.timeoutSpec}${this.oneShot ? ", one-shot" : ""})`);
1254
1258
  let finished = false;
1255
1259
  let failure = null;
1256
1260
  while (!abort.signal.aborted && !finished) {
@@ -24,7 +24,7 @@ const skillGroups = [
24
24
  ["SPEC", ["ase-spec-lint", "ase-spec-edit", "ase-spec-activate"]],
25
25
  ["CODE", ["ase-code-edit", "ase-code-analyze", "ase-code-insight", "ase-code-explain", "ase-code-lint"]],
26
26
  ["REPO", ["ase-repo-review", "ase-repo-diff", "ase-repo-changelog", "ase-repo-commit",
27
- "ase-repo-dissect", "ase-repo-merge", "ase-repo-resolve"]],
27
+ "ase-repo-dissect", "ase-repo-stage", "ase-repo-merge", "ase-repo-resolve"]],
28
28
  ["SYNC", ["ase-sync-reconcile", "ase-sync-import", "ase-sync-export"]],
29
29
  ["TASK: MANAGE", ["ase-task-list", "ase-task-rename", "ase-task-delete"]],
30
30
  ["TASK: HANDLE", ["ase-task-id", "ase-task-status", "ase-task-assign"]],
@@ -3,7 +3,7 @@
3
3
  ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
5
  */
6
- import { giteaApi } from "gitea-js";
6
+ import { giteaApi, ContentType } from "gitea-js";
7
7
  import { LRUCache } from "lru-cache";
8
8
  import * as TaskFormat from "./ase-task-format.js";
9
9
  import * as TaskIssues from "./ase-task-store-issues.js";
@@ -396,7 +396,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
396
396
  for (const blocker of after) {
397
397
  if (have.has(blocker.number))
398
398
  continue;
399
- await this.call(loc, this.gt.repos.issueCreateIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker)))
399
+ await this.call(loc, this.gt.repos.issueCreateIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker), { type: ContentType.Json }))
400
400
  .catch((err) => {
401
401
  if (statusOf(err) === 404)
402
402
  throw new Error(`Gitea rejected the dependency of issue #${issue.number} on issue #${blocker.number} ` +
@@ -406,7 +406,7 @@ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
406
406
  }
407
407
  for (const blocker of current)
408
408
  if (!want.has(blocker.number))
409
- await found(this.call(loc, this.gt.repos.issueRemoveIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker))));
409
+ await found(this.call(loc, this.gt.repos.issueRemoveIssueDependencies(loc.owner, loc.repo, String(issue.number), meta(blocker), { type: ContentType.Json })));
410
410
  }
411
411
  /* synchronize the comments with the attachments, touching changed ones only
412
412
  (see the common issue tracker parts) */
@@ -19,6 +19,7 @@
19
19
  \[`--lifecycle` *model*\]
20
20
  \[`--serial`\]
21
21
  \[`--unclaimed`\]
22
+ \[`-M`|`--model` *model*\]
22
23
  \[`--params` *name*`=`*value*\]
23
24
  \[`--state-`*item* *state*\]
24
25
  \[`--tag-`*item* *tag*\]
@@ -169,12 +170,12 @@ tags (except the request state tag) and, by default, gets its previous
169
170
  assignee back. An agent consumes the request prompts if its prompt
170
171
  template references `{prompt}` (except within the conditional
171
172
  `{?prompt:`*text*`}` only, which makes the prompt *optional*) or it
172
- has a hook `prepare`, `prompt`, or `finish` (which receive the request
173
- prompt in their *job*), where it then refuses a request with an empty
174
- prompt as failed, else it accepts requests with an empty prompt (which
175
- an agent with an optional prompt still passes on if non-empty), and
176
- announces this to the agent relay, so the task boards accept an empty
177
- prompt for its requests, too.
173
+ has a hook `prepare`, `prompt`, `model`, or `finish` (which receive
174
+ the request prompt in their *job*), where it then refuses a request
175
+ with an empty prompt as failed, else it accepts requests with an empty
176
+ prompt (which an agent with an optional prompt still passes on if
177
+ non-empty), and announces this to the agent relay, so the task boards
178
+ accept an empty prompt for its requests, too.
178
179
 
179
180
  Only the prompt (and `--state-incoming` for `--type phase`) is
180
181
  mandatory, while all other options default to the safe *annotate*
@@ -431,6 +432,16 @@ and `--prompt-skill` with `--prompt-args`, and `--tag-retry` and
431
432
  `passes`, and `--workdir changeset` resp. `target` (as the task can
432
433
  be claimed by another agent meanwhile).
433
434
 
435
+ - \[`-M`|`--model` *model*\]:
436
+ The model of the agent tool, passed to it as `--model` *model*
437
+ verbatim (e.g., an alias like `sonnet` or a full model name), either a
438
+ *model* for all tasks, or comma-separated *size*`=`*model* pairs per
439
+ `Effort:` size of the task (`XS`, `S`, `M`, `L`, `XL`, or `*` for all
440
+ other tasks, e.g., `XS=haiku,S=haiku,M=sonnet,*=opus`), where a task
441
+ without a matching pair gets the default model of the agent tool (the
442
+ default for all tasks). The hook `model` can override it per task (see
443
+ **HOOKS**).
444
+
434
445
  - \[`--params` *name*`=`*value*\]:
435
446
  (repeatable) The value of the parameter *name* declared by the
436
447
  definition (see **PARAMETERS**), where a later value of the same
@@ -986,6 +997,14 @@ invocation:
986
997
  `{ text }`, a plain prompt text, or `undefined` to keep the
987
998
  configured one, where a thrown error fails the processing.
988
999
 
1000
+ - `model(task, model, job, ctx)`:
1001
+ The model of the agent tool for the job, given the configured *model*
1002
+ of `--model` for the `Effort:` size of the task (empty for the default
1003
+ model of the agent tool): a model (empty for the default one), or
1004
+ `undefined` to keep the configured one, where a thrown error fails the
1005
+ processing. This allows choosing the model per task by any means,
1006
+ e.g., by asking a cheap model via `ctx.exec`.
1007
+
989
1008
  - `verdict(task, result, ctx)`:
990
1009
  (after a successful agent tool only) The verdict of the result (an
991
1010
  upper-case word, accepted only if listed by `--verdict-allow`), an
@@ -9,6 +9,7 @@
9
9
  \[`-T`|`--timeout` *duration*\]
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
+ \[`-M`|`--model` *model*\]
12
13
  \[`--`*group*`-`*item* *value* \[...\]\]
13
14
  \[`-f`|`--foreground`\]
14
15
 
@@ -145,6 +146,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
145
146
  (`phase` only). Built-in: not set. Not allowed, as the condenser
146
147
  claims the tasks into its activity state.
147
148
 
149
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
150
+ for all tasks, or comma-separated *size*`=`*model* pairs per
151
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
152
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
153
+ model of the agent tool. Built-in: none.
154
+
148
155
  - \[`--params` *name*`=`*value*\]: The value of a parameter
149
156
  declared by the definition (repeatable). Built-in: none declared.
150
157
 
@@ -11,6 +11,7 @@
11
11
  \[`--agent-relay` *url*\]
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
+ \[`-M`|`--model` *model*\]
14
15
  \[`--`*group*`-`*item* *value* \[...\]\]
15
16
  \[`-f`|`--foreground`\]
16
17
 
@@ -192,6 +193,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
192
193
  (`phase` only). Built-in: not set. Not allowed, as the editor is
193
194
  of type `request`.
194
195
 
196
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
197
+ for all tasks, or comma-separated *size*`=`*model* pairs per
198
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
199
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
200
+ model of the agent tool. Built-in: none.
201
+
195
202
  - \[`--params` *name*`=`*value*\]: The value of a parameter
196
203
  declared by the definition (repeatable). Built-in: none declared.
197
204
 
@@ -10,6 +10,7 @@
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
12
  \[`-m`|`--mode` `phase`|`request`\]
13
+ \[`-M`|`--model` *model*\]
13
14
  \[`--`*group*`-`*item* *value* \[...\]\]
14
15
  \[`-f`|`--foreground`\]
15
16
 
@@ -204,6 +205,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
204
205
  (`phase` only). Built-in: enabled (mode `phase`) resp. not set
205
206
  (mode `request`).
206
207
 
208
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
209
+ for all tasks, or comma-separated *size*`=`*model* pairs per
210
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
211
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
212
+ model of the agent tool. Built-in: none.
213
+
207
214
  - \[`--params` *name*`=`*value*\]: The value of a parameter
208
215
  declared by the definition (repeatable). Built-in: none declared.
209
216
 
@@ -79,7 +79,7 @@ members) or `ase agent fleet stop`.
79
79
  (with all its options commented out, so they fall back to
80
80
  `project.relay.url`, `project.relay.crt`, and `project.relay.key`),
81
81
  the section `default` (with `tool`, `parallel: 1`, and `timeout: 60m`,
82
- plus `repo` and `tempdir` commented out), the edit request agent
82
+ plus `model`, `repo`, and `tempdir` commented out), the edit request agent
83
83
  `editor`, the effort estimation agent `estimator` (mode `phase`,
84
84
  estimating the planned tasks, i.e., the `OPEN` tasks for `solo`, the
85
85
  `PLANNING` tasks for `team`, and the `PLANNED` tasks for `enterprise`,
@@ -201,8 +201,9 @@ value omits an option, and a list repeats an option:
201
201
  `project.relay.url` selecting HTTPS requires both.
202
202
 
203
203
  - `default`:
204
- (optional) The default options of all agents: `tool`, `parallel`,
205
- `timeout`, `repo`, `tempdir`, and `agent-relay` (see `ase-agent`(1)),
204
+ (optional) The default options of all agents: `tool`, `model`,
205
+ `parallel`, `timeout`, `repo`, `tempdir`, and `agent-relay` (see
206
+ `ase-agent`(1) and `ase-agent-adhoc`(1)),
206
207
  each one applied only to the agents supporting it (e.g., `repo` not to
207
208
  `rebooter`), and overridden by the section of an agent. The option
208
209
  `agent-relay` is not allowed together with the section `relay`.
@@ -315,7 +316,8 @@ $ ase agent fleet stop
315
316
  ```
316
317
 
317
318
  A configuration `.ase/fleet.yaml` with the agent relay, the built-in
318
- agents, two editors of different agent tools, an agent of the
319
+ agents (with an implementer using cheaper models for smaller tasks),
320
+ two editors of different agent tools, an agent of the
319
321
  definition file `.ase/agents/reviewer.mts`, and an ad-hoc request
320
322
  agent:
321
323
 
@@ -329,6 +331,7 @@ default:
329
331
  rebooter:
330
332
  implementer:
331
333
  parallel: 2
334
+ model: XS=haiku,S=haiku,*=sonnet
332
335
  editor-claude:
333
336
  kind: editor
334
337
  editor-codex:
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `implement`|`draft`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -260,6 +261,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
260
261
  (`phase` only). Built-in: not set. Not allowed, as the implementer
261
262
  claims the tasks into its activity state.
262
263
 
264
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
265
+ for all tasks, or comma-separated *size*`=`*model* pairs per
266
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
267
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
268
+ model of the agent tool. Built-in: none.
269
+
263
270
  - \[`--params` *name*`=`*value*\]: The value of a parameter
264
271
  declared by the definition (repeatable). Built-in: `effort-wait`
265
272
  (`true` or `false`, default: `true`), whether to wait for the
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `merge`|`rebase`|`squash`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -206,6 +207,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
206
207
  (`phase` only). Built-in: not set. Not allowed, as the integrator
207
208
  claims the tasks into its activity state.
208
209
 
210
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
211
+ for all tasks, or comma-separated *size*`=`*model* pairs per
212
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
213
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
214
+ model of the agent tool. Built-in: none.
215
+
209
216
  - \[`--params` *name*`=`*value*\]: The value of a parameter
210
217
  declared by the definition (repeatable). Built-in: none declared.
211
218
 
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `judge`|`qualify`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -187,6 +188,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
187
188
  (`phase` only). Built-in: not set. Not allowed, as the qualifier
188
189
  claims the tasks into its activity state.
189
190
 
191
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
192
+ for all tasks, or comma-separated *size*`=`*model* pairs per
193
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
194
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
195
+ model of the agent tool. Built-in: none.
196
+
190
197
  - \[`--params` *name*`=`*value*\]: The value of a parameter
191
198
  declared by the definition (repeatable). Built-in: none declared.
192
199
 
@@ -9,6 +9,7 @@
9
9
  \[`-T`|`--timeout` *duration*\]
10
10
  \[`-o`|`--one-shot`\]
11
11
  \[`--agent-relay` *url*\]
12
+ \[`-M`|`--model` *model*\]
12
13
  \[`--`*group*`-`*item* *value* \[...\]\]
13
14
  \[`-f`|`--foreground`\]
14
15
 
@@ -145,6 +146,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
145
146
  (`phase` only). Built-in: not set. Not allowed, as the rebooter
146
147
  claims the tasks into its activity state.
147
148
 
149
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
150
+ for all tasks, or comma-separated *size*`=`*model* pairs per
151
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
152
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
153
+ model of the agent tool. Built-in: none.
154
+
148
155
  - \[`--params` *name*`=`*value*\]: The value of a parameter
149
156
  declared by the definition (repeatable). Built-in: none declared.
150
157
 
@@ -12,6 +12,7 @@
12
12
  \[`-r`|`--repo` *url*\]
13
13
  \[`-d`|`--tempdir` *dir*\]
14
14
  \[`-m`|`--mode` `phase`|`request`\]
15
+ \[`-M`|`--model` *model*\]
15
16
  \[`--`*group*`-`*item* *value* \[...\]\]
16
17
  \[`-f`|`--foreground`\]
17
18
 
@@ -246,6 +247,12 @@ All other options belong to the sub-commands (see **COMMANDS**).
246
247
  - \[`--unclaimed`\]: Process the tasks of the incoming states
247
248
  without claiming them (`phase` only). Built-in: not set.
248
249
 
250
+ - \[`-M`|`--model` *model*\]: The model of the agent tool: a model
251
+ for all tasks, or comma-separated *size*`=`*model* pairs per
252
+ `Effort:` size (`XS`, `S`, `M`, `L`, `XL`, or `*` for all other
253
+ tasks), see `ase-agent-adhoc`(1). Generic default: the default
254
+ model of the agent tool. Built-in: none.
255
+
249
256
  - \[`--params` *name*`=`*value*\]: The value of a parameter
250
257
  declared by the definition (repeatable). Built-in: none declared.
251
258
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "homepage": "https://ase.tools",
7
7
  "repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
8
8
  "bugs": { "url": "https://github.com/rse/ase/issues" },
9
- "version": "2.0.6",
9
+ "version": "2.0.7",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -86,8 +86,8 @@
86
86
  "string-width": "8.3.0",
87
87
  "react": "19.3.1",
88
88
  "chokidar": "6.0.0",
89
- "marked": "18.1.2",
90
- "shiki": "4.5.0",
89
+ "marked": "18.1.3",
90
+ "shiki": "4.6.0",
91
91
  "cli-table3": "0.6.5",
92
92
  "chalk": "6.0.1",
93
93
  "pretty-ms": "10.0.0",
@@ -97,7 +97,7 @@
97
97
  "zod": "4.6.7",
98
98
  "which": "7.0.0",
99
99
  "update-notifier": "7.3.1",
100
- "semver": "7.8.5",
100
+ "semver": "7.8.6",
101
101
  "shell-quote": "1.12.0",
102
102
  "get-stdin": "10.0.0",
103
103
  "proper-lockfile": "4.1.2",
@@ -108,7 +108,7 @@
108
108
  "undici": "8.12.3",
109
109
  "picomatch": "4.0.7",
110
110
  "lru-cache": "11.5.3",
111
- "@rse/specbook": "1.6.1",
111
+ "@rse/specbook": "1.6.2",
112
112
  "markdownlint": "0.42.0",
113
113
  "tail": "2.2.6",
114
114
  "source-code-error": "1.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",