---
title: internal/session
description: Go API reference for the session package.
---
# session
```go
import "github.com/jbcom/radioactive-ralph/internal/session"
```
Package session wraps a \`claude \-p\` subprocess so the supervisor can drive it via stream\-json over stdin/stdout.
The stream\-json protocol pairs message objects, one per JSON line:
- Messages FROM the supervisor to Claude carry \`\{"type":"user", …\}\` with a user\-scoped content block.
- Messages FROM Claude carry \`\{"type":"assistant", …\}\` or \`\{"type":"result", …\}\` when the agent finishes its turn.
Only the shape Ralph depends on is modeled here. Unknown fields are preserved as raw JSON so a future Claude Code version that adds a field doesn't break replay.
## Index
- [Constants](<#constants>)
- [func RenderSystemPrompt\(opts PromptOptions\) string](<#RenderSystemPrompt>)
- [type BiasChoice](<#BiasChoice>)
- [type Event](<#Event>)
- [type Inbound](<#Inbound>)
- [type Options](<#Options>)
- [type Outbound](<#Outbound>)
- [func NewUserMessage\(text string\) Outbound](<#NewUserMessage>)
- [type OutboundContentPart](<#OutboundContentPart>)
- [type OutboundInner](<#OutboundInner>)
- [type PromptOptions](<#PromptOptions>)
- [type Session](<#Session>)
- [func Spawn\(ctx context.Context, opts Options\) \(\*Session, error\)](<#Spawn>)
- [func \(s \*Session\) Close\(\) error](<#Session.Close>)
- [func \(s \*Session\) Events\(\) \<\-chan Event](<#Session.Events>)
- [func \(s \*Session\) Interrupt\(\) error](<#Session.Interrupt>)
- [func \(s \*Session\) SendUserMessage\(\_ context.Context, text string\) error](<#Session.SendUserMessage>)
- [func \(s \*Session\) SessionID\(\) string](<#Session.SessionID>)
- [func \(s \*Session\) WaitForIdle\(ctx context.Context\) error](<#Session.WaitForIdle>)
## Constants
DefaultClaudeBin is the binary name the supervisor invokes when no override is provided. Tests override this to use a fake binary.
```go
const DefaultClaudeBin = "claude"
```
## func [RenderSystemPrompt]()
```go
func RenderSystemPrompt(opts PromptOptions) string
```
RenderSystemPrompt combines the variant's bias snippets with operator preferences and installed inventory to produce the \-\-append\-system\-prompt text.
Rules:
1. For each bias category the variant declares a snippet for: a. If OperatorChoices has Disabled=true, skip. b. Else if OperatorChoices has Skill\!="" AND inventory has it, render the snippet with \{skill\} expanded. c. Else if variant snippet contains \{skill\} and inventory has ANY helper matching the category name, pick the first alphabetically for determinism. d. Else skip.
2. Output is newline\-joined with a fixed preamble announcing the variant and a safety note about the tool allowlist.
3. Output is deterministic given the same inputs — categories are iterated in alphabetical order.
## type [BiasChoice]()
BiasChoice is the operator's resolved preference for a single bias category. Typically sourced from config.toml's \[capabilities\] section plus per\-variant overrides.
Skill is the full helper name \(e.g. "coderabbit:review"\). Empty means "no preference, use variant default if inventory has it".
Disabled means "skip this bias entirely regardless of inventory".
```go
type BiasChoice struct {
Skill string
Disabled bool
}
```
## type [Event]()
Event is a parsed inbound frame plus any decoding error. Consumers handle Err before Inbound.
```go
type Event struct {
Inbound Inbound
Err error
}
```
## type [Inbound]()
Inbound is a JSON\-line frame received from \`claude \-p\`. Each frame is one of several shapes depending on type; the raw bytes are retained so downstream consumers \(event log, supervisor\) can re\-emit or re\-parse as needed.
```go
type Inbound struct {
// Type is the top-level message kind. Common values: "assistant",
// "user", "result", "system". Unknown values are preserved as
// strings and handled gracefully.
Type string `json:"type"`
// SessionID is Claude's view of its session UUID. The supervisor
// uses this to correlate resume operations.
SessionID string `json:"session_id,omitempty"`
// Subtype narrows the meaning of Type. For type=result, the
// subtypes Ralph cares about are "success" (agent done, clean exit)
// and "error_max_turns" (turn cap hit).
Subtype string `json:"subtype,omitempty"`
// Message is the assistant/user payload for type in {assistant, user}.
// Kept as a raw JSON so we don't flatten Claude's content-block shape.
Message json.RawMessage `json:"message,omitempty"`
// Result is the final result payload for type=result.
Result json.RawMessage `json:"result,omitempty"`
// Raw is the full JSON line as received, retained for event-log
// archival.
Raw []byte `json:"-"`
}
```
## type [Options]()
Options configures a Session spawn.
```go
type Options struct {
// ClaudeBin overrides DefaultClaudeBin. Tests use the fake-claude
// binary path here.
ClaudeBin string
// WorkingDir is the cwd for the subprocess. Typically a worktree.
WorkingDir string
// SystemPrompt is the content for --append-system-prompt. Combined
// variant + inventory biases go here.
SystemPrompt string
// Model pins the model tier — "haiku", "sonnet", or "opus". Empty
// means "let claude choose" which in practice is sonnet.
Model string
// Effort pins the reasoning-effort level — "low", "medium",
// "high", or "max" (as accepted by `claude --effort`). Empty
// means "claude decides" which for sonnet is medium. Fixit's
// advisor subprocess defaults to "high" so opus reasons deeply
// during planning.
Effort string
// AllowedTools are passed to --allowed-tools. Supervisor builds
// this from the variant profile's ToolAllowlist.
AllowedTools []string
// SessionID is the Claude session UUID to pin. Empty means
// generate a new one (Spawn will populate this field).
SessionID string
// ResumeMode triggers `claude -p --resume ` instead of a
// fresh spawn. Requires a non-empty SessionID.
ResumeMode bool
// SentinelTaskID is the task identifier the supervisor prompts
// about after a resume, to verify continuity. Empty in fresh spawns.
SentinelTaskID string
// ExtraArgs are additional `claude -p` flags the caller wants.
ExtraArgs []string
}
```
## type [Outbound]()
Outbound is a JSON\-line frame sent TO \`claude \-p\`. Only user messages and interrupts are supported — the CLI does not accept assistant or system messages over stdin.
```go
type Outbound struct {
Type string `json:"type"`
Message OutboundInner `json:"message"`
}
```
### func [NewUserMessage]()
```go
func NewUserMessage(text string) Outbound
```
NewUserMessage builds an Outbound frame wrapping the given text as a single user\-role text block.
## type [OutboundContentPart]()
OutboundContentPart is a single content block. Ralph only emits text.
```go
type OutboundContentPart struct {
Type string `json:"type"` // "text"
Text string `json:"text"`
}
```
## type [OutboundInner]()
OutboundInner mirrors the subset of the Anthropic message shape that \`claude \-p \-\-input\-format stream\-json\` accepts.
```go
type OutboundInner struct {
Role string `json:"role"`
Content []OutboundContentPart `json:"content"`
}
```
## type [PromptOptions]()
PromptOptions feeds RenderSystemPrompt.
```go
type PromptOptions struct {
// Variant is the active profile. Required.
Variant variant.Profile
// Inventory is the capability inventory. Required; if empty, bias
// injection silently skips slots.
Inventory inventory.Inventory
// OperatorChoices is the per-category operator preference map.
// Keys are BiasCategory values; missing keys fall back to the
// variant's declared snippet target.
OperatorChoices map[variant.BiasCategory]BiasChoice
}
```
## type [Session]()
Session wraps a running \`claude \-p\` subprocess speaking stream\-json.
Usage:
```
s, err := session.Spawn(ctx, session.Options{...})
for ev := range s.Events() {
// handle ev.Inbound
}
s.SendUserMessage(ctx, "do X")
s.WaitForIdle(ctx)
s.Close()
```
The session is single\-goroutine\-safe on Send\* and Close; Events\(\) may be consumed from any goroutine. Resume reuses the SessionID so Claude's conversation history is preserved across subprocess restarts.
```go
type Session struct {
// contains filtered or unexported fields
}
```
### func [Spawn]()
```go
func Spawn(ctx context.Context, opts Options) (*Session, error)
```
Spawn launches a new Claude subprocess and starts the reader goroutine.
For ResumeMode=false, a fresh SessionID is generated \(UUID v4\) and passed via \-\-session\-id so Ralph can later resume by that ID. For ResumeMode=true, the SessionID must be set and is passed via \-\-resume; on first successful read, Spawn emits a sentinel user message naming SentinelTaskID so the caller can verify continuity.
### func \(\*Session\) [Close]()
```go
func (s *Session) Close() error
```
Close terminates the subprocess and waits for the reader to exit.
### func \(\*Session\) [Events]()
```go
func (s *Session) Events() <-chan Event
```
Events returns a channel of inbound frames. Closed when the reader goroutine exits \(EOF or error\).
### func \(\*Session\) [Interrupt]()
```go
func (s *Session) Interrupt() error
```
Interrupt sends SIGINT to the subprocess, which Claude Code treats as an in\-flight cancellation. Safe to call on a closed session.
### func \(\*Session\) [SendUserMessage]()
```go
func (s *Session) SendUserMessage(_ context.Context, text string) error
```
SendUserMessage writes a user\-role message as a JSON line on stdin. Resets the idle signal so a subsequent WaitForIdle waits for the next result frame.
### func \(\*Session\) [SessionID]()
```go
func (s *Session) SessionID() string
```
SessionID returns Claude's session UUID for this process.
### func \(\*Session\) [WaitForIdle]()
```go
func (s *Session) WaitForIdle(ctx context.Context) error
```
WaitForIdle blocks until the subprocess emits a \`type=result\` frame or the context is cancelled.
Generated by [gomarkdoc]()