55 errors to zero. Nearly all of them traced back to two causes rather than 55.
THE DECORATOR. @logged wraps ~24 functions across this package and was declared
`def decorator(func: Callable):` with no ParamSpec and no return annotation, so
it erased the signature of everything it touched. ToolResult.execute() is
annotated `-> ToolResult`; through the decorator it came back Any, and mypy
reported 33 no-any-return errors spread across the tools and agents. Each looked
like a local annotation slip. All of them were one decorator. Typed with
ParamSpec/TypeVar; the async branch casts at the await rather than loosening R,
because loosening R would put the Any straight back into every caller.
THE MISSING TYPE PARAMETER. BaseAgent was not generic, so _create_agent returned
a bare Agent — Agent[Any, Any] — and pydantic_ai then typed every run() result
as Any. BaseAgent is now Generic[CtxT] bound to AgentContext, _agent is declared
on the base instead of reached through hasattr, and the three tool-registration
functions take their agent's real context type. tools_streaming.py already did
this; the other three had not been updated.
Eight `execute` overrides carry a targeted ignore rather than a package-wide
disable_error_code. Every tool narrows the base's **kwargs to its own named
parameters, which is a real LSP violation — but nothing anywhere is typed as
BaseTool, and every call site constructs the concrete tool. The abstract method
earns its place by making a tool without execute impossible to instantiate. The
reasoning lives in BaseTool.execute's docstring; the per-site suppressions mean
an override that IS unsound still gets caught.
BaseAgent.run_stream widened to AsyncIterator[str | StreamEvent], which is what
callers already receive: task streams structured events, explore and plan stream
strings, and the router branches on isinstance with a comment calling the string
path legacy. The annotation now says what the code does.
AND THE PART THAT MATTERS MORE THAN THE TYPES.
Chasing the last error found that the Ollama sanitiser has been broken. It
fetched the parent's chat getter with `AsyncOpenAI.chat.fget`, and openai made
`chat` a functools.cached_property, whose getter is `.func`. Touching `.chat`
raised AttributeError — meaning the content: null workaround that CLAUDE.md
documents as live would have failed on the first completion any agent attempted.
Confirmed in the running container (openai 2.46.0) as well as locally (2.15.0).
Two things hid it. The line carried a bare `# type: ignore`, which suppressed
precisely the complaint that would have caught it. And /agents/run and
/agents/stream have served zero requests in 30 days, so nothing exercised the
path. A mitigation can rot completely while every check stays green, if no check
actually runs it.
The lookup now reads whichever getter the descriptor exposes and raises a
legible TypeError if openai adopts a third shape. tests/test_ollama_provider.py
walks the chain an agent request walks, short of the network call —
mutation-checked: all four fail against the old lookup.
215 passed, 23 skipped, plus the four new. mypy clean over 90 files.
Co-Authored-By: Claude <noreply@anthropic.com>
97 findings to zero. Most were mechanical — 52 unsorted import blocks, 10
unsorted __all__, assorted pyupgrade and simplify hints. Two were not, and both
were visible only because the lint made me look.
`webber version` did not exist. src/cli/commands/version.py defines
show_version(), main.py imported it, and the registration line was never
written — the CLI exposed chat and explore only. The import carried
`# noqa: F401`, which is what kept the omission quiet: someone marked the
symptom as intentional instead of asking why it was unused. show_version is not
redundant with the --version flag; it prints the resolved Ollama URL, model and
debug state, which is the form worth having when something is misconfigured.
Registered, and the suppression dropped because the import is now genuinely used.
test_spawn_explore_agent asserted nothing. It built a mock RunContext, patched
get_agent, and stopped at the comment "For now, verify the explore agent would
be called correctly". It had been counted as a passing test. An AST sweep of all
238 test functions found it was the only one, which is worth knowing — the
problem was contained, not systemic. It is now skipped with a reason, so it
reports as unfinished rather than as passing. Reducing it rather than deleting
its imports was the point: tidying the imports would have made a hollow test
look clean.
Two findings were false positives, and both are recorded rather than silently
worked around:
B023 flagged run_agent closing over full_prompt and ctx. Traced: agent_task is
awaited at line 326 before `continue` reaches the next iteration, so neither
name can be rebound while the closure is pending, and the exception path
cancels and awaits too. Not a bug. Bound as defaults anyway, because that stays
true if the await ever moves. I had called it a live bug before tracing it,
which is the mistake Rule 5 exists for.
RUF012 flagged `rules: list[ApprovalRule] = []` on ApprovalRuleSet. Its
suggested fix — annotate ClassVar — would remove the field from the model.
ApprovalRuleSet is a pydantic model and pydantic deep-copies defaults per
instance; verified by constructing two and confirming their lists are distinct
objects. Suppressed with that evidence in the comment. Ruff cannot see the
pydantic base because BaseSchema is a local subclass of BaseModel.
Also moved a stray `from src.shared.logging import ...` that had drifted below
a function definition, and merged a nested if in the ollama provider.
215 passed, 23 skipped, unchanged except for the new skip. `webber version`
exercised end to end.
mypy is NOT addressed here and the gate still fails on it — 55 errors in 14
files, 35 of them no-any-return from pydantic_ai's untyped returns. That was
hidden behind ruff, because the gate stops at the first failing stage.
Co-Authored-By: Claude <noreply@anthropic.com>
One agent doc per repo, and it is CLAUDE.md. This repo carried two --
one at the root and one under webber-api/ -- which is the drift problem
in its purest form: two documents, one subject, and no way to know which
the last reader trusted. Written fresh rather than reformatted.
README.md linked to webber-api/AGENTS.md, so that pointer moves with the
file rather than dangling.
The architecture section states the method used to establish what is
live -- import the app inside the container and read sys.modules -- and
then the case where that method fails here. src/domains/tools is absent
from a cold snapshot and is entirely live: each agent's _register_tools
imports its tool package from inside the method body, on every
/agents/run. Absence from a snapshot taken before any request is served
is a timing artifact, not evidence of death, and deleting on that basis
would have removed the tool layer.
Co-Authored-By: Claude <noreply@anthropic.com>
mistral-nemo-large holds ~9.2 GB of the 11 GB card it shares with Speaches,
which starves Whisper and breaks voice transcription. gemma4:e2b holds
1.9 GB and is faster. The deployed stack already overrides this via
OLLAMA_AGENT_MODEL; this aligns the default so a deployment without that
override does not reintroduce the contention.
Co-Authored-By: Claude <noreply@anthropic.com>
Patch release for the network migration: docker-hostname config
defaults for Tatlock/SearXNG and CI image push via the
git.schweitz.net registry route.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The homelab is retiring *.schweitz.internal and will rebind host ports
to 127.0.0.1, so container-to-container traffic must use container
names on the docker-dataplane network. Switch defaults from host
IP:port to http://tatlock:8000 and http://searxng:8080 (SearXNG's
internal port is 8080; 8087 is only the host-published port).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Event-based streaming for task agent
- Retry logic when LLM responds without calling tools
- Hardened prompts to enforce tool use
- Working directory context in all agent prompts
- Project paused: local LLMs not capable enough for agentic use
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove mode=mode.value from trace_span calls (trace_span only accepts
name and logger parameters)
- Add TestPermissionModeIntegration tests that verify mode string->enum
conversion works correctly through the full request flow
- Add TestAgentMethodSignatures tests that verify function signatures
match expected interfaces (catches invalid kwargs at test time)
These tests would have caught both the mode string/enum issue and the
trace_span invalid kwarg issue before they hit production.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The BaseSchema has use_enum_values=True which makes Pydantic store
enum values as strings. Added _get_mode() helper to convert back to
PermissionMode enum before passing to agent methods.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ~/.webber/config.toml for persistent settings
- Support api.url, api.key, cli.mode, cli.stream, history.file
- Environment variables override config file values
- Add 'config' command to show settings and init config file
- Update all commands to use config defaults
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add save-only endpoint (POST /conversations/{id}/save) for persisting
messages without triggering agent execution
- Add sessions command to list previous conversation sessions
- Add --resume flag to chat command for resuming sessions by ID
- Buffer streamed responses and save after completion
- Update AGENTS.md with session commands and remove outdated limitation
- Add 3 new tests for save endpoint (208 total)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Permission Modes:
- Add default/plan/auto_accept modes controlling tool access
- Plan mode restricts Task agent to read-only tools only
- Auto-accept mode bypasses approval prompts (with confirmation)
Approval Scaffolding:
- Add ApprovalRule/ApprovalRuleSet for granular tool control
- Pattern-based matching on tool name and arguments
- Default rules for common safe/dangerous patterns
- Prep for future bidirectional approval flow
CLI Refactor:
- Default to Task agent (main orchestrator)
- Add --mode flag and runtime mode switching
- Integrate prompt_toolkit for better UX:
- Persistent command history (~/.webber_history)
- Tab completion for commands and file paths
- Auto-suggest from history
- Deprecate standalone 'explore' command
Other:
- Split CHANGELOG.md into per-package files
- Update AGENTS.md release procedure for both packages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- @with_retry decorator and retry_async() function
- Exponential backoff with jitter
- Retries on: timeout, connection errors, HTTP 429/5xx
- Web search tool now retries on network failures
- Configurable via RETRY_MAX_ATTEMPTS, RETRY_BASE_DELAY, RETRY_MAX_DELAY
- 29 new tests (205 total passing)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- litellm had dependency conflicts with pydantic-ai
- tiktoken is lighter and already required by pydantic-ai
- Updated documentation (README.md, architecture.md)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PlanAgentImpl with read-only tools only
- System prompts optimized for architecture planning
- Outputs step-by-step implementation plans with critical files
- 15 unit tests for registration, tools, and API
- Update COVERAGE.md to ~70% complete
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add pytest markers (integration, e2e, slow) with skip logic
- Add command line options (--run-integration, --run-e2e)
- Create sample_project and sample_project_with_bug fixtures
- Add test_integration.py with 10 LLM tests
- Add test_e2e.py with 12 API server tests
- Update COVERAGE.md to reflect ~65% complete
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update COVERAGE.md to reflect completed features (now ~60%)
- Update main README with features and tools list
- Update CLI README with streaming options
- Expand API tests from 5 to 11 (add stream endpoint tests)
- Add 14 security tests for path traversal, command injection
- Total tests: 109 (up from 88)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add WebSearchTool that queries the self-hosted SearXNG metasearch engine
for current information, documentation, and facts beyond training data.
- Add SEARXNG_URL and SEARXNG_TIMEOUT config settings
- Create WebSearchTool with query, num_results, categories params
- Register web_search tool with explore agent
- Add 10 tests for search functionality
Usage: Agents can now use web_search(query="...") to find current info.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add real-time streaming support for agent responses using Server-Sent
Events (SSE). Responses now appear as they're generated instead of
waiting for completion.
- Add run_stream method to BaseAgent and ExploreAgentImpl
- Add /agents/stream SSE endpoint to API router
- Add run_agent_stream method to CLI client
- Add --stream flag to chat and explore commands (enabled by default)
- Use --no-stream for batch mode with spinner
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New tools for code modification:
- EditFileTool: find-and-replace with safety checks (unique match required)
- WriteFileTool: create/overwrite files with path validation
- BashTool: full bash with controlled write access
Security controls on BashTool:
- Allowed: mkdir, touch, cp, mv, rm (single files), git, pip, pytest
- Forbidden: sudo, curl, wget, ssh, rm -rf, chmod 777
Includes 39 new tests (78 total now passing).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Structure webber into three independent subprojects:
- webber-api/: FastAPI backend server with all agent code
- webber-cli/: Standalone CLI client (renamed from cli/ to webber_cli/)
- webber-sandbox/: Test project for functional testing
Key changes:
- Each subproject has its own .venv (Python 3.12+)
- Added sandbox.sh for managing test project templates
- Created sandbox-templates/ with calculator-cli and empty starter
- Updated CI/CD for prefixed tags (api/v*, cli/v*)
- Added comprehensive AGENTS.md with operational instructions
- Added gitignore filtering to glob and grep tools
- Created pyproject.toml for each subproject
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>