Per-session reasoning effort for Claude and Codex - #164
Merged
PleasePrompto merged 1 commit intoJul 12, 2026
Conversation
ryuhaneul
marked this pull request as draft
June 25, 2026 01:05
ryuhaneul
marked this pull request as ready for review
June 25, 2026 02:31
Expose reasoning effort for Claude (--effort) with a /effort command, provider-aware validation (claude low..max, codex low..xhigh with a no-cache fallback), /status display, cron/webhook executor support, model-picker effort sub-selector, and i18n — scoped per-session like model (replaces the closed PleasePrompto#161, which made topic effort changes leak globally and broke codex). Mirror the existing per-session model mechanism exactly: - SessionData gains a reasoning_effort field; sync_session_target/resolve_session store and capture-on-first-use (fixed at creation time, like model). - AgentRequest gains effort_override; CLIService resolves request.effort_override or config default (like model_override or default_model). - flows (_prepare_normal, named, heartbeat), injection, tasks/hub, and the background observer thread the effective per-session effort into every turn. - switch_model applies the global default only from main/DM (if not is_topic); in a topic it writes the topic session's effort, with provider-switch re-validation resetting an unsupported carried-over effort to medium. - NamedSession/BackgroundSubmit/BackgroundTask/TaskSubmit/TaskEntry carry reasoning_effort so named foreground/background runs keep their session effort. - claude_provider (--effort) and codex_provider (-c model_reasoning_effort) both consume the resolved per-turn effort. - /status shows the effective effort (session in a topic, else global default).
ryuhaneul
force-pushed
the
feat/claude-effort-session
branch
from
June 25, 2026 06:12
4b28d8c to
dccc9c9
Compare
This was referenced Jul 9, 2026
PleasePrompto
added a commit
that referenced
this pull request
Jul 12, 2026
- cron one-shot Claude command: gate --effort on the 'default' sentinel like the -p path, instead of silently dropping an explicit 'medium' - extract TaskHub._prepare_request from _run (PLR0915 after the #157/#164 merges pushed _run past 50 statements) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PleasePrompto
added a commit
that referenced
this pull request
Jul 12, 2026
Post-merge fixes threading the Slack transport through main's newer subsystems (#164 per-session effort, #157 append_system_prompt_files, #166 topic-scoped kills, #137/#138 task changes): - Delete fork-only .github/workflows/upstream-release-watch.yml - Remove dead _is_message_addressed() in messenger/slack/bot.py - Fix trailing comma in config.example.json Slack section (invalid JSON) - Drop fork-only "ductor-slack" brand strings from en/wizard.toml - Harden _normalized_transport_list() in __main__.py: honor a non-empty transports list whenever the primary is a member (primary-first, others preserved) instead of collapsing to [primary] on any ordering mismatch, so telegram+matrix multi-transport setups are never silently dropped; add regression tests - named.py create(): keep both reasoning_effort and key params (# noqa: PLR0913, matching the codebase convention e.g. tasks/registry.py) - Update #164 create() test stubs for the new key kwarg - mypy: guard the optional slack_bolt import behind TYPE_CHECKING/else to satisfy strict no-redef - uv lock: add slack-bolt/slack-sdk (also picks up pre-existing lock drift) - ruff format integration-touched files Note: orch._sessions.list_active_for_chat() in slack/bot.py follows the existing private-access convention (telegram/startup.py does the same); no public accessor exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PleasePrompto
added a commit
that referenced
this pull request
Jul 12, 2026
Reconcile #169 with main's #164 (per-session reasoning effort) and #166 (topic-scoped kills): - keep #166's kill_by_chat_topic(chat_id, topic_id) instead of #169's kill_all - keep #164's per-session effort resolution and sync_session_target(reasoning_effort=) - route the topic branch of switch_model through #169's resolve_session_target Review fixes for effort persistence, per-provider bucket staleness, and DRY follow in a subsequent commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PleasePrompto
added a commit
that referenced
this pull request
Jul 12, 2026
…olve_session_target Review fixes on top of the #169 merge: 1. Effort persistence (reconcile with #164): resolve_session_target now takes reasoning_effort and writes it onto the created/retargeted session, so a topic /model that also picks an effort survives to the next message. The topic branch of switch_model passes the resolved effort through. 2. Per-provider staleness: when a session is stale only because the TARGET provider bucket hit max_session_messages, reset ONLY that bucket and keep the other providers' history instead of wiping the whole shell. Session-wide staleness (idle timeout / daily reset) still replaces the full shell. 3. DRY: factor the freshness primitives (_within_message_limit, _time_fresh) shared by resolve_session and resolve_session_target; drop the provider- mutating _is_fresh_for_target hack. 4. switch_model: extract _persist_switch_target/_SwitchContext to stay within complexity limits (no lint suppressions); keep #166's topic-scoped kills. Tests: update the bucket-wipe assertions to per-bucket reset, add a genuine idle-stale whole-shell test, add the codex-fresh + claude-count-stale -> /model claude keeps codex case, and switch the topic mocks to kill_by_chat_topic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PleasePrompto
added a commit
that referenced
this pull request
Jul 12, 2026
…ics) Integrates ryuhaneul's resolve_session_target with review fixes on top: per-session reasoning effort is persisted on the switch target (#164 compatibility), message-count staleness now resets only the target provider bucket instead of wiping all bucket history, and the freshness logic is shared with resolve_session instead of duplicated. Closes #169
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This brings reasoning effort to parity with model selection: effort is resolved per session — each forum topic can run at its own effort, and the main chat / DM sets the global default — for both providers (Claude
--effort, Codex-c model_reasoning_effort).The two providers started from very different places, so this is a larger change than "make a setting per-session":
claude_providernever emitted--effortand ignoredreasoning_effortentirely. This PR adds Claude reasoning effort for the first time — the--effortflag, the/effortcommand + selector, provider-aware validation, and/statusdisplay.config.reasoning_effort(-c model_reasoning_effort), but only as a single service-wide default: there was no command to change it at runtime and no per-conversation scope, so changing effort never actually took effect in a given topic. This PR makes a topic's/effortapply to Codex in that topic.After this change:
Starting point (v0.18.2)
config.reasoning_effortexists as one global value (defaultmedium).codex_providerreads it → service-wide only; no runtime command, no per-topic scope.claude_providerdoes not read effort at all./effortcommand and no per-session effort field (onlymodelis per-session).What changed
claude_providernow emits--effort <value>(skipped only when the value isdefault). First-class reasoning effort for Claude./effortcommand + selector — new command and inline selector inmodel_selector, offering the efforts valid for the active session's provider/model.SessionData.reasoning_effort— new per-session field, captured when a session is created and preserved afterward, mirroring howmodelis captured (preserve_existing_target). A session keeps the effort it was created with instead of drifting when the global default later changes.reasoning_effort_overrideis threaded through every turn path (normal flow, named foreground/background sessions, injection, task hub, webhook observer, cron) and resolved in_make_cliaseffort = request.effort_override or config.reasoning_effort, exactly mirroringmodel_override or default_model. Both providers read the single resolved value, so Claude and Codex alike honor per-session effort.NamedSession,BackgroundSubmit,BackgroundTask,TaskSubmit,TaskEntrynow carryreasoning_effort, so named foreground/background runs keep their session's effort./status— shows the effective effort (the session's value in a topic, otherwise the global default)./modelalignment fix —switch_modelshares its resolution path with per-session effort, and this PR also makes it compare the requested model against the active session's model in the main chat / DM, not only in topics (the main chat previously compared againstconfig.model). This lets/modelre-align a session whose model has drifted fromconfig.model, and realignsconfig.providerto the new model's provider. It only has a visible effect whenconfig.modelhas been changed out of band so that it differs from the active session — e.g. editingconfig.jsondirectly, or the per-agent config hot-reload used to switch a sub-agent's model. Under normal in-chat/modeluse the active session and the global default always move together, so this is a no-op. Flagged explicitly as it is a/modelbehavior change rather than an effort one; it can be split into a separate PR on request.Provider-aware validation
Effort values are validated against the selected provider's supported set — Claude
low/medium/high/xhigh/max; Codex's set is discovered per model, falling back tolow/medium/high/xhigh. An effort that isn't valid for a newly selected model/provider is reset rather than passed through.Tests
New and extended coverage across the model selector, orchestrator core, flows, commands, named-session recovery, tasks, cron execution, the Claude provider, and the param resolver (the model-selector suite alone adds ~830 lines). Full suite is green with no new failures.
Note for merging alongside #156 (interactive REPL provider)
Both this PR and #156 modify the single per-request config builder
CLIService._make_cli, so they conflict there. Effort is resolved once in_make_cli(effort = request.effort_override or config.reasoning_effort) and stored on the provider asself._config.reasoning_effort; every execution path reads it from there — the streaming path (_build_command) and #156's_send_interactivealike. When both land, resolve the_make_cliconflict by keepingreasoning_effort=effort(the per-session value) and adding #156'sinteractive_repl_pool/interactive_enabledfields. Note that_make_cliis shared by both paths, so keeping the globalreasoning_effort=self._config.reasoning_effortinstead would silently drop per-session effort everywhere.A pooled interactive REPL (#156) is spawned once and reused, so it keeps the effort it was spawned with. #156 already drops the pooled REPL on a model change (
switch_model→kill_interactive_repl); an effort change needs the same, otherwise a topic's REPL keeps answering at the old effort until it happens to respawn. This PR adds that drop in the effort handler through a smallgetattr-guarded helper — a no-op on this branch (which targets a base without #156) and an active REPL-respawn once #156 lands. Changing effort in a topic running interactive then respawns its REPL at the new effort immediately.Supersedes #161, which added Claude reasoning effort but applied it globally.