polecat

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package polecat provides polecat workspace and session management.

Package polecat provides polecat lifecycle management.

Index

Constants

View Source
const (
	// DefaultPoolSize is the number of name slots in the pool.
	// NOTE: This is a pool of NAMES, not polecats. Polecats are spawned fresh
	// for each task and nuked when done - there is no idle pool of polecats.
	// Only the name slots are reused when a polecat is nuked and a new one spawned.
	DefaultPoolSize = 50

	// DefaultTheme is the default theme for new rigs.
	DefaultTheme = "mad-max"
)
View Source
const SessionHeartbeatStaleThreshold = 3 * time.Minute

SessionHeartbeatStaleThreshold is the age at which a polecat session heartbeat is considered stale, indicating the agent process is likely dead. Configurable via operational.polecat.heartbeat_stale_threshold in settings/config.json.

Variables

View Source
var (
	ErrPolecatExists      = errors.New("polecat already exists")
	ErrPolecatNotFound    = errors.New("polecat not found")
	ErrHasChanges         = errors.New("polecat has uncommitted changes")
	ErrHasUncommittedWork = errors.New("polecat has uncommitted work")
	ErrShellInWorktree    = errors.New("shell working directory is inside polecat worktree")
	ErrDoltUnhealthy      = errors.New("dolt health check failed")
	ErrDoltAtCapacity     = errors.New("dolt server at connection capacity")
)

Common errors

View Source
var (
	ErrSessionRunning  = errors.New("session already running")
	ErrSessionNotFound = errors.New("session not found")
	ErrIssueInvalid    = errors.New("issue not found or tombstoned")
)

Session errors

View Source
var BuiltinThemes = map[string][]string{
	"mad-max": {
		"furiosa", "nux", "slit", "rictus", "dementus",
		"capable", "toast", "dag", "cheedo", "valkyrie",
		"keeper", "morsov", "ace", "warboy", "imperator",
		"organic", "coma", "splendid", "angharad", "max",
		"immortan", "bullet", "toecutter", "goose", "nightrider",
		"glory", "scrotus", "chumbucket", "corpus", "dinki",
		"prime", "vuvalini", "rockryder", "wretched", "buzzard",
		"gastown", "bullet-farmer", "citadel", "wasteland", "fury",
		"road-warrior", "interceptor", "blackfinger", "wraith", "witness",
		"chrome", "shiny", "mediocre", "guzzoline", "aqua-cola",
	},
	"minerals": {
		"obsidian", "quartz", "jasper", "onyx", "opal",
		"topaz", "garnet", "ruby", "amber", "jade",
		"pearl", "flint", "granite", "basalt", "marble",
		"shale", "slate", "pyrite", "mica", "agate",
		"malachite", "turquoise", "lapis", "emerald", "sapphire",
		"diamond", "amethyst", "citrine", "zircon", "peridot",
		"coral", "jet", "moonstone", "sunstone", "bloodstone",
		"rhodonite", "sodalite", "hematite", "magnetite", "calcite",
		"fluorite", "selenite", "kyanite", "labradorite", "amazonite",
		"chalcedony", "carnelian", "aventurine", "chrysoprase", "heliodor",
	},
	"wasteland": {
		"rust", "chrome", "nitro", "guzzle", "witness",
		"shiny", "fury", "thunder", "dust", "scavenger",
		"radrat", "ghoul", "mutant", "raider", "vault",
		"pipboy", "nuka", "brahmin", "deathclaw", "mirelurk",
		"synth", "institute", "enclave", "brotherhood", "minuteman",
		"railroad", "atom", "crater", "foundation", "refuge",
		"settler", "wanderer", "courier", "lone", "chosen",
		"tribal", "khan", "legion", "ncr", "ranger",
		"overseer", "sentinel", "paladin", "scribe", "initiate",
		"elder", "lancer", "knight", "squire", "proctor",
	},
}

Built-in themes with themed polecat names.

View Source
var ReservedInfraAgentNames = map[string]bool{
	"witness":  true,
	"mayor":    true,
	"deacon":   true,
	"refinery": true,
	"crew":     true,
	"polecats": true,
}

ReservedInfraAgentNames contains names reserved for infrastructure agents. These names must never be allocated to polecats.

Functions

func GetThemeNames

