hostpoller

package
v0.35.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package hostpoller is the host side of Hadron's host operations (hadron-cli#831, cor:agt:070): a poller that registers the workers this host can launch, collects the operations issued to it, carries out only the closed set of five, and reports every outcome.

It talks to the server through a narrow Server interface and to the host through a Host interface, so the loop and every operation's rules are tested without a server, git or a real agent.

Index

Constants

View Source
const (
	OpWorktreeCreate = "worktree.create"
	OpWorkerStart    = "worker.start"
	OpWorkerDeliver  = "worker.deliver"
	OpWorkerEnd      = "worker.end"
	OpWorktreeRemove = "worktree.remove"
)

The closed set of operation types a host carries out (cor:agt:070:03). Anything else is refused here as well as by the server, and there is no shell passthrough of any kind.

View Source
const (
	Succeeded = "SUCCEEDED"
	Failed    = "FAILED"
)

Outcomes a host reports.

View Source
const CapabilityResetContext = OpWorkerResetContext

CapabilityResetContext is the registration capability for that operation. Declaring it is a separate, authorized activation; this package never adds it to a registration by itself.

View Source
const (

	// HostKeyEnv is never read as a key source (a same-user agent could
	// read it from the poller's environment); it is scrubbed from launched
	// agents in case the operator's shell carries one.
	HostKeyEnv = "HADRON_HOST_KEY"
)
View Source
const LaunchGrantTTL = 10 * time.Minute

LaunchGrantTTL is how long a collected start's grant lives (#1883, Proposed). Waiting for the bind longer than this cannot succeed.

View Source
const OpWorkerResetContext = "worker.reset-context"

