跪拜 Guibai
← All articles
Artificial Intelligence · Agent · AIGC

The Harness That Gives AI Agents Real Hands: OpenHands' PTY Sandbox, Rebuilt in 500 Lines of Java

By 码里奥的大冒险 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

An agent that cannot safely run real commands with persistent state is a toy. The PTY session, sentinel protocol, and output trimming shown here are the minimum viable architecture for any agent that modifies files, runs CI, or operates a terminal — and virtual threads make it cheap to run thousands of such sessions concurrently on a handful of OS threads.

Summary

Three prior systems gave an agent a thinking loop, deterministic control flow, and self-editing memory — but none could safely touch a real filesystem or run commands with persistent state. OpenHands fills that gap with a Harness architecture built on four physical pillars. Container isolation makes `rm -rf /` physically impossible. A long-lived bash session inside a tmux pane means `cd` and `export` persist across commands, something `bash -c` per command can never do. A sentinel protocol injected into the shell prompt captures real exit codes and marks command boundaries precisely.

When a command produces 100,000 lines of output, a trimmer keeps the head, tail, and every line containing Error or Traceback while discarding the middle — preventing token-window amnesia without losing critical diagnostics. A defensive parser never crashes on malformed model output; it degrades gracefully into an InvalidFormatObservation that the model reads and uses to self-correct on the next turn. The entire system communicates through a typed event stream where Actions and Observations alternate, making every step auditable and replayable.

The article strips OpenHands' production codebase down to the essential 5% — the event system, the persistent PTY session with its sentinel protocol, and the output safeguards — then rebuilds it in about 500 lines of zero-dependency Java 25 code using virtual threads, sealed interfaces, and record pattern matching. A Groovy version collapses the same logic into a single 333-line file, demonstrating where dynamic languages win for prototyping and where strong typing pays off for production harnesses.

Takeaways
Container isolation is not a security add-on; it is the architectural premise that lets an agent act freely without destroying the host.
A persistent bash session (via tmux or a single long-lived process) is the only way to make `cd`, `export`, and shell state survive across multiple command executions.
Injecting a sentinel marker into the shell prompt (PS1) captures the real `$?` exit code and marks the exact end of command output, eliminating guesswork.
Two virtual-thread drainers — one for stdout, one for stderr — prevent pipe-buffer deadlock that would freeze a long-lived bash process.
Log trimming keeps the first N lines, the last N lines, and every line matching Error/Traceback keywords; the middle is replaced with an explicit placeholder stating how many lines were removed.
A defensive parser never throws exceptions on malformed model output; it degrades into a typed InvalidFormatObservation that the model reads and uses to self-correct.
Sealed interfaces and record types in Java turn event-type matching into a compile-time exhaustive check — missing a new event type becomes a compiler error, not a 3 a.m. production bug.
Virtual threads (JDK 21+) let thousands of blocking I/O agent sessions queue on a few dozen platform threads, removing the old 1:1 thread-per-agent bottleneck.
The Groovy re-implementation is 333 lines versus Java's 793, but sacrifices compile-time exhaustiveness for runtime map flexibility — a tradeoff that matters when an event system grows to dozens of action types.
Conclusions

The four-pillar Harness — isolation, session persistence, trimming, guardrails — is not OpenHands-specific; it is the checklist any agent framework must satisfy before it can run unattended in production.

The sentinel protocol is a clever abuse of the shell's own prompt-rendering lifecycle: bash evaluates `$?` and `$(pwd)` at PS1 render time, so the metadata is always truthful and requires no separate side-channel.

OpenHands' real codebase is 90% engineering scaffolding (Docker SDK, k8s, frontend, telemetry); the architectural soul lives in roughly 5% of the code — the event system, the PTY session, and the output safeguards.

The jump from CodeAct-era ` ```bash ` block parsing to strongly-typed `TerminalAction` tool calls marks the moment agent-command protocols stopped being text-parsing problems and became schema-enforced contracts.

Virtual threads are a perfect fit for agent runtimes: command execution is pure blocking I/O, and collapsing thousands of waiting tasks onto a small carrier pool is exactly the problem Project Loom solved.

The Groovy-to-Java comparison surfaces a real engineering tension: dynamic map-and-closure styles erase ceremony and speed up prototyping, but sealed types catch event-dispatch bugs at compile time — a property that grows in value as the number of action types increases.

Concepts & terms
Harness
An architectural kit — not a single line of code — that gives an AI agent safe, persistent, observable access to a real execution environment. It rests on four pillars: sandbox isolation, session state persistence, log trimming, and defensive guardrails.
PTY (Pseudo-Terminal) Session Persistence
Running all commands inside a single long-lived shell process (e.g., a tmux pane) so that working directory, environment variables, and shell functions physically persist across multiple command executions — impossible with per-command `bash -c` invocations.
Sentinel Protocol
A technique that rewrites the shell prompt (PS1) to emit a unique marker line containing JSON metadata — including the real exit code `$?` — after every command. The harness reads stdout until it sees the sentinel, then extracts the exit code precisely.
ObservationTrimmer
A safeguard that prevents command output from blowing up the LLM context window by keeping only the first N lines, the last N lines, and every line matching critical keywords (Error, Traceback, Exception), replacing the middle with an explicit placeholder.
Defensive Parser / Degrading Parser
A parser that never throws exceptions on malformed model output. Instead, it classifies bad input into typed degradation results (e.g., Malformed with a reason and correction hint) and feeds them back into the event stream so the model can self-correct.
Sealed Interface + Record Pattern Matching
A Java 21+ idiom where a sealed interface locks down the set of permitted subtypes, and record types carry data. Switch expressions over sealed types are exhaustiveness-checked at compile time — omitting a case is a compiler error, not a runtime bug.
Virtual Threads (Project Loom)
JDK 21+ lightweight threads that decouple Java threads from OS threads. Blocking I/O operations (waiting on pipes, processes, timeouts) no longer pin an OS thread; thousands of concurrent blocking tasks can multiplex over a small pool of platform threads.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