session¶
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¶
DefaultClaudeBin is the binary name the supervisor invokes when no override is provided. Tests override this to use a fake binary.
const DefaultClaudeBin = "claude"
func RenderSystemPrompt¶
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:
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.
Output is newline-joined with a fixed preamble announcing the variant and a safety note about the tool allowlist.
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”.
type BiasChoice struct {
Skill string
Disabled bool
}
type Event¶
Event is a parsed inbound frame plus any decoding error. Consumers handle Err before Inbound.
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.
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.
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 <SessionID>` 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.
type Outbound struct {
Type string `json:"type"`
Message OutboundInner `json:"message"`
}
func NewUserMessage¶
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.
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.
type OutboundInner struct {
Role string `json:"role"`
Content []OutboundContentPart `json:"content"`
}
type PromptOptions¶
PromptOptions feeds RenderSystemPrompt.
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.
type Session struct {
// contains filtered or unexported fields
}
func Spawn¶
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¶
func (s *Session) Close() error
Close terminates the subprocess and waits for the reader to exit.
func (*Session) Events¶
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¶
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¶
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¶
func (s *Session) SessionID() string
SessionID returns Claude’s session UUID for this process.
func (*Session) WaitForIdle¶
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