Architecture

Harnez uses a local client/server architecture. Running harnez connects to an existing server or starts one. The client and server still work as one application.

Runtime

The server is a long-lived process implemented in TypeScript on Bun. The TUI stays thin. Expensive session and agent state lives on the server, not the client, so the interface can be killed and reattached without losing work. Each top-level request runs inside a fresh task runtime with its own capability snapshot and execution ledger.

The server-owned subagent manager coordinates isolated child runs. It enforces the 16-child limit, owns lifecycle state, cancellation, waiters, and recovery, and publishes live state to the TUI. It does not own model execution or conversation storage: the task runtime and agent runtime execute each child, while SQLite child lanes, task records, and validated handoffs remain the authoritative durable state. Live execution state is memory-only and is not resumed after a server restart.

flowchart LR
    T[TUI] --> S[Harnez server]
    S --> C[Context tree and lanes]
    C --> A[80% admission gate]
    A --> L[Bounded LLM condensation]
    L --> F[Deterministic fallback]
    A --> M[Provider request]
    M --> O[OpenTelemetry metadata]

At 80% of the usable input budget, admission targets 60%. When LLM compaction is enabled, Harnez starts one bounded condensation operation and can retry invalid output once. An unavailable or failed operation uses the deterministic checkpoint fallback. With compaction.enabled set to false, fallback waits until the working set exceeds its budget. A provider context-length response creates a recovery checkpoint retaining the current turn and retries once.

Agent loop

A turn sends a user message through context construction into a model call. Tool calls run and their results return to context for the next model call. The turn ends when the model responds without requesting further action.

User message → context → model call → tool calls
  → tool execution → tool results → context → model call → … → response

Three inputs steer that loop from the TUI:

  • Enter modifies the active task in place. The model restarts around the new message.
  • Option + Enter queues a follow-up task for after the current one finishes, without altering it.
  • Esc aborts the current foreground step, including generation, running tools, and anything pending in that step.

Subagents

Subagents are isolated agents with predefined profiles, such as implementer, explorer, and reviewer. A subagent does not inherit the parent's full transcript. It starts with its profile, relevant skills, applicable repository instructions, and an explicit task brief. It reports status, changes, verification, and unresolved issues. The parent never imports a child's raw transcript.

Context management

Harnez separates lossless session history from the bounded working set sent to a model. Every context item is tracked as pinned, active, retained, or archived. System instructions remain protected. User messages and explicit pins collapse into one bounded rolling summary only as a final fallback. Tool output is externalized when Harnez creates it. The model sees a bounded preview and a reference that it can use to recall the exact result later.

When the context budget reaches 80%, Harnez targets 60% of the usable input budget. If LLM compaction is enabled, it attempts a bounded condensation before archiving completed tool exchanges and work in a deterministic order. Failed condensation uses the deterministic checkpoint fallback; disabled LLM compaction defers fallback until the budget is exceeded. Capability content is charged against the same final input budget. Nothing is deleted. The full event history persists independently of the model's current context. Main and child lanes share immutable prefix nodes but have independent heads and revisions. Checkpoints record their covered range and exact retained tail, so a restart reconstructs the same provider-visible history.

Tool discovery

Each task gets a capability catalog for workspace tools and model-invocable skills. capabilities_list and capabilities_search return compact metadata; capabilities_inspect returns one validated contract. A discovered tool can be admitted with tools_load, while skills use skills_activate. Core workspace tools are loaded when the task starts. See Tool discovery for the full flow.