UH v0.11.0: run control, guarded teams, the hive and the delivery loop - #239
Draft
Mateo-GarciaL wants to merge 90 commits into
Draft
Mateo-GarciaL wants to merge 90 commits into
Mateo-GarciaL wants to merge 90 commits into
Conversation
The 1.0 branch was validated on Windows, so its CI job failed on Linux for four platform-dependent assumptions. - src/harness/tool-guard.ts resolved paths with the host path module, so Windows-style targets (out\x, C:\other\x) were a single literal filename on POSIX and the containment checks misjudged them. Paths now normalize to one canonical form independent of the host, which is what the guard needs: it judges commands written for whatever platform the agent targets. - tests/acceptance.test.ts created temp dirs under the hardcoded Windows drive path T:/tmp; they now use os.tmpdir(). The Windows-style workspace and artifact_root strings in the evidence fixtures stay as data. - tests/independent-review.test.ts pointed cli_command at a fixture with no shebang and no exec bit; it is now a real executable on POSIX. - tests/oh-my-pi.test.ts compared the child cwd against the unresolved sandbox root; on macOS /tmp resolves to /private/tmp. Before: 12 failed / 88 passed across these files. After: 1058 passed / 7 skipped / 0 failed across the full suite, with typecheck and build clean.
The cancellation regression asserted that process.kill(pid, 0) throws for the killed grandchild. That tests whether init reaped the process, not whether the harness terminated it: a killed orphan is re-parented and stays visible as a zombie until init reaps it, and kill(pid, 0) succeeds for a zombie. In a container whose PID 1 does not reap orphans the zombie persists indefinitely, so the assertion failed on CI while passing on macOS, where launchd reaps promptly. The test now polls until the process is gone or is a zombie on Linux, which is what "terminated" actually means for a process whose parent has already exited. The SIGKILL group termination itself was working; only the assertion was wrong. Verified in a Linux container with bun as PID 1 and node present, matching the runner: the assertion reproduces "expected [Function] to throw an error" before this change and passes after it. Full suite in that same Linux environment: 91/91 files, 1058 passed, 7 skipped, 0 failed, with typecheck and build clean.
…imeout assertion Two tests decided whether a killed process was gone by asking whether its pid still exists, which only proves that init reaped it. The cancellation regression hit this on CI; the verify timeout assertion had the same latent flake — it polled process.kill(pid, 0) and would have hung until its poll timeout if the child were ever left as an unreaped zombie rather than reaped. Extracts the zombie-aware probe into tests/process-state.ts and uses it for both assertions, so the "a zombie has stopped executing" reasoning lives in one place. The rest of the suite was audited for the same pattern: - tests/runtime-process.test.ts:94 is inside a Windows-only test, skipped on Linux, and Windows has no zombies, so it needs no change. - the remaining `.pid` matches in tests are fixture data, not liveness checks; the tui-suspend kill is an injected stub. Verified in a Linux container matching the runner (bun 1.3.14): full suite 91/91 files, 1058 passed, 7 skipped, 0 failed, with typecheck and build clean. Probe discrimination checked directly there — a live process reads as not terminated, and an orphaned grandchild that process.kill(pid, 0) still reports as present reads as terminated.
…s deny worker spawns Agent-client denial was a whole-command text match gated on deny_network_clients. codex.cmd, omp.exe, path-qualified binaries, claude, and uh mission run all passed, needs_network disabled the check entirely, and grep -r omp src was a false denial that consumed the denial budget. Judge segment heads, launcher targets, nested shell bodies and command substitutions instead. Decouple the check from network denial; an explicit empty agent_clients list is the only opt-out. Deny UH commands that start paid runtimes while keeping the orchestrator controller allowance.
…oject fleet policy A runtime that resolves helper or sub-agent models from operator-global settings could spend on a route other than the assigned one: only the top-level session was pinned, the runtime's own sub-agent tool was never judged by Tool Guard, and route attestation read only top-level messages. - deny native sub-agent tools by name; guard.allow_native_subagents opts in - attest delegated routes from structured tool metadata; stop with route_mismatch - write a per-run oh-my-pi overlay pinning every model role and removing the sub-agent tool - fleet.routes in the project file authorizes models per adapter and role; run, run-all and run-team workers are refused before spawn, --force included - the acceptance report stops linking absent evidence and resolves present links from docs/acceptance/
git worktree prune runs from any linked worktree and deletes the registration of every worktree whose directory is missing at that moment, including ones owned by another controller or stored on a removable volume. Create team and sandbox worktrees with --lock --reason, unlock before removal, and clean up only the affected registration when a directory was deleted out of band.
Add an optional runtime_config.model, pass it to codex exec, and run Codex through the shared supervised process runner so a run that reports another model stops with route_mismatch and one that never attests stops with route_unverified. Recognize Codex thread and turn events as route metadata. Without a configured model behavior is unchanged.
The adapter check ran the CLI without --no-auto-update, so a health probe could start a background update that replaces the runtime while another run is launching it. Pass the flag and parse the version from output that may carry an update banner and ANSI escapes.
Read-only index over run directories with grouping and a success-rate versus cost Pareto frontier. Unknown cost, tokens and durations stay unknown and are never treated as zero; unreadable artifacts are skipped.
Offline OTLP/JSON export following the GenAI semantic conventions: invoke_agent, chat and execute_tool spans with deterministic ids and usage attributes. Tool arguments, results, message text and prompts are never exported; tool targets are opt-in. Streaming deltas are skipped.
Processes started through Win32_Process.Create, scheduled tasks, services, setsid, systemd-run, disown, at, batch, crontab edits and backgrounded nohup leave the Job Object or process group, so the memory cap and tree kill never reach them. Add the containment_escape class, judged by executable position like agent clients. Read-only forms such as schtasks /query stay allowed.
A runtime that reports Qwen/Qwen3.8-Flash for a mission pinned to qwen/qwen3.8-flash was stopped with route_mismatch. Compare provider and model identifiers after trimming and lowercasing, reconcile an optional provider prefix, and use the same comparison for fleet admission. No alias table and no partial matching; a different model still mismatches.
A model that reasons for minutes before its first tool call was stopped as stalled while working. Reasoning deltas now refresh the stall clock, bounded by max_thinking_ms (default four times the stall timeout). Repetitive reasoning is detected from window frequencies and does not count as live. Reasoning text is never persisted. Text deltas remain non-progress.
Replacing runtime-control.json by rename fails with EPERM on Windows while any process has the file open, and the failed heartbeat stopped the run with controller_error. Retry the rename with bounded backoff on EPERM, EACCES and EBUSY, keep supervising when a periodic heartbeat cannot be persisted, and keep terminal writes strict. Readers tolerate a momentarily unreadable file.
uh mission run gains --quiet, always ends with a single UH_RESULT JSON line without absolute paths, and exits 0 passed, 1 failed, 2 blocked, 130 cancelled. Add uh observatory runs with grouping and a Pareto marker, and uh observatory export --otlp for one run.
Worker worktrees were committed with git add -A, so files the harness writes into the worker root reached the leader branch: the Command Code hook configuration with absolute local paths, the worktree-local ignore file, and the audit log when the repository already tracks it. Stage with pathspec exclusions for the protected roots and gate the commit on a non-empty index, so a worker that touched only harness state produces no commit.
…ndbox uh mission run fell back to the project root when no sandbox was bound to the mission, so a guarded worker edited the operator's working tree. Refuse before spawn with exit code 2 and a blocked settlement line unless --no-sandbox is explicit, and print the routing in dry-run. Acceptance campaigns pass the flag for their own workspaces.
… hard-stop tamper A command that changed directory before a relative write was judged against the worker root, so cd <elsewhere> && Set-Content <relative path> wrote outside the sandbox. Track cd, pushd, popd, Set-Location and Push-Location through a command, including nested shell bodies and an explicit cwd on the tool input, and resolve relative targets against the effective directory. An unresolvable directory change denies every later write in that command; reading elsewhere stays allowed. Add the guard_tamper class for writes to the guard policy or log and to harness state outside the worker root; supervision stops such a run with policy. Harness state inside the worker root remains protected_root.
Add the architecture record for who may start an agent, how a route is pinned, attested and authorized, how write targets and the process tree are contained, how liveness is judged for reasoning models, why observers cannot fail a run, and the rules for running the harness on its own repository. Bring the Unreleased changelog in line with the changes and update the roadmap entry on model identity.
…inment A session template bundles how an attempt is executed: adapter, runtime overrides, limits, recovery, guard defaults and a budget tier. Mission values win over template values, write roots are never widened by a template, and a strict template refuses missions without narrow explicit write roots, with native sub-agents, or with network clients allowed. Ships generic examples and the architecture record.
…requests Replace the single broad verdict question with one yes/no question per criterion that has no deterministic result, plus a fixed battery about the report itself, and compose the three-verdict result in code with named thresholds. Deterministic results are never sent and always dominate. Drop the tamper question; tamper is a deterministic fact supplied by the caller. Requests time out, retry 429 and 529 with backoff, and return a discriminated result that separates disabled, unavailable and malformed. Record the versioned model that answered, latency and usage in the receipt; the requested model can be pinned through UH_TYPESAFE_MODEL.
Newline-delimited JSON-RPC server with no new dependency, answering both the stateless 2026-07-28 revision (server/discover, resultType, cacheable tool list) and the 2025-11-25 handshake. Three read-only tools: uh_status, uh_runs and uh_run. Identifiers are validated before any path is built, every returned path is relative, and no prompt, runtime output or file content is returned.
Adapters handed runtimes a hook inside the mutable build directory, loaded at start and, for hook-style runtimes, on every tool call. Rebuilding during a run could replace the guard under a live worker. Publish the hook and the closed set of files it imports, including its package dependency, into a content-addressed per-user cache with atomic publication, verify an existing snapshot before reuse and fail closed on a mismatch.
A missing sandboxes index now means an empty index and is created on demand, so a project can stop tracking runtime state and a fresh clone still works; an invalid index still fails loudly and is never overwritten. The directory backend retries once with --no-hardlinks when git cannot hard-link objects because the object store lives on another volume.
… harness runtime_config.role: orchestrator arms the guard with controller commands, so harness mission commands pass while agent CLIs, native sub-agent tools, forced runs and chained commands stay denied. Workers are unchanged. Fleet admission applies the role.
…stem One Verification builds per-criterion state: criteria with a check command carry their deterministic result and are never sent; the others carry their description and only facts the harness established. Tamper comes from the policy stop code. Deterministic failures still dominate.
uh mcp serve exposes the read-only MCP server on stdin and stdout; only protocol messages are written to stdout and diagnostics go to stderr. uh mission run and dry-run accept --template. Mission values win over the template, command-line overrides win over both, fleet admission runs after application, and strict containment, unknown or invalid templates are blocked with exit code 2 before spawn. The run records the adopted template and the run index groups by template and tier.
UH-Team-Role: worker
…ve-fix-7/leader UH-Team-Role: leader
UH-Team-Role: worker
# Conflicts: # tests/fixtures/runtime-events/README.md
UH-Team-Role: worker
…m/wave-fix-10/leader UH-Team-Role: leader
UH-Team-Role: worker
UH-Team-Role: worker
…eam/wave-fix-9/leader UH-Team-Role: leader
UH-Team-Role: worker
…ave-fix-12/leader UH-Team-Role: leader
UH-Team-Role: worker
UH-Team-Role: worker
UH-Team-Role: worker
UH-Team-Role: leader
UH-Team-Role: worker
…/leader UH-Team-Role: leader
UH-Team-Role: worker
UH-Team-Role: leader
# Conflicts: # src/cli.ts
This was referenced Sep 23, 2026
Mateo-GarciaL
force-pushed
the
release/v0.11.0
branch
from
September 24, 2026 23:56
ea9a36f to
a1179c6
Compare
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.
Everything since 0.9.0, the last published version, as one unreleased 0.11.0 line and one draft into
main. This branch starts at the consolidation commit below; the rest arrives through ten stacked pull requests, each reviewed and merged into the one beneath it until everything reaches this branch. Supersedes #237, which carried the same starting commit on a branch whose name did not match its version.Starting point (this branch,
ea9a36f)--yolosafety), deadline grace preservation, and zombie-safe process termination.Validated at
ea9a36f:bun install --frozen-lockfile,bun run typecheckandbun run buildclean;bun run test91/91 files, 1,072 tests passed; Linux CI passed on #238.Stacked pull requests
Run control that needs no model (
uh ps,uh wait,uh report,uh steer,uh kill), guarded team execution in isolated worktrees, a single fail-closed guard core for every runtime, an intervention ledger, notifications, ACP and MCP integration, operator post-checks the agent never sees, the delivery-loop commandsuh queueanduh land, and the hive: a shared, tamper-evident blackboard that only the controller writes.Merge order is 1 to 10: each pull request targets the branch of the one before it, and part 1 targets this branch. At the top of the stack (
stack/v0.11.0-10-notify-toast):bun run typecheckclean; full suite 147/147 files, 2,196 tests passed. Every commit is authored and committed by Mateo-GarciaL.Known issues
docs/known-issues.mdlists every open defect, gap and unproven claim of the line, each marked confirmed or reported. Nothing after 0.9.0 is published on npm.