Skip to content

Changelog

All notable changes to wishful will be documented here. Follows Keep a Changelog and Semantic Versioning.

  • evolve() now returns an EvolutionResult instead of the bare winner function: callable like the winner (delegates __call__), proxies __name__/__doc__/__wishful_source__/__wishful_evolution__, sets __wrapped__ for inspect.signature(), and carries the run’s history and best_score. Code doing isinstance(result, types.FunctionType) must use result.fn instead. Exported as wishful.EvolutionResult.
  • __wishful_evolution__ gained a schema_version key (int, currently 1); treat unknown keys as forward-compatible additions.
  • wishful.settings.cache_dir is now always an absolute path, resolved at construction — os.chdir() can no longer silently move the cache. Comparisons against relative Path literals will no longer match.
  • Dynamic modules are lazy: attribute access returns a call wrapper without an LLM call (so hasattr() is always True for public names); generation happens only when the wrapper is invoked. Dynamic modules expose functions only — constants raise AttributeError at call time.
  • explore() owns its event loop: one persistent background loop on a wishful-owned daemon thread replaces the cached-loop + nest_asyncio design. explore() now works inside a running loop (Jupyter) without patching the host, including from a test/benchmark callable that itself calls explore().
  • explore(verbose=...) defaults to sys.stdout.isatty() and save_results to the WISHFUL_EXPLORE_SAVE_RESULTS env var (on unless "0"), so headless runs stay quiet by default.
  • context_radius setting (env WISHFUL_CONTEXT_RADIUS, default 3): the import-site context capture radius is now a regular Settings field — configure(context_radius=...) and reset_defaults() aware; set_context_radius() is a thin wrapper.
  • Nested wishes are rejected up front: import wishful.static.a.b raises a clear ImportError (wish names are single-level) before any LLM call, instead of generating the parent and failing with ”’…’ is not a package”.
  • explore/evolve candidate containment: user test/benchmark/fitness callables run on a bounded worker with a per-variant timeout; a hanging or sys.exit()-ing candidate is recorded as a variant failure instead of stalling the run or killing the host. ExplorationError.failures now carries full, untruncated failure text.
  • @wishful.type schemas reach explore(): registered type schemas and output_for bindings are included in explore’s generation prompts.
  • Removed nest-asyncio — no longer needed with the owned event loop. Projects that imported it transitively through wishful must add it as a direct dependency.

Note: 0.3.0 was an internal milestone and was never published; its changes first ship publicly together with 0.4.0.

  • wishful.evolve(): generational improvement of a function — keeps a history of prior attempts, scores, and failures, then feeds that history into the next mutation prompt. Returns a function carrying __wishful_source__ and __wishful_evolution__. New EvolutionError.
  • Real wishful console script ([project.scripts]) alongside python -m wishful; both are equivalent entry points.
  • --json output on every subcommand for machine-readable results.
  • --version flag.
  • Documented exit codes: 0 success, 1 runtime error, 2 usage error.
  • request_timeout setting (env WISHFUL_REQUEST_TIMEOUT, default 300s): per-request LLM timeout, applied at import time, with an empty-content retry.
  • Model precedence clarified: WISHFUL_MODEL now takes precedence over DEFAULT_MODEL (the wishful-specific var wins; DEFAULT_MODEL is the fallback). Built-in default remains "azure/gpt-4.1".
  • log_to_file now defaults to False (opt-in via WISHFUL_LOG_TO_FILE=1): a bare import wishful no longer creates files in your CWD.
  • loguru-based logging with log_level control.
  • WishfulError base exception: SecurityError, GenerationError, ExplorationError, and EvolutionError now derive from it.
  • Hardened safety validator, framed honestly as “defense in depth, not a sandbox”: AST scan blocks forbidden imports/calls, __builtins__/globals()/vars()/locals() gadget access, introspection escape chains (__subclasses__/__globals__/__code__/__bases__), aliased dangerous builtins (f = open), write-mode (or non-literal-mode) open(), and system calls on unbound modules. The same scan re-runs on cache load, so tampered .wishful/ files are re-checked. Only genuinely computed access (runtime-built attribute names, globals().get(...)) remains a documented residual.
  • Namespace isolation: static and dynamic modules now map to separate cache files (<cache>/<name>.py vs <cache>/_dynamic/<name>.py) — they previously collided, so a regenerate or review-rejection on one namespace could clobber the other. Cached sources are re-validated on load, and writes are atomic (temp file + rename).
  • Review-gate ordering + headless guard: review happens before execution on every path, with a guard for non-interactive environments (fails closed, stays usable in notebooks).
  • Type-registry fixes for serialization and output-type binding (parameterized generics like list[str] are preserved; default_factory fields serialize correctly).
  • __version__ now derives from installed package metadata (importlib.metadata.version) instead of being hardcoded.
  • max_tokens default raised 409616384: reasoning models (e.g. gpt-5.5) spend part of the budget on hidden reasoning tokens, so the old cap produced empty or truncated output.
  • evolve(verbose=...) removed: the parameter was accepted but never used; passing it now raises TypeError.
  • log_to_file defaults to False (was True).
  • WISHFUL_MODEL takes precedence over DEFAULT_MODEL (was the reverse).
  • Refreshed: litellm (≥1.83.14, for day-0 gpt-5.5 support), rich, python-dotenv, pydantic.
  • Added loguru and nest-asyncio.
  • Removed pyglove (was unused).

  • Type registration decorator (@wishful.type) for Pydantic models, dataclasses, and TypedDict
  • Pydantic Field constraint support: LLM now respects min_length, max_length, gt, ge, lt, le, and pattern constraints
  • Docstring-driven LLM behavior: Class docstrings influence generated code tone and style (e.g., “written by master yoda” generates Yoda-speak)
  • Type binding to functions: @wishful.type(output_for="function_name") tells LLM which functions should return which types
  • Multi-function type sharing: wishful.type(TypeClass, output_for=["func1", "func2"]) for shared types
  • wishful.static.* namespace: Cached generation (default behavior, fast subsequent imports)
  • wishful.dynamic.* namespace: Runtime-aware regeneration on every import (for creative/contextual content)
  • Both namespaces share the same cache file for consistency
  • Type schema integration: Registered types are automatically included in LLM prompts
  • Function output type hints: LLM receives information about expected return types
  • Type definitions are serialized with full docstrings and Field constraints
  • Metadata-based constraint extraction: Properly parses Pydantic v2’s constraint storage (MinLen, MaxLen, Gt, etc.)
  • _PydanticGeneralMetadata handling: Extracts pattern and other general constraints
  • Backward compatibility: Still supports Pydantic v1 direct attribute access
  • External library support: Changed from “only use Python standard library” to “you may use any Python libraries available in the environment”
  • Pydantic, requests, and other common libraries now explicitly allowed in generated code
  • Added 07_typed_outputs.py: Comprehensive type registry demonstration
  • Added 08_dynamic_vs_static.py: Static vs dynamic namespace comparison
  • Added 09_context_shenanigans.py: Context discovery behavior showcase
  • All examples updated to use wishful.static.* namespace convention
  • AGENTS.md: Complete sync with current codebase state
    • Added Pydantic Field constraint documentation
    • Added docstring influence documentation
    • Added type registry implementation details
    • Updated TDD process documentation
  • README.md: Added type registry section, static/dynamic namespace explanation, and updated FAQ
  • _build_field_args(): New method to extract Field() arguments from Pydantic field_info
  • _serialize_pydantic(): Enhanced to include Field constraints in serialized schemas
  • Docstring serialization for all type systems (Pydantic, dataclass, TypedDict)

Discovery System (src/wishful/core/discovery.py)

Section titled “Discovery System (src/wishful/core/discovery.py)”
  • ImportContext: Extended with type_schemas and function_output_types fields
  • discover(): Now fetches registered type schemas and output type bindings
  • Integration with wishful.types.get_all_type_schemas() and get_output_type_for_function()
  • Enhanced build_messages() to include type definitions in prompts
  • System prompt updated to allow external libraries
  • Type schemas formatted as executable Python code for LLM
  • 83 total tests with 80% code coverage
  • Added 4 new tests in test_discovery.py for type registry integration
  • Added 30 tests in test_types.py for type serialization (all scenarios)
  • Added 6 tests in test_namespaces.py for static vs dynamic behavior
  • Added pydantic>=2.12.4 as runtime dependency

  • Basic import hook system with LLM code generation
  • Cache management (static .wishful/ directory)
  • Context discovery from import sites
  • Safety validation (AST-based checks)
  • CLI interface (wishful inspect, clear, regen)
  • Configuration system with environment variables
  • litellm integration for multi-provider LLM support
  • Fake LLM mode for deterministic testing (WISHFUL_FAKE_LLM=1)