claudesession¶
import "github.com/jbcom/radioactive-ralph/internal/provider/claudesession"
Package claudesession wraps a `claude -p` subprocess so the claude provider runner can drive it via stream-json over stdin/stdout.
The stream-json protocol pairs message objects, one per JSON line:
Messages FROM the runtime 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 runtime invokes when no override is provided. Tests override this to use a fake binary.
const DefaultClaudeBin = "claude"
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, runtime) 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 runtime
// 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. Variant
// directives plus runtime policy 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. The runtime 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 runtime 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 Session¶
Session wraps a running `claude -p` subprocess speaking stream-json.
Usage:
s, err := claudesession.Spawn(ctx, claudesession.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