func GetThemeNames(theme string) ([]string, error)

GetThemeNames returns the names in a specific theme.

func IsSessionHeartbeatStale added in v0.9.0

func IsSessionHeartbeatStale(townRoot, sessionName string) (stale bool, exists bool)

IsSessionHeartbeatStale returns true if the session's heartbeat is older than the stale threshold, or if no heartbeat file exists.

When no heartbeat file exists, this returns false to avoid false positives during the rollout period where sessions may not yet be touching heartbeats. The caller should fall back to other liveness checks in that case.

func ListThemes

func ListThemes() []string

ListThemes returns the list of available built-in themes.

func RemoveSessionHeartbeat added in v0.9.0

func RemoveSessionHeartbeat(townRoot, sessionName string)

RemoveSessionHeartbeat removes the heartbeat file for a session. Called during session cleanup.

func ThemeForRig added in v0.3.0

func ThemeForRig(rigName string) string

ThemeForRig returns a deterministic theme for a rig based on its name. This provides variety across rigs without requiring manual configuration.

func TouchSessionHeartbeat added in v0.9.0

func TouchSessionHeartbeat(townRoot, sessionName string)

TouchSessionHeartbeat writes or updates the heartbeat file for a polecat session. This is best-effort: errors are silently ignored because heartbeat signals are non-critical and should not interrupt gt commands.

Types

type AddOptions

type AddOptions struct {
	HookBead   string // Bead ID to set as hook_bead at spawn time (atomic assignment)
	BaseBranch string // Override base branch for worktree (e.g., "origin/integration/gt-epic")
}

AddOptions configures polecat creation.

type CleanupStatus added in v0.2.2

type CleanupStatus string

CleanupStatus represents the git state of a polecat for cleanup decisions. The Witness uses this to determine whether it's safe to nuke a polecat worktree.

const (
	// CleanupClean means the worktree has no uncommitted work and is safe to remove.
	CleanupClean CleanupStatus = "clean"

	// CleanupUncommitted means there are uncommitted changes in the worktree.
	CleanupUncommitted CleanupStatus = "has_uncommitted"

	// CleanupStash means there are stashed changes that would be lost.
	CleanupStash CleanupStatus = "has_stash"

	// CleanupUnpushed means there are commits not pushed to the remote.
	CleanupUnpushed CleanupStatus = "has_unpushed"

	// CleanupUnknown means the status could not be determined.
	CleanupUnknown CleanupStatus = "unknown"
)

func (CleanupStatus) CanForceRemove added in v0.2.2

func (s CleanupStatus) CanForceRemove() bool

CanForceRemove returns true if the status allows forced removal. Uncommitted changes can be force-removed, but stashes and unpushed commits cannot.

func (CleanupStatus) IsSafe added in v0.2.2

func (s CleanupStatus) IsSafe() bool

IsSafe returns true if the status indicates it's safe to remove the worktree without losing any work.

func (CleanupStatus) RequiresRecovery added in v0.2.2

func (s CleanupStatus) RequiresRecovery() bool

RequiresRecovery returns true if the status indicates there is work that needs to be recovered before removal. This includes uncommitted changes, stashes, and unpushed commits.

type Manager

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

Manager handles polecat lifecycle.

func NewManager

func NewManager(r *rig.Rig, g *git.Git, t *tmux.Tmux) *Manager

NewManager creates a new polecat manager.

func (*Manager) Add

func (m *Manager) Add(name string) (*Polecat, error)

Polecat state is derived from beads assignee field, not state.json.

Branch naming: Each polecat run gets a unique branch (polecat/<name>-<timestamp>). This prevents drift issues from stale branches and ensures a clean starting state. Old branches are ephemeral and never pushed to origin.

func (*Manager) AddWithOptions

func (m *Manager) AddWithOptions(name string, opts AddOptions) (_ *Polecat, retErr error)

AddWithOptions creates a new polecat with the specified options. This allows setting hook_bead atomically at creation time, avoiding cross-beads routing issues when slinging work to new polecats.

func (*Manager) AllocateName

func (m *Manager) AllocateName() (string, error)

