Skip to content

First-class reasoning effort: translate graded effort to each provider's native control and persist it per session #4452

Description

@MervinPraison

Summary

PraisonAI exposes a graded reasoning-effort control (praisonai run --thinking off|minimal|low|medium|high, and the Agent.thinking_budget property), but it is currently a dormant, provider-blind, non-persistent knob:

  1. It is mapped only to an extended-thinking token budget (an Anthropic/Gemini-style mechanism), and that budget is not applied to the request pipeline at all.
  2. No provider's native reasoning-effort parameter (reasoning_effort for OpenAI o-series / GPT‑5 / xAI reasoning models) is ever emitted by core.
  3. The chosen effort is a per-invocation override and is not persisted on the session, so a resumed session (--continue) does not restore it — even though the model is restored on resume (Resuming a session should restore the model it was created with #3685).

Reasoning effort is one of the most important cost/quality dials for CLI-first coding workflows. Today, setting it has little or no effect on the actual provider request for a large class of models, and it does not travel with a session.

Current behaviour

  • The CLI flag is graded and threaded onto the agent:
    • src/praisonai-code/praisonai_code/cli/commands/run.py:1124--thinking (off, minimal, low, medium, high), resolved via thinking_to_budget(...) and set with agent.thinking_budget = thinking_budget (run.py:1984). A code comment (run.py:1878) states it is "a per-invocation override".
  • The grading is mapped only to a token budget:
    • src/praisonai-code/praisonai_code/cli/features/thinking.pyTHINKING_BUDGET_MAP = {off: None, minimal: 2000, low: 4000, medium: 8000, high: 16000}.
  • The budget is never applied by the core request pipeline. The core module says so explicitly:
    • src/praisonai-agents/praisonaiagents/thinking/__init__.py"agent.thinking_budget stores this object on the agent as a hint, but the core Agent request pipeline does not yet apply it automatically."
    • agent/agent.py stores _thinking_budget (property at ~agent.py:3195) but nothing in llm/ consumes it.
  • No native reasoning-effort parameter is emitted anywhere in core: a repository-wide search for reasoning_effort under src/praisonai-agents/praisonaiagents/ returns zero matches.
  • The effort is not persisted with the session: src/praisonai-agents/praisonaiagents/session/store.py records the model on the session (restored on resume per Resuming a session should restore the model it was created with #3685) but has no reasoning-effort/variant field.

Net effect: on OpenAI/xAI reasoning models --thinking high is effectively a no-op at the provider layer, and on any model the setting is forgotten when a session is resumed.

Desired behaviour

  • A single, graded, provider-portable reasoning-effort setting that core translates to each provider's native mechanism:
    • OpenAI o-series / GPT‑5 / xAI → native reasoning_effort (minimal|low|medium|high).
    • Anthropic / Gemini extended-thinking models → the existing thinking token budget.
    • Models without a reasoning control → silently ignored (no error, backward-compatible).
  • The setting is persisted on the session and restored on --continue/--session, exactly as the model already is (Resuming a session should restore the model it was created with #3685), so effort is sticky across a multi-turn coding session while remaining overridable per invocation.
  • Uniform 3-way surface: Agent(reasoning_effort="high") (Python), agent: { reasoning_effort: high } (YAML), --thinking/--effort high (CLI) — all resolving to the same core control.

Layer placement

  • Primary layer: core (praisonaiagents)
  • Why not core → n/a (this is core). The translation from a unified effort level to each provider's native request parameter belongs in the LLM request pipeline (llm/llm.py), and persistence belongs in the session store (session/store.py) — both core.
  • Why not wrapper: the wrapper already has the graded CLI flag; it cannot make the setting take effect at the provider layer or persist it in the session record without core support. Wrapper is a secondary touch only.
  • Why not tools: reasoning effort is a property of the model request, not an agent-callable integration.
  • Why not plugins: this is core request-shaping and lifecycle state, not an optional lifecycle guardrail/policy.
  • Secondary touch (optional): wrapper (praisonai-code) — thread --thinking/--effort into the persisted session field and restore it on resume; surface the effort in --output json.
  • 3-way surface (CLI + YAML + Python): yes

Proposed approach

  1. Introduce a normalised reasoning_effort on the LLM request path (reuse the existing off|minimal|low|medium|high levels). Keep thinking_budget as a backward-compatible alias that resolves into the same normalised effort.
  2. In llm/llm.py, when building provider parameters, resolve effort against the detected provider family (the code already classifies providers, e.g. _is_anthropic_model() / provider detection around llm.py:615): emit native reasoning_effort for OpenAI/xAI reasoning models, an extended-thinking thinking budget for Anthropic/Gemini, and nothing for non-reasoning models.
  3. Persist the resolved effort alongside the model on the session record (session/store.py), and restore it on resume in the wrapper next to the existing model-restore logic (run.py ~1266).
  4. Expose the control identically across Python/YAML/CLI; keep off and unset as no-ops with zero overhead when unused (lazy, per existing thinking/__init__.py design notes).

Resolution sketch

  • llm/llm.py: add a small _resolve_reasoning_params(effort, model) helper returning the provider-appropriate kwargs; call it where request params are assembled on both the custom and OpenAI-native paths.
  • agent/agent.py: accept reasoning_effort= (string level) in addition to the existing thinking_budget int; normalise both to one internal value.
  • session/store.py: persist reasoning_effort in the session/model record; run.py: restore it on --continue/--session unless --thinking/--effort overrides.
  • Docs/tests: a real agentic test that Agent(model="<openai-reasoning-model>", reasoning_effort="high").start("real task") sends reasoning_effort=high to the provider, and that resume restores the effort.

Severity

Medium-High — reasoning effort is a primary cost/quality lever for coding agents; today it is effectively inert at the provider layer for a major model family and is not sticky across a resumed session.

Validation

  • thinking/__init__.py documents that thinking_budget is not applied by the request pipeline; grep reasoning_effort under praisonaiagents/ returns zero matches, confirming no native reasoning parameter is emitted.
  • cli/features/thinking.py:THINKING_BUDGET_MAP shows effort is collapsed to a token budget only.
  • run.py treats --thinking as a per-invocation override; session/store.py persists the model but not effort, so resume cannot restore it.
  • The existing model-restore-on-resume behaviour (Resuming a session should restore the model it was created with #3685) is the precedent to mirror for persistence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingclaudeAuto-trigger Claude analysisdocumentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions