0.3.38
[0.3.38] - 2026-07-24
MCP resources and skills reach the model, the CLI talks to any
OpenAI-compatible local endpoint, and the todo list stops invalidating the
provider's prompt cache on every mutation.
Added
- MCP resources and
skill://skills are now visible to the model (#180, closes #178) (pydantic_deep/mcp/resources.py,pydantic_deep/mcp/registry.py,pydantic_deep/mcp/config.py,pydantic_deep/mcp/loader.py). pydantic-ai surfaces only an MCP server's tools, so a server publishing skills through FastMCP'sSkillsDirectoryProviderwas unreachable from a delegated agent unless the app hand-rolled a bridge. Setinclude_resources/include_skillson anMCPServerConfig(or in themcpServersJSON) andMCPRegistry.build_active()attaches a second toolset withlist_mcp_resources/read_mcp_resourceand, for skills,list_mcp_skills/load_mcp_skill. It binds to the sameMCPToolset, so tools and resources share one connection and one resource cache. Both flags default to off.- The resource tools are prefixed per server (the config's
tool_prefix, else its name), so several servers can expose their resources in the same run — without a prefix their identical tool names collide and pydantic-ai fails the run. - With
include_skills, the server's skills are listed in the system prompt, so the model knows the guidance exists before it starts calling the server's operational tools. - An unreachable server returns the failure as tool text rather than raising out of
agent.run(), matching howmake_resilientdegrades the tools toolset. - New export:
create_mcp_resources_toolset(alsoMCPResourceProvider,SKILL_URI_SCHEME,SKILL_DOC_NAMEfrompydantic_deep.mcp).
- The resource tools are prefixed per server (the config's
- OpenAI-compatible local endpoints in the CLI (#176, closes #175) (
apps/cli/config.py,apps/cli/providers.py,apps/cli/agent.py,apps/cli/screens/onboarding.py,apps/cli/commands.py,docs/cli/settings.md). Only Ollama worked from the CLI, because it has a provider prefix pydantic-ai recognises; llama.cpp, LM Studio, vLLM and text-generation-webui need anOpenAIChatModelbuilt with a custombase_url, which a plain model string can't carry.CliConfiggainsbase_urlandlocal_api_key, anopenai-compatibleprovider entry is added to the picker, and theopenai-compatible:<name>sentinel is resolved into a real model pointed at that endpoint./providerroutes the choice to a newLocalEndpointModal(base URL + model name + optional key), and keyless local providers report as ready in onboarding. Contributed by @OchnikBartek, requested by @nenoro.
Fixed
-
The
openai-compatible:sentinel is now resolved everywhere, not just increate_cli_agent(#176) (apps/cli/model_resolve.py,apps/cli/agent.py,apps/cli/reminder.py,apps/cli/goal.py,apps/cli/commands.py,apps/cli/credentials.py,apps/cli/screens/onboarding.py). The sentinel is a CLI-only marker that pydantic-ai'sinfer_modelrejects withValueError: Unknown provider: openai-compatible, and only the primary model was being converted — sofallback_model,/remind llm(which degraded to the zero-cost generator with a log warning),/goal(which answered "Evaluator error; continuing." every turn and never completed) and/improveall received the raw string. The conversion moves intoresolve_cli_model, which every site now goes through; plain strings and already-builtModelinstances pass through untouched, and the/improvechain takesstr | Model.OPENAI_COMPATIBLE_API_KEYis registered incredentials.py./providerwrote it, butkeys list,keys setand/keysall iterateCREDENTIALS, so it was invisible and unfixable from the UI. It deliberately has noprovider_id, so it stays out of the key-first first-run flow where picking it would set a model with no endpoint.LocalEndpointModalrejects a scheme-less base URL.localhost:8080/v1built a provider fine and only failed on the first message, with an httpx protocol error that reads like an agent bug.- Known gap:
/forkbranch models and the merge judge model can still be set to the sentinel from the model picker.MergeStrategy.judge_modelandBranchSpec.modelare strings by design (vote-model detection, cache keys), so that fix belongs in its own change.
-
Todo mutations no longer rewrite the system prompt (#182) (
pydantic_deep/instructions.py,pydantic_deep/agent.py,pydantic_deep/spec.py,pydantic_deep/deps.py).create_deep_agentdoesn't useTodoCapability— it builds its own instruction providers — so the static-by-default prompt introduced inpydantic-ai-todo0.2.7 never applied here:make_todo_sectioncalledget_todo_system_prompt(proxy), which appends## Current Todoswhenever the run has any. The instructions open the provider's prompt-cache prefix, so everywrite_todos/update_todo_statusinvalidated the whole cached prefix mid-run. The todo section is now the staticTODO_SYSTEM_PROMPT, with the live list behind a newinclude_current_todos=True(also aDeepAgentSpecfield), matching the upstream flag name. Reported by @jb2197.- The instruction providers no longer touch the todo proxy at all, so
build_instruction_providersdrops itstodo_proxyargument._TodoProxyBinderre-binds the proxy in each tool's owncontextvarscontext (issue #148), which was always the only binding the tools saw. The end-to-endwrite_todospersistence test covers this. - The opt-in list is appended as its own section, so it composes with
tool_search=Trueinstead of being dropped by the lean todo section. DeepAgentDeps.get_todo_prompt()— until now unused by the library — is what renders that section, and now includes each todo's id (- [ ] [id] content, matching upstream) so the model can address a task withupdate_todo_statuswithout re-reading the list.- The 0.3.37 note below claimed pydantic-deep's prompt behavior was unaffected by the upstream default. It wasn't; this is that fix.
- The instruction providers no longer touch the todo proxy at all, so
Changed
- vstorm-co dependency floors raised (#184) (
pyproject.toml):summarization-pydantic-ai>=0.1.11— compression no longer produces a history that providers reject. Withkeep=("messages", 0)(theContextManagerCapabilitydefault) the rebuilt history was a singleModelRequestof system parts only, which Anthropic and Google route into the top-levelsystemparameter — mapping to zero provider messages, so any run long enough to cross the compression threshold failed deterministically. A zerokeepalso no longer summarizes away the in-flight request, and summaries stop accumulating in the system channel. Note for anything reading a summary back out of a compressed history: it's carried by aUserPromptPartnow, not aSystemPromptPart.subagents-pydantic-ai>=0.2.10—wait_tasksused to hard-slice a completed task's result to 2000 characters with no marker, so the orchestrator read a well-formed answer that stopped mid-sentence, assumed the subagent was cut off, and re-delegated work that was already done. Truncated results now say so and name thecheck_task(...)call that returns the full text, and the budget is configurable viamax_result_chars.
ruffcapped below 0.16 in the lint group (pyproject.toml).uv.lockis gitignored, so CI resolved the latest release on every run; ruff 0.16.0 started formatting code blocks inside Markdown, which flags 49 docs andexamples/skills/*/SKILL.mdfiles that have never been formatted — turningruff format --checkred on unrelated PRs. Lifting the cap belongs in a dedicated commit that reformats those files in one go.