Skip to content

feat!: every way in runs the new flow API; the old one is gone (U5) - #81

Merged
futrime merged 4 commits into
feat/new-flow-apifrom
flow-api/u5-ways-in
Sep 25, 2026
Merged

futrime merged 4 commits into
feat/new-flow-apifrom
flow-api/u5-ways-in

Conversation

@futrime

@futrime futrime commented Sep 25, 2026

Copy link
Copy Markdown
Member

Summary

Unit 5 of the new flow API: every way into humanize -- hmz exec, the runtime facade, the TUI, the daemon and the SDK -- now runs flows through the new engine (hmz.flows + hmz.runtime.flowing), and the old flow API is gone.

hmz exec

  • hmz exec -f <ref> [-a role=cli[@provider]/model:effort]... [-e role=backend@provider/workdir]... [-p key=value]... -b duration=...,cost=...,output_tokens=...[,graceful=...] [--resume] [--json] <task> -- every flag repeatable and comma-listable, read with specs.py.
  • -b is required (usage error, exit 2, before any agent starts) except for the builtin chat, which runs under Budget(cost=inf).
  • Usage errors (exit 2, nothing started, no epic): a role the flow does not declare, a role given twice, a required role left out, a runtime-filled role (Outworlder, LocalEnv) named with -a/-e, harness mismatch / capability the harness does not serve, bad spec, unknown or unreadable param, flow not found / will not import, --resume of a non-resumable flow or with nothing to pick up, an env that cannot be reached (probed before the run).
  • --resume picks up the newest run of that flow in the workspace whose journal holds a call; without it every run starts fresh. hmz exec is always "away" (open_outworlder() with no ask).
  • The run is shown from the agents' own event streams (Run.watch → every driver's watch → cli.output.Shown); --json NDJSON is unchanged; -c/--config is gone (params are -p). A cost cap over a model nobody prices is said on stderr.

Runtime

  • runner.py: read_line (the hmz exec line), Runner (load via finding.resolved, check roles/harness/capabilities/params/budget/resume statically, open drivers with open_agent/open_env; arun probes every env, then run_flow with a Recorder, then closes every driver), Refused, Recorder (writes a record per sub-flow call under its caller, each session in the record of the call that opened it and named for its role, and the run's usage).
  • epic.py: hosts the engine journal as resume.jsonl; a resumed run copies the journal into its own epic (picked_up); picks_up/state/resumed read the journal; State/state.json removed; Ran gains ref, envs, params, budget, picked_up; Drove is role + cli/provider/model/effort.
  • settings.py/kept.py: per flow, role→agent spec (cli@provider/model:effort), role→env spec, params, budget.
  • Facade: Hmz.read/runner/run/exec take roles; Run gains watch, opened, usage, budget, epic, result; stop() cancels the flow task, close() also interrupts every session. Flows.places/check/prophecy/foretell/configures/set_up_from are gone; Flows.declared(ref) -> Declaration; Flows.running() is the engine's running().
  • finding.py reads flows through the engine's loader (listing: the bare-ref flow under its module's name, other visible flows as <flow>:<sub>, hidden ones unlisted), resolved() loads what a way in names (marking flows humanize ships with full_view), BUILTIN_AT is hmz/flows/builtin.
  • Builtin chat (new API) at src/hmz/flows/builtin/chat: roles assistant: Agent (full harness view, online=ALL) and human: Outworlder, workspace: LocalEnv; loops agent turn → outworlder until the outworlder says nothing; a question the agent asks (ask-user hook) is put to the outworlder.
  • Engine fix: a LocalEnv role given an environment on another machine is refused with CapabilityMissing, on both the fast path and _env().

Daemon / SDK / TUI

  • Daemon status carries flows (canonical refs) and calls (the running tree: ref, name, depth, seconds, id, parent index); the TUI adds budget/usage through Held.says.
  • hmz.sdk also offers Refused and fakes (hmz.runtime.flowing.fakes).
  • TUI (src/hmz/tui/**): the flow menu is by role -- one row per agent role (auto roles hidden; CLIs offered only where the harness matches role.harness and serves role.capabilities per spi.HARNESS_CAPABILITIES), one row per non-auto env role taking an -e spec, the params form (the existing pydantic-driven Configures sheet over the flow's FlowParams), and a budget row (duration/cost/output tokens/graceful; required except chat). A run is Hmz.run(...) with open_outworlder(ask=..., away=...): an outworlder question is asked at the prompt and answered by the next typed line, /afk makes it away; events come from Run.watch, sessions' agents from Run.opened; /stop = Run.stop(), third ctrl+c = Run.close(); /resume carries on the last run of a resumable flow from its journal; the running line uses the engine's running tree; Humanize.said() reports flow/budget/usage to the daemon status. The unbounded-run question and anchors view are gone.

Deleted

src/hmz/_legacy_flows/** and runtime/flowing/{checking,driving,prophecy,prophesying,stepping,proving}.py, their lazy exports, and the tests that only tested them.

Specs

specs/SPEC.md (legacy clause dropped; builtin flows live in flows/builtin), specs/runtime/SPEC.md, specs/runtime/doing.md, specs/runtime/flowing.md (legacy sections removed), specs/cli.md, specs/tui.md, specs/sdk.md (__all__).

Tests

  • Deleted 17 legacy-only test files (225 test functions: checking, prophesying, proving, stepping, legacy flow calling/recursion/reloading/async/budget/config/where-agents-work/session-skills, capabilities, goals, leniency, settling ladder, flow interface).
  • Ported/edited 61 files (942 -> 879 test functions; the drop is legacy-only tests removed from mixed files: catalogue, container/Isolated, permission settling, legacy places), including CLI exec, epics, export, resuming, run budgets, profiled runs, SDK, daemon holding/opening, flowverses/naming/skills/builtin chat, agents (acp/cloning/cursor/zcode/permissions/web-search), machines isolation, tests/recording.py, and 24 TUI test files (474 -> 465 functions).
  • New: tests/system/runtime/test_exec.py (real claude through hmz exec, chat without -b, --json), engine test for the LocalEnv refusal, runtime tests for the call tree/usage/stop/budget-stop, settings/kept tests, sdk fakes test.

Test plan

  • uv run pre-commit run --all-files
  • uv run pytest (all three tiers)
  • uv run pytest tests/system --run-agents
  • E2E with stand-in CLIs: hmz exec plain/--json, epic + journal, --resume, usage errors, chat without -b.

🤖 Generated with Claude Code

futrime and others added 4 commits September 25, 2026 12:41
A LocalEnv is this machine. An environment view on an ssh host passed for a
LocalEnv-typed role was accepted by the per-call fast path and by _env();
both now refuse it with CapabilityMissing. The fast path pays one attribute
test for auto roles only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ld API

Every way in now runs flows written against hmz.flows through
hmz.runtime.flowing's engine, and the previous flow API is gone.

- hmz exec -f REF [-a ROLE=CLI[@Provider]/MODEL:EFFORT]... [-e ROLE=SPEC]...
  [-p KEY=VALUE]... -b duration=...,cost=...,output_tokens=... [--resume]
  [--json] TASK. -b is required except for the builtin chat, which runs
  under Budget(cost=inf). A role the flow does not declare, a runtime-filled
  role (Outworlder, LocalEnv) named on the line, a missing required role, an
  agent whose harness cannot fill its role, an unknown param, a bad spec, and
  --resume with nothing to pick up are usage errors (exit 2) before any agent
  starts. hmz exec is always away. -c/--config is gone; params are -p.
- runtime.runner: read_line, Runner (static checks, drivers made last,
  environments probed before the flow is called), Refused, and a Recorder
  that writes a record per flow call under its caller, each session in the
  record of the call that opened it, and what the run spent.
- runtime.epic hosts the engine journal as resume.jsonl; a resumed run is a
  new epic holding a copy of the journal it picked up. State/state.json are
  gone; picks_up/state/resumed read the journal.
- Settings remember per flow the agent spec and env spec of each role, the
  params and the budget. kept.Runs is a CLI, an account, a model and an
  effort.
- Hmz.run/runner/read/exec take roles; Run gains watch, opened, usage,
  budget, epic and result, stop() cancels the flow and close() also cuts
  every session. Flows.declared(ref) replaces places/check/prophecy/
  foretell/configures; Flows.running() is the engine's running tree.
- finding reads flows through the engine's loader; resolved() loads what a
  way in names and marks the flows humanize ships with full_view. BUILTIN_AT
  is hmz/flows/builtin, where the new-API chat lives: assistant (Agent, full
  harness view) and human (Outworlder), talking until the human says nothing.
- The daemon's status carries the running tree; hmz.sdk offers Refused and
  the fakes.
- hmz._legacy_flows and runtime/flowing/{checking,driving,prophecy,
  prophesying,stepping,proving} are deleted, with the tests that only tested
  them; the rest are ported to the new API.

BREAKING CHANGE: flows written against the previous flow API no longer load,
hmz exec takes roles and a budget, and settings and epics are written in the
new shapes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The flow menu is one row per agent role the flow declares, with the roles
  the runtime fills (Outworlder, LocalEnv) left out and each role offered only
  the CLIs whose harness it names and whose capabilities it asks; one row per
  environment role, taking a spec as -e spells one; the flow's params, asked
  with its own params model; and a budget (duration, cost, output tokens,
  graceful), required for every flow but chat.
- A run is Hmz.run with the interface as whoever is outside it: a question a
  flow puts to its outworlder is asked at the prompt and answered by the next
  line typed, and /afk makes it away. The transcript is drawn from Run.watch,
  each session's agent is learnt from Run.opened, and /stop is Run.stop, the
  third ctrl+c Run.close. A change made while a flow runs takes effect from the
  next run.
- /resume carries on the last run here of a flow that can be picked up, from
  its journal; the running line reads the engine's running tree; the daemon's
  status is told the flow, its budget and its usage through Humanize.said.

BREAKING CHANGE: what the interface remembers per flow is the new settings
shape; the anchors and the unbounded-run question are gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only the parts the switch-over contradicts: the temporary _legacy_flows
clause of SPEC.md goes, and builtin flows live in flows/builtin; the runtime,
doing, cli, tui and sdk specs describe the role-based hmz exec line, Runner,
Run, the journal inside an epic, Flows.declared and the settings shape;
flowing.md loses its legacy sections and gains resolved/builtin and the
LocalEnv refusal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@futrime
futrime merged commit 0cb58da into feat/new-flow-api Sep 25, 2026
1 of 2 checks passed
@futrime
futrime deleted the flow-api/u5-ways-in branch September 25, 2026 12:44
futrime pushed a commit that referenced this pull request Sep 25, 2026
… gone

feat!: every way in runs the new flow API; the old one is gone (U5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant