Documentation
¶
Overview ¶
Package worker provides shared command-parsing and execution primitives used by the play_as REPL and the headless worker agent.
Index ¶
- Constants
- Variables
- func Assist(ctx context.Context, deps AssistDeps) error
- func Autopilot(ctx context.Context, deps AutopilotDeps, targetSystem, targetPOI string) error
- func BlockPreview(stmts []Statement) string
- func CaptureMarket(ctx context.Context, client game.GameClient, kb knowledge.Base)
- func CurrentBoundary(freq string, now time.Time) time.Time
- func ExecuteLoop(ctx context.Context, out io.Writer, count int, force bool, body []Statement, ...) error
- func Explore(ctx context.Context, deps ExploreDeps) error
- func FormatDuration(seconds int) string
- func GetLocationPOI(ctx context.Context, client game.GameClient) (game.POI, error)
- func HasTopLevelOpenBrace(s string) bool
- func Haul(ctx context.Context, deps HaulDeps) error
- func KBUpdateAll(ctx context.Context, client game.GameClient, kb knowledge.Base, ...) error
- func KBUpdateFacilities(ctx context.Context, client game.GameClient, kb knowledge.Base) error
- func KBUpdateMissions(ctx context.Context, client game.GameClient, kb knowledge.Base) error
- func KBUpdatePOI(ctx context.Context, client game.GameClient, kb knowledge.Base, ...) error
- func KBUpdatePOIData(ctx context.Context, client game.GameClient, kb knowledge.Base, ...) (game.POI, error)
- func KBUpdateStation(ctx context.Context, client game.GameClient, kb knowledge.Base, ...) error
- func KBUpdateSystem(ctx context.Context, client game.GameClient, kb knowledge.Base, ...) error
- func ListScripts(agentID string) (perAgent, shared []string)
- func LoadRoles(path string) (map[string]Role, error)
- func Missions(ctx context.Context, deps MissionDeps) error
- func NextBoundary(freq string, now time.Time) time.Time
- func NextExploreTarget(ctx context.Context, kb knowledge.Base, currentSystem string, ...) (string, bool, error)
- func ParseLoopHeader(stmt Statement) (count int, force bool, body string, isBlock bool, err error)
- func PayRescueDebt(ctx context.Context, c GiftClient, out io.Writer, agentsDir, agentID string)
- func RankHaulOpportunities(opps []market.ArbitrageOpportunity, currentSystemID string, ...) []market.ArbitrageOpportunity
- func ResolveScriptArg(arg, agentID string) (string, bool)
- func ResolveTokens(args []string, state *game.State) ([]string, error)
- func RunStanding(ctx context.Context, role Role, deps StandingDeps) error
- func SaveScript(name, content string) error
- func ScanBraceDepth(s string) (depth int, inQuote bool)
- func ScriptSearchPaths(agentID string) []string
- func SelectMissionSet(cands []missionCandidate, credits, cargoFree float64, maxJumps int) []missionCandidate
- func Shuttle(ctx context.Context, deps ShuttleDeps) error
- func SplitArgs(s string) []string
- func SplitScriptCommands(content string) ([]string, error)
- func SubstituteParams(lines []string, params map[string]string) []string
- func UsernameFor(agentsDir, agentID string) (string, error)
- type AssignedTask
- type AssistDeps
- type AutopilotDeps
- type CaptureFunc
- type CommandRunner
- type ExploreDeps
- type FuelPriceSource
- type GiftClient
- type HaulDeps
- type MissionDeps
- type MissionStore
- type OpportunityStore
- type RescueQueue
- type Role
- type ScheduleEntry
- type ScheduledTask
- type Scheduler
- func (s *Scheduler) Add(freq, command string, now time.Time) (ScheduledTask, error)
- func (s *Scheduler) Due(now time.Time) []ScheduledTask
- func (s *Scheduler) List() []ScheduledTask
- func (s *Scheduler) Remove(id int) bool
- func (s *Scheduler) StartLoop(ctx context.Context, interval time.Duration, execMu *sync.Mutex, ...)
- type ShuttleDeps
- type StandingDeps
- type Statement
- type TokenError
- type WaypointCheckFunc
- type WorkerDispatch
- func (d *WorkerDispatch) BuyDirected(ctx context.Context, itemID string, qty int, station string, maxUnit float64, ...) error
- func (d *WorkerDispatch) CraftOutputs(ctx context.Context, recipeID string, numOutputs int, station, facility string, ...) error
- func (d *WorkerDispatch) Deliver(ctx context.Context, itemID string, qty int, from, to, recipient string) error
- func (d *WorkerDispatch) HandoffPass(ctx context.Context, q *handoff.Queue) error
- func (d *WorkerDispatch) MineQty(ctx context.Context, itemID string, qty int, to, recipient string) error
- func (d *WorkerDispatch) Run(ctx context.Context, tokens []string) error
- func (d *WorkerDispatch) SetActivitySink(p *atomic.Pointer[string])
- func (d *WorkerDispatch) Supports(cmd string) bool
Constants ¶
const ( // MissionMaxStack is the server's concurrent-mission cap per player (the // Mission Runner guide's "accept 5 simultaneous missions" stacking play). MissionMaxStack = 5 // DefaultMissionMaxJumps caps how far (jumps) a mission destination may be, // matching the haul fleet's reposition philosophy (DefaultHaulMaxJumps): // several nearby runs net more than one distant payday. DefaultMissionMaxJumps = 5 )
const AutopilotRefuelThreshold = 0.5
AutopilotRefuelThreshold: when find_route fuel estimates are unavailable, the pre-route station refuel triggers if current fuel is below this fraction of capacity.
const DefaultAgentsDir = "data/agents"
DefaultAgentsDir is WorkerDispatch's default location for agent credential files (data/agents/<agent-id>/credentials.json), used by Deliver to resolve a gift recipient's agent id to the in-game username send_gift requires.
const DefaultExploreStaleTicks int64 = 8640
DefaultExploreStaleTicks is the age, in game ticks, past which a visited system is worth re-capturing. ~1 day at 10s/tick (the KB system-freshness convention): 86400s / 10s = 8640 ticks.
const DefaultHaulMaxJumps = 5
DefaultHaulMaxJumps caps how far (in jumps from the hauler's current system) a buy station may be for its opportunity to be considered. Repositioning halfway across the galaxy for one large payday wastes more time and fuel than several nearby hauls of comparable margin, so the fleet nets more from many short hops. 0 in a request means "use this default"; the pure ranker treats maxJumps<=0 as no cap (for tests).
const DefaultHaulPoolLimit = 500
DefaultHaulPoolLimit caps how many available opportunities a hauler considers. GetOpportunities orders by gross_profit DESC, so a small limit returns only the highest-gross (and typically most distant) opportunities. With a reposition-distance cap (DefaultHaulMaxJumps), nearby-but-lower-gross opportunities must be in the pool or the cap would drop the whole pool and the hauler would idle — so the pool is generous.
const DefaultShuttleMaxJumps = 40
DefaultShuttleMaxJumps bounds how far (in jumps) a shuttle will ferry a passenger batch. Passenger fares scale with distance (and first-class long-hauls pay a large lump plus a speedy-delivery bonus), and the ranking already favors the biggest total fare, so this is a generous safety bound — effectively "any reachable, non-stronghold destination" for this galaxy — not a turnover throttle.
const HaulMaxHaulJumps = 40
HaulMaxHaulJumps is the hard backstop on the haul leg (buy->sell) jump count. Fuel cost already penalizes long hauls economically; this caps the tail for when price/graph data is thin (median ~ 0). Set at 40 so legitimate long-haul routes (e.g. empire capital-to-capital runs, several of which exceed 20 jumps) are not dropped; it only trims the far tail. The approach leg keeps DefaultHaulMaxJumps.
const MineQtyMaxDryPasses = 5
MineQtyMaxDryPasses bounds how many consecutive Mine() calls that grow the target item's cargo count by zero MineQty tolerates before it gives up on the belt and delivers whatever it has aboard. A pass that DOES grow cargo resets this counter back to zero.
const ( // TreasuryProfitCut is the share of a job's realized profit deposited into the // faction treasury on completion. TreasuryProfitCut = 0.05 )
Faction treasury policy. Mobile roles deposit a slice of each completed job's realized profit into their faction's shared treasury, building a buffer that rescues members who run themselves broke. Deposits are open to any member; withdrawals require the manage_treasury permission (the server rejects them otherwise, which we treat as a harmless no-op so un-permissioned agents simply idle until rescued by a human or granted the permission).
Variables ¶
var ValidFrequencies = map[string]bool{"ten_minutely": true, "quarter_hourly": true, "half_hourly": true, "hourly": true, "daily": true, "weekly": true}
ValidFrequencies is the closed set of supported frequencies.
Functions ¶
func Assist ¶
func Assist(ctx context.Context, deps AssistDeps) error
Assist runs one pass of the assist standing behavior (the standing loop re-invokes it): resume an owned claim, else claim the nearest pending rescue, else make sure we are docked at home with a full tank.
func Autopilot ¶
func Autopilot(ctx context.Context, deps AutopilotDeps, targetSystem, targetPOI string) error
Autopilot executes a multi-jump route to targetSystem, then travels to targetPOI within the destination system when targetPOI != "". It uses FindRoute for the route, jumps each hop (attempting fuel-cell use / refuel when fuel runs short), invokes OnWaypoint after each arrival, and performs the final in-system Travel. Returns on FindRoute failure, a jump that fails after fuel attempts, a jump interruption, or ctx cancellation.
func BlockPreview ¶
BlockPreview returns a short, single-line summary of a block body suitable for inclusion in a "🔁 Repeating {...}" status message. Newlines within a statement's Raw are collapsed to spaces; statements are joined with '; '; the result is truncated to roughly 60 runes with a trailing ellipsis if longer.
func CaptureMarket ¶
CaptureMarket persists the full source-classified market data from the client's most recent (full, no-item_id) view_market response: buy-order demand into the demand tables and sell-order supply into the supply tables, each replacing the station's entire order set. Best-effort: silently no-ops when the KB is absent, there is no market data, or the player is not at a station. A single freshness gate (keyed on the demand ledger) guards both sides, since they come from the same read.
func CurrentBoundary ¶
CurrentBoundary returns the most recent wall-clock boundary (UTC) for freq at or before now: the most recent ten-minute mark (:00, :10, …), quarter hour, half hour (:00 or :30), the top of the hour, midnight, or the most recent Sunday midnight. Returns the zero time for an unknown frequency.
func ExecuteLoop ¶
func ExecuteLoop( ctx context.Context, out io.Writer, count int, force bool, body []Statement, depth int, runStatement func(tokens []string) error, ) error
ExecuteLoop runs count iterations of body. For each statement whose first token is "loop", ParseLoopHeader + ParseStatements is applied and ExecuteLoop recurses; otherwise runStatement is called. Each loop enforces errors according to its own force flag: a loop with force continues past errors and returns nil; a loop without force returns the first error. depth controls indentation of status lines.
func Explore ¶
func Explore(ctx context.Context, deps ExploreDeps) error
Explore performs one exploration step: resolve the current system, choose the next target via NextExploreTarget, and autopilot to it (capturing each hop via the worker's plain KB capture). When there is no KB, no current system, or no reachable frontier, it logs and returns nil so the worker idles and retries on the next pass.
func FormatDuration ¶
FormatDuration formats seconds as "Xm Ys", "Xm", or "Xs".
func GetLocationPOI ¶
GetLocationPOI fetches the current POI via the get_location command and returns it as a fully-populated game.POI.
The server retired get_poi (2026-06-24); resource richness/remaining is now only available from get_location, while structural fields (class, description, position, base linkage) come from get_system. This helper reads the live resources from get_location and enriches the static structural fields from the cached system data in client state (populated by a prior get_system, which the capture flows always run first). Fields absent from both commands — per-POI description (when not already cached), hidden, reveal_difficulty — are left zero and preserved across updates by the knowledge base's merge-UPSERT.
func HasTopLevelOpenBrace ¶
HasTopLevelOpenBrace reports whether s contains a '{' outside of any '"..."' or "'...'" quoted string.
func Haul ¶
Haul performs one hauling step: load available opportunities (scanning if the pool is empty), rank them for the current system, claim the best reachable one, and run it (buy -> transit -> sell -> complete). On any mid-run failure it logs and returns nil so the worker idles and retries; the claimed row is left claimed (harmless — the spread regenerates on the next scan).
func KBUpdateAll ¶
func KBUpdateAll(ctx context.Context, client game.GameClient, kb knowledge.Base, mc *market.Collector, detectedBy string) error
KBUpdateAll runs update_system, update_poi, and (if docked) update_station, update_facilities, and update_missions. detectedBy records which agent observed the system/POI data (provenance). mc is optional: when non-nil, the market snapshot is written via the collector.
func KBUpdateFacilities ¶
KBUpdateFacilities fetches facility details via 'facility list' and saves enriched data (description, active, maintenance, service, recipe_id) to the knowledge base.
func KBUpdateMissions ¶
KBUpdateMissions fetches the mission board at the current station and upserts each hand-authored entry into the KB mission catalog. Procedural missions (empty template_id) are skipped. Ported from play_as so the worker fleet's hourly kb_update captures mission boards fleet-wide.
func KBUpdatePOI ¶
func KBUpdatePOI(ctx context.Context, client game.GameClient, kb knowledge.Base, detectedBy string) error
KBUpdatePOI fetches current POI data and saves it to the knowledge base. detectedBy records which agent observed the data (POI provenance).
func KBUpdatePOIData ¶
func KBUpdatePOIData(ctx context.Context, client game.GameClient, kb knowledge.Base, detectedBy string) (game.POI, error)
KBUpdatePOIData is like KBUpdatePOI but also returns the captured POI so callers can reuse it (e.g. play_as's faction-intel file side effect) without issuing a second get_location query.
func KBUpdateStation ¶
func KBUpdateStation(ctx context.Context, client game.GameClient, kb knowledge.Base, mc *market.Collector, source string) error
KBUpdateStation fetches base details, market listings, and ship listings at the current station and saves them to the knowledge base. source is the tool tag recorded as the origin of the captured data (e.g. "play_as" or "worker"). mc is optional: when non-nil, the market snapshot is also written to the market collector DB via mc.WriteSnapshot.
func KBUpdateSystem ¶
func KBUpdateSystem(ctx context.Context, client game.GameClient, kb knowledge.Base, detectedBy string) error
KBUpdateSystem fetches the current system data and saves it to the knowledge base. detectedBy records which agent observed the data (POI provenance).
func ListScripts ¶
ListScripts returns the sorted script names (without extension) in the per-agent and shared directories.
func LoadRoles ¶
LoadRoles parses the roles config at path. Every schedule entry must name a valid frequency.
func Missions ¶
func Missions(ctx context.Context, deps MissionDeps) error
Missions performs one mission-runner pass: complete any resumable active missions, read the local board, accept + provision a stackable deliver set, run it to the shared destination system, and record every outcome. Mirrors Haul's resilience contract: mid-run failures log and return nil so the worker idles and retries; the pass never kills the worker loop.
func NextBoundary ¶
NextBoundary returns the next time freq will fire strictly after now, used for the "next due" column in view_scheduled.
func NextExploreTarget ¶
func NextExploreTarget(ctx context.Context, kb knowledge.Base, currentSystem string, staleTicks, nowTick int64) (string, bool, error)
NextExploreTarget picks the next system an explorer should visit from currentSystem, ranked by jump distance, preferring (1) undiscovered frontier systems — connection endpoints not yet in the KB's systems table — then (2) known-but-unvisited systems (LastVisitedTick == 0), then (3) stale known systems (nowTick-LastUpdatedTick > staleTicks). ok is false when nothing within reach is worth visiting, in which case target is "".
func ParseLoopHeader ¶
ParseLoopHeader parses the header of a "loop" statement. It accepts the existing single-command form (e.g. "loop 5 mine") and the new block form (e.g. "loop 5 { mine; refuel }"). For the block form the returned body is the content between the outermost matching braces; for the single form it is the concatenation of remaining args.
func PayRescueDebt ¶
PayRescueDebt pays the head outstanding rescue debt for agentID when the worker is docked (send_gift credits requires a base with storage) and can afford it. One debt per call respects the 1-gift-per-tick rate limit; the rest wait for the next docked pass. Best-effort: every failure logs and leaves the debt in place.
func RankHaulOpportunities ¶
func RankHaulOpportunities(opps []market.ArbitrageOpportunity, currentSystemID string, nameToID map[string]string, graph navigation.JumpGraph, maxJumps int, fuelPerJump int, priceOf func(stationID string) float64) []market.ArbitrageOpportunity
RankHaulOpportunities orders available opportunities best-first for a hauler at currentSystemID. Primary order is net-of-fuel profit descending — gross profit minus the fuel cost of the approach leg (current->buy) and the haul leg (buy->sell), priced at fuelPerJump*priceOf(buy station) — lifted by the route's stability boost. fuelPerJump<=0 or a nil priceOf disables the fuel model (net == effective gross), the graceful-degradation path for callers with no fuel data. Opportunities within haulNearTieFraction of the top net are instead ordered by reposition cost (jumps current->buy), then a chaining bonus (sell at/adjacent to another opp's buy), then id. Opportunities whose buy-system name does not resolve to a known system id, or whose buy-system is unreachable, are dropped. When maxJumps > 0, any opportunity whose buy-system is more than maxJumps from currentSystemID is also dropped (maxJumps <= 0 disables the cap). Independently, any opportunity whose haul leg (buy->sell) exceeds HaulMaxHaulJumps is dropped as a hard backstop, regardless of maxJumps.
func ResolveScriptArg ¶
ResolveScriptArg maps a `run` argument to a file path. An explicit path (see isExplicitScriptPath) is used verbatim if it exists. A bare name is resolved as "<dir>/<name>.smolt" against scriptSearchPaths in order.
func ResolveTokens ¶
ResolveTokens replaces every $TOKEN$ occurrence in each argument with a value derived from live game state. It returns the substituted arguments, or a *TokenError if any token cannot be resolved.
func RunStanding ¶
func RunStanding(ctx context.Context, role Role, deps StandingDeps) error
RunStanding drives a worker's default standing behavior until ctx is cancelled: it registers the role's scheduled commands with the Scheduler and runs the role's idle script in a loop, both serialized on deps.ExecMu so they never interleave on the single game connection. It returns when ctx is cancelled, after any in-flight idle pass completes.
func SaveScript ¶
SaveScript writes content to the shared scripts dir as "<name>.smolt", creating the directory if needed. A trailing newline is appended.
func ScanBraceDepth ¶
ScanBraceDepth reports the net brace depth of s and whether the scan ended inside a quoted string. Braces inside '"..."' or "'...'" are ignored. A negative depth indicates more '}' than '{' in s.
func ScriptSearchPaths ¶
ScriptSearchPaths returns the script directories searched by bare-name resolution, in precedence order: per-agent first (shadows shared), then the shared library.
func SelectMissionSet ¶
func SelectMissionSet(cands []missionCandidate, credits, cargoFree float64, maxJumps int) []missionCandidate
SelectMissionSet picks up to MissionMaxStack candidates to run as one trip: the best-net candidate anchors the trip, and only candidates sharing its destination system stack onto it (cross-system banding is a phase-2 upgrade). The greedy fill respects the buy budget (missionBuyBudgetFraction of credits) and free cargo space; anchors farther than maxJumps are skipped entirely.
func Shuttle ¶
func Shuttle(ctx context.Context, deps ShuttleDeps) error
Shuttle performs one passenger-ferry step: ensure docked, survey the station's waiting passengers, pick the best-paying reachable destination (excluding pirate strongholds and anything beyond the jump budget), board whoever fits, fly them there, and collect the auto-paid fares on docking — depositing the treasury's cut. On any mid-run failure it logs and returns nil so the worker idles and retries; aboard passengers auto-deliver whenever the ship next docks at their destination, so a partial run is never a hard loss.
func SplitArgs ¶
SplitArgs splits a command string into arguments, respecting double and single quotes. e.g. `create_faction "Covenant of the Eternal Spark" SPRK` → ["create_faction", "Covenant of the Eternal Spark", "SPRK"]
func SplitScriptCommands ¶
SplitScriptCommands splits a script file's content into logical commands. Top-level blank lines and '#' comment lines are skipped; a multi-line block (e.g. a loop { ... }) is kept together until its braces balance, using the same brace/quote scanning the REPL uses for multi-line prompt input.
func SubstituteParams ¶
SubstituteParams replaces every $KEY$ occurrence in each line with params[KEY], for keys present in params. Keys absent from params (e.g. live state tokens like $ASTEROID_BELT$) are left untouched so they pass through to ResolveTokens. Returns a new slice; the input is not mutated. A nil/empty params map is a no-op.
func UsernameFor ¶
UsernameFor resolves agentID to its in-game username by reading <agentsDir>/<agentID>/credentials.json (the same file every worker/agent process already carries its own login credentials in). agentsDir defaults to DefaultAgentsDir when empty.
Types ¶
type AssignedTask ¶
AssignedTask is a one-shot task handed to the worker by the overmind. It is a local mirror of control.Assign so pkg/worker does not import pkg/overmind/control.
type AssistDeps ¶
type AssistDeps struct {
Client game.GameClient
KB knowledge.Base
Queue RescueQueue
Out io.Writer
AgentID string
HomeStation string // station POI id at the home capital (fleet yaml `station`)
Navigate func(ctx context.Context, system, poi string) error
// SetActivity publishes the status-page "current activity" string (nil in
// tests). Set while rescuing a target, cleared ("") when idle at home.
SetActivity func(string)
}
AssistDeps wires one assist worker: fly rescue fuel to quarantined workers from the shared queue, then return to the home capital and re-tank.
type AutopilotDeps ¶
type AutopilotDeps struct {
Client game.GameClient
Out io.Writer // progress lines; nil -> io.Discard
OnWaypoint CaptureFunc // per-arrival capture; nil -> no-op
// WaypointCheck is an optional per-arrival early-stop decision (re-route hook);
// nil -> never stops. It runs after OnWaypoint on each jump arrival.
WaypointCheck WaypointCheckFunc
}
AutopilotDeps are the injected dependencies for Autopilot.
type CaptureFunc ¶
CaptureFunc records knowledge-base state at a waypoint. Autopilot calls it once after each jump arrival; it is best-effort — a returned error is logged to the autopilot's writer and never aborts the route. nil means no capture.
type CommandRunner ¶
CommandRunner executes one already-tokenized command line. tokens[0] is the command name; the remaining elements are its arguments. It is the seam shared by the play_as REPL (rich dispatch) and the headless worker (lean dispatch).
type ExploreDeps ¶
type ExploreDeps struct {
Client game.GameClient
KB knowledge.Base
Out io.Writer // progress; nil -> io.Discard
StaleTicks int64 // 0 -> DefaultExploreStaleTicks
}
ExploreDeps are the injected collaborators for one Explore step.
type FuelPriceSource ¶
type FuelPriceSource interface {
GetStationFuelPrice(ctx context.Context, stationID string) (allIn int, capturedAt time.Time, ok bool, err error)
MedianStationFuelAllIn(ctx context.Context) (median int, ok bool, err error)
}
FuelPriceSource supplies captured station fuel prices for net-of-fuel haul economics. Satisfied by *market.Collector. Optional on HaulDeps: a nil source makes every fuel cost 0, so ranking/gating fall back to gross-only behavior.
type GiftClient ¶
type GiftClient interface {
GetState() *game.State
SendGift(ctx context.Context, payload map[string]any) error
}
GiftClient is the slice of game.GameClient the rescue-fee payment needs.
type HaulDeps ¶
type HaulDeps struct {
Client game.GameClient
KB knowledge.Base
Market OpportunityStore
Out io.Writer // nil -> io.Discard
AgentID string // claim owner
PoolLimit int // 0 -> DefaultHaulPoolLimit
MaxJumps int // 0 -> DefaultHaulMaxJumps; reposition-distance cap (jumps) to a buy station
// RecaptureBuyMarket refreshes the current (buy) station's market into the store
// so the pre-buy gate prices against a real-time ask. nil skips the live capture
// (tests seed prices directly via the store).
RecaptureBuyMarket func(ctx context.Context) error
// Now returns the current wall-clock time (nil -> time.Now). Injected for deterministic tests.
Now func() time.Time
// Treasury rate-limits faction-treasury rescue withdrawals across haul passes
// (held per worker process). nil disables the rescue path.
Treasury *treasuryRescue
// FuelPrices supplies captured station fuel prices for net-of-fuel ranking and
// gating. nil disables fuel accounting (ranking/gating fall back to gross-only).
FuelPrices FuelPriceSource
// SetActivity publishes a short human-readable "current activity" string for
// the fleet status page (nil in tests). Best-effort; set when a haul is
// claimed, cleared ("") when the pass finds no work.
SetActivity func(string)
}
HaulDeps are the injected collaborators for one Haul step.
type MissionDeps ¶
type MissionDeps struct {
Client game.GameClient
KB knowledge.Base
Market MissionStore
Out io.Writer // nil -> io.Discard
AgentID string
// MaxJumps caps mission-destination distance (0 -> DefaultMissionMaxJumps).
MaxJumps int
// Categories is the board-category allowlist (nil/empty -> delivery only).
// The learning pool runs {"delivery", "exploration"}.
Categories []string
// Treasury rate-limits faction-treasury rescue withdrawals (nil disables).
Treasury *treasuryRescue
// FuelPrices supplies captured station fuel prices for the fuel-cost model.
// nil disables fuel accounting (net == reward - item cost).
FuelPrices FuelPriceSource
// Now returns wall-clock time (nil -> time.Now); injected for tests.
Now func() time.Time
// State carries the cross-pass dry-streak/reposition memory (nil disables
// repositioning — tests that don't care simply omit it).
State *missionRunState
// SetActivity publishes the status-page "current activity" string (nil in
// tests). Set when a mission is accepted or a held one resumed, cleared
// ("") on a dry pass.
SetActivity func(string)
// EnableFreight opts this worker into the /shipping carrier path, evaluated
// co-equally with the mission board. Default false so existing fleets are
// unchanged until the canary opts in explicitly.
EnableFreight bool
// FreightMaxPackages caps concurrent freight contracts (sub-project C
// multi-package trips). 0 and 1 both mean the v1 single-contract
// behavior; the cap layers UNDER the server/cargo headroom gates and is
// never a target. Canary: fighter-4 at 3.
FreightMaxPackages int
// FreightBootstrap enables the probationary loss-leader floor relaxation
// (effectiveFreightFloor). Defaults on wherever EnableFreight is set; a
// false value forces the normal 500 floor — the kill switch. Inert unless
// EnableFreight is also true (freightCandidate only runs on the freight path).
FreightBootstrap bool
// contains filtered or unexported fields
}
MissionDeps are the injected collaborators for one Missions pass.
type MissionStore ¶
type MissionStore interface {
RecordMissionResult(ctx context.Context, r market.MissionResult) error
GetReferenceAsk(ctx context.Context, itemID string) (market.ReferenceAsk, bool, error)
// GetReferencePrice returns a robust "cheap" cross-station reference
// (20th-percentile over lookback), used as the surge-ceiling basis for
// the local depth-walk gate — see missionSurgeMult.
GetReferencePrice(ctx context.Context, itemID string, lookback time.Duration) (float64, bool, error)
// RecordFreightResult persists one settled shipping-contract outcome.
RecordFreightResult(ctx context.Context, r market.FreightResult) error
}
MissionStore is the subset of *market.Collector the mission runner needs (result telemetry + item pricing for the cost gate).
type OpportunityStore ¶
type OpportunityStore interface {
GetOpportunities(ctx context.Context, status string, limit int) ([]market.ArbitrageOpportunity, error)
GetClaimedByAgent(ctx context.Context, agentID string) ([]market.ArbitrageOpportunity, error)
ClaimOpportunity(ctx context.Context, id int, agentID string) (bool, error)
ReleaseOpportunity(ctx context.Context, id int, agentID string) (bool, error)
CompleteOpportunity(ctx context.Context, id int, agentID string) (bool, error)
ScanArbitrage(ctx context.Context, opts market.ScanOptions) (market.ScanResult, error)
AdmitBookClaim(ctx context.Context, book market.BookKey, candidates []market.BookCandidate, agentID string, capN int, expiresIn time.Duration) (market.AdmitResult, error)
SettleBookClaim(ctx context.Context, claimID int64, agentID string, boughtUnits float64) error
CompleteBookClaim(ctx context.Context, claimID int64, agentID string) error
ReleaseBookClaim(ctx context.Context, claimID int64, agentID string) error
GetActiveBookClaim(ctx context.Context, itemID, fromStation, agentID string) (int64, bool, error)
InvalidateBook(ctx context.Context, itemID, fromStation, agentID, reason string) error
ReapExpiredBookClaims(ctx context.Context) (int, error)
GetItemStationPrices(ctx context.Context, itemID string) ([]market.ItemStationPrice, error)
GetAskLadder(ctx context.Context, itemID, stationID string) ([]market.AskLevel, error)
GetStationOrders(ctx context.Context, stationID, itemID string) ([]market.Order, error)
FindBestPrices(ctx context.Context, itemID, side string, limit int) ([]market.BestPrice, error)
RecordHaulResult(ctx context.Context, r market.HaulResult) error
}
OpportunityStore is the subset of *market.Collector the hauler needs. Defining it here keeps the engine testable with a fake and leaves pkg/market unmodified.
type RescueQueue ¶
type RescueQueue interface {
List() ([]rescue.Record, error)
Transition(agentID string, from, to rescue.Status, mutate func(*rescue.Record)) (bool, error)
}
RescueQueue is the slice of rescue.Queue the assist behavior consumes.
type Role ¶
type Role struct {
Schedule []ScheduleEntry `yaml:"schedule"`
Idle string `yaml:"idle"` // bare script name (data/scripts)
IdleParams map[string]string `yaml:"idle_params"` // substituted into the idle script
}
Role is a worker's default standing behavior: recurring scheduled commands plus an idle script run on idle cycles.
type ScheduleEntry ¶
type ScheduleEntry struct {
Every string `yaml:"every"` // hourly | daily | weekly
Command string `yaml:"command"` // a command line; may contain $TOKEN$s
}
ScheduleEntry is one recurring command in a role's standing behavior.
type ScheduledTask ¶
type ScheduledTask struct {
ID int `json:"id"`
Frequency string `json:"frequency"` // ten_minutely | quarter_hourly | half_hourly | hourly | daily | weekly
Command string `json:"command"`
CreatedAt time.Time `json:"created_at"`
LastRun time.Time `json:"last_run,omitzero"` // UTC; zero = never run
}
ScheduledTask is a recurring command the user has registered. The command is a full play_as command line (e.g. "view_storage --station_id frontier") run through executeLogicalCommand at the task's frequency.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler owns the persisted set of scheduled tasks for one agent.
func LoadScheduler ¶
LoadScheduler reads the scheduler state at path. A missing file yields an empty scheduler with no error.
func (*Scheduler) Add ¶
Add validates and appends a new task. With run-immediately semantics the caller runs the command at add time, so LastRun is stamped to now: the task will not fire again until the next boundary. Persists before returning.
func (*Scheduler) Due ¶
func (s *Scheduler) Due(now time.Time) []ScheduledTask
Due returns the tasks whose current period boundary is newer than their last run — i.e. they are owed a run as of now. It does not mutate state.
func (*Scheduler) List ¶
func (s *Scheduler) List() []ScheduledTask
List returns a copy of the scheduled tasks, ordered by id.
func (*Scheduler) Remove ¶
Remove deletes the task with the given id, persisting the change. Returns false if no such task exists.
func (*Scheduler) StartLoop ¶
func (s *Scheduler) StartLoop(ctx context.Context, interval time.Duration, execMu *sync.Mutex, run func(ScheduledTask), nowFn func() time.Time)
StartLoop runs the scheduler in a background goroutine until ctx is cancelled. It performs an immediate catch-up pass (logging any backfill), then fires due tasks at each tick. run executes one task; it is invoked under execMu so scheduled commands never interleave with foreground REPL commands. nowFn supplies the current time (injectable for tests).
type ShuttleDeps ¶
type ShuttleDeps struct {
Client game.GameClient
KB knowledge.Base
Out io.Writer // nil -> io.Discard
AgentID string
MaxJumps int // 0 -> DefaultShuttleMaxJumps
// Now returns the current wall-clock time (nil -> time.Now). Injected for tests.
Now func() time.Time
// Treasury rate-limits faction-treasury rescue withdrawals across passes.
Treasury *treasuryRescue
// State carries cross-pass shuttle memory (dry-pass streak + reposition
// cursor). nil disables repositioning (the shuttle just idles in place).
State *shuttleState
// SetActivity publishes the status-page "current activity" string (nil in
// tests). Set when passengers are boarded, cleared ("") on an idle pass.
SetActivity func(string)
}
ShuttleDeps are the injected collaborators for one Shuttle step.
type StandingDeps ¶
type StandingDeps struct {
Runner CommandRunner // executes a tokenized command (WorkerDispatch)
Scheduler *Scheduler // per-agent recurring tasks
Client interface{ GetState() *game.State } // for token resolution
ExecMu *sync.Mutex // serializes scheduled + idle work on the one game conn
Paused func() bool // gate from the control reader's paused flag
Draining func() bool // second gate (drain): finish current pass, take no new work
SetDrained func(bool) // publishes whether the worker is held idle due to drain
Out io.Writer // worker stdout / logs
NowFn func() time.Time // injectable clock
IdleInterval time.Duration // between idle passes (0 → game.SleepShort)
ScheduleInterval time.Duration // scheduler tick (0 → game.SleepLong)
AgentID string // for script resolution search paths
// Task hooks (nil when the worker has no control channel). NextTask returns
// and consumes the pending assigned task (or nil); OnTaskResult reports a
// finished task's id and error (nil = success).
NextTask func() *AssignedTask
OnTaskResult func(taskID string, err error)
// PayDebts, when set, runs once per non-drained idle pass under ExecMu to
// pay any outstanding rescue-fee debt. nil for workers with no fee wiring.
PayDebts func(context.Context)
// Handoffs, when set, runs once per non-drained idle pass under ExecMu to
// fulfill pending stock-handoff records for this agent. nil when the worker
// has no handoff queue configured.
Handoffs func(context.Context)
}
StandingDeps are the collaborators RunStanding needs. All are injectable so the driver is testable without a game connection.
type Statement ¶
Statement is one top-level command inside a loop body. Raw is the original string (with surrounding whitespace trimmed); Tokens is the SplitArgs result over Raw.
func ParseStatements ¶
ParseStatements splits body into top-level statements, separating on ';' and '\n' at brace-depth 0 outside quotes. '#' begins a line comment (to end-of-line) outside quotes. Nested '{...}' content is preserved verbatim in the enclosing Statement's Raw.
type TokenError ¶
type TokenError struct {
// contains filtered or unexported fields
}
TokenError indicates a $TOKEN$ in a command could not be resolved from game state. Inside a loop it is fatal: the loop aborts immediately, even under -f.
func (*TokenError) Error ¶
func (e *TokenError) Error() string
type WaypointCheckFunc ¶
WaypointCheckFunc is an optional per-arrival decision hook (distinct from the capture-only CaptureFunc). Autopilot calls it after each jump arrival; returning stop=true makes Autopilot halt at the current system and return errAutopilotStopped so the caller can re-plan. A returned error is logged and treated as "do not stop" (best-effort — the watchdog never aborts a route on its own failure). nil means no check.
type WorkerDispatch ¶
type WorkerDispatch struct {
Client game.GameClient
KB knowledge.Base
Market *market.Collector
Out io.Writer
AgentID string // claim owner for opportunity-claiming roles (e.g. hauler)
Station string // home station POI id, used by the assist role
Rescue RescueQueue // shared stranded-worker rescue queue, used by the assist role
// AgentsDir is where agent credential files live (<AgentsDir>/<agent-id>/
// credentials.json), used by Deliver to resolve a gift recipient's agent
// id to its in-game username. Empty -> DefaultAgentsDir ("data/agents").
AgentsDir string
// EnableFreight opts the missions role into the /shipping carrier path
// (freight evaluated co-equally with the mission board). Default false =
// the freight code is fully inert.
EnableFreight bool
// FreightMaxPackages caps concurrent freight contracts; see
// MissionDeps.FreightMaxPackages. 0/1 = single-contract v1 behavior.
FreightMaxPackages int
// FreightBootstrap enables the probationary loss-leader floor relaxation for
// the missions role. Defaults on when EnableFreight is set; false is the
// kill switch. See MissionDeps.FreightBootstrap.
FreightBootstrap bool
// MissionCategories is the mission-board category allowlist for the
// missions role (nil/empty -> delivery only; the learning pool adds
// "exploration").
MissionCategories []string
// contains filtered or unexported fields
}
WorkerDispatch is the lean, headless command dispatch used by cmd/worker. It covers the curated worker-script vocabulary only; each command maps directly to a game.GameClient method, plus shared KB-capture for tracking commands. Unknown commands return an error (never silently ignored). KB may be nil, in which case tracking commands degrade to a no-op capture (movement/mining still work).
func NewWorkerDispatch ¶
func NewWorkerDispatch(client game.GameClient, kb knowledge.Base, mc *market.Collector, out io.Writer) *WorkerDispatch
NewWorkerDispatch builds a dispatch over the given client, KB, and optional market collector. out receives human-readable progress lines (worker stdout / logs).
func (*WorkerDispatch) BuyDirected ¶
func (d *WorkerDispatch) BuyDirected(ctx context.Context, itemID string, qty int, station string, maxUnit float64, recipient string) error
BuyDirected buys ITEM xQTY at STATION with a per-unit price ceiling (0 = no ceiling), then hands to RECIPIENT (gift, or deposit when self). Recompute-remaining: counts recipient-bound goods already in cargo first. The live ask is checked once against maxUnit right after docking; buying then proceeds in cargo-capacity-sized chunks, handing each chunk off to RECIPIENT before buying the next (freeing cargo room) — no second travel leg, since the handoff happens at STATION where the buy already occurred.
func (*WorkerDispatch) CraftOutputs ¶
func (d *WorkerDispatch) CraftOutputs(ctx context.Context, recipeID string, numOutputs int, station, facility string, estFee float64) error
CraftOutputs crafts NUM_OUTPUTS units of RECIPE's output item at STATION. facility "hand" hand-crafts at the docked station's local workshop (the server auto-routes when facility_id is omitted from the payload — Task 0 findings, 2026-07-10, confirmed hand/workshop dry-run and craft ARE supported live); any other value is passed as an explicit facility instance id (crafting requires being docked at that facility's station — a remote facility_id errors no_facility). NUM_OUTPUTS is passed directly as the craft command's `quantity`: the server does the ceil-divide into production runs itself (live-verified: quantity 6 on a 3-per-run recipe produced "runs": 2), so no output-per-run math is needed for the craft call itself.
Recompute-remaining: before crafting, any of the recipe's output item already sitting in the worker's own storage at STATION (or already carried in cargo) counts against NUM_OUTPUTS, so a resumed/retried node does not over-produce. The recipe's output item id is looked up from the KB (recipe_outputs) — the craft_node task's params (RECIPE, NUM_OUTPUTS, STATION, FACILITY) never carry it directly.
Budget gate: a live dry-run always runs first — it is both the have_inputs/have_credits preflight and the 2x-replan gate. If the dry-run reports !HaveInputs or !HaveCredits, this fails immediately (no craft call) with an error naming which precondition failed plus the server's Message verbatim — live probes show the message names the nearest facility that CAN make the recipe when this station can't (Task 0 findings #6), which operators need in park details. Separately, the plan runner's budget admission already approved estFee (the planner's catalog fee estimate for this node); if the live dry-run's credits_total comes back more than 2x that, the catalog is stale and this fails with an error naming "replan" instead of queuing a possibly much more expensive job. estFee <= 0 disables the fee gate (no estimate to compare against).
Crafting is asynchronous: the server queues the job and delivers output directly to the worker's own storage at STATION over subsequent ticks. Hand-crafts (the "hand" facility default) run at the Station Workshop — they advance only while docked there and PAUSE on undock (Task 0 findings #7). Since the plan runner marks a node done the moment this verb returns and may then have the worker undock for its next task, this verb does NOT return once the job is merely queued: it polls `craft action=queue` (via RawCommand — the only client-side surface that reports job progress without inventing a new server command) at game.SleepTick cadence, staying put, until the job reports complete for BOTH the hand and facility paths. The wait is bounded by the dry-run's EstCompletionTick plus a generous margin (GameClock only drifts forward — memory: reference_gameclock_forward_drift — so a tight bound risks a false-positive timeout); on timeout this returns an error, never a silent success.
func (*WorkerDispatch) Deliver ¶
func (d *WorkerDispatch) Deliver(ctx context.Context, itemID string, qty int, from, to, recipient string) error
Deliver moves ITEM xQTY from the worker's own storage at FROM to RECIPIENT at TO. recipient "self" (or the worker's own agent id) means deposit into own storage at TO instead of gifting. Gifts are cargo-only (live-verified, 2026-07-10): the verb withdraws into cargo at FROM, then travels to TO and gifts/deposits from cargo, repeating in cargo-capacity-sized batches until qty is satisfied. If FROM held less than qty, it delivers what existed and returns nil with a short-delivery note written to d.Out instead of looping forever.
from == "" means the goods are already in cargo (e.g. just-mined ore handed off by MineQty): the withdraw leg AND the FROM travel leg are both skipped entirely — Deliver goes straight to TO with whatever is aboard. If cargo holds less than qty in this mode, that is a shortfall (mirroring the "source exhausted" case below, since there is no source to make a second withdraw pass against): Deliver delivers what's on hand and returns nil.
func (*WorkerDispatch) HandoffPass ¶
HandoffPass fulfills pending handoff records owned by this agent (d.AgentID) at its current docked station: withdraw the staged stock from storage into cargo, gift it to the resolved recipient, and mark the record done with however much actually moved. Records held by another agent, or staged at a station this worker isn't currently docked at, are left untouched — those belong to a different pass (a different agent's, or this agent's next visit). A worker that isn't currently docked anywhere is a no-op pass, not an error.
Every matching record is processed; a single record's failure (bad recipient, invalid plan state) transitions that record to failed and moves on to the next one. The returned error is reserved for a failure of the pass itself — e.g. the queue file could not be read — not a per-record outcome.
func (*WorkerDispatch) MineQty ¶
func (d *WorkerDispatch) MineQty(ctx context.Context, itemID string, qty int, to, recipient string) error
MineQty mines ITEM at its nearest known resource POI until QTY units are in cargo (recomputed against whatever is already aboard, so a resumed run doesn't over-mine) or the belt stops yielding, then hands the haul off to RECIPIENT at TO via Deliver's empty-FROM mode (the goods are already in cargo — Deliver skips the withdraw leg and travels straight to TO).
Bounded: stops mining after MineQtyMaxDryPasses consecutive Mine() calls that grow the item's cargo count by zero, or as soon as the cargo hold has no free space left — either way, whatever got mined is delivered and this returns nil (partial success is success; the plan runner's DoneQty semantics handle shortfalls). Only a hard failure (no known resource POI at all, a Mine() error, or a Deliver error such as a bad recipient) returns an error.
func (*WorkerDispatch) Run ¶
func (d *WorkerDispatch) Run(ctx context.Context, tokens []string) error
Run dispatches one tokenized command. Token resolution ($SYSTEM$, $STATION$, POI-type tokens) is the caller's responsibility (RunStanding resolves before calling) — Run treats tokens as literal.
func (*WorkerDispatch) SetActivitySink ¶
func (d *WorkerDispatch) SetActivitySink(p *atomic.Pointer[string])
SetActivitySink wires the shared atomic the worker heartbeat reads so role commands can publish a "current activity" string for the fleet status page. The caller (cmd/worker) owns the pointer; the dispatch only writes through it.
func (*WorkerDispatch) Supports ¶
func (d *WorkerDispatch) Supports(cmd string) bool
Supports reports whether cmd is in the curated worker vocabulary.
Source Files
¶
- assist.go
- autopilot.go
- buy_directed.go
- capture.go
- craft_node.go
- deliver.go
- dispatch.go
- explore.go
- freight.go
- freight_chain.go
- handoff_pass.go
- haul.go
- haul_fuel.go
- loop.go
- mine_qty.go
- mission.go
- mission_explore.go
- mission_select.go
- params.go
- parse.go
- rescue_fee.go
- roles.go
- runner.go
- schedule.go
- scripts.go
- shuttle.go
- standing.go
- tokens.go
- treasury.go
- watchdog.go