Skip to content

Add per-role effort with per-backend translation - #54

Closed
TON14 wants to merge 4 commits into
AMAP-ML:mainfrom
TON14:feat/per-role-effort
Closed

Add per-role effort with per-backend translation#54
TON14 wants to merge 4 commits into
AMAP-ML:mainfrom
TON14:feat/per-role-effort

Conversation

@TON14

@TON14 TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #38.

The harness discovered models' supported reasoning efforts (model_catalog) but offered no way to set one. This PR adds an effort setting whose value is the model's own depth-of-thought level, passed to the backend verbatim — no cross-backend mapping or substitution.

Usage

Globally, per role, or per run, resolving down the same fallback chain as --<role>-agent / --<role>-model:

[run]
effort = "medium"

[run.roles.manager]
effort = "high"       # a role's own effort outranks [run].effort
lh-harness run --task "..." --effort medium --auditor-effort low

The generated config ships effort = "medium" in [run] and a commented # effort = "medium" in every role section.

Per-backend dials

Each backend accepts exactly the levels it documents and rejects anything else with a clear error naming the valid set:

Backend Dial Accepted levels
Codex -c model_reasoning_effort= minimal, low, medium, high, xhigh
Claude Code CLAUDE_CODE_EFFORT_LEVEL env (outranks a session's /effort) low, medium, high, xhigh, max
DeepSeek Harness reasoningEffort in the dsh profile patch the runner already writes low, high, max
OpenCode --variant the model's variant presets; custom names from opencode.jsonc pass through

BACKEND_EFFORT_LEVELS in types.py is the single source of those sets. Every entry point behaves the same way: the CLI, the config file, and the Web API all accept any well-formed name and the value is judged once, at the role → backend boundary, by the backend it is addressed to. That keeps OpenCode's user-defined variant names usable from every entry point while a typo on the other backends still fails fast with the list of valid levels. The requested level is recorded in every episode's metadata (effort).

Web workbench

  • Run creation: each role card gains an Effort select offering the selected model's own discovered levels (model catalogue reasoning_efforts), falling back to the backend's documented set.
  • Run details: a role-binding strip shows each role's model with its effort as a badge, from the durable owner.json role_configs.

Practical tests

Real full MEA runs to completion (task executed, audited, verified) on three backends:

  • claude_code (--effort low): all 10 spawned episodes carried CLAUDE_CODE_EFFORT_LEVEL=low in their commands and "effort": "low" in metadata; an unsupported level (--effort minimal) fails at agent setup: Claude Code effort must be one of low, medium, high, xhigh, max.
  • deepseek_harness (@deepseek-ai/dsh 0.1.0-rc.7, real DeepSeek API, --effort high): all 12 episodes carried --reasoning-effort high; every on-disk profile patch contains reasoningEffort: "high", and dsh --dump-config confirms it merges into the llm-deepseek plugin config; --effort medium fails at agent setup with DeepSeek Harness effort must be one of low, high, max.
  • opencode (1.18.19, real DeepSeek API, --effort high resolving to a custom variant high = { reasoningEffort: "high" } in the workspace opencode.jsonc): all 10 episodes carried --variant high and "effort": "high" / "opencode_variant": "high" in metadata. A request-capturing endpoint confirmed the variant's options land in the API request body as "reasoning_effort". Note: OpenCode itself silently ignores a variant name the model does not define (no error) — upstream behavior, outside this harness.
  • opencode with a fully custom variant name (--effort deep, defined only in the workspace opencode.jsonc): full MEA run from the CLI to completion — all 14 episodes carried --variant deep and "effort": "deep" in metadata, while --agent claude_code --effort deep still fails at agent setup naming the valid levels.

Docs & tests

  • README.md / README.zh-CN.md: effort row in the [run] table, updated [run.roles.*] fallback chain, and an "Effort levels" subsection with the per-backend table.
  • Python suite on current main with the frontend bundle built: 281 passed, 1 skipped; frontend core suite: 47 passed; tsc/vite build clean.

Closes the gap reported in AMAP-ML#38: the harness discovered models' supported
reasoning efforts but offered no way to set one.

The parameter is named "effort", following Anthropic's umbrella term:
on Claude it is a distinct control from thinking, and on backends whose
only dial is reasoning-specific it drives that dial.

One normalized scale - min, low, med, high, xhigh, max: the union
of the levels the backends document - selectable globally
(--effort / [run] effort) and per role
(--manager-effort, [run.roles.auditor] effort, ...),
resolving down the same fallback chain as --<role>-agent/--<role>-model.
Unlike models, the scale is backend-independent, so it safely crosses an
explicit agent boundary.

Each backend translates a level into its documented dial, and a level a
backend lacks maps to its nearest supported one:

- codex: `-c model_reasoning_effort="<level>"`
  (min and med spell out to minimal and medium; max -> xhigh);
- claude_code: the CLAUDE_CODE_EFFORT_LEVEL environment variable
  (min -> low, med spells out to medium, rest native; the env var also
  outranks a session /effort choice, which is the isolation a harness
  role needs);
- opencode: `--variant <name>` - OpenAI-style models ship variants named
  after the effort levels themselves (min/med spell out to
  minimal/medium), other providers ship a subset and
  accept custom variants from opencode.jsonc, so the requested name
  passes through as-is;
- deepseek_harness: `reasoningEffort` in the dsh profile patch the
  runner already writes (low|high|max natively; DeepSeek's own API docs
  map medium and xhigh onto high; min -> low).

Every episode's metadata records both effort (requested) and
effort_effective (after the backend mapping) so the requested
depth and any substitution stay auditable. The Web API accepts
roles.<role>.effort on run creation, validates it at the
boundary, and forwards it to the worker; the value rides along in
owner.json role_configs for resume.

tests: config keys (valid/invalid, per-role), CLI fallback chain, the
full translation table per backend including nearest-level substitution,
the dsh runner patch contents, opencode wiring, episode metadata
recording, supervisor validation and worker-command forwarding.
@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@lerogo Saw you self-assigned #38 — this PR implements it (per-role effort with per-backend translation, details and the mapping table in the description). Happy to adjust anything to fit your plans for the issue.

@lerogo 看到你认领了 #38——这个 PR 实现了该功能(按角色的 effort 配置 + 各后端的等级翻译,映射表见描述)。如与你对该 issue 的规划有出入,我很乐意按需调整。

The other half of AMAP-ML#38: the web UI now both sets and shows the effort
level.

- Run creation: each role card gets an Effort select next to Harness and
  Model, fed by the new `defaults.effort_levels` list in /api/meta
  (falls back to the built-in scale on older servers). "Default" leaves
  the choice to the worker's config chain.
- Run details: a role-binding strip above the tabs shows each public
  role's model with its effort as a badge, sourced from the run's
  durable owner role_configs.
- The snapshot provenance projection (_safe_role_configs) passes a
  valid `effort` through instead of stripping it, so the strip works
  for historical runs too; invalid values are still dropped.

tests: provenance keeps a valid role effort and drops an unknown one;
frontend core suite and tsc/vite build pass; verified visually in the
running workbench (picker in all three role cards, chips on a created
run).
@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a second commit covering the other half of #38 — the web UI now sets and shows effort:

  • Run creation: each role card gains an Effort select (fed by defaults.effort_levels from /api/meta; Default defers to the worker's config chain).
  • Run details: a role-binding strip shows each role's model with its effort as a badge, sourced from the durable owner.json role_configs (snapshot provenance now passes a valid effort through).

Verified in the running workbench (picker renders in all three role cards; a run created with manager=high / executor=med / auditor=low shows the chips). Frontend tsc/vite build and the core suite (47) pass; python suite: 275 passed, 1 skipped.

第二个 commit 补上 #38 的另一半——Web 界面现在既能设置也能展示 effort:创建任务时每个角色卡新增 Effort 下拉框;任务详情顶部新增角色配置条,以徽标形式显示各角色的 effort(数据来自 owner.jsonrole_configs)。已在运行中的工作台实际验证,前端构建与测试、Python 测试全部通过。

@lerogo

lerogo commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

No need to do mapping, just use the corresponding depth of thought of the model directly. Could you help modify it and then test it more in practical use? Thank you very much.

Review feedback on AMAP-ML#54: no normalized scale, no nearest-level
substitution - a role's effort is the model's own depth-of-thought
value, passed through untouched.

- Each adapter accepts exactly the levels its backend documents
  (codex: minimal|low|medium|high|xhigh; claude_code:
  low|medium|high|xhigh|max; deepseek_harness: low|high|max) and
  rejects anything else with a clear error naming the valid set.
  OpenCode keeps free-form variant names, since variants are per-model
  presets that may be user-defined in opencode.jsonc.
- BACKEND_EFFORT_LEVELS in types.py is the single source of those sets;
  the CLI/config keep only a union-level typo guard because the backend
  is not known at parse time - the adapter (or, for Web runs, the
  supervisor boundary, which validates against the role's own backend)
  makes the final call.
- effort_effective is gone from episode metadata: with no mapping the
  recorded `effort` is exactly what the backend received.
- The web workbench effort picker now offers the selected model's own
  discovered levels (model catalogue `reasoning_efforts`), falling back
  to the backend's documented set; changing the backend resets the
  choice since the sets differ.

Practically tested with a real claude_code run (--effort low,
3-role MEA loop to completion): all 10 episodes carry
CLAUDE_CODE_EFFORT_LEVEL=low in the spawned commands and
"effort": "low" in metadata; an unsupported level (--effort minimal on
claude_code) fails at agent setup with the level list in the message.
@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@lerogo Done — mapping removed in bc6f116; the effort value is now the model's own depth-of-thought level, passed to the backend verbatim. Each backend accepts exactly the levels it documents and rejects anything else with a clear error naming the valid set (BACKEND_EFFORT_LEVELS is the single source; the Web API validates against the role's own backend, and the web picker offers the selected model's discovered levels from the catalogue).

Practically tested with a real claude_code run (--effort low, full Manager→Executor→Auditor loop to completion): all 10 spawned episodes carried CLAUDE_CODE_EFFORT_LEVEL=low in their commands and "effort": "low" in metadata; --effort minimal on claude_code fails at agent setup with effort must be one of low, medium, high, xhigh, max. Suites: python 281 passed, frontend 47 passed. PR description updated.

@lerogo 已按建议修改(bc6f116):去掉了映射,effort 取值即模型自身的思考深度等级,原样传给后端;每个后端只接受自己文档中的等级,其余直接报错并列出可用值(BACKEND_EFFORT_LEVELS 为唯一来源,Web API 按角色所选后端校验,Web 选择器展示模型目录中检测到的等级)。

已实测:用真实 claude_code 跑通完整 Manager→Executor→Auditor 循环(--effort low),全部 10 个 episode 的命令都带 CLAUDE_CODE_EFFORT_LEVEL=low、metadata 记录 "effort": "low";不支持的等级(如 minimal)在启动阶段即报错。Python 281 通过,前端 47 通过。PR 描述已更新。

@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@lerogo Following up on "test it more in practical use" — I have now also run the two remaining backends end-to-end against the real DeepSeek API, on their current releases:

DeepSeek Harness (@deepseek-ai/dsh 0.1.0-rc.7, --effort high): full 3-role MEA run to completion, task executed and passed the audit. All 12 episodes carried --reasoning-effort high in their commands and "effort": "high" in metadata; every on-disk profile patch contains reasoningEffort: "high", and dsh --dump-config confirms it merges into the llm-deepseek plugin config. An unsupported level (--effort medium) fails at agent setup: DeepSeek Harness effort must be one of low, high, max.

OpenCode (1.18.19, --effort high resolving to a custom variant high = { reasoningEffort: "high" } in the workspace opencode.jsonc): full MEA run to completion, task executed and passed the audit. All 10 episodes carried --variant high in their commands and "effort": "high" / "opencode_variant": "high" in metadata. I also pointed OpenCode at a request-capturing endpoint to confirm the variant's options actually reach the provider: the request body carries "reasoning_effort" with the variant's value. One upstream note: OpenCode silently ignores a variant name the model does not define (no error) — that is OpenCode's own behavior, not something this harness can catch.

With the earlier claude_code run, three backends are now verified end-to-end in practice (claude_code, deepseek_harness, opencode), each receiving the effort value verbatim and failing fast on levels it does not document; codex uses the same pass-through path and is covered by the unit suite.


@lerogo 继续跟进"在实际使用中多做测试"——我已在真实 DeepSeek API 上,用当前版本把其余两个后端也做了端到端测试:

DeepSeek Harness@deepseek-ai/dsh 0.1.0-rc.7,--effort high):完整的三角色 MEA 循环跑通,任务执行并通过审计。全部 12 个 episode 的命令都带有 --reasoning-effort high,元数据中为 "effort": "high";磁盘上的每个 profile patch 都包含 reasoningEffort: "high"dsh --dump-config 确认它合并进了 llm-deepseek 插件配置。不支持的级别(--effort medium)在 agent 启动阶段即失败:DeepSeek Harness effort must be one of low, high, max

OpenCode(1.18.19,--effort high 解析到工作区 opencode.jsonc 中的自定义 variant high = { reasoningEffort: "high" }):完整 MEA 循环跑通,任务执行并通过审计。全部 10 个 episode 的命令都带有 --variant high,元数据为 "effort": "high" / "opencode_variant": "high"。我还让 OpenCode 指向一个记录请求的端点,确认 variant 的选项确实到达了 provider:请求体中带有 "reasoning_effort" 及对应的值。一个上游备注:对于模型未定义的 variant 名称,OpenCode 会静默忽略(不报错)——这是 OpenCode 自身的行为,harness 无法拦截。

加上此前的 claude_code 运行,已有三个后端完成端到端实测(claude_code、deepseek_harness、opencode):effort 值均原样透传,并对各自文档之外的级别快速报错;codex 走同一条透传路径,由单元测试覆盖。

The CLI and config validated effort against a hard-coded union of the
built-in backends' levels, while the web supervisor validates against
the role's own backend and accepts any well-formed name for OpenCode.
That split rejected legitimate OpenCode variant names (user-defined in
opencode.jsonc) from the command line and the config file only.

- Drop the argparse `choices` guard from --effort/--<role>-effort and
  the union check from config.toml parsing; both now accept any
  non-empty name up to 64 characters without NUL - the same shape
  check the supervisor applies.
- Remove EFFORT_CHOICES from types.py: BACKEND_EFFORT_LEVELS is the
  only remaining source, consulted by the adapters and the supervisor
  at the role->backend boundary, so CLI, config, and web now behave
  identically - the value travels untouched to the role's backend and
  is judged only there.
- README (en/zh-CN): note that any well-formed name is accepted at the
  boundary and validated by the backend it is addressed to.

Practically tested: a custom OpenCode variant (--effort deep, defined
in the workspace opencode.jsonc) runs from the CLI through a full MEA
loop to completion - all 14 episodes carry --variant deep and
"effort": "deep" - while --agent claude_code --effort deep still fails
at agent setup naming the valid levels.
@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

One more refinement surfaced by the practical testing, pushed as 46e1de0: the CLI and config file used to validate effort against a hard-coded union of the built-in backends' levels, while the Web API accepts any well-formed name and validates against the role's own backend. That split rejected legitimate user-defined OpenCode variant names from the command line and config.toml only.

All three entry points now behave identically: the value travels untouched to the role → backend boundary and is judged only there, by the backend it is addressed to. EFFORT_CHOICES is gone — BACKEND_EFFORT_LEVELS is the single remaining source. Practically verified: --effort deep with deep defined only in the workspace opencode.jsonc runs a full MEA loop from the CLI to completion (14 episodes, each carrying --variant deep), while --agent claude_code --effort deep still fails at agent setup with Claude Code effort must be one of low, medium, high, xhigh, max. Suite: 282 passed, 1 skipped.


实际测试还暴露出一处需要打磨的地方,已随 46e1de0 推送:此前 CLI 和配置文件用内置后端等级的硬编码并集来校验 effort,而 Web API 接受任何格式合法的名称、按角色自己的后端校验。这种不一致导致用户在 opencode.jsonc 中自定义的合法 variant 名称只在命令行和 config.toml 入口被拒绝。

现在三个入口行为完全一致:值原样传递到"角色 → 后端"边界,只在那里由目标后端做判断。EFFORT_CHOICES 已删除——BACKEND_EFFORT_LEVELS 是唯一保留的数据源。实测验证:deep 仅定义于工作区 opencode.jsonc--effort deep 从 CLI 跑完整个 MEA 循环(14 个 episode,命令均带 --variant deep);而 --agent claude_code --effort deep 仍在 agent 启动阶段报错:Claude Code effort must be one of low, medium, high, xhigh, max。测试套件:282 通过,1 跳过。

@lerogo

lerogo commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Thank you for your PR. I referred to your code to implement this part, but since I have made many local changes, this PR will not be merged. Thank you very much for your participation. Additionally, we have released a new version. Could you help us test it? #56

@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Totally understood — the feature landing in v0.1.7 is what matters, and I'm glad the PR could serve as a reference. Closing this one. I'll take you up on testing the new release and will report anything I find on #56's features. Thanks for the reviews along the way!


完全理解——功能在 v0.1.7 落地才是最重要的,很高兴这个 PR 能作为参考。关闭此 PR。我会接下新版本的测试,#56 相关功能如有发现会及时反馈。感谢一路以来的 review!

@TON14

TON14 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@lerogo Done — I tested the v0.1.7 reasoning-effort feature end-to-end on claude_code, opencode, and deepseek_harness against real providers; the full report (what works, two observations, and an offer to cover dsh's reasoningEffort) is in #56: #56 (comment)

@lerogo 已完成——我在 claude_code、opencode 和 deepseek_harness 三个后端上对接真实服务,端到端测试了 v0.1.7 的思考强度功能;完整报告(正常部分、两个观察、以及为 dsh 补上 reasoningEffort 的提议)见 #56#56 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lh-harness 里思考强度如何设置

2 participants