Skip to content

UH v0.11.0: run control, guarded teams, the hive and the delivery loop - #239

Draft
Mateo-GarciaL wants to merge 90 commits into
mainfrom
release/v0.11.0
Draft

Mateo-GarciaL wants to merge 90 commits into
mainfrom
release/v0.11.0

Conversation

@Mateo-GarciaL

@Mateo-GarciaL Mateo-GarciaL commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

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)

  • Adapters & Tool Guard: Native Command Code and Claude Code adapters with path-contained Tool Guard and Windows/POSIX platform-neutral normalization.
  • Runtime Supervision & Recovery: Process-tree lifecycle management, non-interactive permission enforcement (--yolo safety), deadline grace preservation, and zombie-safe process termination.
  • Governed Verification: Independent reviewer integration, deterministic check preservation, and optional typed semantic evaluation receipts (TypeSafe System One / JEV).
  • Quality & Platform Neutrality: Platform-neutral fixes for POSIX/Windows (hermes-plugin pytest pins, cross-platform paths, process termination probes).
  • Repository Hygiene: Excludes local run histories from the publishable repository, removes private identifiers, and documents remaining mechanism limitations in the roadmap.

Validated at ea9a36f: bun install --frozen-lockfile, bun run typecheck and bun run build clean; bun run test 91/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 commands uh queue and uh land, and the hive: a shared, tamper-evident blackboard that only the controller writes.

  1. v0.11.0 (1/10): supervision, guard and evidence hardening #240: supervision, guard and evidence hardening
  2. v0.11.0 (2/10): run control: steering, the live run digest and acceptance invariants #241: run control: steering, the live run digest and acceptance invariants
  3. v0.11.0 (3/10): workers and teams: headless console, prompts off the command line, uh wait #242: workers and teams: headless console, prompts off the command line, uh wait
  4. v0.11.0 (4/10): intervention ledger, session templates, one guard core, notifications #243: intervention ledger, session templates, one guard core, notifications
  5. v0.11.0 (5/10): review fixes, ACP, windowed reads for Command Code, operator post-checks #244: review fixes, ACP, windowed reads for Command Code, operator post-checks
  6. v0.11.0 (6/10): memory admission, long paths, uh queue and uh land #245: memory admission, long paths, uh queue and uh land
  7. v0.11.0 (7/10): documentation, agent guides, land review binding and known issues #246: documentation, agent guides, land review binding and known issues
  8. v0.11.0 (8/10): the hive: a shared, tamper-evident blackboard only the controller writes #247: the hive: a shared, tamper-evident blackboard only the controller writes
  9. v0.11.0 (9/10): uh ps counts turns for Claude Code and ACP runs #248: uh ps counts turns for Claude Code and ACP runs
  10. v0.11.0 (10/10): Windows toasts under a registered app id, honest delivery reports #249: Windows toasts under a registered app id, honest delivery reports

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 typecheck clean; full suite 147/147 files, 2,196 tests passed. Every commit is authored and committed by Mateo-GarciaL.

Known issues

docs/known-issues.md lists every open defect, gap and unproven claim of the line, each marked confirmed or reported. Nothing after 0.9.0 is published on npm.

Mateo-GarciaL and others added 30 commits September 17, 2026 17:53
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.
# Conflicts:
#	tests/fixtures/runtime-events/README.md
UH-Team-Role: worker
UH-Team-Role: worker
UH-Team-Role: worker
UH-Team-Role: worker
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.

2 participants