harness

package
v0.48.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package harness detects which agent harness (claude/codex/...) is running in a wrapped PTY and whether it is working or idle, so the terminal-share heartbeat can stamp live agent info instead of relying on launch-time env vars. Detection is pure and platform-free: the wrapper feeds in foreground-process snapshots and byte-activity timestamps; this package never touches the PTY itself.

The design (and the timing constants) are ported from herdr's pane detection: identification is process-tree based, working/idle is PTY byte-causality based. See docs/specs/agent-detection.md.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Identify

func Identify(comm string, argv []string) string

Identify maps a foreground process (comm + full argv, argv[0] included) to a canonical harness name, or "" when the process is not a known harness. It normalizes names (case, .exe/.cmd/.bat/.ps1/.js suffixes, whitespace) and sees through generic runtimes (node, python, sh/bash/zsh, pwsh) by scanning argv for a known binary name.

Types

type ActivityTracker

type ActivityTracker struct {
	// contains filtered or unexported fields
}

ActivityTracker classifies a wrapped harness as working or idle from PTY byte causality alone — no screen parsing. Output observed while no recent user input is in flight marks the harness working for outputActivityWindow; quiescence past the window reads as idle. Callers pass `now` explicitly so tests never sleep.

Not safe for concurrent use; the wrapper serializes observations onto its detection goroutine.

func NewActivityTracker

func NewActivityTracker(start time.Time) *ActivityTracker

NewActivityTracker starts tracking for a harness identified at `start`. The first startupGraceWindow after start reads as idle regardless of output.

func (*ActivityTracker) ObserveInput

func (t *ActivityTracker) ObserveInput(now time.Time)

ObserveInput records user-originated input at `now` (local stdin, remote .stdin messages, alert auto-submission).

func (*ActivityTracker) ObserveOutput

func (t *ActivityTracker) ObserveOutput(now time.Time)

ObserveOutput records PTY output bytes from the child at `now`. Output inside the startup grace is boot noise, and output inside the input-taint window is presumed local echo / prompt redraw; neither counts as agent work.

func (*ActivityTracker) State

func (t *ActivityTracker) State(now time.Time) State

State returns StateWorking or StateIdle as of `now`: working while agent-attributed output is fresher than outputActivityWindow, idle otherwise. The window doubles as the working→idle hysteresis.

type Detection

type Detection struct {
	Harness  string // canonical harness name, "" when none
	ChildPID int    // foreground pid when a harness is identified
	State    State  // StateUnknown when Harness is ""
}

Detection is the point-in-time snapshot the heartbeat loop stamps into payloads. Zero value means "no harness in the foreground".

type Detector

type Detector struct {
	// contains filtered or unexported fields
}

Detector owns harness detection for one wrapped PTY: it polls the foreground process via the injected inspector and folds byte observations into an ActivityTracker. Inspect is the platform seam — TIOCGPGRP + process lookup in production, a fake in tests.

Inspect errors retain the previous identification (a transient ps failure must not flap `ppz who`); a successful inspect of a non-harness clears it. A harness change resets the activity tracker so each identification gets its own startup grace.

Safe for concurrent use: the wrapper calls Observe* from its PTY I/O goroutines, Poll from the detection ticker, and Snapshot from the heartbeat loop.

func NewDetector

func NewDetector(inspect func() (ForegroundProc, error)) *Detector

NewDetector creates a Detector around the given inspector. Activity tracking (and its startup grace) is keyed to identification time — the Poll that first sees a harness — not to PTY spawn time, so the detector needs no clock input of its own.

func (*Detector) ObserveInput

func (d *Detector) ObserveInput(now time.Time)

ObserveInput forwards user input activity to the tracker.

func (*Detector) ObserveOutput

func (d *Detector) ObserveOutput(now time.Time)

ObserveOutput forwards PTY output activity to the tracker.

func (*Detector) Poll

func (d *Detector) Poll(now time.Time)

Poll re-inspects the foreground process and updates identification.

func (*Detector) SetScreen added in v0.48.0

func (d *Detector) SetScreen(src func() string)

SetScreen wires an optional visible-screen source (the wrapper's live screen model, bottom lines). When set, an identified harness whose byte causality says "not working" is further arbitrated by its ScreenDetector: blocker chrome on screen → StateBlocked. The source is called with d's lock held and must not call back into d.

func (*Detector) Snapshot

func (d *Detector) Snapshot(now time.Time) Detection

Snapshot returns the current detection for heartbeat stamping.

type ForegroundProc

type ForegroundProc struct {
	PID  int
	Comm string
	Argv []string
}

ForegroundProc is what the platform inspector reports about the PTY's foreground process group: the group leader's pid, its comm (binary name), and full argv (argv[0] included).

type ScreenDetector added in v0.48.0

type ScreenDetector interface {
	// Blocked reports whether the visible screen content (the bottom
	// lines of the live screen model) shows the harness waiting on
	// human input.
	Blocked(content string) bool
}

ScreenDetector is the phase-3 extension point: a per-harness matcher over the visible terminal screen that recognizes "waiting on human input" (permission dialogs, forms, choice prompts). It is consulted only when byte causality already says the harness is not working — PTY activity remains the authority for working, the screen only arbitrates idle vs blocked (same division of authority as herdr).

func ScreenDetectorFor added in v0.48.0

func ScreenDetectorFor(name string) ScreenDetector

ScreenDetectorFor returns the screen detector for a canonical harness name, or nil when that harness has no screen patterns yet — nil simply means the harness never reports blocked. Phase 3 ships Claude Code first; adding another harness is one case here plus one screen_<name>.go pattern file (herdr's per-agent module layout).

type Spec

type Spec struct {
	Name        string
	BinaryNames []string
}

Spec describes one known harness. Extending detection to a new harness is one new row here. Names are the canonical ids `ppz agent --harness` already uses, so heartbeat consumers see one vocabulary regardless of whether the value came from env or detection.

func Specs

func Specs() []Spec

Specs returns the registry of known harnesses.

type State

type State string

State of a wrapped agent harness, as stamped into heartbeat payloads.

const (
	StateUnknown State = ""        // no harness identified in the PTY foreground
	StateIdle    State = "idle"    // harness at rest, prompt waiting
	StateWorking State = "working" // harness actively producing output
	StateBlocked State = "blocked" // harness waiting on human input (phase 3)
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL