---
title: internal/agent
description: Go API reference for the agent package.
---
# agent
```go
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](<#variables>)
- [func Watch\(ctx context.Context, a \*Agent, cfg WatchdogConfig\) \<\-chan Signal](<#Watch>)
- [type Agent](<#Agent>)
- [func Start\(ctx context.Context, opts Options\) \(\*Agent, error\)](<#Start>)
- [func \(a \*Agent\) Done\(\) \<\-chan struct\{\}](<#Agent.Done>)
- [func \(a \*Agent\) ExitErr\(\) error](<#Agent.ExitErr>)
- [func \(a \*Agent\) Kill\(\) error](<#Agent.Kill>)
- [func \(a \*Agent\) Output\(\) \<\-chan \[\]byte](<#Agent.Output>)
- [func \(a \*Agent\) PID\(\) int](<#Agent.PID>)
- [func \(a \*Agent\) Wait\(\) error](<#Agent.Wait>)
- [func \(a \*Agent\) WriteInput\(b \[\]byte\) error](<#Agent.WriteInput>)
- [type Options](<#Options>)
- [type Signal](<#Signal>)
- [type SignalKind](<#SignalKind>)
- [type WatchdogConfig](<#WatchdogConfig>)
## 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.
```go
var ErrPTYUnsupported = fmt.Errorf("agent: pty allocation is unsupported on %s; run radioactive-ralph under WSL on Windows", runtime.GOOS)
```
## func [Watch]()
```go
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.
```go
type Agent struct {
// contains filtered or unexported fields
}
```
### func [Start]()
```go
func Start(ctx context.Context, opts Options) (*Agent, error)
```
Start launches opts.Command under a pty and begins streaming its output.
### func \(\*Agent\) [Done]()
```go
func (a *Agent) Done() <-chan struct{}
```
Done is closed when the process exits.
### func \(\*Agent\) [ExitErr]()
```go
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]()
```go
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]()
```go
func (a *Agent) Output() <-chan []byte
```
Output is the line\-oriented output stream; closed when the process exits.
### func \(\*Agent\) [PID]()
```go
func (a *Agent) PID() int
```
PID returns the subprocess PID \(0 before start / after release\).
### func \(\*Agent\) [Wait]()
```go
func (a *Agent) Wait() error
```
Wait blocks until the process exits.
### func \(\*Agent\) [WriteInput]()
```go
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.
```go
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.
```go
type Signal struct {
Kind SignalKind
Detail string
}
```
## type [SignalKind]()
SignalKind classifies a watchdog observation.
```go
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.\)
```go
const (
Progress SignalKind = iota
Stall
Prompt
Exited
)
```
## type [WatchdogConfig]()
WatchdogConfig tunes stall and prompt detection.
```go
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]()