AllocateName allocates a name from the name pool. Returns a themed pooled name (furiosa, nux, etc.) if available, otherwise returns an overflow name (just a number like "51"). The rig prefix is added by SessionName to create full session names like "gt-<rig>-51". After allocation, kills any lingering tmux session for the name (gt-pqf9x) to prevent "session already running" errors when reusing names from dead polecats.

func (*Manager) AssignIssue

func (m *Manager) AssignIssue(name, issue string) error

AssignIssue assigns an issue to a polecat by setting the issue's assignee in beads.

func (*Manager) CheckDoltHealth added in v0.6.0

func (m *Manager) CheckDoltHealth() error

CheckDoltHealth verifies that the Dolt database is reachable before spawning. Returns an error if Dolt exists but is unhealthy after retries. Returns nil if beads is not configured (test/setup environments). If read-only errors persist after retries, attempts server recovery (gt-chx92). Fails fast on configuration/initialization errors (gt-2ra).

func (*Manager) CheckDoltServerCapacity added in v0.6.0

func (m *Manager) CheckDoltServerCapacity() error

CheckDoltServerCapacity verifies the Dolt server has capacity for new connections. This is an admission control gate: if the server is near its max_connections limit, spawning another polecat (which will make many bd calls) could overwhelm it. Returns nil if capacity is available, ErrDoltAtCapacity if the server is overloaded. Fails closed if the check errors — a server that can't report capacity is likely already under stress (gt-lfc0d).

func (*Manager) CleanupStaleBranches

func (m *Manager) CleanupStaleBranches() (int, error)

CleanupStaleBranches removes orphaned polecat branches that are no longer in use. This includes: - Branches for polecats that no longer exist - Old timestamped branches (keeps only the most recent per polecat name) Returns the number of branches deleted.

func (*Manager) ClearIssue

func (m *Manager) ClearIssue(name string) error

ClearIssue removes the issue assignment from a polecat. In the transient model, this transitions to Done state for cleanup. This clears the assignee from the currently assigned issue in beads. If beads is not available, this is a no-op.

func (*Manager) ClonePath added in v0.6.0

func (m *Manager) ClonePath(name string) string

ClonePath returns the path to a polecat's git worktree.

func (*Manager) DetectStalePolecats added in v0.2.1

func (m *Manager) DetectStalePolecats(threshold int) ([]*StalenessInfo, error)

DetectStalePolecats identifies polecats that are candidates for cleanup. A polecat is considered stale if: - No active tmux session AND - Either: way behind main (>threshold commits) OR no agent bead/activity - Has no uncommitted work that could be lost

threshold: minimum commits behind main to consider "way behind" (e.g., 20)

func (*Manager) FindIdlePolecat added in v0.9.0

func (m *Manager) FindIdlePolecat() (*Polecat, error)

FindIdlePolecat returns the first idle polecat in the rig, or nil if none. Idle polecats have completed their work and have a preserved sandbox (worktree) that can be reused by gt sling without creating a new worktree. Persistent polecat model (gt-4ac).

func (*Manager) Get

func (m *Manager) Get(name string) (*Polecat, error)

Get returns a specific polecat by name. State is derived from beads assignee field + tmux session state: - If an issue is assigned to this polecat: StateWorking - If no issue but tmux session is running: StateWorking (session alive = still working) - If no issue and no tmux session: StateIdle (persistent, ready for reuse)

func (*Manager) GetNamePool added in v0.9.0

func (m *Manager) GetNamePool() *NamePool

GetNamePool returns the manager's name pool for external use (e.g., pool init).

func (*Manager) List

func (m *Manager) List() ([]*Polecat, error)

List returns all polecats in the rig.

func (*Manager) PoolStatus

func (m *Manager) PoolStatus() (active int, names []string)

PoolStatus returns information about the name pool.

func (*Manager) ReconcilePool

func (m *Manager) ReconcilePool()

ReconcilePool derives pool InUse state from existing polecat directories and active sessions. This implements ZFC: InUse is discovered from filesystem and tmux, not tracked separately. Called before each allocation to ensure InUse reflects reality.

In addition to directory checks, this also: - Kills orphaned tmux sessions (sessions without directories are broken)

func (*Manager) ReconcilePoolWith added in v0.3.0

func (m *Manager) ReconcilePoolWith(namesWithDirs, namesWithSessions []string)

ReconcilePoolWith reconciles the name pool given lists of names from different sources. This is the testable core of ReconcilePool.

- namesWithDirs: names that have existing worktree directories (in use) - namesWithSessions: names that have tmux sessions

Names with sessions but no directories are orphans and their sessions are killed. Only namesWithDirs are marked as in-use for allocation.

func (*Manager) ReleaseName

func (m *Manager) ReleaseName(name string)

ReleaseName releases a name back to the pool. This is called when a polecat is removed.

func (*Manager) Remove

func (m *Manager) Remove(name string, force bool) error

Remove deletes a polecat worktree. If force is true, removes even with uncommitted changes (but not stashes/unpushed). Use nuclear=true to bypass ALL safety checks.

func (*Manager) RemoveWithOptions

func (m *Manager) RemoveWithOptions(name string, force, nuclear, selfNuke bool) (retErr error)

RemoveWithOptions deletes a polecat worktree with explicit control over safety checks. force=true: bypass uncommitted changes check (legacy behavior) nuclear=true: bypass ALL safety checks including stashes and unpushed commits selfNuke=true: bypass cwd-in-worktree check (for polecat deleting its own worktree)

ZFC #10: Uses cleanup_status from agent bead if available (polecat self-report), falls back to git check for backward compatibility.

func (*Manager) RepairWorktree added in v0.2.0

func (m *Manager) RepairWorktree(name string, force bool) (*Polecat, error)

RepairWorktree repairs a stale polecat by removing it and creating a fresh worktree. This is NOT for normal operation - it handles reconciliation when AllocateName returns a name that unexpectedly already exists (stale state recovery).

The polecat starts with the latest code from origin/<default-branch>. The name is preserved (not released to pool) since we're repairing immediately. force controls whether to bypass uncommitted changes check.

Branch naming: Each repair gets a unique branch (polecat/<name>-<timestamp>). Old branches are left for garbage collection - they're never pushed to origin.

func (*Manager) RepairWorktreeWithOptions added in v0.2.0

func (m *Manager) RepairWorktreeWithOptions(name string, force bool, opts AddOptions) (*Polecat, error)

RepairWorktreeWithOptions repairs a stale polecat and creates a fresh worktree with options. This is NOT for normal operation - see RepairWorktree for context. Allows setting hook_bead atomically at repair time. After repair, uses new structure: polecats/<name>/<rigname>/

func (*Manager) ReuseIdlePolecat added in v0.9.0

func (m *Manager) ReuseIdlePolecat(name string, opts AddOptions) (*Polecat, error)

ReuseIdlePolecat prepares an idle polecat for new work using branch-only operations. Unlike RepairWorktreeWithOptions, this does NOT create/remove git worktrees. It simply creates a fresh branch on the existing worktree, which eliminates the ~5s overhead of worktree creation. Phase 3 of persistent-polecat-pool.md.

Steps:

  1. Verify polecat exists and worktree is accessible
  2. Fetch latest from origin
  3. Create fresh branch: git checkout -b <branch> <startPoint>
  4. Reset agent bead and set hook_bead atomically
  5. Return polecat in working state

func (*Manager) SetAgentState added in v0.6.0

func (m *Manager) SetAgentState(name string, state string) error

SetState updates a polecat's state. In the beads model, state is derived from issue status: - StateWorking: issue status set to in_progress SetAgentState updates the agent bead's agent_state field. This is called after a polecat session successfully starts to transition from "spawning" to "working", making gt polecat identity show accurate status. Valid states: "spawning", "working", "done", "stuck", "idle"

func (*Manager) SetAgentStateWithRetry added in v0.6.0

func (m *Manager) SetAgentStateWithRetry(name string, state string) error

SetAgentStateWithRetry wraps SetAgentState with retry logic. Returns an error after exhausting retries, but callers may choose to warn rather than fail — e.g., in StartSession where the tmux session is already running and failing hard would orphan it. Agent state is a monitoring concern, not a correctness requirement. Fails fast on configuration/initialization errors (gt-2ra).

func (*Manager) SetState

func (m *Manager) SetState(name string, state State) error

- StateDone: assignee cleared from issue (polecat ready for cleanup) - StateStuck: issue status set to blocked (if supported) If beads is not available, this is a no-op.

type NamePool

