Skip to content

v1.4.8

Choose a tag to compare

@shibing624 shibing624 released this 07 Jul 12:02
· 329 commits to main since this release

Fixed

  • TaskAnchor no longer leaks agent.run(message)'s first message into the system prompt every turn. TaskAnchor gains a source: Literal["message", "goal"] = "message" field that gates to_prompt_block(). Only explicit goal entry points — Agent.run_goal(), CLI /goal, and an active session-log goal — produce source="goal" anchors that render as ## Original Task. Ordinary agent.run(message) produces source="message" anchors that are still used as the retrieval query but stay out of the system prompt. This restores pre-1.4.0 prompt behavior for plain agent.run() callers (e.g. private chat seed, workflow handoff, session resume) where the "first message" is a transcript / replay / dump and pinning it system-wide was a bug. Callers that need long-task drift defense should use Agent.run_goal() or set agent.task_anchor = TaskAnchor(..., source="goal") explicitly.

Changed

  • Claude max_tokens resolution (ported from hermes-agent's anthropic_adapter.py):
    • Default changed from max_tokens: int = 8192 to max_tokens: Optional[int] = None. When None, a per-model output ceiling is looked up from _ANTHROPIC_OUTPUT_LIMITS (Opus 4.6/4.7 → 128K, Sonnet 4.5/4.6 → 64K, 3.5 Sonnet → 8192, etc.). Previously every model was capped at 8K which starved thinking-enabled models (thinking tokens count toward the limit).
    • Resolved cap is clamped to max(context_window - 1, 1) for small custom endpoints whose context window is smaller than the model's native output ceiling. No-op for full-size native models.
    • Positive-finite guard rejects locally: max_tokens=0 / -1 / 0.5 / NaN / True no longer leak to the API and 400 — they fall back to the model ceiling.
  • Claude auto-recovery from "max_tokens too large given prompt": Claude.invoke() and invoke_stream() now parse the API error message for available_tokens: N and retry once with max_tokens = N - 64 (safety margin). Prompt-too-long errors are NOT touched — that path still flows through _learn_context_limit_from_error. New module: agentica/model/anthropic/_max_tokens.py with resolve_anthropic_messages_max_tokens + parse_available_output_tokens_from_error (28 unit tests).

Removed (Breaking)

  • agentica.model.providers module deleted (ProviderConfig, create_provider, list_providers, register_provider, PROVIDER_REGISTRY). The registry indirection had a single concrete output (OpenAILike(**config)) so every OpenAI-compatible factory now directly constructs OpenAIChat with hardcoded base_url / api_key_env / default_model / context_window.
  • agentica.OpenAILike deleted. Was a 22-line subclass of OpenAIChat whose only behavior was a placeholder-api_key warning. Use OpenAIChat(id=..., api_key=..., base_url=...) for custom OpenAI-compatible endpoints.
  • agentica.model.openai.like deleted. AzureOpenAIChat now subclasses OpenAIChat directly.

Changed (Breaking)

  • Each XxxChat is now a thin top-level factory in agentica/__init__.py (e.g. DeepSeekChat, ZhipuAIChat, QwenChat, ArkChat, …). Added 5 previously-only-by-slug factories: NvidiaChat, SambanovaChat, OpenRouterChat, FireworksChat, InternLMChat.
  • New agentica.PROVIDER_FACTORIES: dict[str, Callable] exposes slug → factory dispatch for gateway / multi-tenant code (replaces PROVIDER_REGISTRY lookups).
  • agentica.model.defaults.create_default_model() now uses an inline env-var table + PROVIDER_FACTORIES.
  • agentica.gateway.services.model_factory.create_model() dispatches via PROVIDER_FACTORIES instead of create_provider.

Migration

# Before
from agentica.model.providers import create_provider
model = create_provider("deepseek", id="deepseek-v4-pro", api_key="sk-...")

# After
from agentica import DeepSeekChat
model = DeepSeekChat(id="deepseek-v4-pro", api_key="sk-...")
# Custom OpenAI-compatible endpoint
# Before:
from agentica import OpenAILike
model = OpenAILike(id="my-model", api_key="sk-...", base_url="https://...")
# After:
from agentica import OpenAIChat
model = OpenAIChat(id="my-model", api_key="sk-...", base_url="https://...")

Added

  • Standing-goal loop judge hardening (hermes-validated + beyond):

    • Tool-call summary fed to judge: Agent.run_goal() extracts (tool_name, is_error) pairs from each turn's RunResponse.tool_calls and passes them to judge_goal. Judge prompt now includes a Tools used this turn: edit_file, run_pytest(error), ls line so it can distinguish "answered with no tools" from "actually did work". Zero extra LLM calls — names + flags only. New optional tool_calls param on GoalManager.evaluate_after_turn() and judge_goal().
    • Tool-stuck auto-pause: GoalState.consecutive_tool_failures counts consecutive turns where every tool call errored. After MAX_CONSECUTIVE_TOOL_FAILURES = 3 the loop auto-pauses with paused_reason="tool-stuck". Any successful tool call resets; turns with no tool calls do NOT reset (a "just thinking while stuck" turn shouldn't get a free pass).
    • Subgoal "find evidence" rule: when subgoals are present, judge prompt now demands concrete evidence for each criterion (file excerpt / command output / result value) and explicitly rejects vague summaries like "all requirements met". Borrowed from hermes-agent's hard-won production prompt.
    • JSON parsing accepts weak-model output: _parse_judge_response now coerces "yes", "true", "1", "done", "y" strings and numeric 1 to done=true (small chat models and some reasoning models don't always emit JSON booleans).
    • Static prompts lifted to agentica/prompts/base/md/: goal_judge.md (judge system prompt) and goal_continuation.md (continuation template) now live alongside soul.md / heartbeat.md for consistency. New module agentica/prompts/base/goal.py exposes GOAL_JUDGE_SYSTEM_PROMPT, GOAL_CONTINUATION_PROMPT_TEMPLATE, and render_goal_continuation_prompt(). The dynamic per-turn user prompt stays in goals.py (it's conditional logic, not a static template).
    • Reasoning-judge guidance documented, no magic in code: judge models that need a large output budget (DeepSeek-Reasoner, o-series, qwq) must be constructed with max_completion_tokens set explicitly by the caller. The prior in-place mutation helper _ensure_judge_output_budget was removed — it was opaque, surprising, and mutated user-owned state. See docs/advanced/goals.md "Reasoning judge 的特别注意" for the recipe.
  • Standing-goal loop P0 + P1 (S + A tiers):

    • Ergonomic SDK surface on Agent:
      • Agent.run_goal(objective, *, turn_budget=..., token_budget=..., wall_clock_budget_sec=..., attach_goal_tool=True, event_callback=...) -> GoalRunResult — one-liner that drives the whole loop. Replaces the previous low-level GoalManager(agent._session_log, judge_model=...) + hand-written driver loop.
      • Agent.get_goal_manager(...) for power users who want to drive turns by hand without touching SessionLog.
      • Agent.enable_goal_tool() attaches GoalTool.update_goal so the model can self-mark complete / paused.
      • Agent._session_log and Agent.goal_manager are now formally declared dataclass fields (no getattr speculation).
      • New agentica.goals.GoalRunResult(status, reason, run_response, goal, turns_used) with response_content convenience property.
    • Runner._run_impl early-loads any persisted active GoalState from SessionLog and binds TaskAnchor to the goal objective — SDK paths now get goal-aware retrieval automatically, not just the CLI.
    • GoalState gains token_budget / tokens_used / wall_clock_budget_sec / wall_clock_used_sec and a new budget_limited status (semantically distinct from paused). Hard budget caps take precedence over tool short-circuit and judge.
    • agentica.tools.goal_tool.GoalTool.update_goal(status, reason): receive-only model tool letting the agent self-mark complete or paused (cannot rewrite the objective). CLI auto-attaches on /goal set and detaches on goal termination.
    • RunEventType.goal_set / goal_continuing / goal_completed / goal_paused events emitted through an optional GoalManager.event_callback.
  • New example examples/cli/03_goal_loop_demo.py: 4-scenario SDK tutorial (run_goal() one-liner / budgets / event_callback / manual loop) against a real LLM.

Changed

  • GoalManager.evaluate_after_turn now charges turn counters (turns_used, tokens_used, wall_clock_used_sec) BEFORE any short-circuit branch so per-turn cost is always tracked, even when a tool ends the loop. Decision priority is now: budget cap > tool signal > judge.
  • GoalRunResult field renamed final_responserun_response (typed Optional[RunResponse], was untyped Any) and the convenience property final_textresponse_content, to align with Agentica's existing Agent.run_response / RunResponse.content terminology. final_* was an LLM-style modifier that didn't add information.
  • agentica.goals.DEFAULT_TURN_BUDGET bumped 20 → 100. Rationale: with token_budget and wall_clock_budget_sec now acting as the real hard caps, turn_budget is the safety-net against runaway loops; aggressive values (20–50) tripped accidentally on real coding workflows. Token / wall-clock budgets still bound actual cost, so a loose default is safe.

Changed

  • Top-level lazy imports (e.g. from agentica import Knowledge, Claude, SqliteDb, Swarm, ...) no longer emit DeprecationWarning. They are now treated as stable v1.x public API alongside the sub-module paths. The DEPRECATED_TOP_LEVEL registry has been removed; the planned v2.0 forced migration is dropped.
  • SearchSerperTool: fix misuse of logger.warning(..., DeprecationWarning) for the serper_api_key alias (the extra arg was silently ignored).

Full Changelog: v1.4.7...v1.4.8