All notable changes to Milo are documented here.
- Added pipeline observability:
PhaseLogdataclass with@@PHASE_LOGaction and ring-buffer reducer for per-phase stdout/stderr capture (opt-in viaPipeline(capture_output=True)). Newphase_detail()andpipeline_detail()kida macros for interactive TUI with cursor navigation, log scrolling, and auto-follow.PipelineViewState+make_detail_reducer()for Elm-style keyboard-driven expand/collapse interaction.milo://pipeline/timelineMCP resource exposes phase execution timeline as structured JSON. Gateway--statusnow shows real CLI metrics and pipeline state.
- Bumped minimum kida-templates dependency to 0.5.0. This brings a correctness fix for variable bindings inside unrolled for-loops (affects form, select, pipeline, and component templates) and faster template compilation from cached
str.joinand filter folding. - Optimized dispatch lock hold time by replacing SHA256 with builtin hash and deferring recording append outside the lock. Fixed
get_env()singleton cache that was never written, reducing repeated calls from 122μs to 125ns. Added bulk task accounting for Batch effects. - Refactored
CLIcommand dispatch internals to share builtin-mode handling, command resolution, hook execution, middleware execution, generator consumption, and output writing acrossrun(),call(), andcall_raw()without changing the public API.
- Eliminate remaining sharp edges: warn on silent template/config fallbacks, validate PhasePolicy and pipeline dependencies eagerly, fix exit code on aborted confirmations (130 instead of 0), suppress
display_result=Falseacross all output formats, tighten Context injection type check, returndefaultfromconfirm()in dry-run mode, addfail_fastoption to hook invocation and parallel pipelines. - Fix 7 Python 2
except A, B:syntax errors, replace silent exception swallowing with warnings/logging, add atomic file writes for registry and version cache, guard unhandled template lookups, and addraise_on_errortoConfig.validate(). - Fix
call()andcall_raw()to re-raise exceptions instead of callingsys.exit(1), restoring the pre-refactor behavior for programmatic invocations. - Fix sharp edges: syntax errors, silent failures, strict APIs, tests
- Fixed group bare invocation showing "Unknown command" instead of group help, and help output now lists subcommands by name instead of raw argparse internals.
- Lazy commands now propagate function signature defaults to argparse. Schema defaults are JSON-safe, boolean schema defaults are respected, boolean
default=Trueparameters use--no-xxxflags, schemaenumvalues become argparsechoices, and a newdisplay_result=Falseoption suppresses plain-format output while preserving--output-fileand--format json.Group.lazy_command()now supportsexamples,confirm, andannotationskwargs for parity withCLI.lazy_command().
- Saga effects expansion — Race (first-wins with loser cancellation), All (wait-all with fail-fast), Take (pause until action dispatched), and Debounce (cancel-and-restart timer). Fixed Python 2 exception syntax bug in pipeline handler introspection. Added gateway test suite covering namespacing, routing, proxying, idle reaping, and error handling.
- Saga hardening — SagaContext for structured cancellation trees, EffectResult handler registry, TakeEvery/TakeLatest higher-order effects, configurable thread pool (max_workers, on_pool_pressure), and comprehensive benchmarks and free-threading stress tests.
- Orchestration hardening — Timeout wrapper effect, TryCall structured error handling, and saga cancellation tokens. PhasePolicy with retry/skip/stop failure semantics, DFS cycle detection, and phase context forwarding. Per-request timeout and graceful child restart for MCP gateway. Tuple/set/frozenset schema support,
$reffor recursive dataclasses, and fallback warnings. - Towncrier changelog — Adopted Towncrier for changelog management. Fragments in
changelog.d/are compiled intoCHANGELOG.mdat release time. CI enforces a fragment for every PR that touchessrc/. - Extended theme colors —
ThemeStylesupports 256-color (int index), truecolor (#rrggbbhex), and background colors (bgfield) alongside existing named ANSI colors. - Pipeline TUI — Interactive pipeline TUI in
buildpipeexample usingApp+Store+ saga for real-time phase visualization with progress bar. pipeline_progressmacro — Reusablepipeline_progress(state)component macro in_defs.kidafor renderingPipelineStatewith phase status and progress bar.
- kida-templates 0.4.0 — Adopt match blocks, try/fallback error boundaries, and unless conditionals in templates; fix color detection to use public terminal_color API.
- Reducer combinators —
quit_on,with_cursor,with_confirmdecorators eliminate boilerplate key handling in Elm-style reducers - Shell completions —
install_completions()generates bash/zsh/fish completions from CLI definitions - Doctor diagnostics —
run_doctor()withCheckspecs validates environment, dependencies, and config health - Version checking —
check_version()detects newer PyPI releases and prints upgrade notices (respectsNO_UPDATE_CHECK) App.from_dir()— automatic template directory discovery relative to the calling fileContext.run_app()— bridge CLI commands to interactive Elm Architecture apps- Built-in template macros —
selectable_list,scrollable_list,format_timeincomponents/_defs.kida Config.validate()— type-check config values against spec defaults- Structured error handling —
run()showsMiloErrorcode + hint; other exceptions show type + message with optional traceback - Command examples in help — examples render in
HelpRendererandgenerate_help_all() invoke()test helper — split stdout/stderr for cleaner test assertions- Progress bars —
CLIProgressfor long-running command feedback Context.log()— leveled logging to stderr (quiet/normal/verbose/debug)- Before/after hooks —
HookRegistryfor command lifecycle interception - Confirm gates — require user confirmation before destructive commands
- Did-you-mean suggestions — fuzzy match on unknown commands
- Dry-run and output-file flags — built-in global options for safe command execution
Retrysaga effect — retry with backoff for transient failuresConfig.init()scaffolding — generate starter config filesdevtoolexample — showcases doctor, hooks, examples-in-help, structured errors, and completions- Commands — Lightweight
Cmdeffect type as a simpler alternative to sagas for one-shot side effects. ACmdis a plain function() -> Action | Nonethat runs on the thread pool. - Batch and Sequence combinators —
Batch(*cmds)runs commands concurrently;Sequence(*cmds)runs them serially. Both support recursive nesting. compact_cmds()— Helper to stripNoneentries from command tuples.TickCmd(interval)— Self-sustaining tick pattern. Schedules a single@@TICKafter interval seconds. Return anotherTickCmdfrom@@TICKto keep ticking; omit to stop. Gives per-component, dynamic tick control.ViewState— Declarative terminal state (alt_screen,cursor_visible,window_title,mouse_mode). Returned viaReducerResult(state, view=ViewState(...)). The renderer diffs previous vs. current and applies only the changes.- Message filter —
App(filter=fn)accepts a function(state, action) -> action | Nonethat intercepts actions before dispatch. ReturnNoneto drop, return a different action to transform. - Saga error recovery — Unhandled saga exceptions now dispatch
@@SAGA_ERRORinstead of being swallowed silently. Payload:{"error": "message", "type": "ExceptionTypeName"}. - Cmd error recovery — Unhandled
Cmdexceptions dispatch@@CMD_ERRORwith the same payload shape. - Bulletproof terminal cleanup — Each step in
App.run()finally block is individually guarded so a failure in one does not prevent the rest from running. - MCP dispatch router — Extracted shared
_mcp_router.pyto deduplicate tool/resource/prompt dispatch betweenmcp.pyandgateway.py spinnerexample — showcasesCmd,Batch,TickCmd, andViewStatepatterns
- Modularized commands — extracted
_command_defs.py(data types) and_cli_help.py(help generation) from the monolithiccommands.py - Shared JSON-RPC helpers — extracted
_jsonrpc.pywithMCP_VERSIONconstant to reduce duplication ingateway.pyandmcp.py - Unified command registration —
_make_command_defhelper shared between CLI and Group - Updated all Elm examples — counter, stopwatch, todo, filepicker, and wizard use the new combinator and
App.from_dir()APIs ReducerResult— now acceptscmdsandviewfields alongsidesagasQuit— now acceptscmdsandviewfields alongsidesagascombine_reducers— collectscmdsandviewfrom child reducers_TerminalRenderer— supportsapply_view_state()for declarative terminal feature control- Middleware wired into dispatch — registered middleware now executes in
CLI.run()andCLI.call()paths - Parallelized gateway — health checks and gateway discovery use
ThreadPoolExecutor - Lazy
walk_commands— converted to generators to avoid eager materialization - Workflow detection — pre-computed property sets eliminate O(n²) redundant work
- HelpRenderer as default formatter —
CLI.build_parser()and subparsers now wireHelpRendererby default - Action group capture in help —
_render_with_template()uses formatter lifecycle instead of accessing parser attributes - Docstring propagation — schema correctly propagates function docstrings to argparse help text
- Package data —
components/*.kidaincluded in distribution - Registry N+1 reads —
doctor/check_alluses_health_check_entryto batch file reads - MCP tool caching —
_list_toolscached inrun_mcp_serverto avoid recomputing on everytools/list execution_order()performance — parallelseenset eliminates per-iteration rebuild--versionrendering — guards template path when no action groups are capturedgenerate_help_allformatting — fixed unclosed backtick in global options section- uv detection — version upgrade notices use
uv pip installwhen running under uv - Version string drift —
cli.py,mcp.py, andgateway.pynow use__version__from__init__.pyinstead of hardcoded strings - ViewState merging in
combine_reducers— multiple child reducers' ViewState fields are now merged instead of last-wins overwrite - Message filter + Ctrl+C —
quit_dispatchedflag is only set after the filter passes, so filtered @@QUIT no longer locks out subsequent Ctrl+C CLI.call()middleware context — now provides a properContextinstead ofNone- Gateway child I/O timeout —
_read_line()enforces a 30-second deadline to prevent deadlocks when a child process dies mid-write ThreadPoolExecutorshutdown —Store.shutdown()now waits for pending work (wait=True)- Batch timeout — nested
BatchinsideSequenceuses a 60-second timeout onconcurrent.futures.wait() - Error recovery logging — failed
@@SAGA_ERROR/@@CMD_ERRORdispatches are logged at DEBUG level instead of silently swallowed - Before/after hook error handling — before-command hook errors now exit with code 1; after-command hook errors are logged without crashing
Initial release. See release notes.