OpWorkerResetContext is the capability-gated operation (#2162, cor:agt:050:27): reset a worker's context after its task, keeping the binding. Args {workerId, sessionId, taskId, resetId}; no epoch, the claim is made at 0 and moves it (hadron-cli#1043).

Variables

View Source
var ErrClock = errors.New("the boot clock gave no usable reading, so the stop lease cannot be measured; nothing was sent")

ErrClock: the boot clock could not be read, or read earlier than the window's start, so the lease cannot be measured; nothing was sent.

View Source
var ErrDeliveryScope = errors.New("the host key lacks attention:deliver: delivery is off for this run")

ErrDeliveryScope is a host key without attention:deliver (APP_KEY_SCOPE_REQUIRED). It does not heal by retrying, so delivery is switched off for the rest of the run; host operations carry on.

View Source
var ErrFinal = errors.New("the operation is already finished")

ErrFinal is a Report refusal meaning the operation is already finished server-side (HOST_OPERATION_ALREADY_REPORTED): asking again cannot change it.

View Source
var ErrHandleClosed = errors.New("the process handle is closed; nothing was sent")

ErrHandleClosed: the handle was closed; nothing was sent.

View Source
var ErrLeaseExpired = errors.New("the stop lease expired before the signal; nothing was sent")

ErrLeaseExpired: the stop lease had run out at the check; no signal sent.

View Source
var ErrNotDone = errors.New("no session bound with this start's grant yet")

ErrNotDone is a Report refusal meaning a worker.start's session is not bound yet (HOST_OPERATION_NOT_DONE); the poller asks again.

View Source
var ErrUnsupportedPlatform = errors.New("worker.reset-context needs Linux (pidfd and CLOCK_BOOTTIME); this host refuses it")

ErrUnsupportedPlatform: this platform has no pidfd and boot clock, so the handler refuses before any claim rather than signal a bare pid.

View Source
var ResetStepCodes = []string{"STALE_EPOCH", "CONTEXT_RESET_WRONG_PHASE", "CONTEXT_RESET_NOT_FOUND", "HOST_OPERATION_NOT_COLLECTED"}

ResetStepCodes are the typed rejections a step can carry.

Functions

func CanonicalFamily added in v0.27.0

func CanonicalFamily(s string) string

CanonicalFamily is the server's identity for an EXPLICIT family: trimmed, lowercased, and a provider alias folded to its family. Every other token is itself, so a custom family compares only to itself. It never infers a family from a title or a model and does not validate: an empty or malformed result is not a launch identity and callers must refuse it (familyRE).

func ResetStepCode added in v0.35.0

func ResetStepCode(err error) string

ResetStepCode returns the typed code of a step rejection, or "".

func SameFamily added in v0.27.0

func SameFamily(a, b string) bool

SameFamily reports whether two explicit families are one launch identity. An empty or malformed family is never one, even against itself.

func ScrubbedEnv added in v0.33.0

func ScrubbedEnv(parent []string, hostKey string) []string

ScrubbedEnv is parent without the credential variables and any variable whose value carries hostKey: the environment for a child that must not see the host's credentials (the Orca CLI a status read runs).

func ValidNameToken

func ValidNameToken(s string) bool

ValidNameToken reports whether s is a host name token the server accepts.

Types

type Agent

type Agent interface {
	// Done is closed when the process exits.
	Done() <-chan struct{}
	// ExitDetail describes how it exited, once Done is closed.
	ExitDetail() string
	// PID is its process id (0 when unknown).
	PID() int
}

Agent is a launched agent process.

type AgentRecord

type AgentRecord struct {
	Worktree string `json:"worktree"`
	PID      int    `json:"pid"`
	// Start is the launch's kernel start time, observed while it ran (empty
	// when not observed): with PID, its launch identity, so a restarted
	// poller can tell this launch from another at the same pid (cli#1582).
	Start string `json:"start,omitempty"`
}

AgentRecord is one launched agent.

type BatchSource added in v0.25.0

type BatchSource interface {
	Batches(ctx context.Context) ([]delivery.Batch, error)
}

BatchSource reads this host's due batches with the host key.

type Config

type Config struct {
	// Capabilities is an explicit full declaration at every refresh, never inferred.
	Capabilities []string `yaml:"capabilities"`
	// Server is the Hadron server URL; empty means the CLI's usual resolution.
	Server string `yaml:"server"`
	// App is the team App the host serves (hrn:app:<root>:<slug>, or an id).
	App string `yaml:"app"`
	// Label names the host to people; empty means the hostname.
	Label string `yaml:"label"`
	// PollIntervalSeconds is how often the host checks in (1 to 86400).
	PollIntervalSeconds int `yaml:"poll_interval_seconds"`
	// KeyFile holds the App key carrying host:operate for this host
	// (required; mode 0600 or tighter). Relative paths resolve against the
	// configuration's directory.
	KeyFile string `yaml:"key_file"`
	// WorktreesRoot is the directory every worktree name token is relative to.
	WorktreesRoot string `yaml:"worktrees_root"`
	// StateFile records the worktrees this poller created and any it must
	// keep. Empty means hadron-host-state.json beside the config.
	StateFile string `yaml:"state_file"`
	// Repos maps a repo token (as worktree.create names it, e.g.
	// hadron-memory/hadron-cli) to the local clone worktrees are added from.
	Repos map[string]string `yaml:"repos"`
	// Workers maps a worker reference (its name in the App, URN or id) to
	// how this host runs it.
	Workers map[string]WorkerConfig `yaml:"workers"`
	// Delivery turns on chat delivery into this host's Orca terminals
	// (cli#907). Absent, the poller carries out host operations only.
	Delivery DeliveryConfig `yaml:"delivery"`
}

The host-local configuration. Hadron never sees it: the launch command and any AI-plan login are the host's own (Holger #10777, cor:agt:070 Out of Scope), and the host key is read from a file named here — never from an environment variable, which a same-user agent could read from the poller's /proc entry.

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig reads and validates a host configuration file.

func (*Config) CheckWorktreeAliases added in v0.25.0

func (c *Config) CheckWorktreeAliases(s *State) error

CheckWorktreeAliases refuses two workers whose worktrees, configured or created by this poller, name one directory under different tokens (cli#906). validate already refuses one token listed twice; a symlink gives one directory a second token. A tree that does not exist yet is compared by its cleaned path only. One worker's own aliases are allowed.

func (*Config) DeliveryStateDir added in v0.25.0

func (c *Config) DeliveryStateDir() string

DeliveryStateDir holds delivery's records and per-worker locks: beside the state file, where C2's --orca-host-config finds the same locks.

func (*Config) ReadHostKey

func (c *Config) ReadHostKey() (string, error)

ReadHostKey returns the host:operate key from key_file. Never from HADRON_TOKEN or the token store (the holder's own credential, which the launched agents bind on), and never from an environment variable.

The file must not be readable by group or others. That is the part of key custody the poller CAN check. The part it cannot: an agent running as the same OS user as the poller can read any file the poller can. Keeping the key from a launched agent's environment, argv and files is the poller's; keeping it from the agent's OS user is the deployment's — run the poller as its own user, with key_file readable by that user only.

func (*Config) Recipient added in v0.25.0

func (c *Config) Recipient(s *State, ref, workerID string) delivery.Recipient

Recipient is the configured worker ref, resolved to workerID, as delivery needs it: its expected interface and its configured and created worktrees as absolute paths. The poller and C2 build it the same way.

func (*Config) RefFor added in v0.25.0

func (c *Config) RefFor(id, urn, name string) string

RefFor is the configured reference for a worker, matched by id, URN or name (names are unique per App case-insensitively, cor:agt:020:02), or "" when none matches. It is the poller's registration rule, for a caller that delivers on this host without a live registration (C2, cli#908).

func (*Config) WorkerRefs

func (c *Config) WorkerRefs() []string

WorkerRefs lists the configured worker references in a stable order.

func (*Config) WorktreePath

func (c *Config) WorktreePath(token string) string

WorktreePath is the absolute path of a worktree name token.

type ContextReset added in v0.35.0

type ContextReset struct {
	ID          string
	Epoch       int
	Phase       string
	Reason      string
	WorkerID    string
	SessionID   string
	TaskID      string
	StopGranted bool
	StopLeaseMs int
	// CheckpointNode is the v2 URN of the checkpoint node registered for
	// the relaunch (hadron-server#3300); empty before registration.
	CheckpointNode string
}

ContextReset is the part of the server's reset record the handler reads.

type Deliverer added in v0.25.0

type Deliverer interface {
	Deliver(ctx context.Context, b delivery.Batch, r delivery.Recipient) delivery.Outcome
}

Deliverer carries one batch to its recipient; *delivery.Deliverer is one.

type DeliveryConfig added in v0.25.0

type DeliveryConfig struct {
	// Enabled turns delivery on. The host key then also needs
	// attention:deliver; without it delivery switches off for the run.
	Enabled bool `yaml:"enabled"`
	// AckMixed asserts that the server carries A-prime (#2072): mixed wake
	// acknowledgment records ticks/tasks without advancing any chat mark.
	AckMixed bool `yaml:"ack_mixed"`
	// OrcaBin is the absolute path of the orca CLI.
	OrcaBin string `yaml:"orca_bin"`
}

DeliveryConfig is chat delivery's part of a host configuration.

type Event

type Event struct {
	Time      time.Time
	Kind      string
	Operation string
	Type      string
	Outcome   string
	Detail    string
}

Event is one line of what the poller did, for the operator. The --json shape is the command package's DTO, not this.

type ExecHost

type ExecHost struct {
	// PromptTimeout bounds worker.end's prompt command.
	PromptTimeout time.Duration
}

ExecHost is the real Host: git for worktrees, exec for agents. Every command is a fixed argv — git's own, or the one in host configuration — never a shell string, and nothing in it comes from an operation's arguments except git ref and path names, which git receives as separate arguments after the option they belong to.

func (ExecHost) Alive

func (ExecHost) Alive(pid int) bool

Alive reports whether pid is a running process.

func (ExecHost) CreateWorktree

func (ExecHost) CreateWorktree(ctx context.Context, repoPath, path, branch, base string) error

CreateWorktree fetches the base from origin, then adds the worktree on a new branch from origin/<base> (or the local <base> when origin has none).

func (ExecHost) Inspect

func (ExecHost) Inspect(ctx context.Context, path string) (WorktreeState, error)

Inspect reads a worktree's branch, cleanliness and push state.

func (ExecHost) Launch

func (ExecHost) Launch(_ context.Context, spec LaunchSpec) (Agent, error)

Launch starts the agent and returns at once. On Unix it gets its own session (setsid), so the poller's signals do not reach it; on Windows it stays in the poller's console group.

func (ExecHost) Occupied

func (ExecHost) Occupied(_ context.Context, path string) (string, error)

Occupied looks for a process whose working directory is inside path, via /proc (Linux). Without /proc it cannot tell and reports none.

func (ExecHost) Prompt

func (h ExecHost) Prompt(ctx context.Context, argv []string, dir string, env []string) error

Prompt runs worker.end's prompt command to completion, bounded.

func (ExecHost) RemoveWorktree

func (ExecHost) RemoveWorktree(ctx context.Context, path string) error

RemoveWorktree removes a worktree git considers clean; git refuses a dirty one on its own, as a second line behind Inspect.

type GitTreeState added in v0.35.0

type GitTreeState struct {
	// InGit: `git -C <dir> rev-parse --is-inside-work-tree` printed true.
	InGit bool
	// The rest is read only when InGit.
	Dirty    bool
	Upstream string
	Ahead    int
}

GitTreeState is the reset handler's Git determination (ruling 0012, cor:agt:050:27 rule 7; hadron-cli#1043 amendment 6586883519).

func GitTree added in v0.35.0

func GitTree(ctx context.Context, dir string) (GitTreeState, error)

GitTree decides whether dir is inside a Git work tree, and only then reads its cleanliness and push state. A missing directory, a non-Git directory and a host without git are all "not in a Git tree". Any other git failure is an error, so the caller refuses: an unreadable tree is never taken for a non-Git one.

type Host

type Host interface {
	// CreateWorktree adds a worktree at path on a new branch from base.
	CreateWorktree(ctx context.Context, repoPath, path, branch, base string) error
	// Inspect reads a worktree's state; Exists is false for a missing path.
	Inspect(ctx context.Context, path string) (WorktreeState, error)
	// RemoveWorktree removes a clean worktree.
	RemoveWorktree(ctx context.Context, path string) error
	// Launch starts an agent and returns at once; the agent keeps running.
	Launch(ctx context.Context, spec LaunchSpec) (Agent, error)
	// Prompt runs a short command (worker.end's prompt) to completion.
	Prompt(ctx context.Context, argv []string, dir string, env []string) error
	// Occupied names a process (other than the poller) working inside path,
	// or "" when none is seen. Where the host cannot tell (no /proc), it
	// reports none, and worktree.remove leans on the recorded agents alone.
	Occupied(ctx context.Context, path string) (string, error)
	// Alive reports whether a process id is still running.
	Alive(pid int) bool
}

Host is everything the poller does to the machine: git worktrees and agent processes. The real one shells out to git and execs the configured argv; tests fake it.

type LaunchSpec

type LaunchSpec struct {
	Argv []string
	Dir  string
	Env  []string
}

LaunchSpec is one agent start: the argv from host configuration, run in the worktree, with an environment that carries the launch grant and no Hadron credential of the host's.

type LinuxProcessControl added in v0.35.0

type LinuxProcessControl struct{}

LinuxProcessControl is ProcessControl over pidfd_open, pidfd_send_signal and CLOCK_BOOTTIME.

func (LinuxProcessControl) Now added in v0.35.0

Now reads CLOCK_BOOTTIME. It exists on every kernel with pidfd (5.3+), so a failure is unreachable in practice; it is still an error, never a stand-in time, because two equal stand-ins measure a zero-length window.

func (LinuxProcessControl) Observe added in v0.35.0

func (LinuxProcessControl) Observe(pid int) (ProcessHandle, error)

Observe opens a pidfd first, then reads the start time, then proves the pidfd's process is still alive: if it is, the pid has not been reused between the open and the read, so the token belongs to the pinned process.

type Operation

type Operation struct {
	ID            string
	Type          string
	Args          map[string]any
	WorkerID      string
	Status        string
	Result        map[string]any
	FailureReason string
	// LaunchGrant is set only on a collected worker.start.
	LaunchGrant string
}

Operation is a collected host operation.

type Poller

type Poller struct {

	// BindPoll is how often a pending start asks whether its session bound.
	BindPoll time.Duration
	// Fatal, when set, decides that a failed check-in ends Run: a refused or
	// revoked host key does not heal by retrying, and looping on it would
	// hide it.
	Fatal func(error) bool

	// DeliveryStep, when set, is told how each delivery step's batch read
	// went (delivery.StepRead, StepReadFailed, StepOffByScope).
	DeliveryStep func(at time.Time, read string)
	// WakeStep, when set, is told how each step's wake read went (cli#906
	// row 1).
	WakeStep func(delivery.WakeRead)
	// contains filtered or unexported fields
}

Poller is one host's loop.

func New

func New(cfg *Config, server Server, host Host, state *State, key string, emit func(Event)) *Poller

New builds a poller. key is the host key, kept only to scrub it from launched agents' environments (defence in depth: it is never put there).

func (*Poller) DeliveryEnabled added in v0.25.0

func (p *Poller) DeliveryEnabled() bool

DeliveryEnabled reports whether this run delivers.

func (*Poller) EnableDelivery added in v0.25.0

func (p *Poller) EnableDelivery(src BatchSource, d Deliverer)

EnableDelivery turns chat delivery on.

func (*Poller) EnableResets added in v0.35.0

func (p *Poller) EnableResets(rs ResetServer, deps ResetDeps)

EnableResets turns the handler on for this poller. It declares no capability: registration still sends exactly cfg.Capabilities, and advertising worker.reset-context is a separate authorized activation.

func (*Poller) EnableWakes added in v0.25.0

func (p *Poller) EnableWakes(src WakeSource, d WakeDeliverer)

EnableWakes turns tick and task wake delivery on (cli#906 row 1). Wakes are read only while chat delivery is on: they share its key, scope and per-worker chain.

func (*Poller) ReportFailures

func (p *Poller) ReportFailures() int

ReportFailures is how many reports did not reach the server; those operations are re-delivered at the next collect.

func (*Poller) Run

func (p *Poller) Run(ctx context.Context) error

Run polls until ctx ends, then waits for operations in flight.

func (*Poller) Step

func (p *Poller) Step(ctx context.Context) error

Step is one check-in: register (always — a host that stops registering lapses), then collect and dispatch.

A worker.start waiting for its bind is re-delivered by every collect and skipped here as in flight. It must not come back with a fresh grant: that would revoke the one its agent was handed. The server stops doing that (Liam, team chat #11415: a re-collect omits a start whose grant is still live), and this poller depends on it rather than pausing collection.

func (*Poller) Wait

func (p *Poller) Wait()

Wait blocks until every dispatched operation has been carried out.

type ProcessControl added in v0.35.0

type ProcessControl interface {
	Observe(pid int) (ProcessHandle, error)
	Now() (time.Duration, error)
}

ProcessControl is the reset handler's only way to touch an agent process (#1043). It observes a process by pid into a handle that pins it (a pidfd on Linux), so a later signal can only reach that process, never a reused pid. Now is CLOCK_BOOTTIME: it keeps counting across a suspend, so a lease measured on it is never longer than the server meant. A clock that cannot be read is an error, never a stand-in value: a lease is only ever measured between two real readings.

type ProcessHandle added in v0.35.0

type ProcessHandle interface {
	Identity() ProcessIdentity
	// SignalWithin sends sig only if now() - t0 < lease, with the check and
	// the send adjacent. t0 is read BEFORE the authorizeStop request goes
	// out, so a slow answer shortens the window and never lengthens it. It
	// re-reads the clock after the send and returns how far past the lease
	// the send landed (0 when inside): a late stop the caller records
	// (ruling 0087 A). ErrLeaseExpired means nothing was sent; so does
	// ErrClock, when the check's reading failed or is before t0. A failed
	// reading after the send cannot unsend: the stop stands and late is 0,
	// the check before it having passed on a real reading.
	SignalWithin(sig syscall.Signal, t0, lease time.Duration, now func() (time.Duration, error)) (late time.Duration, err error)
	Close()
}

ProcessHandle is one observed process.

type ProcessIdentity added in v0.35.0

type ProcessIdentity struct {
	PID       int    `json:"pid"`
	StartTime string `json:"startTime"`
}

ProcessIdentity names one agent process: its pid and the kernel's start time for it, an opaque token, so a reused pid is a different process.

type RegisteredHost

type RegisteredHost struct {
	HostID  string
	Workers []RegisteredHostWorker
}

RegisteredHost is the server's answer to a registration: which worker id each configured reference resolved to.

type RegisteredHostWorker

type RegisteredHostWorker struct {
	WorkerID string
	URN      string
	Name     string
}

RegisteredHostWorker pairs a resolved worker id with its name.

type RegisteredWorker

type RegisteredWorker struct {
	Worker    string // the configured reference
	Family    string
	Worktrees []string
}

RegisteredWorker is one worker in a registration.

type Registration

type Registration struct {
	Capabilities        []string
	Label               string
	PollIntervalSeconds int
	Workers             []RegisteredWorker
}

Registration is one registerHost call: the whole worker list.

type ResetCheckpoint added in v0.35.0

type ResetCheckpoint struct {
	Title   string
	Content string
}

ResetCheckpoint is the checkpoint node the server writes for the host: a nonblank title of at most 200 characters and at most 65536 bytes of UTF-8 content (#3276). The server picks the loc.

type ResetDeps added in v0.35.0

type ResetDeps struct {
	// Proc observes and signals agent processes (LinuxProcessControl).
	Proc ProcessControl
	// Git is the Git determination (GitTree).
	Git func(ctx context.Context, dir string) (GitTreeState, error)
	// Idle proves the worker's agent idle with an empty composer and no
	// menu (BUSY, DRAFT_OR_MENU). Nil means this host cannot prove it, and
	// every reset refuses NOT_IDLE: idle is never assumed.
	Idle func(ctx context.Context, workerRef string) error
}

ResetDeps is what the worker.reset-context handler needs beyond the poller (#1043). Each is injected so the handler runs against fakes; in production only `team host run` could supply them, and this slice wires none, so the handler is unreachable there.

type ResetRecord added in v0.35.0

type ResetRecord struct {
	ResetID     string `json:"resetId"`
	OperationID string `json:"operationId"`
	WorkerID    string `json:"workerId"`
	// Epoch is the last epoch a step returned (0 before the claim).
	Epoch int `json:"epoch"`
	// Old is the agent process observed before the claim; New is the
	// relaunched one, once started.
	Old ProcessIdentity  `json:"old"`
	New *ProcessIdentity `json:"new,omitempty"`
	// Checkpoint is the checkpoint node the recovery was registered with,
	// handed to the relaunched agent; empty until registerRecovery.
	Checkpoint string `json:"checkpoint,omitempty"`
	// Relaunching is set on disk before the relaunch starts an agent, so a
	// restart in between never starts a second one.
	Relaunching bool `json:"relaunching,omitempty"`
	// LateStopMs is how far past its lease the stop landed, written as
	// soon as the signal returns, so the late-stop event survives an agent
	// that does not exit, a cancellation and a restart (cor:agt:050:27
	// part 10). 0 when on time or not yet sent.
	LateStopMs int64 `json:"lateStopMs,omitempty"`
}

ResetRecord is one reset in flight on this host.

type ResetServer added in v0.35.0

type ResetServer interface {
	ClaimReset(ctx context.Context, resetID string, epoch int, p ProcessIdentity) (ResetStep, error)
	// RegisterResetRecovery writes the checkpoint with the host key and
	// registers it (#3276, ruling 0090 A): the host never names a node.
	RegisterResetRecovery(ctx context.Context, resetID string, epoch int, cp ResetCheckpoint, sessionID, taskID string) (ResetStep, error)
	AuthorizeResetStop(ctx context.Context, resetID string, epoch int, p ProcessIdentity) (ResetStep, error)
	// late is how long after its lease the stop signal landed (0 when on
	// time; procctl SignalWithin's result); it is sent as lateStopMs.
	ReportResetAgentStopped(ctx context.Context, resetID string, epoch int, late time.Duration) (ResetStep, error)
	ReportResetRelaunched(ctx context.Context, resetID string, epoch int, p ProcessIdentity) (ResetStep, error)
	ReportResetRelaunchFailed(ctx context.Context, resetID string, epoch int, reason string) (ResetStep, error)
	// ReadReset reads one reset this host collected (#3273), for restart
	// recovery; it changes nothing.
	ReadReset(ctx context.Context, resetID string) (ContextReset, error)
}

ResetServer is the reset protocol's six steps, made with the host key. Each names the reset and the epoch the host holds.

type ResetStep added in v0.35.0

type ResetStep struct {
	Refused  string
	Replayed bool
	Reset    ContextReset
}

ResetStep is one protocol step's answer. Refused is non-empty when the step refused or failed the reset (it has then ended server-side); Replayed means the step was already made and nothing changed.

type ResetStepError added in v0.35.0

type ResetStepError struct {
	Code string
	Err  error
}

ResetStepError is a step the server rejected outright with a typed code (STALE_EPOCH, CONTEXT_RESET_WRONG_PHASE, CONTEXT_RESET_NOT_FOUND, HOST_OPERATION_NOT_COLLECTED). Every one of them stops the handler: none is retried and none is a reason to touch the process.

func (*ResetStepError) Error added in v0.35.0

func (e *ResetStepError) Error() string

func (*ResetStepError) Unwrap added in v0.35.0

func (e *ResetStepError) Unwrap() error

type Server

type Server interface {
	Register(ctx context.Context, r Registration) (RegisteredHost, error)
	Collect(ctx context.Context) ([]Operation, error)
	// Report returns the operation as the server recorded it.
	Report(ctx context.Context, id, outcome, worktree, reason string) (Operation, error)
}

Server is the host's four server calls, made with the host key.

type State

type State struct {

	// Created maps a configured worker reference to the worktree tokens this
	// poller created for it.
	Created map[string][]string `json:"created"`
	// Keep maps a worktree token to why it must not be removed.
	Keep map[string]string `json:"keep"`
	// HostID is the id the server derived from the host key at the last
	// registration — how `status` finds this host's own registration, since
	// a label is not unique.
	HostID string `json:"hostId"`
	// Agents are the agents this poller launched, by worker id: the
	// worktree each runs in and its process id. A launched agent outlives a
	// poller restart (it runs in its own session), so the in-memory record
	// alone would forget it and let worktree.remove take a tree from under
	// a live agent. A reused pid only makes a check refuse: the safe way.
	Agents map[string]AgentRecord `json:"agents"`
	// Resets are the worker.reset-context operations this poller collected
	// and has not finished reporting, by reset id (#1043). Written before
	// the claim, so a restarted poller knows which resets to read; it never
	// stores a phase, which recovery always reads from the server.
	Resets map[string]ResetRecord `json:"resets,omitempty"`
	// contains filtered or unexported fields
}

State is what the poller remembers across restarts: the worktrees it created (so the next registration lists them — the server does not add a created worktree to the registration itself), and the worktrees it must keep because a worker.end on them failed (cor:agt:020:10: never remove a worktree after a failed end).

func LoadState

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

LoadState reads the state file; a missing file is an empty state.

func (*State) RegisteredHostID

func (s *State) RegisteredHostID() string

RegisteredHostID is the host id saved at the last registration.

func (*State) Snapshot

func (s *State) Snapshot() (map[string][]string, map[string]string)

Snapshot copies the state for display.

type WakeDeliverer added in v0.25.0

type WakeDeliverer interface {
	DeliverWake(ctx context.Context, w delivery.Wake, r delivery.Recipient) delivery.Outcome
}

WakeDeliverer carries one wake to its recipient; *delivery.Deliverer is one.

type WakePage added in v0.25.0

type WakePage struct {
	Wakes []delivery.Wake
	// BudgetExhausted: WAKE_BUDGET_EXHAUSTED. Nothing is typed; the
	// withheld wakes stay due.
	BudgetExhausted bool
	// Dormant and Withheld are counted in status only.
	Dormant, Withheld int
}

WakePage is one step's due wakes.

type WakeSource added in v0.25.0

type WakeSource interface {
	Wakes(ctx context.Context) (WakePage, error)
}

WakeSource reads this host's due wakes with the host key (dueWakes, paged to exhaustion over this host's registered workers).

type WorkerConfig

type WorkerConfig struct {
	// Family is the model family it runs as here, e.g. claude or codex.
	Family string `yaml:"family"`
	// Launch is the argv that starts its agent in a worktree. The poller adds
	// HADRON_LAUNCH_GRANT to its environment and nothing else of Hadron's.
	Launch []string `yaml:"launch"`
	// End is an optional argv that prompts the running agent to end its own
	// session with a handoff (worker.end). It is never a kill.
	End []string `yaml:"end"`
	// EndWait bounds how long worker.end waits for the session to end
	// (default 15m).
	EndWait string `yaml:"end_wait"`
	// Recover is an optional argv that hands a relaunched agent its recovery
	// prompt after a worker.reset-context (#1043), the way End prompts an
	// end. It gets HADRON_SESSION_ID, HADRON_WORKER_ID and
	// HADRON_RECOVERY_NODE (the checkpoint node's reference): ids only. A
	// worker without it is never reset on this host.
	Recover []string `yaml:"recover"`
	// Worktrees are this worker's existing worktree name tokens, relative to
	// worktrees_root, beyond the ones this poller created.
	Worktrees []string `yaml:"worktrees"`
	// TUI is the agent interface its terminal shows, claude or codex, when
	// the model family does not say (a glm worker runs in Claude Code).
	// Delivery holds a terminal that shows anything else.
	TUI string `yaml:"tui"`
}

WorkerConfig is one worker this host can launch.

type WorktreeState

type WorktreeState struct {
	Exists bool
	// IsWorktree is false for a path that exists but is not a git worktree.
	IsWorktree bool
	Branch     string
	// Dirty is uncommitted or untracked changes.
	Dirty bool
	// Upstream is the branch's upstream, empty when it has none.
	Upstream string
	// Ahead counts commits not on the upstream.
	Ahead int
}

WorktreeState is what worktree.create and worktree.remove decide on.

Jump to

Keyboard shortcuts

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