type NamePool struct {

	// RigName is the rig this pool belongs to.
	RigName string `json:"rig_name"`

	// Theme is the current theme name (e.g., "mad-max", "minerals").
	Theme string `json:"theme"`

	// CustomNames allows overriding the built-in theme names.
	CustomNames []string `json:"custom_names,omitempty"`

	// InUse tracks which pool names are currently in use.
	// Key is the name itself, value is true if in use.
	// ZFC: This is transient state derived from filesystem via Reconcile().
	// Never persist - always discover from existing polecat directories.
	InUse map[string]bool `json:"-"`

	// OverflowNext is the next overflow sequence number.
	// Starts at MaxSize+1 and increments.
	OverflowNext int `json:"overflow_next"`

	// MaxSize is the maximum number of themed names before overflow.
	MaxSize int `json:"max_size"`
	// contains filtered or unexported fields
}

NamePool manages a bounded pool of reusable polecat NAME SLOTS. IMPORTANT: This pools NAMES, not polecats. Polecats are spawned fresh for each task and nuked when done - there is no idle pool of polecat instances waiting for work. When a polecat is nuked, its name slot becomes available for the next freshly-spawned polecat.

Names are drawn from a themed pool (mad-max by default). When the pool is exhausted, overflow names use N format (just numbers). The rig prefix is added by SessionName to create session names like "gt-<rig>-N".

func NewNamePool

func NewNamePool(rigPath, rigName string) *NamePool

NewNamePool creates a new name pool for a rig.

func NewNamePoolWithConfig

func NewNamePoolWithConfig(rigPath, rigName, theme string, customNames []string, maxSize int) *NamePool

NewNamePoolWithConfig creates a name pool with specific configuration.

func (*NamePool) ActiveCount

func (p *NamePool) ActiveCount() int

ActiveCount returns the number of names currently in use from the pool.

func (*NamePool) ActiveNames

func (p *NamePool) ActiveNames() []string

ActiveNames returns a sorted list of names currently in use from the pool.

func (*NamePool) AddCustomName

func (p *NamePool) AddCustomName(name string)

AddCustomName adds a custom name to the pool.

func (*NamePool) Allocate

func (p *NamePool) Allocate() (string, error)

Allocate returns a name from the pool. It prefers names in order from the theme list, and falls back to overflow names when the pool is exhausted.

func (*NamePool) GetTheme

func (p *NamePool) GetTheme() string

GetTheme returns the current theme name.

func (*NamePool) IsPoolName

func (p *NamePool) IsPoolName(name string) bool

IsPoolName returns true if the name is a pool name (themed or numbered).

func (*NamePool) Load

func (p *NamePool) Load() error

Load loads the pool state from disk.

func (*NamePool) MarkInUse

func (p *NamePool) MarkInUse(name string)

MarkInUse marks a name as in use (for reconciling with existing polecats).

func (*NamePool) Reconcile

func (p *NamePool) Reconcile(existingPolecats []string)

Reconcile updates the pool state based on existing polecat directories. This should be called on startup to sync pool state with reality.

func (*NamePool) Release

func (p *NamePool) Release(name string)

Release returns a name slot to the available pool. Called when a polecat is nuked - the name becomes available for new polecats. NOTE: This releases the NAME, not the polecat. The polecat is gone (nuked). For overflow names, this is a no-op (they are not reusable).

func (*NamePool) Reset

func (p *NamePool) Reset()

Reset clears the pool state, releasing all names.

func (*NamePool) Save

func (p *NamePool) Save() error

Save persists the pool state to disk using atomic write. Only runtime state (OverflowNext, MaxSize) is saved - configuration like Theme and CustomNames come from settings/config.json and are not persisted here.

func (*NamePool) SetTheme

func (p *NamePool) SetTheme(theme string) error

SetTheme sets the theme and resets the pool. Existing in-use names are preserved if they exist in the new theme.

type Polecat

type Polecat struct {
	// Name is the polecat identifier.
	Name string `json:"name"`

	// Rig is the rig this polecat belongs to.
	Rig string `json:"rig"`

	// State is the current lifecycle state.
	State State `json:"state"`

	// ClonePath is the path to the polecat's clone of the rig.
	ClonePath string `json:"clone_path"`

	// Branch is the current git branch.
	Branch string `json:"branch"`

	// Issue is the currently assigned issue ID (if any).
	Issue string `json:"issue,omitempty"`

	// CreatedAt is when the polecat was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is when the polecat was last updated.
	UpdatedAt time.Time `json:"updated_at"`
}

Polecat represents a worker agent in a rig.

func (*Polecat) Summary

func (p *Polecat) Summary() Summary

Summary returns a Summary for this polecat.

type SessionHeartbeat added in v0.9.0

type SessionHeartbeat struct {
	Timestamp time.Time `json:"timestamp"`
}

SessionHeartbeat represents a polecat session's heartbeat file.

func ReadSessionHeartbeat added in v0.9.0

func ReadSessionHeartbeat(townRoot, sessionName string) *SessionHeartbeat

ReadSessionHeartbeat reads the heartbeat for a polecat session. Returns nil if the file doesn't exist or can't be read.

type SessionInfo added in v0.2.2

type SessionInfo struct {
	// Polecat is the polecat name.
	Polecat string `json:"polecat"`

	// SessionID is the tmux session identifier.
	SessionID string `json:"session_id"`

	// Running indicates if the session is currently active.
	Running bool `json:"running"`

	// RigName is the rig this session belongs to.
	RigName string `json:"rig_name"`

	// Attached indicates if someone is attached to the session.
	Attached bool `json:"attached,omitempty"`

	// Created is when the session was created.
	Created time.Time `json:"created,omitempty"`

	// Windows is the number of tmux windows.
	Windows int `json:"windows,omitempty"`

	// LastActivity is when the session last had activity.
	LastActivity time.Time `json:"last_activity,omitempty"`
}

SessionInfo contains information about a running polecat session.

type SessionManager added in v0.2.2

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

SessionManager handles polecat session lifecycle.

func NewSessionManager added in v0.2.2

func NewSessionManager(t *tmux.Tmux, r *rig.Rig) *SessionManager

NewSessionManager creates a new polecat session manager for a rig.

func (*SessionManager) Attach added in v0.2.2

func (m *SessionManager) Attach(polecat string) error

Attach attaches to a polecat session.

func (*SessionManager) Capture added in v0.2.2

func (m *SessionManager) Capture(polecat string, lines int) (string, error)

Capture returns the recent output from a polecat session.

func (*SessionManager) CaptureSession added in v0.2.2

func (m *SessionManager) CaptureSession(sessionID string, lines int) (string, error)

CaptureSession returns the recent output from a session by raw session ID.

func (*SessionManager) Inject added in v0.2.2

func (m *SessionManager) Inject(polecat, message string) error

Inject sends a message to a polecat session.

func (*SessionManager) IsRunning added in v0.2.2

func (m *SessionManager) IsRunning(polecat string) (bool, error)

IsRunning checks if a polecat session is active and healthy. Checks both tmux session existence AND agent process liveness to avoid reporting zombie sessions (tmux alive but Claude dead) as "running".

func (*SessionManager) List added in v0.2.2

func (m *SessionManager) List() ([]SessionInfo, error)

List returns information about all sessions for this rig. This includes polecats, witness, refinery, and crew sessions. Use ListPolecats() to get only polecat sessions.

func (*SessionManager) ListPolecats added in v0.6.0

func (m *SessionManager) ListPolecats() ([]SessionInfo, error)

ListPolecats returns information only about polecat sessions for this rig. Filters out witness, refinery, and crew sessions.

func (*SessionManager) SessionName added in v0.2.2

func (m *SessionManager) SessionName(polecat string) string

SessionName generates the tmux session name for a polecat. Validates that the polecat name doesn't contain the rig prefix to prevent double-prefix bugs (e.g., "gt-gastown_manager-gastown_manager-142").

func (*SessionManager) Start added in v0.2.2

func (m *SessionManager) Start(polecat string, opts SessionStartOptions) error

Start creates and starts a new session for a polecat.

func (*SessionManager) Status added in v0.2.2

func (m *SessionManager) Status(polecat string) (*SessionInfo, error)

Status returns detailed status for a polecat session.

func (*SessionManager) Stop added in v0.2.2

func (m *SessionManager) Stop(polecat string, force bool) error

Stop terminates a polecat session.

func (*SessionManager) StopAll added in v0.2.2

func (m *SessionManager) StopAll(force bool) error

StopAll terminates all polecat sessions for this rig.

type SessionStartOptions added in v0.2.2

type SessionStartOptions struct {
	// WorkDir overrides the default working directory (polecat clone dir).
	WorkDir string

	// Issue is an optional issue ID to work on.
	Issue string

	// Command overrides the default "claude" command.
	Command string

	// Account specifies the account handle to use (overrides default).
	Account string

	// RuntimeConfigDir is resolved config directory for the runtime account.
	// If set, this is injected as an environment variable.
	RuntimeConfigDir string

	// Agent is the agent override for this polecat session (e.g., "codex", "gemini").
	// If set, GT_AGENT is written to the tmux session environment table so that
	// IsAgentAlive and waitForPolecatReady read the correct process names.
	Agent string
}

SessionStartOptions configures polecat session startup.

type StalenessInfo added in v0.2.1

type StalenessInfo struct {
	Name               string
	CommitsBehind      int    // How many commits behind origin/main
	HasActiveSession   bool   // Whether tmux session is running
	HasUncommittedWork bool   // Whether there's uncommitted or unpushed work
	AgentState         string // From agent bead (empty if no bead)
	IsStale            bool   // Overall assessment: safe to clean up
	Reason             string // Why it's considered stale (or not)
}

StalenessInfo contains details about a polecat's staleness.

type State

type State string

State represents the current lifecycle state of a polecat.

Polecats are PERSISTENT: they survive work completion and can be reused. The four operating states are:

  • Working: Session active, doing assigned work (normal operation)
  • Idle: Work completed, session killed, sandbox preserved for reuse
  • Stalled: Session stopped unexpectedly, was never nudged back to life
  • Zombie: Session called 'gt done' but cleanup failed - tried to die but couldn't

The distinction matters: idle polecats completed their work successfully and are ready for new assignments. Stalled polecats failed mid-work. Zombies tried to exit but couldn't complete cleanup.

Note: These are LIFECYCLE states. The polecat IDENTITY (CV chain, mailbox, work history) and SANDBOX (worktree) persist across sessions. An idle polecat keeps its worktree so it can be quickly reassigned without creating a new one.

"Stalled" and "zombie" are detected conditions, not stored states. The Witness detects them through monitoring (tmux state, age in StateDone, etc.).

const (
	// StateWorking means the polecat session is actively working on an issue.
	// This is the initial and primary state after sling.
	StateWorking State = "working"

	// StateIdle means the polecat completed its work and the session was killed,
	// but the sandbox (worktree) is preserved for reuse. An idle polecat has no
	// hook_bead and no active session. It can be reassigned via gt sling without
	// creating a new worktree.
	StateIdle State = "idle"

	// StateDone means the polecat has completed its assigned work and called
	// 'gt done'. This is normally a transient state - the session should exit
	// immediately after. If a polecat remains in StateDone, it's a "zombie":
	// the cleanup failed and the session is stuck.
	StateDone State = "done"

	// StateStuck means the polecat has explicitly signaled it needs assistance.
	// This is an intentional request for help from the polecat itself.
	// Different from "stalled" (detected externally when session stops working).
	StateStuck State = "stuck"

	// StateZombie means a tmux session exists but has no corresponding worktree directory.
	// This is a detected condition: the polecat was incompletely nuked or has a
	// session naming mismatch, leaving an orphaned tmux session.
	StateZombie State = "zombie"
)

func (State) IsIdle added in v0.9.0

func (s State) IsIdle() bool

IsIdle returns true if the polecat has completed work and is available for reuse.

func (State) IsWorking

func (s State) IsWorking() bool

IsWorking returns true if the polecat is currently working.

type Summary

type Summary struct {
	Name  string `json:"name"`
	State State  `json:"state"`
	Issue string `json:"issue,omitempty"`
}

Summary provides a concise view of polecat status.

type UncommittedWorkError

type UncommittedWorkError struct {
	PolecatName string
	Status      *git.UncommittedWorkStatus
}

UncommittedWorkError provides details about uncommitted work.

func (*UncommittedWorkError) Error

func (e *UncommittedWorkError) Error() string

func (*UncommittedWorkError) Unwrap

func (e *UncommittedWorkError) Unwrap() error

Jump to

Keyboard shortcuts

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