agent#
import "github.com/jbcom/radioactive-ralph/internal/agent"
Package agent runs a single AI-agent CLI subprocess under Ralph’s own pty, so Ralph owns its stdio and can stream, control, and kill it. The developer never touches this terminal — Ralph does, as the control layer.
Index#
Variables#
ErrPTYUnsupported is returned by Start on platforms where creack/pty cannot allocate a pseudo-terminal — in practice, native Windows, where pty.Start returns pty.ErrUnsupported because there is no ConPTY-backed implementation. Ralph’s control model requires owning the agent’s pty, so on Windows operators run Ralph under WSL. Callers can match this with errors.Is to distinguish “this host can’t host agents” from a transient spawn failure.
var ErrPTYUnsupported = fmt.Errorf("agent: pty allocation is unsupported on %s; run radioactive-ralph under WSL on Windows", runtime.GOOS)
func Watch#
func Watch(ctx context.Context, a *Agent, cfg WatchdogConfig) <-chan Signal
Watch observes an agent and emits Signals. It NEVER blocks waiting on the agent: a prompt pattern or a stall is surfaced immediately so the caller can kill-and-reclaim. The channel closes when the agent exits.
type Agent#
Agent is a pty-owned agent subprocess.
type Agent struct {
// contains filtered or unexported fields
}
func Start#
func Start(ctx context.Context, opts Options) (*Agent, error)
Start launches opts.Command under a pty and begins streaming its output.
func (*Agent) Done#
func (a *Agent) Done() <-chan struct{}
Done is closed when the process exits.
func (*Agent) ExitErr#
func (a *Agent) ExitErr() error
ExitErr returns the subprocess’s exit status once it has exited on its own (nil if it exited 0). It returns nil while the process is still running and nil when the process was KILLED by this Agent (a signal-death from Kill / ctx-cancel / a stall-or-prompt watchdog kill is not a real failure — the caller already knows it forced the exit). Call only after Output() closes / Done() fires. This lets a runner without a structured terminal frame (codex) distinguish a clean completion from a failed CLI exit, rather than treating any exit as success.
func (*Agent) Kill#
func (a *Agent) Kill() error
Kill terminates the process immediately and releases the pty. Killing an agent that already exited on its own is a no-op success — a normal shutdown that races an agent finishing its task must not surface a spurious “already closed” error.
func (*Agent) Output#
func (a *Agent) Output() <-chan []byte
Output is the line-oriented output stream; closed when the process exits.
func (*Agent) PID#
func (a *Agent) PID() int
PID returns the subprocess PID (0 before start / after release).
func (*Agent) Wait#
func (a *Agent) Wait() error
Wait blocks until the process exits.
func (*Agent) WriteInput#
func (a *Agent) WriteInput(b []byte) error
WriteInput writes raw bytes to the agent’s pty stdin. Per spec §1, agents run non-interactively and need little/no input; this exists for the providers that drive a CLI’s stdin-based protocol (e.g. `claude -p –input-format stream-json`, which reads one JSON-line user message per turn) rather than passing the whole prompt as an argv/file. Direct Write() to the ptmx, per spec §2.
type Options#
Options configures one agent subprocess.
type Options struct {
Command string
Args []string
Dir string
Env []string
ResultPath string // file the CLI is told to write its structured result to
// DisableEcho turns OFF the pty's terminal echo before the child starts.
// Providers that drive the CLI over stdin (claude/opencode stream-json)
// MUST set this: otherwise the pty echoes every stdin line back onto the
// output stream, where the never-block watchdog pattern-matches the
// operator's OWN prompt text ("do you want to…", "approve", …) as an
// interactive prompt and kills the turn. Disabling echo removes the
// echoed line at the source. No effect on native Windows (no pty there).
DisableEcho bool
}
type Signal#
Signal is one watchdog observation about an agent.
type Signal struct {
Kind SignalKind
Detail string
}
type SignalKind#
SignalKind classifies a watchdog observation.
type SignalKind int
The recognized SignalKind values. (There is deliberately no resource-exceeded signal: Watch does no RSS/CPU sampling, so it could never emit one — a runaway agent is bounded by the stall timeout, not a resource ceiling. A real resource limiter would be its own feature.)
const (
Progress SignalKind = iota
Stall
Prompt
Exited
)
type WatchdogConfig#
WatchdogConfig tunes stall and prompt detection.
type WatchdogConfig struct {
StallTimeout time.Duration
PromptPatterns []*regexp.Regexp
// SkipPromptMatchOnJSONLines, when true, suppresses prompt-pattern
// matching for any output line that is a valid JSON value. Stream-json
// providers (claude/opencode) emit structured frames whose text can
// innocently contain prompt-like words ("permission", "continue?"),
// which would false-match and kill a valid turn — but a GENUINE raw
// interactive prompt from such a CLI is NOT valid JSON, so it still gets
// matched. This keeps real prompt detection while eliminating the
// false-kill-on-structured-content bug.
SkipPromptMatchOnJSONLines bool
}
Generated by gomarkdoc