autofix

package
v0.32.39 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package autofix holds the whitelist of corrective actions pulse may invoke without further human approval. Every autofix must be:

  • idempotent: running it twice in a row produces the same end state;
  • scoped: touches only files pulse understands to be its own;
  • bounded: completes in under a minute on typical workspaces;
  • safe-on-failure: partial failures leave the workspace in a consistent state (no half-deleted, half-renamed, half-compressed files).

New autofixes are added by implementing the Fixer interface and registering a constructor in the DefaultRegistry initializer below. The decider cannot invoke an autofix whose name is not in both the code registry AND the operator's config allow-list — this double gate is intentional.

Index

Constants

View Source
const AutoContinueChatName = "auto_continue_chat"
View Source
const AutoContinueGoalPlanName = "auto_continue_goal_plan"
View Source
const AutoResumeFailedChatName = "auto_resume_failed_chat"
View Source
const CleanupStaleTmpName = "cleanup_stale_tmp"

CleanupStaleTmpName is the canonical name used in configs and logs.

View Source
const CompressOldLogsName = "compress_old_logs"

CompressOldLogsName is the canonical name used in configs and logs.

Variables

This section is empty.

Functions

This section is empty.

Types

type AutoContinueChat added in v0.31.114

type AutoContinueChat struct {
	Continuer ChatAutoContinuer
}

func (*AutoContinueChat) Name added in v0.31.114

func (a *AutoContinueChat) Name() string

func (*AutoContinueChat) Run added in v0.31.114

func (a *AutoContinueChat) Run(ctx context.Context) (Result, error)

type AutoContinueGoalPlan added in v0.31.145

type AutoContinueGoalPlan struct {
	Continuer GoalPlanAutoContinuer
}

AutoContinueGoalPlan runs one chat turn per session whose plan has just completed with auto-continue opted in. The turn asks the LLM to either declare the session goal achieved (which terminates the loop) or propose the next plan. A hard iteration cap on the Plan keeps the loop bounded.

func (*AutoContinueGoalPlan) Name added in v0.31.145

func (a *AutoContinueGoalPlan) Name() string

func (*AutoContinueGoalPlan) Run added in v0.31.145

type AutoResumeFailedChat added in v0.31.144

type AutoResumeFailedChat struct {
	Resumer FailedChatResumer
}

AutoResumeFailedChat retries chat turns that halted with a recoverable failure (tool error from a non-mutating tool, or a user message the LLM never finished responding to). Mutating-tool failures are filtered out at detection time, so by the time a candidate reaches this autofix it is safe to re-run the turn.

func (*AutoResumeFailedChat) Name added in v0.31.144

func (a *AutoResumeFailedChat) Name() string

func (*AutoResumeFailedChat) Run added in v0.31.144

type ChatAutoContinueResult added in v0.31.114

type ChatAutoContinueResult struct {
	Resumed    int      `json:"resumed"`
	Skipped    int      `json:"skipped"`
	Escalated  int      `json:"escalated"`
	SessionIDs []string `json:"session_ids,omitempty"`
}

type ChatAutoContinuer added in v0.31.114

type ChatAutoContinuer interface {
	AutoContinueStalledChats(ctx context.Context) (ChatAutoContinueResult, error)
}

type CleanupStaleTmp

type CleanupStaleTmp struct {
	// Dirs is the list of absolute tmp paths to scan. Non-existent
	// entries are silently skipped. Typical values:
	//   <workspace>/tmp
	//   <workspace>/_shared/tmp
	Dirs []string
	// MaxAge is the minimum file age required before deletion. Zero
	// falls back to 7 days, matching the Phase 1 default.
	MaxAge time.Duration
	// Now is injectable for tests; real code uses time.Now.
	Now func() time.Time
}

CleanupStaleTmp removes plain files older than MaxAge from the workspace's temporary directories. It intentionally DOES NOT:

  • follow symlinks (they are skipped; pulse must never chase links out of workspace-controlled paths);
  • delete directories (even empty ones — directory cleanup is a separate concern and can remove user state accidentally);
  • descend into subdirectories (recursion widens the blast radius for a background agent; tmp layouts are expected to be flat);

The fixer is idempotent: a second run in quick succession finds no candidates and reports Changed=false.

func (*CleanupStaleTmp) Name

func (c *CleanupStaleTmp) Name() string

func (*CleanupStaleTmp) Run

func (c *CleanupStaleTmp) Run(ctx context.Context) (Result, error)

type CompressOldLogs

type CompressOldLogs struct {
	// LogsDir is an absolute path to the directory being scanned. It is
	// usually <workspace>/logs. Non-existent dirs are treated as empty.
	LogsDir string
	// MaxAge is the minimum file age required before compression. Zero
	// falls back to 7 days, matching the Phase 1 default.
	MaxAge time.Duration
	// Now is injectable for tests; real code uses time.Now.
	Now func() time.Time
}

CompressOldLogs gzips .log files older than MaxAge found under the workspace logs directory. It is designed to run under the pulse watchdog as a periodic housekeeping fix, so its touch is narrow:

  • only files ending in ".log" (not ".log.gz", not other extensions)
  • only files with modification time older than MaxAge
  • compressed output is written alongside as "<file>.log.gz"
  • original is removed only after successful compression and fsync

The fixer is idempotent: a second run finds no candidates (they are already gzipped) and reports Changed=false.

func (*CompressOldLogs) Name

func (c *CompressOldLogs) Name() string

func (*CompressOldLogs) Run

func (c *CompressOldLogs) Run(ctx context.Context) (Result, error)

type ErrUnknown

type ErrUnknown struct{ Name string }

ErrUnknown is returned when a requested autofix is not registered.

func (ErrUnknown) Error

func (e ErrUnknown) Error() string

type FailedChatResumeResult added in v0.31.144

type FailedChatResumeResult struct {
	Resumed    int      `json:"resumed"`
	Skipped    int      `json:"skipped"`
	Escalated  int      `json:"escalated"`
	SessionIDs []string `json:"session_ids,omitempty"`
}

type FailedChatResumer added in v0.31.144

type FailedChatResumer interface {
	ResumeFailedChats(ctx context.Context) (FailedChatResumeResult, error)
}

type Fixer

type Fixer interface {
	Name() string
	Run(ctx context.Context) (Result, error)
}

Fixer is the single-method interface each autofix implementation must satisfy. Fixers are expected to be pure functions of their constructor arguments (workspace dir, clock, etc.) — no package-level state.

type GoalPlanAutoContinueResult added in v0.31.145

type GoalPlanAutoContinueResult struct {
	Continued      int      `json:"continued"`
	GoalsCompleted int      `json:"goals_completed"`
	Skipped        int      `json:"skipped"`
	Escalated      int      `json:"escalated"`
	SessionIDs     []string `json:"session_ids,omitempty"`
}

type GoalPlanAutoContinuer added in v0.31.145

type GoalPlanAutoContinuer interface {
	ContinueGoalPlans(ctx context.Context) (GoalPlanAutoContinueResult, error)
}

type Registry

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

Registry is a concurrent-safe name → Fixer map. Its primary use is the pulse runtime looking up a named autofix from a Decision.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty registry.

func (*Registry) AllowedIntersection

func (r *Registry) AllowedIntersection(configured []string) []string

AllowedIntersection returns the intersection of the registry's names and the operator-configured allow-list. This is the exact list that should be handed to the decider as the autofix policy — anything outside this set cannot safely run.

func (*Registry) Has

func (r *Registry) Has(name string) bool

Has reports whether a fixer with the given name is registered.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns all registered fixer names in sorted order. Used by pulse startup logging and by HTTP handlers that want to report which autofixes are known.

func (*Registry) Register

func (r *Registry) Register(f Fixer)

Register adds a fixer to the registry. Re-registering a name replaces the previous fixer — callers that care should check Has first.

func (*Registry) Run

func (r *Registry) Run(ctx context.Context, name string) (Result, error)

Run executes the named fixer. It returns ErrUnknown when the name is not registered, letting callers distinguish that from legitimate fixer errors.

type Result

type Result struct {
	Name    string         `json:"name"`
	Summary string         `json:"summary,omitempty"`
	Details map[string]any `json:"details,omitempty"`
	Changed bool           `json:"changed"`
}

Result captures what an autofix did. Fields are optional; an empty Result is valid for autofixes that have nothing meaningful to report (e.g. "there was nothing to clean up").

Jump to

Keyboard shortcuts

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