v1.4.8
Fixed
- TaskAnchor no longer leaks
agent.run(message)'s first message into the system prompt every turn.TaskAnchorgains asource: Literal["message", "goal"] = "message"field that gatesto_prompt_block(). Only explicit goal entry points —Agent.run_goal(), CLI/goal, and an active session-log goal — producesource="goal"anchors that render as## Original Task. Ordinaryagent.run(message)producessource="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 plainagent.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 useAgent.run_goal()or setagent.task_anchor = TaskAnchor(..., source="goal")explicitly.
Changed
- Claude
max_tokensresolution (ported from hermes-agent'santhropic_adapter.py):- Default changed from
max_tokens: int = 8192tomax_tokens: Optional[int] = None. WhenNone, 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 / Trueno longer leak to the API and 400 — they fall back to the model ceiling.
- Default changed from
- Claude auto-recovery from "max_tokens too large given prompt":
Claude.invoke()andinvoke_stream()now parse the API error message foravailable_tokens: Nand retry once withmax_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.pywithresolve_anthropic_messages_max_tokens+parse_available_output_tokens_from_error(28 unit tests).
Removed (Breaking)
agentica.model.providersmodule 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 constructsOpenAIChatwith hardcodedbase_url/api_key_env/default_model/context_window.agentica.OpenAILikedeleted. Was a 22-line subclass ofOpenAIChatwhose only behavior was a placeholder-api_keywarning. UseOpenAIChat(id=..., api_key=..., base_url=...)for custom OpenAI-compatible endpoints.agentica.model.openai.likedeleted.AzureOpenAIChatnow subclassesOpenAIChatdirectly.
Changed (Breaking)
- Each
XxxChatis now a thin top-level factory inagentica/__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 (replacesPROVIDER_REGISTRYlookups). agentica.model.defaults.create_default_model()now uses an inline env-var table +PROVIDER_FACTORIES.agentica.gateway.services.model_factory.create_model()dispatches viaPROVIDER_FACTORIESinstead ofcreate_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'sRunResponse.tool_callsand passes them tojudge_goal. Judge prompt now includes aTools used this turn: edit_file, run_pytest(error), lsline so it can distinguish "answered with no tools" from "actually did work". Zero extra LLM calls — names + flags only. New optionaltool_callsparam onGoalManager.evaluate_after_turn()andjudge_goal(). - Tool-stuck auto-pause:
GoalState.consecutive_tool_failurescounts consecutive turns where every tool call errored. AfterMAX_CONSECUTIVE_TOOL_FAILURES = 3the loop auto-pauses withpaused_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_responsenow coerces"yes","true","1","done","y"strings and numeric1todone=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) andgoal_continuation.md(continuation template) now live alongsidesoul.md/heartbeat.mdfor consistency. New moduleagentica/prompts/base/goal.pyexposesGOAL_JUDGE_SYSTEM_PROMPT,GOAL_CONTINUATION_PROMPT_TEMPLATE, andrender_goal_continuation_prompt(). The dynamic per-turn user prompt stays ingoals.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_tokensset explicitly by the caller. The prior in-place mutation helper_ensure_judge_output_budgetwas removed — it was opaque, surprising, and mutated user-owned state. Seedocs/advanced/goals.md"Reasoning judge 的特别注意" for the recipe.
- Tool-call summary fed to judge:
-
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-levelGoalManager(agent._session_log, judge_model=...)+ hand-written driver loop.Agent.get_goal_manager(...)for power users who want to drive turns by hand without touchingSessionLog.Agent.enable_goal_tool()attachesGoalTool.update_goalso the model can self-markcomplete/paused.Agent._session_logandAgent.goal_managerare now formally declared dataclass fields (nogetattrspeculation).- New
agentica.goals.GoalRunResult(status, reason, run_response, goal, turns_used)withresponse_contentconvenience property.
Runner._run_implearly-loads any persisted activeGoalStatefromSessionLogand bindsTaskAnchorto the goal objective — SDK paths now get goal-aware retrieval automatically, not just the CLI.GoalStategainstoken_budget/tokens_used/wall_clock_budget_sec/wall_clock_used_secand a newbudget_limitedstatus (semantically distinct frompaused). 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-markcompleteorpaused(cannot rewrite the objective). CLI auto-attaches on/goalset and detaches on goal termination.RunEventType.goal_set / goal_continuing / goal_completed / goal_pausedevents emitted through an optionalGoalManager.event_callback.
- Ergonomic SDK surface on
-
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_turnnow 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.GoalRunResultfield renamedfinal_response→run_response(typedOptional[RunResponse], was untypedAny) and the convenience propertyfinal_text→response_content, to align with Agentica's existingAgent.run_response/RunResponse.contentterminology.final_*was an LLM-style modifier that didn't add information.agentica.goals.DEFAULT_TURN_BUDGETbumped 20 → 100. Rationale: withtoken_budgetandwall_clock_budget_secnow acting as the real hard caps,turn_budgetis 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 emitDeprecationWarning. They are now treated as stable v1.x public API alongside the sub-module paths. TheDEPRECATED_TOP_LEVELregistry has been removed; the planned v2.0 forced migration is dropped. SearchSerperTool: fix misuse oflogger.warning(..., DeprecationWarning)for theserper_api_keyalias (the extra arg was silently ignored).
Full Changelog: v1.4.7...v1.4.8