0.3.35
[0.3.35] - 2026-07-03
Onboarding and per-user configuration, a dynamic model catalogue in the picker,
credential management from the CLI/TUI, reasoning-effort and project-init
commands, a prompt/reminder overhaul, and a Terminal-Bench harness.
Added
CLI / TUI
- First-run onboarding — on first launch (when no model is configured
anywhere) the CLI walks you through picking a provider, entering its key, and
choosing a model. The choice is saved to the user-level config so it carries
across every project. Re-runnable any time withpydantic-deep onboard. - Credential management —
pydantic-deep keys list / set / removeand an
in-TUI/keyspicker set any of the known credentials (model providers,
Vertex, Logfire, AWS, Azure) from one registry into a git-ignored keystore.
Keys are user-global by default (~/.pydantic-deep/keys.toml) and loaded into
the environment on startup;--projectscopes them to one repo. Real env vars
always win. - Dynamic model catalogue — the
/modelpicker (aliased/models)
now lists recently-used models, the live OpenRouter catalogue (fetched and
cached under~/.pydantic-deep/cache/, refreshed in the background), and every
native provider pydantic-ai recognises (read from itsKnownModelName, keyed
providers first), all with real fuzzy search. On the CLI:pydantic-deep models recent / openrouter / select. Recently-used models are remembered per user. /thinking— change reasoning effort (off → xhigh) from a picker or
/thinking <level>; it applies live and persists./init— analyse the project and write anAGENTS.mdso the agent has
context (and seedMEMORY.md).- Welcome hero — the splash now renders a magenta
pydanticASCII wordmark
and a rotating tip (it was previously a defined-but-never-mounted widget). - Interactive CLI pickers —
keys setandmodels selectopen
arrow-selectable lists with hidden value entry (prompt_toolkit) when run
without arguments; the argument forms stay scriptable.
Framework
- Submodels inherit the primary model when not explicitly configured — an
undefined summarization / reminder / judge model now falls back to the main
model instead of a hard-coded default.
Benchmark
apps/harbor— a HarborBaseInstalledAgentadapter to evaluate the CLI
agent on Terminal-Bench 2.0, with Logfire tracing and per-task trace tags,
installing pydantic-deep into the task container and forwarding the full agent
feature set. A benchmarkAGENTS.mdencodes the task-solving rules.
Changed
- User-level vs project configuration —
load_config()now merges a global
~/.pydantic-deep/config.toml(user defaults: model, theme, thinking) as the
base with the project.pydantic-deep/config.tomlas overrides; environment
variables win over both. The model chosen during onboarding lives in the global
config so new projects don't re-prompt. - Onboarding is triggered by "no model configured" (env / project / global
config) rather than by the absence of~/.pydantic-deep/, which incidental
caches (update check, keystore) created too early to be a reliable signal. - Context files are no longer auto-created.
AGENTS.md,SOUL.mdand
MEMORY.mdare written only by the agent when it decides to (write_memory/
write_file), by/init, or by the user — never scaffolded on launch. Missing
files are silently skipped andwrite_memorycreatesMEMORY.mdon demand, so
the agent works with or without them. - System prompt consolidated into
pydantic_deep/prompts/— one set of
composable fragments + a builder, replacing the scattered CLI/prompt strings;
the oldapps/cli/prompts.pyis now a thin re-export. - Periodic reminder cadence relaxed to every 15 turns (first after 15, no
per-run cap) so long tasks aren't nagged as often. - Ctrl+C aligns with Claude Code / Codex (#154) — it now copies the current
text selection to the clipboard when there is one, and an idle Ctrl+C arms exit
(a second press within 2 s quits) instead of closing on the first press. A
running agent is still interrupted by a single Ctrl+C. Text selection (dragging
to highlight agent output) works via Textual's built-in selection.
Fixed
/modelwas effectively unusable — the picker didn't focus its input (you
couldn't type), the "filter" never actually filtered the list, and pressing
Enter on a typed term submitted the raw text (qwen→ "Unknown model:
qwen") instead of selecting the highlighted match. All three fixed; a
duplicate-id crash when a model appeared in both "recent" and a provider list
was resolved.- Changing the model now updates the input footer live — the session line
under the prompt readmodel_namelazily and only refreshed after an app
restart. - Headless runs survive flaky provider responses — the graceful-exit path
now also catches transport/gateway failures (ModelHTTPError,
httpx.HTTPError, andjson.JSONDecodeErrorfrom a non-JSON gateway body) and
exits 0 so downstream verification can still grade the filesystem, instead of a
hard non-zero crash. - MCP subagents failed in the TUI with
[Errno 9] Bad file descriptor
(#167).run_tui()built the agent beforeapp.run(), so a subagent
agent_factorythat spins up anMCPToolset(StdioTransport(...))bound
fastmcp's async primitives to the orphaned pre-app.run()event loop; when a
task()delegation later used them inside Textual's loop, the stdio connection
died instantly. Agent construction is now deferred toDeepApp.on_mount
(agent_factorybuilt via_build_deferred_agent, before the chat screen
mounts) so those transports bind to the running Textual loop. The "launch the
TUI anyway on failure so the key can be fixed via/provider" behaviour is
preserved.