Repository navigation
fix(tui,web): call an execution of a flow an epic in every label and message (#447) - #448
Merged
Merged
Conversation
… pre-release (#387) release-please takes the last release to be the one its manifest names, so 0.1.0's notes said only what changed since 0.1.0-beta.2. release.yml now opens the release pull request through release-please's API, held to a lockfile, telling it the line's last release, or for a `.0` none, so a release's notes cover all its pre-releases did. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d35b69a)
The composer's Options chip stood 22px tall beside an 18px role summary, fields' rows centred on their labels rather than their controls, and selects, segmented controls, icon buttons and small buttons each had a height of their own. Two tokens, --control-h and --control-h-sm, now set how tall a control stands; rows mixing type sizes sit on one baseline; and two sizes a later .icon-btn rule silently overrode are restored. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit dd78794)
…n main (#398) release.yml looked it up by its `autorelease: pending` label, which release-please had not yet added for 0.2.0-beta.2: the lookup came back empty and CI was dispatched on main, leaving the release pull request without `ci-ok`. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit ce90040)
… row on one column (#399) * fix(web): hang every sidebar row on one column The sidebar's brand, its three links, the workspace folders and the epics each started at an x of their own: icons at 16, 17, 19 and 29px, words at 36, 39, 53 and 60px. As ChatGPT and Claude lay theirs out, every row's icon now stands centred in one slot (--side-in, --side-slot) and every row's words start at --side-text just past it; an epic opened out runs its line under the slot's middle and its agents' icons where the epic's words start. A workspace's folder and an epic's mark stand in that slot themselves, giving way to the chevron that folds them where the row is pointed at or the chevron has the focus, rather than the chevron taking a column of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): centre the traffic lights on the sidebar's top row Tauri's `y` is where the title bar is made to end, and AppKit keeps its buttons 9 points above that, so `y: 21` stood the lights' centre 19 down, 8 above the logo, the bell and the fold at 27 and the bar's 28. They now stand at `y: 30`, centred 28 down, and at `x: 19`, the close button centred on the sidebar's column of icons. The sidebar's top row is as tall as the bar over the view and centred on the same line, in the app and the browser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit fc0588f)
…top row (#402) The lights' position the app set never took: they stood where macOS puts them, in the window's top 32 points, with the sidebar's top row beside them and centred lower. The app now leaves them there, and the sidebar keeps that strip for them alone, drawing its top row under it, from its left edge as every row below it is. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 01d54c4)
…403) The sidebar kept a strip for the lights, but folded away the bar over the view stood beside them, centred lower, as it did before. The strip now runs across the whole window: the view is drawn under it too, and moves the window where it is dragged, and a dialog that fills a narrow window starts under it. Nothing is pushed right past the lights any more. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit dcf9d02)
…filing, TUI parity (#422) * fix(web): drop Exomyth's arrows and keep its panel's text from overlapping Exomyth no longer draws an arrow from a span to the process it started, nor offers the Arrows toggle; Perfetto draws nothing like them. The panel's facts overlapped in WebKit: a process named over several lines sits in its fact as a button, an inline-block whose baseline is its last line, so the facts' baseline alignment drew it up over the fact above. It is top-aligned now. The chart's tooltip, kept only inside the window, also lay over the panel after a click; it is kept over the chart now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web): tell nothing of the epic you are on, and mark unread epics in the sidebar A notification of the epic the page is on, while it is looked at, is read as it comes: no card and no system notification. An epic with one unread has a mark in the sidebar, as does its workspace while folded; opening the epic reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web): drop the log of every session and the sidebar's Exomyth row An epic's sidebar tree lists only its agents and their sessions: the "Log" row every agent and person appeared on, and the "Exomyth" row, are gone, as is "Open log" from the epic's menu. Exomyth opens from the epic's menu or the chart in its bar. `#/epics/<name>/logs` with no agent now opens the epic's graph; per-agent and per-person logs and sessions stay. The transcript's shared mode, which only that log used, goes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: profile the flow and its environment apart in the web page and the TUI The New epic form's Options carries two boxes, Profile flow and Profile environment, and the /flow roles page two switches, profile flow and profile environment, each remembered per flow, carried on resume and sent with the start as profile_flow and profile_environment. The epic panel says which of the two an epic was profiled for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web): open nothing on Flowverses until chosen, and list no local or user The Flowverses tab no longer shows the Add flowverse form by default: a flowverse's detail, or the form adding one, appears only once its card is chosen, and choosing it again closes it. A `?verse=` address still opens that flowverse. `local` and `user` are places read where they stand rather than indexes, so they are no longer listed among flowverses; they remain under Installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format the flowverses unit test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web): say a chat running only while its agent answers, and each turn done as a notice A chat epic's row and detail say how=running while a turn is open and done otherwise (stopped, left or over, short of failing). The host's status and run standing carry turn: running|done for chat, done where it asks what to say next. Notices tell each finished chat turn as done and never a chat stopped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): find /epics by status, flow, workspace and task, a page at a time /epics now has filter rows above the list (status, flow, and workspace on this machine), each a dropdown with counts, beside the `/` search, and pages of 50 behind Newer and Older buttons, as the web's search dialog has. How an epic went and whether a search finds it move into hmz.runtime.epic (`ending`, `found`, `ENDINGS`), which the web's /api/runs and the TUI share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): read a chat as Claude Code reads one conversation A chat is one agent and one conversation, so the TUI drops everything that is about a flow of several agents while one runs: no monitor on left, no views to round on tab/shift+tab, no outworlder line above the prompt, no Exomyth for its epic, and nothing about stopping. The status line says running while the assistant answers and done once it waits for you. ctrl+c clears the line and, on an empty one, twice leaves, as in Claude Code; /stop is refused; /clear ends the chat so the next line starts another; /exit and ctrl+q leave without asking, a host keeping a chat it holds. Tests that exercised flow-wide behaviour through chat now run chat's own code under another name (TALKS), which the interface reads as any flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): usage page, download from backups, smoother import/export, flow-update count Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): give Exomyth in the terminal what the web's has Exomyth in the TUI now does what the web page's Exomyth does, all from the keyboard: - `v` picks a range from one span to another. Press it again to hold the range and a third time to drop it. The pane shows the range's spans, time and tokens by category, and `f` zooms to the range. - `p` opens a pane on the process of the row under the cursor. - The flame is built like the web's: one stack per agent, flow or program, then its spans as they nest. `m` puts sessions apart or merges them. `t` cycles the weight through time, tokens in, tokens out and count. - A Slices tab lists every span name with its count, total, self, avg, p50, p95, max, in and out. `o` changes the sort column, `O` reverses it, and `enter` finds the slice on the timeline. - `tab` and `shift+tab` go round the three views. Lanes of any process are drawn and weighed the same way, so a lane of nested slices, such as a sampled flow, shows without special code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web): draw a chat as its conversation alone, as a chat app does A chat epic is opened on its conversation wherever it is opened: no graph, no agents or sessions under its sidebar row, no Exomyth, Log or People, and no Stop. It is said only as running while its agent answers and done otherwise, from the live `turn` where the server says it. A new epic started in its workspace ends the chat going there, as a new chat leaves the last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): open the epic from the monitor, and stop or force it there The monitor gains a `◫ epic` node, under the all-agents one, saying how the epic stands and its budget against what has gone. Enter or a click opens its page, the web's epic panel in the terminal: figures (time, spend, tokens), the budget limit by limit with a meter, what it was set up with (flow, task, agents, environments, params, whether profiled) and the flows called, live. For every flow but chat, the page has a `Stop` button while the epic runs and `Force stop` while it is stopping, each asked first in a box and only acting on the epic it was asked about. ctrl+c and /stop work as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web): a chat on another machine tells each turn done, not its stopping Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): tell of this machine's epics as notifications, and dot them in /epics The TUI now tells, as the web interface does, of every epic on this machine that ends, asks, or waits for its next chat turn -- a toast and, unless the system is turned off, the terminal bell and an OSC 9 system notification -- never of the epic on the screen. Each epic told of is dotted in /epics until it is opened there, and a Notifications page in /settings switches the same kinds and system setting the web interface's does. What is news is worked out once: hmz.web.notices moves to hmz.runtime.watching.notices, with the notification preferences, so both interfaces share it; the web routes for /api/notices move to hmz.web.notifications. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): drop the all-agents log; land a run on its first conversation No view shows every agent's lines at once any more. The monitor's first row is a line of how far the epic has got rather than a node, and tab/shift+tab round only the conversations running and the outworlders. A run lands on the agent of its first conversation as it opens, with its task put there; before then the interface's own (home) transcript is up. A question part of no conversation is shown, and answered, on the view being read; /stop and /afk work from any view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tui): download one epic at a time, mark here by directory, read usage of its days only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): read an epic back as its graph, sessions and transcripts `/epics` rows now say an epic's agents, spend, duration and backup targets, as the web lists them. An epic's sheet gains a `graph` row: its agents drawn as the monitor draws them, turns and handovers read back from its logs, its sessions under each agent, and the tree of flows it called; enter on a session reads what it did, searchable. The graph's button and the monitor's `ctrl+e` (or a click on `Exomyth` above it) open Exomyth, never for a chat. The transcript reader moves from `hmz.web.runs` into `Epics.transcripts`, which the web and the terminal both ask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): daemons page, /epics @daemon and an m menu on every list The TUI reaches other machines' daemons as the web page does: a Daemons page in /settings lists Local and every SSH daemon with how it stands, adds, edits, imports, connects and removes them; `/epics @<name>` (or the list's Daemon… button) lists that daemon's epics through its own web interface, each exported here, backed up there or copied. `m` on a row of epics, flows, flowverses or daemons opens that row's menu, the keyboard equivalent of the web page's right-click menu. A chat epic offers no Exomyth. What the daemons are and how one is reached moves from hmz.web into hmz.runtime.doing.daemons (Hmz.daemons), so both frontends share it; the web routes keep their behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: profile a flow's own Python apart from the programs its agents start `--profile` is split into `--profile-flow` and `--profile-environment`, and `profile` into `profile_flow` and `profile_environment` on `hmz.run`, `Runner`, `Epic`, the daemon's `start`, run status, `Ran`, settings and the web API. The environment is the psutil profile it was (`profile.jsonl`). The flow is sampled with pyinstrument on the thread and task it runs in, written as `flow-profile.jsonl` (`t`, `dt`, `stack` per stretch), and drawn as a `python` process under the epic: one `flow` track of `sample` slices, each frame nested in what called it, in the Exomyth timeline and the Chrome trace. The TUI's single switch now drives `profile_environment` until its own controls land; the web page is left to its own change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: sort imports after merging the profiling frontends Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: end the web walkthrough's chat with /clear, and name all nine settings pages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tui): batch apply settings to other daemons, and start or follow an epic on one The daemons page offers Batch apply, writing this machine's general, fallback, runtimes and backup settings to the daemons picked as the web page's Batch apply does. /epics @<name> starts a new epic there with n, and follows one going there from its menu, through that machine's web interface (hmz.daemon.link.remote over Remotes.followed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(daemon): a remote link asks that interface and is told its stream Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: type the stand-in URL striker Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(tui): reach batch apply and refusals through the layers the terminal may name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(tui): read a stopped epic's row whichever line it wraps on Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): find a chat between turns as the epic going, not only one running A chat whose turn is done is listed done, so #/live looked for a running epic and never named it; it now takes the epic a host holds first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: settle what review found in the batch - a new epic started on another daemon from the terminal carries the profiling and backup that directory remembers, rather than writing them off - the terminal reaches other daemons through one reach, however many threads ask first - a download from a backup lets go of its sheet whatever goes wrong - a new chat from the web replaces the chat a host holds, not merely the newest epic, and starts once that one has unwound - the epic's page says which of its parts are profiled, and nothing of the old flag Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(tui): a chat lands on its conversation, read without a reading line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let the docs build, and read a pasted path whichever line it wraps onto A line of the backup download's reference began with <target>, which VitePress took for an HTML block; a long temporary path wrapped the pasted one on CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d863c64)
…ing away when idle (#427) Every chat now runs on a host of its own (slot `chat:<hex>`) beside the workspace's own host, which still runs one flow at a time: a chat never ends or blocks a flow or another chat. A chat's host that nobody reads between turns for 60 s (`hosting.FOLD`) folds the chat away and goes; saying something in it starts `chat` with `resume=<epic>` on a new slot, which reopens the same epic and carries the assistant's session on. - daemon: hosts keyed by (workspace, slot) through hello, attach, running, host, list, remote; `where.slot`/`where.chat` - runtime: per-slot `Hmz.host(slot)`; `Host` folds, says its slot and epic; a chat picked up reopens its epic (`reopened` event); chat flow resumable, keeping its conversation as it ends - notices keyed per host, carrying `slot` - web: `/api/held/*` take `slot`; a chat started without one gets its own host; rows carry `slot` by the epic each host reports; new.js no longer stops a chat; folded chats continue from their composer - tui: `/clear` starts another chat without ending the last; flows run beside chats; `/epics` offers `continue` on a chat's epic - specs and docs updated Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 881711d)
…x, exec user, running container (#426) * feat(runtime): let a docker or swarm runtime name its containers and services (name_prefix) A docker or swarm runtime can now set name_prefix. Its containers or services are then named <name_prefix>-<role>-<8 hex> instead of humanize-<runtime>-<role>-<8 hex>, for a daemon or swarm shared with others whose rules ask for names of one's own. A runtime that sets none names them as before. - store: DockerRuntime and SwarmRuntime take name_prefix, refused where it is made unless it is one word of letters, digits, dashes and underscores from a letter or digit to a letter or digit, of at most 54 characters, so that a swarm service's name with its 8 hex digits stays within the 63 docker takes. - environing: one helper builds every container, service and job name in place of four copies; Apple container and Slurm names are unchanged. - tui: correcting a docker or swarm runtime on its form keeps its name_prefix, which the form has no row for. - specs/coganchor/machines.md and specs/runtime/flowing.md say so, as do the reference docs and the containers guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(coganchor): docker:// targets that name the exec user, environment and a wrapper A docker target may now say, after a `?` ahead of its endpoint, the user every command on its road runs as (`user=UID[:GID]`), the variables each runs with (`env=NAME=VALUE`) and the words of a wrapper each runs under (`exec=WORD`). They reach `docker exec` as `-u`, `-e` and words after the container name, in the order written, and survive `Target.describe()`, so an anchor started from the target's spelling reaches the container the same way. A target naming a user keeps the archive in `/tmp/humanize-<UID>` and the mirrors in `/tmp/humanize-mirrors-<UID>`. The far side's shell makes each one and refuses it unless it is a directory (not a link) owned by that user, then makes it 0700. Forgetting a container forgets every spelling of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(runtime): work in a running container a docker runtime names, without starting or stopping it A docker runtime may now name a container already running on its daemon (`container`). Every environment put on that runtime then works in that container: nothing is started, locked, counted against what the runtime may hand out, labelled, reaped or stopped. Bringing an environment up only checks that the container is running (`docker inspect`), and refuses one that is not with EnvUnavailable. What the environment was given is what the container's probe says it has, and closing it removes only its own mirrors, which carry 8 random hex digits so that environments sharing the container never remove one another's. The runtime may also say who every command in an environment's container runs as, with what variables and under what wrapper (`exec_user`, `exec_env`, `exec_wrapper`), for a container of its own too. They reach the container through the docker target's `user`, `env` and `exec` options. Beside `container`, a runtime may not say an image, an OCI runtime, run arguments, a name prefix (the container is named already), amounts to hand out or a container limit. The container's name is the one the runtime gives; a container of its own is still named by the shared helper, under the runtime's name prefix where it sets one. Checking such a runtime asks only whether its container is running. The terminal's docker host form keeps the new fields when a runtime is corrected there, and its row says which container a runtime works in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 555c95a)
…r's work (#423) * fix(coganchor): write a mirror's record whole, for a turn starting beside another A mirror's guard was written by truncating its record and filling it again, and every turn writes it as it starts. A turn starting while another was writing it read no record, found the directory full of files, and was refused as somebody else's: `already contains files and is not an humanize mirror`, exit 1. A placement does not pass `force`, and on a real swarm one turn was refused so in 8 of 40 rounds of three turns starting in one mirror. The record is now written beside itself and moved into place, through `atomic.writes`, so a reader finds the old one or the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(coganchor): share a mirror's bookkeeping between the turns working in it Several turns of one run on one environment work in one mirror at once, each its own `hmz internal anchor` with its own `ShadowTree`, and a tree that knew only what its own turn did took the others' work for something to put right. Its first look at a directory swept away a file another turn had written and not pushed yet, as one the target lacks; and made a placeholder over one another turn had pushed or fetched, which that turn then read as zeroes or pushed again at its next flush as its own edit, zeroes and all. Three turns at once against a `local:` target, each writing two files with a command between, lost or zeroed at least one of the six in every one of 30 rounds; on a Docker swarm a file one of three turns wrote came back zero-filled. What a tree knows -- each file's record and which were written here -- is now kept for every turn in the mirror: each appends its changes to a journal beside the mirror's guard and reads the others' from it, under a lock it holds while it changes the mirror. Every turn in a mirror is then one tree, as every process of one agent always was, and a flush before a listing pushes whatever any of them wrote. A turn starting alone begins the journal again, as a tree always began knowing nothing; where the journal cannot be had, the turn works alone and says so. Two windows the lock does not close are closed where they are: a file written after the flush that precedes a listing, which another turn's write now lands in, is left for the next flush rather than deleted, made a placeholder or fetched over; and a push records the file as it was before it was read, so a write landing while it is sent is pushed too rather than taken for what went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9b24c7b)
…e in a mirror (#424) * fix(coganchor): carry a Claude conversation into the mirror a supervised fork runs in A Claude Code agent whose turns land on another machine, but whose Claude runs here supervised in this machine's mirror of that machine's work, keeps its conversations here, in the run's sessions directory. Yet `_carry` raised `NotImplementedError` for any agent with a machine, so neither a fork into another directory nor a recalled conversation could be carried on, although Claude's forks are documented to go into another directory. And past that refusal it would have copied the transcript under the project directory for the target's own path, where no Claude here looks. - `SessionBase._mirrored(anchor, where)` is the mirror arithmetic `_workspace()` already did, factored out. - `ClaudeCodeSession._carry` copies the conversation under the project directory of the directory Claude runs in for the fork's cwd: the cwd itself without a machine (unchanged), its mirror for a supervised agent. Both spellings are written atomically, and a copy already identical is left alone. A live conversation is looked up first where Claude ran it. - An agent whose Claude runs on the other machine, natively or under a harness put elsewhere, is refused with `NotImplementedError` as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(flows): read a turn's NotImplementedError as UnsupportedOperation What a CLI has no way of doing is refused with `UnsupportedOperation` when a session opens -- a fork of a harness that cannot fork, a fork into another directory where the CLI forks only in place. When coganchor refuses the same thing during a turn, as a fork's first turn brings in the conversation it carries on, `harness_error` read its `NotImplementedError` as the `RuntimeError` it subclasses and raised `SessionError`. A flow could not tell "this harness cannot do that here" from a session gone wrong. `harness_error` now maps `NotImplementedError` to `UnsupportedOperation`, ahead of the `RuntimeError` branch. Every other `RuntimeError` is still `SessionError` (a copy of the conversation already held, among them), and `OSError` and `ValueError` keep their leaves. Both classes are `HarnessError`, so a flow catching that is unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(flows): keep and carry on sessions whose CLI runs here in a mirror A session whose turns land on another machine but whose CLI runs here, supervised in this machine's mirror of that machine's work, keeps its conversation in the run's sessions directory here, like one working here. Yet it could not be kept in a flow's state, and a kept conversation could not be carried on through it: every placement with a machine was refused as if its CLI ran on that machine. - `HarnessSession.keep` and `HarnessDriver.build` refuse a kept conversation only where the CLI runs on the other machine -- driven natively, or under a harness put elsewhere (`_afar`, the same test coganchor keeps its sessions by). The carry-on is checked against the machine as `_harnessed` settles it, before anything is built or copied. - A harness that forks only in place (Qwen Code, Grok, pi, omp, the ACP CLIs) finds a conversation by the directory it runs in. Run here for a container's or a job's work, that is a mirror named for the environment, which no earlier run's conversation was had in. Such a carry-on is refused when the session opens (`_apart`), with `UnsupportedOperation` and before the CLI starts, rather than by the CLI at its first turn. A mirror at the work's own path (ssh) is left to the CLI, as a session working here is. - specs/runtime/flowing.md: the read-back refusal is for a turn whose CLI would run on another machine, or a harness that forks only in place in a mirror apart from the work, rather than for any turn working on another machine. - docs: sessions in state, both refusal tables, the `UnsupportedOperation` leaf, and remote-execution's limitations. - tests: unit tests for keep and carry-on per placement, a Codex fork in a mirror, and an integration test that keeps a session on a docker environment's work in one `hmz exec` run and carries it on in another. Builds on the two commits before it: Claude carrying a conversation into a mirror, and a turn's `NotImplementedError` read as `UnsupportedOperation`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 28c2af2)
…o, only where started (#439) - One credential scrubber, telemetry.scrubbed, for error reports and exported bundles: ghs_, glpat-, hf_, JWTs, Bearer tokens and named secrets are now struck from reports as well. - Transactions get their own before_send_transaction: spans' code paths named as frames are, subprocess spans called "A command" without their cwd, every string scrubbed. Profiles go through a transport that renames their frames and scrubs them before sending. - crash() and snag() no longer start reporting: only a process that started it (hmz, hmz exec, a daemon host, Hmz().reports()) reports, as the docs say. - hmz update prints an install URL without its userinfo, query or fragment. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5dff35f)
…r at once (#442) The test of three turns working in one mirror at once failed now and then on Linux CI with a mirror file left zero-filled while the target had its content. Those turns did not always overlap. Each turn starts in about 150 ms and is done about 15 ms later, so a turn that started a little ahead could finish before the others began. The next turn then started alone in the mirror. As designed in #423 (and covered by the unit test `test_a_turn_alone_in_a_mirror_knows_nothing_of_turns_gone`), it began the journal again and made placeholders of the files the earlier turn had left. Those placeholders are fetched on read, so nothing was lost, but the test reads the mirror's raw bytes. Starting turn 0 a second before the others gives the CI failure every time (`'\x00\x00\x00\x00\x00' == 'one 0'`). Each turn now waits until all three have written their first file before it goes on, so all three are always in the mirror together, which is what the test claims. With the same one-second head start it passes, and it passed 200 runs of the file in a Linux container, eight at a time. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 34f3714)
… a virtual config home (#425) * fix(agents): read Codex's dropped response stream as dropped, not unrecoverable When the provider's response stream breaks partway through a turn, Codex says `stream disconnected before completion: ...` around whatever the transport said, and while it retries by itself it reports `Reconnecting... n/5` under `codexErrorInfo.responseStreamDisconnected`. None of the shared signatures matched either, so the turn's fault stayed "": the session gave it no more tries than its place asked for (none by default), and the flow saw HarnessUnrecoverable. Add both to the end of the shared `dropped` signatures. They go last rather than on Codex's profile because Codex files every failure it retries under `responseStreamDisconnected`, with that failure's own words beside it, and wraps some provider errors in the same sentence: a rate limit or a 503 inside them must still be read as what it is. A stream that breaks with nothing more specific is now `dropped`: tried again in the session, and HarnessDropped if that fails too. A line that names `content_filter` is left out: Codex reports the provider's content-filter stop in the same sentence, and a response the provider refused to finish is not a broken wire, so it stays unrecognised as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agents): start claude and codex in a virtual config home Claude Code rewrites `.claude.json` with every project it is opened in, and Codex's app server writes `projects."<cwd>".trust_level` into `config.toml` for every workspace it opens a writing thread in. Run against the user's own home, each was a change to their configuration that outlived the run. Each process of a CLI whose Profile opts in is now started with its home variable naming a directory built for that process, on the machine the CLI runs on, and removed when it stops (`hmz.coganchor.homes`): - what the CLI rewrites (`Profile.rewrites`) is copied from where it would have read it (`Profile.outside` while the variable is unset; a provider's copy where its account answers the path); a directory starts empty; - everything else of the user's home is linked, so credentials, caches and sessions are the real ones; session paths not there yet are made in the user's home, and glob-named ones the CLI made are moved there on drop; - the user-level skills dir (`Profile.lodges`) holds links to the user's skills with the flow's beside them, replacing the per-session copy into the workspace (`Profile.mounts`) for these backends; - `Profile.secures` keeps Claude's keychain item and `.credentials.json` where the user's own home has them (`CLAUDE_SECURESTORAGE_CONFIG_DIR`). A supervisor's credential and session swaps are said under the virtual home too. A fenced agent may write where its homes are built. Under a native anchor the anchor builds the home on the target (`AnchorConfig.home`, `--home JSON`, a POSIX shell script), around a projected account without any of the target user's credentials. Hooks and the flow's MCP server still reach Claude through `--settings`/`--mcp-config` and Codex through `-c`. claude: rewrites `.claude.json`, `backups`, `settings.json`; skills under `$CLAUDE_CONFIG_DIR/skills`. codex: rewrites `config.toml`; skills under `$CODEX_HOME/skills`, the server restarting when the agent's skills change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agents): give qwen, grok and pi a virtual config home instead of writing the user's Qwen Code was pointed at a settings file of humanize's through QWEN_CODE_SYSTEM_SETTINGS_PATH, replacing the machine's system layer. Grok Build had a hidden [model.hmz-*] entry merged into the user's own $GROK_HOME/config.toml for a gateway account, and pi had a humanize-* provider added to the user's ~/.pi/agent/models.json, both under a lock and taken back out on stop. Each now starts a process at a time in a virtual home (hmz.coganchor.homes): - qwen: rewrites settings.json; the effort, the PRE_TOOL_USE hook table (after the user's own, with disableAllHooks false) and the humanize MCP server are merged into that copy at the user level, and the headless defaults only where the user's file says nothing of them. Neither system variable is set any more; a user's is only watched. A settings.json that is not plain JSON fails the turn. - grok: rewrites config.toml; the gateway entry goes into the copy. The grok-config.lock, _withdrawn and GrokBuildAgent.stop are gone. A gateway account under a harness on another machine is refused up front, as under a native anchor. - pi: rewrites models.json and settings.json; the gateway provider goes into the copy. .pi.models.lock, _withdraw and PiAgent.stop are gone. - qwen, grok, pi and omp lodge the flow's skills in the home's user-level skills directory instead of the workspace's .agents/skills (pi had none; PiAgentConfig.skill_paths stays as --skill). A harness on another machine (afar) still gets no virtual home and no flow skills; the gap is documented in docs/reference/agents.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agents): lodge flow skills at user level for agy, mcode, cursor, kimi and opencode agy, cursor-agent, kimi and mcode are started in a virtual config home that carries the flow's skills beside links to the user's own; agy's is named with --app_data_dir (a new Profile.home_flag), and humanize's agy agents are written into that home's agents/ rather than the user's. mcode adds a LiteLLM gateway's provider to the virtual home's copy of config.yaml, so the account's own is never written. opencode and mimo are told the flow's skill paths under skills.paths in their config variable. None of them gets anything in the workspace any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format the merged skills test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(agents): drop the workspace skill mounts no backend uses Every backend now takes a flow's skills at the user level (a virtual home's skills/ via Profile.lodges, or opencode/mimo skills.paths), and none sets Profile.mounts, so the path that copied them into the workspace was dead. Remove Profile.mounts, skills.mount/unmount/carried and Mounted, SessionBase._mounts, AnchorConfig.carries and --carry with the anchor's claim-file carrying, the skill-name-taken snag, and the tests that kept it alive with a fake mounting backend. Docs now say nothing is ever put in the workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agents): write back credentials a CLI replaces inside its virtual home (#443) A CLI that saves a file by writing another and renaming it over the name replaced the link in its virtual home with a file of the home's own, so a refreshed token (codex/grok/pi auth.json, claude .credentials.json, kimi credentials, ...) or a session file it replaced went with the home. Home.drop now writes each link so replaced back to what it pointed at, or to the account's file a supervisor answers that path with: atomically, with the mode the real file had, a directory a new or changed file at a time, and never over a real file as new or newer. Rewrites copies still go. On a native anchor's target, SCRIPT lists its links beside the home and BACK does the same before the session is swept. agy's own agents/ directory, which it puts in place of the link deliberately, is not written back. Skills sent to a native target are written a file at a time beside themselves and renamed into place with their owner's mode bits, so a script stays executable; one that cannot be written whole is removed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Zijian Zhang <35801754+futrime@users.noreply.github.com> (cherry picked from commit 2efd879)
…c, refuse cross-machine workspace hooks (#444) * docs(specs): add features/btw.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): draft features/epics.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/placements.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/chat.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add specs README and api/cli.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): number epics clauses in three levels Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/agents.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add api/sdk.md (WIP) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add api/flows.md, the flow API in normative clauses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/web.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/environments.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): tighten api/flows.md wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): tighten placements and environments wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): draft features/monitoring.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): resolve review findings in environments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/accounts.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): draft features/backups.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add api/settings.md, tighten cli clauses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add api/daemon.md (WIP) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/daemons.md and features/desktop.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/tui.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): draft features/telemetry.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add features/updates.md and tighten wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web,tui): pick the btw agent in the panel and keep it with the epic Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): add api/http.md and tidy api/daemon.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): resolve review findings in cli and settings specs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): align S7 feature specs with code after review Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agents): refuse workspace config that would run code on another machine than the env's A CLI starts the programs its project names (hooks, stdio MCP servers, helper commands, plugins) where the CLI runs. Supervised and third-machine (afar) placements run the CLI apart from the env's work, so such a program would run on the harness machine rather than the env's. Each CLI's Profile now declares, as data, the project files and keys that can start a program (`Profile.launchers`). `hmz.coganchor.crossing` reads them off the env's machine, in the session's directory and each one above it up to the workspace. When a session opens, moves or carries on, and before each turn, a supervised or afar session whose project sets any of them is refused with UnsupportedOperation, naming each file and key. Local, native and harness-on-the-target placements are not asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): specify every file hmz creates, under specs/api/files/ Seven documents in the shared clause format: the home, a workspace, epic directories and the .hmzepic archive, the machine and cache directories, harness homes (virtual config homes, accounts, sessions kept in epics), the macOS app, and targets (envs, bundles, mirrors, containers, per-turn homes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): replace specs with api/ and features/, align them with what was built, and say epic in the UI Remove the old specs, which mirrored src/hmz and constrained internals. Align the new btw, cross-machine and TUI clauses with the behaviour #433 and #437 implement, list each CLI's program-running workspace configuration, point the docs and the stack diagram at the new files, and rename the /epics rows and messages that still said run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): resolve review findings: contradictions, duplicates, leakage, wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): say which harnesses #425 gives a virtual home, and that replaced links are written back Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 11816bb)
…er afar (#445) A harness put on a machine of its own (`AnchorConfig.harness`, `elsewhere.afar`) got no virtual config home and none of the flow's skills, so the CLI there ran in that user's real home and could rewrite its configuration (`~/.claude.json`, Codex's trust in `config.toml`), against specs/features/agents.md §5.2 and api/files/harness-homes.md §6. - homes: a home's managed items are data (`Virtual.merges`), applied by `build` wherever it runs through functions drivers register with `homes.merger`; a merge that replaces a link is never written back. - qwen (effort, hooks, MCP server), grok and pi (gateway entries) and agy (its agents) say theirs from a session's `_merges` instead of writing into a built home; `AgentBase.virtual` says what a process's home is made of, `homed` builds it here. - Under afar the anchor carries that spec (`AnchorConfig.home`, the session's skills and merges); `afar` puts the skills on the harness's machine under its cache by digest, and the `hmz internal anchor` there builds the home around the CLI it supervises, keeps it there (paths and programs), and removes it when the CLI exits. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 6333ed5)
…lacements (#446) A session's placement is now two plain settings: its env location (`local`, or `standalone` on any other machine) and its harness location (`local` here, `env` natively on the env machine, or `standalone` on a harness runtime). The four named placements -- local, supervised, native, afar -- are gone from the specs, the code and the docs. A runtime's ordered `affinity` is replaced by one `harness` field: `local`, `env` or `<backend>:<name>`, defaulting to `env`. Nothing falls back any more: where the setting cannot hold the harness -- the CLI is missing, the fence cannot be held, the runtime cannot be reached, or a PreToolUse or PermissionRequest hook would gate a CLI on another machine -- the session, and before the flow the whole epic, is refused with `<where>: <cli>'s harness cannot run where its harness setting (<setting>) says: ...`. Work on no saved runtime is `env`. Each session's epic line records `env` where it recorded `self`. BREAKING CHANGE: runtimes take `harness` instead of `affinity`, and work on another machine with no setting no longer drops to a harness here when the CLI is missing or a gating hook is hung: set the runtime's harness to `local`. A saved `affinity` is still read, as its first entry with `self` as `env`. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit be7e586)
…message (#447) * fix(tui,web): call an execution of a flow an epic in every label and message Rename the /epics rows and Export sheet, the /epics and /resume descriptions, the /resume refusals, the budget rows and sheet, the afar prompt, the monitor's environment about, the General page summary and the web server's refusals that still said run. Update the tests and docs that quote them and the tapes that wait on them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(demo): re-record the casts that show a label renamed to epic Step past the graph row on the way to export epic in epics.tape, which stopped on graph since that row was added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): keep the refusals specs/api/http.md quotes as it words them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): say epic in the refusals specs/api/http.md quotes, and in the spec Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: keep main's harness and btw docs that the merge resolution dropped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 3de80e8)
… release Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cherry-picks what landed on
mainsincerelease/0.2was last brought up to date, for0.2.0-beta.3. #387, #392, #395, #398, #399, #400, #402 and #403 add nothing to the changelog, since #396 and #404 brought them already. Afterwardsrelease/0.2holds the same code asmain, and only the version files,CHANGELOG.mdandrelease-please-config.jsondiffer.feat: chat like Claude, no all-sessions log, Exomyth fixes, split profiling, TUI parity (#422)
feat: many chats, each its own epic beside the workspace's flow, folding away when idle (#427)
feat(runtime): let docker and swarm runtimes share a host: name prefix, exec user, running container (#426)
fix(coganchor): let turns working in one mirror at once see each other's work (#423)
fix(flows): keep and carry on supervised sessions, whose CLI runs here in a mirror (#424)
fix(telemetry): scrub reports as bundles are, timings and profiles too, only where started (#439)
fix(coganchor): keep the turns of the shared-mirror test in the mirror at once (#442)
fix(agents): retry Codex's dropped streams and start every harness in a virtual config home (#425)
feat: rewrite specs as api/ and features/, pick the btw agent per epic, refuse cross-machine workspace hooks (#444)
fix(coganchor): build a harness's virtual home on its own machine under afar (#445)
BEGIN_NESTED_COMMIT
feat(runtime)!: say where a harness runs with one setting, not four placements (#446)
BREAKING CHANGE: runtimes take
harnessinstead ofaffinity, and work on another machine with no setting no longer drops to a harness here when the CLI is missing or a gating hook is hung: set the runtime's harness tolocal. A savedaffinityis still read, as its first entry withselfasenv.END_NESTED_COMMIT
🤖 Generated with Claude Code