daemon

package
v0.62.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StopReasonCrash    = "crash"
	StopReasonIdle     = "idle"
	StopReasonUser     = "user"
	StopReasonShutdown = "shutdown"
)

StopReason constants

View Source
const CurrentStateVersion = 12
View Source
const OrchestratorSessionName = "orchestrator"
View Source
const SystemKindOrchestrator = "orchestrator"

Variables

View Source
var ErrDaemonRunning = errors.New("daemon already running")

Functions

func AcquirePIDFile

func AcquirePIDFile(path string) error

func ExecUpgrade

func ExecUpgrade(manifestPath, configFile, clientExecPath string) error

func HandleConnection

func HandleConnection(ctx context.Context, conn net.Conn, sm *SessionManager, log *slog.Logger)

HandleConnection processes the frame protocol for a single client connection.

func IsGraithDaemon added in v0.16.5

func IsGraithDaemon(pid int) bool

func IsSystemSession added in v0.42.0

func IsSystemSession(s *SessionState) bool

func Listen

func Listen(sockPath string) (net.Listener, error)

func ReleasePIDFile

func ReleasePIDFile(path string)

func Run

func Run(cfg *config.Config, paths config.Paths, configFile, adoptFrom string) error

Run starts the daemon: acquires PID file, listens on the Unix socket, serves connections, and blocks until SIGTERM/SIGINT or an upgrade signal.

func SaveState

func SaveState(path string, state *State) error

func StopDaemon

func StopDaemon(pidFile string) error

func ValidateScenarioName added in v0.52.0

func ValidateScenarioName(name string) error

func ValidateSessionName added in v0.29.2

func ValidateSessionName(name string) error

ValidateSessionName checks that a session name is safe for use in git branch names, shell commands, osascript, template expansion, and environment variables.

func WriteManifest

func WriteManifest(dir string, m *UpgradeManifest) (string, error)

Types

type CIStatus added in v0.59.0

type CIStatus struct {
	State         string // passing | failing | pending | "" (unknown)
	FailingChecks []string
}

CIStatus is the runtime-only aggregate CI status for a session's linked PR.

type CreationConfig added in v0.27.0

type CreationConfig struct {
	Agent         config.Agent         `json:"agent"`
	SandboxConfig config.SandboxConfig `json:"sandbox_config"`
}

CreationConfig captures the agent and sandbox configuration at session creation time so the overlay can detect when the live config has diverged.

type IncludedRepoState added in v0.19.0

type IncludedRepoState struct {
	RepoPath     string `json:"repo_path"`
	RepoName     string `json:"repo_name"`
	WorktreePath string `json:"worktree_path"`
	Branch       string `json:"branch"`
	BaseBranch   string `json:"base_branch"`
	// contains filtered or unexported fields
}

type MCPManager added in v0.23.0

type MCPManager struct {
	// contains filtered or unexported fields
}

MCPManager manages MCP server processes. Each proxy connection gets its own dedicated MCP server process, started lazily on connect.

func NewMCPManager added in v0.23.0

func NewMCPManager(cfg *config.Config, extraServers []config.MCPServerConfig, logDir string, log *slog.Logger) *MCPManager

NewMCPManager creates an MCPManager. extraServers are auto-injected servers (like the graith MCP server) that aren't in the config file but must be available for proxy connections.

func (*MCPManager) Connect added in v0.23.0

func (m *MCPManager) Connect(serverName, proxyID string, vars config.TemplateVars) (*MCPProcess, error)

Connect starts a new MCP server process for the given proxy and returns the process's stdin (for writing JSON-RPC) and stdout reader (for reading JSON-RPC). The caller owns the I/O loop. vars supplies the per-session template values ({session_id}, {session_name}, {worktree_path}) expanded in the server's args and env, giving each session an isolated process — e.g. a per-session Chrome profile dir for chrome-devtools-mcp.

func (*MCPManager) Disconnect added in v0.23.0

func (m *MCPManager) Disconnect(proxyID string)

Disconnect kills the MCP server process for the given proxy.

func (*MCPManager) HasServer added in v0.23.0

func (m *MCPManager) HasServer(name string) bool

HasServer returns true if the named MCP server is configured.

func (*MCPManager) Reload added in v0.23.0

func (m *MCPManager) Reload(cfg *config.Config)

Reload updates the server config. Running processes for removed or changed servers are killed so proxies reconnect with the new config.

func (*MCPManager) Shutdown added in v0.23.0

func (m *MCPManager) Shutdown()

Shutdown kills all running MCP server processes.

type MCPProcess added in v0.23.0

type MCPProcess struct {
	// contains filtered or unexported fields
}

MCPProcess represents a running MCP server process for a single proxy connection.

type Message

type Message struct {
	ID         string `json:"id"`
	Seq        int64  `json:"seq"`
	Stream     string `json:"stream"`
	SenderID   string `json:"sender_id"`
	SenderName string `json:"sender_name,omitempty"`
	Body       string `json:"body"`
	ThreadID   string `json:"thread_id,omitempty"`
	ReplyTo    string `json:"reply_to,omitempty"`
	CreatedAt  string `json:"created_at"`
}

type MigrationInfo added in v0.58.0

type MigrationInfo struct {
	Agent          string    `json:"agent"`
	Model          string    `json:"model,omitempty"`
	AgentSessionID string    `json:"agent_session_id,omitempty"`
	RenderedPath   string    `json:"rendered_path,omitempty"`
	At             time.Time `json:"at"`
}

MigrationInfo records the agent a session was migrated from, so a failed migration can be reverted and the user can migrate back later.

type MsgStore

type MsgStore struct {
	// contains filtered or unexported fields
}

func NewMsgStore

func NewMsgStore(dbPath string) (*MsgStore, error)

func (*MsgStore) Ack

func (s *MsgStore) Ack(stream, subscriber string, upToSeq int64) error

func (*MsgStore) AckLatest

func (s *MsgStore) AckLatest(stream, subscriber string) error

func (*MsgStore) AckMessages added in v0.16.5

func (s *MsgStore) AckMessages(stream, subscriber string, seqs []int64) error

func (*MsgStore) Cleanup added in v0.3.0

func (s *MsgStore) Cleanup(maxAge time.Duration, maxPerStream int) (int64, error)

func (*MsgStore) Close

func (s *MsgStore) Close() error

func (*MsgStore) Conversation added in v0.59.0

func (s *MsgStore) Conversation(self string, limit int) ([]Message, error)

Conversation returns every direct message involving `self`, both directions: messages delivered to self's inbox (stream = "inbox:"+self) and messages self sent to any peer's inbox (sender_id = self AND an inbox: stream other than self's own). Topic messages are excluded — a "conversation" is direct messages only. Results are ordered by created_at, with id as a deterministic tie-breaker (seq is per-stream, so it is not a usable cross-stream order key).

When limit > 0, the most recent `limit` messages are returned (still in ascending order). The query reads inbox streams the caller may not own; the daemon authorises the target via checkTarget before calling this, and the sender_id filter ensures the outbound branch only returns messages the target session actually authored.

func (*MsgStore) ListStreams

func (s *MsgStore) ListStreams(subscriber string, includeSystem bool) ([]StreamInfo, error)

func (*MsgStore) Publish

func (s *MsgStore) Publish(stream, senderID, senderName, body, threadID, replyTo string) (Message, error)

func (*MsgStore) Read

func (s *MsgStore) Read(stream, subscriber string, onlyUnread bool, threadID string) ([]Message, error)

func (*MsgStore) Subscribe

func (s *MsgStore) Subscribe(stream string) (chan Message, func())

func (*MsgStore) TotalUnread added in v0.3.0

func (s *MsgStore) TotalUnread(subscriber string) int

type PRStatus added in v0.59.0

type PRStatus struct {
	Number         int
	State          string // open | draft | merged | closed
	URL            string
	ReviewDecision string
	HeadRefOid     string // head commit SHA — keys the per-SHA notify cap
	Mergeable      string // MERGEABLE | CONFLICTING | UNKNOWN
}

PRStatus is the runtime-only linked-PR state for a session, derived by the PR-watch loop. A zero Number means "no PR resolved". The PR-watch loop always assigns a freshly-built value (never mutates in place), so reads from cloned SessionState off-lock are race-free.

type ScenarioSession added in v0.52.0

type ScenarioSession struct {
	Name     string `json:"name"`
	Role     string `json:"role"`
	Task     string `json:"task"`
	TaskDone bool   `json:"task_done,omitempty"`
	Repo     string `json:"repo"`
	Agent    string `json:"agent"`
	Model    string `json:"model,omitempty"`
	Shared   bool   `json:"shared,omitempty"`
}

type ScenarioState added in v0.52.0

type ScenarioState struct {
	ID             string            `json:"id"`
	Name           string            `json:"name"`
	OrchestratorID string            `json:"orchestrator_id"`
	Goal           string            `json:"goal"`
	SessionIDs     []string          `json:"session_ids"`
	Sessions       []ScenarioSession `json:"sessions"`
	CreatedAt      time.Time         `json:"created_at"`
	SourceFileHash string            `json:"source_file_hash,omitempty"`
}

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(l net.Listener, handler func(ctx context.Context, conn net.Conn), log *slog.Logger) *Server

func (*Server) Serve

func (s *Server) Serve(ctx context.Context) error

func (*Server) Shutdown

func (s *Server) Shutdown()

type SessionManager

type SessionManager struct {
	// contains filtered or unexported fields
}

SessionManager orchestrates PTY sessions, state persistence, and git worktrees.

func NewSessionManager

func NewSessionManager(cfg *config.Config, paths config.Paths, log *slog.Logger) *SessionManager

NewSessionManager creates a SessionManager with the given config and paths.

func (*SessionManager) AddToScenario added in v0.52.0

func (sm *SessionManager) AddToScenario(name string, input protocol.ScenarioSessionInput, rows, cols uint16) (*SessionState, error)

func (*SessionManager) AdoptSessions

func (sm *SessionManager) AdoptSessions(manifest *UpgradeManifest) error

func (*SessionManager) ClearAttachedClient

func (sm *SessionManager) ClearAttachedClient(sessionID string, conn net.Conn)

func (*SessionManager) ClearSummary added in v0.32.0

func (sm *SessionManager) ClearSummary(sessionID string) error

func (*SessionManager) Config added in v0.47.0

func (sm *SessionManager) Config() *config.Config

Config returns a snapshot of the current config pointer, safe for use outside the lock. The returned *Config must not be modified.

func (*SessionManager) Create

func (sm *SessionManager) Create(name, agentName, repoPath, baseBranch, prompt, model, parentID string, noRepo bool, shareWorktree string, agentHooks bool, inPlace, allowConcurrent, skipModelValidation bool, rows, cols uint16, envExtra ...map[string]string) (SessionState, error)

Create starts a new agent session, either in a git worktree, in-place in an existing repo, or as a standalone scratch session (when noRepo is true).

The method uses three-phase locking to avoid holding the daemon mutex during potentially blocking git/network operations (fetch, GitHub API calls, PTY spawn):

  1. Lock: validate, reserve session as StatusCreating, unlock
  2. Git setup and PTY spawn (no lock held)
  3. Lock: commit to StatusRunning, unlock

func (*SessionManager) Delete

func (sm *SessionManager) Delete(id string) error

Delete stops a session, removes its worktree/branch, and deletes state. Git teardown is attempted before removing the session from state; if teardown fails the session is kept for retry and the error is returned.

func (*SessionManager) DeleteScenario added in v0.52.0

func (sm *SessionManager) DeleteScenario(name string) ([]string, error)

func (*SessionManager) DeleteWithChildren added in v0.20.0

func (sm *SessionManager) DeleteWithChildren(id string, excludeRoot bool) ([]string, error)

DeleteWithChildren deletes a session and all its transitive descendants. Git teardown is attempted before removing each session from state; sessions whose teardown fails are kept for retry. Returns the list of deleted session IDs and an error if any teardowns failed.

func (*SessionManager) Diagnostics added in v0.28.0

func (sm *SessionManager) Diagnostics() protocol.DiagnosticsMsg

Diagnostics collects runtime health data for gr doctor.

func (*SessionManager) Fork added in v0.10.0

func (sm *SessionManager) Fork(name, sourceSessionID string, rows, cols uint16) (SessionState, error)

Fork creates a new session that branches from an existing session's git state and uses the agent's fork_args to carry over the conversation history.

Uses three-phase locking like Create to avoid holding the mutex during git fetch and PTY spawn.

func (*SessionManager) Get

func (sm *SessionManager) Get(id string) (SessionState, bool)

Get returns a copy of a session state by ID.

func (*SessionManager) GetPTY

func (sm *SessionManager) GetPTY(id string) (*grpty.Session, bool)

GetPTY returns the live PTY session by ID.

func (*SessionManager) HandleHookReport added in v0.11.0

func (sm *SessionManager) HandleHookReport(sr protocol.StatusReportMsg)

HandleHookReport processes a status report from an agent hook, updating the in-memory hookReports map and the session's AgentStatus. This is the authoritative source of agent status when hooks are active.

func (*SessionManager) HasAttachedClient added in v0.52.0

func (sm *SessionManager) HasAttachedClient(sessionID string) bool

func (*SessionManager) IsAttachedClient added in v0.16.5

func (sm *SessionManager) IsAttachedClient(sessionID string, conn net.Conn) bool

func (*SessionManager) KickAttachedClient

func (sm *SessionManager) KickAttachedClient(sessionID string)

func (*SessionManager) List

func (sm *SessionManager) List() []SessionState

List returns copies of all known session states.

func (*SessionManager) ListScenarios added in v0.52.0

func (sm *SessionManager) ListScenarios() []protocol.ScenarioRecord

func (*SessionManager) LoadState

func (sm *SessionManager) LoadState() error

LoadState reads persisted state from disk and reconciles dead processes.

func (*SessionManager) Migrate added in v0.58.0

func (sm *SessionManager) Migrate(id, targetAgent, targetModel string, rows, cols uint16) (SessionState, error)

Migrate swaps the agent on an existing session in place: it renders the current agent's conversation to a neutral Markdown file, stops the current agent, changes the session's agent type, and restarts it in the *same* worktree seeded with that file. The session keeps its id, name, worktree and branch — only the agent type changes — so all code state (commits and uncommitted edits) carries over with no branching.

Ordering is chosen so a doomed migration aborts before the running agent is touched (read+render happen first and fail fast), and so the context file exists before the new agent starts. If the target agent fails to start, the original agent is restored.

See docs/design/2026-06-24-cross-agent-conversation-migration-design.md.

func (*SessionManager) PendingApprovals added in v0.13.0

func (sm *SessionManager) PendingApprovals() []protocol.ApprovalInfo

PendingApprovals returns a snapshot of all current pending approvals.

func (*SessionManager) PrepareUpgrade

func (sm *SessionManager) PrepareUpgrade(listenerFd uintptr, configFile string) (*UpgradeManifest, error)

func (*SessionManager) ReloadConfig added in v0.3.0

func (sm *SessionManager) ReloadConfig() error

ReloadConfig loads the config from disk and swaps it in, logging what changed.

func (*SessionManager) Rename

func (sm *SessionManager) Rename(id, newName string) error

func (*SessionManager) RespondToApproval added in v0.13.0

func (sm *SessionManager) RespondToApproval(requestID, decision, reason string) error

RespondToApproval delivers a user decision to a waiting approval request.

func (*SessionManager) Restart added in v0.26.0

func (sm *SessionManager) Restart(id string, rows, cols uint16) (SessionState, error)

Restart stops a running session (or no-ops if already stopped) and resumes it, picking up the current agent and sandbox configuration.

func (*SessionManager) RestartWithChildren added in v0.53.0

func (sm *SessionManager) RestartWithChildren(rootID string, excludeRoot bool, rows, cols uint16) ([]string, error)

func (*SessionManager) Resume

func (sm *SessionManager) Resume(id string, rows, cols uint16) (SessionState, error)

Resume restarts a stopped session using the agent's resume_args.

Uses two-phase locking: the GitHub username discovery happens before the lock, and the PTY spawn happens after releasing the lock to avoid blocking the daemon.

func (*SessionManager) ResumeScenario added in v0.52.0

func (sm *SessionManager) ResumeScenario(name string, rows, cols uint16) ([]string, error)

func (*SessionManager) RunDetectionLoop

func (sm *SessionManager) RunDetectionLoop(ctx context.Context)

RunDetectionLoop periodically scans PTY scrollback to detect agent status (active, needs approval, ready) for all running sessions.

func (*SessionManager) RunGitPullLoop added in v0.42.0

func (sm *SessionManager) RunGitPullLoop(ctx context.Context)

func (*SessionManager) RunMessageCleanupLoop added in v0.3.0

func (sm *SessionManager) RunMessageCleanupLoop(ctx context.Context)

func (*SessionManager) RunPRWatchLoop added in v0.59.0

func (sm *SessionManager) RunPRWatchLoop(ctx context.Context)

RunPRWatchLoop is the daemon-owned PR/CI watcher. Modeled on RunGitPullLoop: config-gated, tolerant of errors, off the request path.

func (*SessionManager) ScenarioStatus added in v0.52.0

func (sm *SessionManager) ScenarioStatus(name string) (*protocol.ScenarioRecord, error)

func (*SessionManager) ScenarioTaskDone added in v0.52.0

func (sm *SessionManager) ScenarioTaskDone(scenarioName, sessionID string) error

func (*SessionManager) SessionForToken added in v0.52.0

func (sm *SessionManager) SessionForToken(token string) string

SessionForToken returns the session ID that owns the given token, or empty string if the token is not recognized. Must be called under at least RLock.

func (*SessionManager) SetAttachedClient

func (sm *SessionManager) SetAttachedClient(sessionID string, conn net.Conn, kick func(), sendCtrl func(string, any))

func (*SessionManager) SetMCPManager added in v0.23.0

func (sm *SessionManager) SetMCPManager(mm *MCPManager)

func (*SessionManager) SetMsgStore

func (sm *SessionManager) SetMsgStore(ms *MsgStore)

func (*SessionManager) SetSummary added in v0.32.0

func (sm *SessionManager) SetSummary(sessionID, text string, ttlSeconds int) error

func (*SessionManager) Star added in v0.31.0

func (sm *SessionManager) Star(id string) error

Star marks a session as starred.

func (*SessionManager) StartScenario added in v0.52.0

func (sm *SessionManager) StartScenario(msg protocol.ScenarioStartMsg, rows, cols uint16) (*ScenarioState, error)

func (*SessionManager) Stop

func (sm *SessionManager) Stop(id string) error

Stop sends SIGTERM to a session's process without removing the session or worktree.

func (*SessionManager) StopAll

func (sm *SessionManager) StopAll(ctx context.Context)

StopAll gracefully terminates all running sessions concurrently. Each session gets up to 5 seconds to exit after SIGTERM before being force-killed. Sessions are waited on in parallel so the total wait time is bounded by the slowest session, not the sum.

func (*SessionManager) StopScenario added in v0.52.0

func (sm *SessionManager) StopScenario(name string) ([]string, error)

func (*SessionManager) StopWithChildren added in v0.30.0

func (sm *SessionManager) StopWithChildren(rootID string, excludeRoot bool) ([]string, error)

StopWithChildren stops all descendants of rootID. If excludeRoot is true, the root session itself is not stopped. Already-stopped sessions are skipped. Returns the list of session IDs that were actually stopped.

func (*SessionManager) SubmitApproval added in v0.13.0

SubmitApproval registers a pending approval and blocks until a decision is made, the context is cancelled (hook disconnect), or the configured timeout elapses. If localmost is configured, it is tried first.

func (*SessionManager) Unstar added in v0.31.0

func (sm *SessionManager) Unstar(id string) error

func (*SessionManager) Update added in v0.52.0

func (sm *SessionManager) Update(id string, name *string, parentID *string) error

type SessionState

type SessionState struct {
	ID                     string                `json:"id"`
	ParentID               string                `json:"parent_id,omitempty"`
	Name                   string                `json:"name"`
	RepoPath               string                `json:"repo_path"`
	RepoName               string                `json:"repo_name"`
	WorktreePath           string                `json:"worktree_path"`
	Branch                 string                `json:"branch"`
	BaseBranch             string                `json:"base_branch"`
	Agent                  string                `json:"agent"`
	AgentSessionID         string                `json:"agent_session_id,omitempty"`
	Model                  string                `json:"model,omitempty"`
	Status                 SessionStatus         `json:"status"`
	AgentStatus            string                `json:"agent_status,omitempty"`
	StatusChangedAt        time.Time             `json:"status_changed_at"`
	IdleSince              *time.Time            `json:"-"`
	GitDirty               bool                  `json:"-"`
	GitUnpushed            int                   `json:"-"`
	PullRequest            PRStatus              `json:"-"`
	CI                     CIStatus              `json:"-"`
	HookToolName           string                `json:"-"`
	ExitCode               *int                  `json:"exit_code,omitempty"`
	ExitSignal             string                `json:"exit_signal,omitempty"`
	PID                    int                   `json:"pid,omitempty"`
	PIDStartTime           int64                 `json:"pid_start_time,omitempty"`
	Sandboxed              bool                  `json:"sandboxed,omitempty"`
	SandboxConfig          *config.SandboxConfig `json:"sandbox_config,omitempty"`
	SharedWorktree         bool                  `json:"shared_worktree,omitempty"`
	SharedWorktreeSourceID string                `json:"shared_worktree_source_id,omitempty"`
	InPlace                bool                  `json:"in_place,omitempty"`
	Includes               []IncludedRepoState   `json:"includes,omitempty"`
	AgentHooks             bool                  `json:"agent_hooks,omitempty"`
	ApprovalsEnabled       bool                  `json:"approvals_enabled,omitempty"` // deprecated: migrated to AgentHooks in v4
	Starred                bool                  `json:"starred,omitempty"`
	SystemKind             string                `json:"system_kind,omitempty"`
	StopReason             string                `json:"stop_reason,omitempty"`
	BackoffLevel           int                   `json:"backoff_level,omitempty"`
	FreshStart             bool                  `json:"fresh_start,omitempty"`
	LastStartedAt          time.Time             `json:"last_started_at,omitempty"`
	SummaryText            string                `json:"summary_text,omitempty"`
	SummarySetAt           *time.Time            `json:"summary_set_at,omitempty"`
	SummaryTTL             int                   `json:"summary_ttl,omitempty"`
	LastOutputAt           *time.Time            `json:"last_output_at,omitempty"`
	CreatedAt              time.Time             `json:"created_at"`
	LastAttachedAt         *time.Time            `json:"last_attached_at,omitempty"`
	CreationCfg            *CreationConfig       `json:"creation_config,omitempty"`
	Token                  string                `json:"token,omitempty"`
	ScenarioID             string                `json:"scenario_id,omitempty"`
	ScenarioName           string                `json:"scenario_name,omitempty"`
	ScenarioRole           string                `json:"scenario_role,omitempty"`
	ScenarioGoal           string                `json:"scenario_goal,omitempty"`
	MigratedFrom           *MigrationInfo        `json:"migrated_from,omitempty"`
}

type SessionStatus

type SessionStatus string
const (
	StatusRunning  SessionStatus = "running"
	StatusStopped  SessionStatus = "stopped"
	StatusErrored  SessionStatus = "errored"
	StatusDeleting SessionStatus = "deleting"
	StatusCreating SessionStatus = "creating"
)

type State

type State struct {
	Version   int                       `json:"version"`
	Sessions  map[string]*SessionState  `json:"sessions"`
	Scenarios map[string]*ScenarioState `json:"scenarios,omitempty"`
}

func LoadState

func LoadState(path string) (*State, error)

func NewState

func NewState() *State

func (*State) Reconcile

func (s *State) Reconcile()

type StreamInfo

type StreamInfo struct {
	Name     string `json:"name"`
	Total    int64  `json:"total"`
	Unread   int64  `json:"unread"`
	LatestAt string `json:"latest_at,omitempty"`
}

type UpgradeManifest

type UpgradeManifest struct {
	ListenerFd int              `json:"listener_fd"`
	ConfigFile string           `json:"config_file"`
	Profile    string           `json:"profile,omitempty"`
	Sessions   []UpgradeSession `json:"sessions"`
}

func ReadManifest

func ReadManifest(path string) (*UpgradeManifest, error)

type UpgradeSession

type UpgradeSession struct {
	ID  string `json:"id"`
	Fd  int    `json:"fd"`
	PID int    `json:"pid"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL