The four resolve_*_destination use cases now route through a private
_resolve_parsed helper that picks the right entry point:
- source path provided AND it exists -> inspect_release(name, path)
runs the full pipeline (parse + media-type refinement + probe
+ enrich), so missing tech tokens (quality, codec, ...) get
filled by ffprobe and the refreshed tech_string lands in the
destination folder / file names.
- source path missing or absent -> parse_release(name) only,
same behavior as before. Back-compat: tests using fake /dl/*.mkv
paths still pass unchanged.
resolve_episode_destination / resolve_movie_destination reuse their
existing source_file parameter as the inspection target. The two
folder-move use cases (season / series) gain a new OPTIONAL
source_path parameter — threaded through the agent tool wrappers
and documented in the YAML specs.
The lazy import inside _resolve_parsed avoids a circular import:
inspect_release imports detect_media_type / enrich_from_probe from
the same application.filesystem package whose __init__ re-exports
resolve_destination.
Three new tests in TestProbeEnrichmentWiring with a stub MediaProber
prove the wiring: movie picks up probe quality, season picks it up
via source_path, and a missing path correctly skips probe (back-compat
guard).
Introduce a first-class semantic layer for tool descriptions, separated
from Python signatures (which stay the source of truth for types and
required-ness).
New
- alfred/agent/tools/spec.py — ToolSpec / ParameterSpec / ReturnsSpec
dataclasses with strict YAML validation (ToolSpecError on malformed
or inconsistent specs). compile_description() builds the rich text
passed to the LLM as Tool.description, with sections for summary,
description, when_to_use, when_not_to_use, next_steps, and returns.
compile_parameter_description() injects the 'why_needed' field next
to each parameter so the LLM sees the *intent* of each argument.
- alfred/agent/tools/spec_loader.py — discovers tools/specs/*.yaml,
enforces filename ↔ spec.name match, rejects duplicates.
- alfred/agent/tools/specs/ — one YAML per tool:
* resolve_season_destination.yaml
* resolve_episode_destination.yaml
* resolve_movie_destination.yaml
* resolve_series_destination.yaml
* move_to_destination.yaml
Refactor
- alfred/agent/registry.py
* _create_tool_from_function now takes an optional ToolSpec.
When provided, the long description + per-parameter descriptions
come from the spec; types and required-ness still come from the
Python signature.
* Cross-validates spec.parameters against the function signature —
crashes on missing or extra entries.
* make_tools() loads all specs at startup and hands the right one
to each tool. Tools without a spec fall back to the old
docstring-only behaviour, so the 14 not-yet-migrated tools keep
working unchanged.
* Adds 'array' and 'object' to the Python→JSON type mapping and
handles Optional[X] / X | None annotations.
- alfred/agent/tools/filesystem.py
* Drops the '_tool' suffix on the 4 resolve_* wrappers (option 1:
alias the use-case imports as _resolve_*). Tool names exposed to
the LLM now match the underlying use case verbatim.
* Wrapper docstrings shrink to a one-liner pointing to the YAML
spec — no more duplicated when_to_use/Args/Returns in Python.
Verified
- make_tools() loads 19 tools (5 with YAML spec, 14 doc-only).
- Compiled descriptions render cleanly with all sections.