Documentation
¶
Overview ¶
Package webauto stores reusable, prompt-based automations ("recipes") for the ##auto command family. A recipe is a Markdown file with YAML frontmatter (description, args, output_dir, step budget, plus per-kind fields) and a prompt body. Four kinds share the same store/budget engine:
web — `##auto web run` binds a web pane to the recipe's browser
profile (web_profile, url) and dispatches the prompt to the
pane's Ask Rysh AI, which drives the browser through the
browser_action tool.
task — `##auto task run` dispatches the prompt to the ACTIVE pane's
Ask Rysh AI (no web binding, no browser profile).
agent — `##auto agent run` dispatches the prompt to the named
autonomous agent (`agent:` frontmatter), like @name <prompt>.
humanoid — `##auto humanoid run` — same, for humanoids (`humanoid:`).
code — `##auto code run` — like task, anchored to a project
directory (`workdir:` frontmatter, {{workdir}} substituted).
Layout: <workDir>/.rysh/automations/<kind-subdir>/<name>.md — plain files, so recipes are editable, diffable, and shareable through the repo. The automations directory has one child per ##auto subcommand (webs/tasks/agents/humanoids/codes).
Index ¶
- Constants
- func ParseBudgetSize(s string) (int, bool)
- func Render(a *Automation) ([]byte, error)
- func SanitizeName(name string) string
- func SubstituteArgs(a *Automation, runtimeArgs []string) string
- func ValidEffort(s string) bool
- type Automation
- func (a *Automation) EffectiveStep() *StepConfig
- func (a *Automation) HasBothStepForms() bool
- func (a *Automation) ResolveRunBudget() RunBudget
- func (a *Automation) ResolveRunBudgetWith(def *StepConfig) RunBudget
- func (a *Automation) ResolveWhileWith(defLoop *LoopConfig, base RunBudget) (LoopSpec, RunBudget)
- func (a *Automation) ResolveWhileWithFlags(defLoop *LoopConfig, base RunBudget, flags *WhileConfig) (LoopSpec, RunBudget)
- func (a *Automation) ScheduleOff() bool
- func (a *Automation) TakeoverPrompt() string
- func (a *Automation) TakeoverPromptWith(def *StepConfig) string
- type BudgetConfig
- type FloorConfig
- type Kind
- type LoopConfig
- type LoopPromptsConfig
- type LoopSpec
- type NotifyConfig
- type RecordConfig
- type RecordFlags
- type RecordSpec
- type RunBudget
- type StepConfig
- type Store
- type TakeoverFloor
- type WatchConfig
- type WhileConfig
Constants ¶
const ( DefaultRecordInterval = 500 * time.Millisecond DefaultRecordFormat = "jpeg" DefaultRecordQuality = 60 DefaultRecordMaxFrames = 7200 // ~1h at the default interval // MinRecordInterval floors the tick. Below this the capture round-trip // (CDP screenshot on a heavy page) never keeps up and every other frame // is dropped anyway, while the CDP traffic starts contending with the // agent's own browser actions. MinRecordInterval = 100 * time.Millisecond )
Built-in defaults. The interval is the headline feature (a frame every half second); the rest exist to keep a long run from filling the disk. A full-size PNG frame runs 200-500 KB, so a 10-minute run at 2 fps would cost ~500 MB — JPEG at quality 60 lands the same run near 50 MB.
const ( DefaultMaxIterations = 300 DefaultMaxDuration = 20 * time.Minute // DefaultStepInterval is the per-leg step cap (checkpoint/auto-resume cadence). DefaultStepInterval = 50 // step.budget.size units, in tokens. A page is 1000 tokens, a book is 200 // pages, a shelf is 20 books. size takes a count with a p/b/s suffix (bare = // pages), e.g. "50p", "3b", "2s". The default is 3 books. TokensPerPage = 1000 TokensPerBook = 200 * TokensPerPage // 200,000 TokensPerShelf = 20 * TokensPerBook // 4,000,000 DefaultBudgetSize = 3 * TokensPerBook // 600,000 (3 books) // DefaultTakeoverWhen is the consumption threshold at which the takeover leg // starts: the task runs against takeover_when% of each ceiling, and once // that share is spent the takeover prompt takes over with the remaining // (100-takeover_when)% as a fresh sub-budget. DefaultTakeoverWhen = 90 TakeoverFloorIterations = 100 // floor: at least 100 steps TakeoverFloorDuration = 5 * time.Minute // floor: at least 5 minutes TakeoverFloorTokens = TokensPerBook // floor: at least 1 book (200k) )
Auto-continue budget defaults for recipes (used when the frontmatter omits a value). A long browsing task keeps resuming past the per-leg iteration cap until it finishes, ~300 iterations, or 20 minutes elapse.
const ( DefaultStepModel = "claude-sonnet-5" DefaultJudgeModel = "claude-opus-4-8" )
Built-in model seats for automation loops, applied as the LOWEST tier of the model resolution (recipe > config automation.<kind> > config automation.loop > these). The INTERNAL loop (do/step legs, the executor seat) defaults to Sonnet — high-volume browsing/tool work; the until-JUDGE (loop.while seat) defaults to Opus — a single verdict per pass that gates the whole run, worth the stronger model. Config loading folds these into every automation kind (see config.applyAutomationLLMDefaults).
const DefaultLoopIteratePrompt = "Continue the SAME task as before: build on the previous results, " +
"add only NEW work, avoid duplicates, and save the updated results to the same location."
DefaultLoopIteratePrompt drives passes 2..N when prompts.iterate_with is omitted. The pass is additionally seeded with the latest saved result (the same mechanism `##auto <kind> resume` uses), so "continue the same task" has the concrete prior state in front of it.
const DefaultLoopIterations = 5
DefaultLoopIterations is the outer-pass cap applied when a while block omits max_iterations. It is also the redistribution divisor.
Variables ¶
This section is empty.
Functions ¶
func ParseBudgetSize ¶
ParseBudgetSize parses a step.budget.size value into a token count. It accepts a number with an optional unit suffix (case-insensitive):
"50p" or "50" → 50 pages (1 page = TokensPerPage tokens) "3b" → 3 books (1 book = 200 pages) "2s" → 2 shelves (1 shelf = 20 books)
Returns (0, false) for an empty or malformed value (caller applies the default).
func Render ¶
func Render(a *Automation) ([]byte, error)
Render encodes a recipe as frontmatter + prompt body.
func SanitizeName ¶
SanitizeName hardens a recipe name to a single safe path segment (mirrors browserinstance.SanitizeProfile). Returns "" when nothing safe remains.
func SubstituteArgs ¶
func SubstituteArgs(a *Automation, runtimeArgs []string) string
SubstituteArgs expands placeholders in the prompt:
{{args}} → all runtime args joined with spaces
{{arg1}}..{{argN}} → positional args (missing → "")
{{<name>}} → named per Automation.Args ordering (args[i] ↔ Args[i])
func ValidEffort ¶
ValidEffort reports whether s is a known effort level (or empty = unset).
Types ¶
type Automation ¶
type Automation struct {
Name string `yaml:"-"`
Description string `yaml:"description,omitempty"`
Profile string `yaml:"web_profile,omitempty"`
URL string `yaml:"url,omitempty"`
// Agent is the registry name of the autonomous agent an `##auto agent`
// recipe drives (the same name @agent-name prompts address).
Agent string `yaml:"agent,omitempty"`
// Humanoid is the registry name of the humanoid an `##auto humanoid`
// recipe drives.
Humanoid string `yaml:"humanoid,omitempty"`
// Workdir is the project directory an `##auto code` recipe works in
// (exposed to the prompt as {{workdir}}; relative values are anchored
// under the session's work dir, ~ expands to the home directory).
Workdir string `yaml:"workdir,omitempty"`
// WebRead selects how a web recipe's AI observes pages: "text" (default —
// get_text/get_elements DOM extraction) or "screenshot" (the screenshot
// action; captures are delivered to the model as images it can see).
// Web kind only; other kinds ignore it.
WebRead string `yaml:"web_read,omitempty"`
// OnSuccess names the recipe to run when this one COMPLETES — a loop
// ending FULFILLED, or a plain run finishing cleanly. Same kind by
// default; "<kind>:<name>" targets another kind. Cancel/error never
// chains; chains are capped at depth 8.
OnSuccess string `yaml:"on_success,omitempty"`
// Notify, when set, sends the run's outcome summary through a humanoid's
// channel adapter when the run ends (any cause). See NotifyConfig.
Notify *NotifyConfig `yaml:"notify,omitempty"`
// Schedule is an optional cron expression ("0 8 * * *", "@every 2h").
// When set, the daemon's boot-time sync registers an `auto-<kind>-<name>`
// ##cron job running the recipe; absent → the recipe is purely on-demand
// and scheduling machinery never touches it. The explicit off-switch
// "off" (also "none"/"disabled") REMOVES the recipe's auto job at boot —
// absence can't do that, since an existing job may be manually managed.
Schedule string `yaml:"schedule,omitempty"`
Args []string `yaml:"args,omitempty"` // documented arg names, e.g. [query, label]
// OutputDir is where the recipe's results are saved (exposed to the prompt
// as {{output_dir}} and read back by `##auto web results`). Empty →
// <recipe-dir>/<name>/results; a relative value is anchored under the
// recipe directory; an absolute value is used as-is. See ResolveOutputDir.
OutputDir string `yaml:"output_dir,omitempty"`
// Step groups the run-loop / auto-continue budget (see StepConfig). Absent →
// all defaults. When Loop.Do is also present, Loop.Do wins (Step is the
// plain single-run spelling of the same block).
Step *StepConfig `yaml:"step,omitempty"`
// Loop is the loop-engineering layout: `loop: {do, while}`. `do` is the
// per-pass budget (same shape as `step`); `while` is the outer semantic
// loop that repeats the pass until its `until` prompt is judged
// fulfilled. See LoopConfig.
Loop *LoopConfig `yaml:"loop,omitempty"`
// Record turns on run recording (web kind only): a browser screenshot
// every Interval for the whole run, encoded to one video. See RecordConfig.
Record *RecordConfig `yaml:"record,omitempty"`
Prompt string `yaml:"-"`
}
Automation is one saved automation recipe. Profile/URL apply to the web kind only; Agent/Humanoid name the target of the agent/humanoid kinds; Workdir anchors the code kind to a project directory. Each kind ignores the other kinds' fields when they are present.
func Parse ¶
func Parse(name string, data []byte) (*Automation, error)
Parse decodes a recipe file: optional `---`-delimited YAML frontmatter followed by the prompt body. A file with no frontmatter is all prompt.
func (*Automation) EffectiveStep ¶
func (a *Automation) EffectiveStep() *StepConfig
EffectiveStep returns the recipe's per-pass step block: `loop.do` wins over the plain `step` key when both are present (they are the same shape — step is the single-run spelling, loop.do the looping one).
func (*Automation) HasBothStepForms ¶
func (a *Automation) HasBothStepForms() bool
HasBothStepForms reports whether the recipe carries BOTH `step` and `loop.do` — legal (loop.do wins) but worth a warning in `show`.
func (*Automation) ResolveRunBudget ¶
func (a *Automation) ResolveRunBudget() RunBudget
ResolveRunBudget resolves the recipe budget against built-in defaults only (no config layer). Equivalent to ResolveRunBudgetWith(nil).
func (*Automation) ResolveRunBudgetWith ¶
func (a *Automation) ResolveRunBudgetWith(def *StepConfig) RunBudget
ResolveRunBudgetWith returns the effective budget with three-layer, per-field precedence: the recipe's `step` frontmatter wins, then def (the config-level `automation.web.step` defaults from rysh.config.yaml), then the built-ins (auto-continue on, DefaultStepInterval per leg clamped to maxStepInterval, DefaultMaxIterations total, DefaultMaxDuration wall-clock, DefaultBudgetSize of cumulative tokens, DefaultTakeoverWhen, and the built-in takeover floor). Zero/absent/invalid fields fall through to the next layer.
func (*Automation) ResolveWhileWith ¶
func (a *Automation) ResolveWhileWith(defLoop *LoopConfig, base RunBudget) (LoopSpec, RunBudget)
ResolveWhileWith resolves the recipe's while-loop against the ALREADY-RESOLVED per-pass budget (ResolveRunBudgetWith output) and applies the per-axis budget policy, returning the loop spec and the possibly REDISTRIBUTED per-pass budget. defLoop is the config-level automation.<kind>.loop defaults; per-field precedence is recipe > config > built-ins, as everywhere else.
The policy, per axis with a do-counterpart in the same unit (while.max_duration ↔ do.max_duration, while.budget ↔ do.budget.size):
redistribute — while.X > do.X: the while TOTAL is authoritative and is
split evenly across passes: do.X := while.X / max_iterations
(replacing do's own value); the total is kept on the spec
as the outer ceiling.
disable — while.X <= do.X (or omitted/unparseable): the axis cannot
cover even one pass, so it is ignored — do keeps its own
value and there is no outer constraint on that axis.
`enabled: false` short-circuits everything: the do-step runs once with its own literal budget — no judge, no iterate, no redistribution. Omitted `enabled` defaults to true when the merged block carries an until prompt.
func (*Automation) ResolveWhileWithFlags ¶
func (a *Automation) ResolveWhileWithFlags(defLoop *LoopConfig, base RunBudget, flags *WhileConfig) (LoopSpec, RunBudget)
ResolveWhileWithFlags is ResolveWhileWith with a third, highest-precedence override tier: run flags (--passes / --while-duration / --while-budget / --no-loop), expressed as a synthetic WhileConfig merged last.
func (*Automation) ScheduleOff ¶
func (a *Automation) ScheduleOff() bool
ScheduleOff reports whether the recipe explicitly declares "do not schedule me": schedule set to off/none/disabled (case-insensitive). Distinct from an absent key — absence says nothing about intent (existing jobs are left alone); off carries intent, so the boot sync removes the recipe's auto-<kind>-<name> job and ##auto <kind> schedule refuses.
func (*Automation) TakeoverPrompt ¶
func (a *Automation) TakeoverPrompt() string
TakeoverPrompt returns the recipe's budget takeover prompt, or "" when no budget.watch.takeover_prompt is configured on the effective step.
func (*Automation) TakeoverPromptWith ¶
func (a *Automation) TakeoverPromptWith(def *StepConfig) string
TakeoverPromptWith returns the effective takeover prompt: the recipe's, else the config-level default (def = automation.web.step from rysh.config.yaml).
type BudgetConfig ¶
type BudgetConfig struct {
Size string `yaml:"size,omitempty"`
Watch *WatchConfig `yaml:"watch,omitempty"`
}
BudgetConfig is the budget block nested under `step.budget`: the token ceiling (Size) and the Watch block that reacts when the budget runs out.
budget:
size: 3b # cumulative token ceiling (p/b/s units; see ParseBudgetSize)
watch:
takeover_when: 90 # takeover starts once this % of every ceiling is consumed
takeover_prompt: > # graceful wrap-up; runs once when the task share is spent
...
type FloorConfig ¶
type FloorConfig struct {
TriggerIterations int `yaml:"trigger_iterations,omitempty"`
TriggerDuration string `yaml:"trigger_duration,omitempty"`
TriggerSize string `yaml:"trigger_size,omitempty"`
Iterations int `yaml:"iterations,omitempty"`
Duration string `yaml:"duration,omitempty"`
Size string `yaml:"size,omitempty"`
}
FloorConfig tunes the takeover floor, nested under `step.budget.watch.floor` (recipe frontmatter or the config-level automation.web.step defaults in rysh.config.yaml). The floor fires when the reserved takeover share is below ANY Trigger* threshold and raises every axis to at least the floor values. Durations are Go duration strings; sizes take p/b/s units (ParseBudgetSize). Zero/absent/invalid fields keep the previous layer's value (built-ins: trigger <1m / <50 steps / <60p → floor 5m / 100 steps / 1b).
type Kind ¶
type Kind string
Kind selects which automation family a Store manages — one child directory per ##auto subcommand under .rysh/automations.
type LoopConfig ¶
type LoopConfig struct {
Do *StepConfig `yaml:"do,omitempty"`
While *WhileConfig `yaml:"while,omitempty"`
}
LoopConfig is the top-level `loop` frontmatter block — a declarative do-while:
loop:
do: # per-pass budget — the same fields as `step`
interval: 30
max_duration: 7m
budget: {size: 3b, watch: {...}}
while: # outer loop — see WhileConfig
enabled: true
max_iterations: 5
max_duration: 40m
budget: 15b
prompts: {until: ..., iterate_with: ...}
type LoopPromptsConfig ¶
type LoopPromptsConfig struct {
Until string `yaml:"until,omitempty"`
IterateWith string `yaml:"iterate_with,omitempty"`
}
LoopPromptsConfig holds the outer loop's two prompts (under `loop.while.prompts`): Until is the LLM-judged exit condition; IterateWith drives passes 2..N (empty falls back to a built-in continue-the-task prompt). Both support {{args}}/{{argN}}/{{<name>}}/ {{output_dir}} substitution.
type LoopSpec ¶
type LoopSpec struct {
// Enabled is true when a while block is configured, not switched off
// (`enabled: false`), and has an until prompt (without an exit condition
// there is nothing to loop for).
Enabled bool
// MaxIterations caps the outer passes while Until stays unfulfilled, and
// divides the while totals during redistribution.
MaxIterations int
// MaxDuration is the while-loop's wall-clock TOTAL, kept as a hard outer
// deadline. Non-zero only when the redistribute branch fired for the
// duration axis (while.max_duration > per-pass do.max_duration); 0 means
// no outer time constraint (axis omitted, unparseable, or disabled by the
// policy).
MaxDuration time.Duration
// MaxTokens is the while-loop's token TOTAL, mirrored for reporting.
// Non-zero only when the redistribute branch fired for the token axis;
// its enforcement is the even division itself — each pass is armed with
// MaxTokens/MaxIterations, so N passes spend ≈ the total (a takeover
// floor may raise a wrap-up leg past its share; a clipped wrap-up loses
// the pass's work, which costs more than the overshoot).
MaxTokens int
// Until is the LLM-judged exit condition, evaluated against the latest
// saved results after each pass.
Until string
// IterateWith is the prompt for passes 2..N (never empty when Enabled —
// falls back to DefaultLoopIteratePrompt).
IterateWith string
// JudgeModel / JudgeEffort are the resolved judge seat (while.model /
// while.effort, recipe > config; empty → session defaults).
JudgeModel string
JudgeEffort string
}
LoopSpec is the resolved while-loop for a run. Zero value = no loop.
type NotifyConfig ¶
type NotifyConfig struct {
Humanoid string `yaml:"humanoid,omitempty"`
Channel string `yaml:"channel,omitempty"`
To string `yaml:"to,omitempty"`
}
NotifyConfig routes a run-completion notification through one of a humanoid's channel adapters:
notify: humanoid: assistant # a loaded humanoid (##humanoid spawn ...) channel: whatsapp # one of its configured channels to: "+4915..." # channel-specific recipient/thread id
type RecordConfig ¶
type RecordConfig struct {
Enabled *bool `yaml:"enabled,omitempty"`
Path string `yaml:"path,omitempty"`
Interval string `yaml:"interval,omitempty"`
Format string `yaml:"format,omitempty"`
Quality int `yaml:"quality,omitempty"`
MaxFrames int `yaml:"max_frames,omitempty"`
}
RecordConfig is the `record:` block, shared by recipe frontmatter and the `automation.web.record` config section:
record: enabled: true path: recordings/run.mp4 # relative → under the recipe's output_dir interval: 500ms format: jpeg # jpeg | png | webp quality: 60 # jpeg/webp only max_frames: 7200
type RecordFlags ¶
RecordFlags is the run-flag tier: --record / --no-record / --recording-path / --record-interval. On and Off are separate booleans (not one tristate) so "flag absent" stays distinguishable from "flag set false" — --no-record has to beat a recipe that enables recording.
type RecordSpec ¶
type RecordSpec struct {
Enabled bool
Path string // may be empty/relative here; see ResolvePath
Interval time.Duration
Format string
Quality int
MaxFrames int
}
RecordSpec is the fully resolved plan the recorder actor runs on.
func ResolveRecord ¶
func ResolveRecord(recipe, cfg *RecordConfig, f RecordFlags) RecordSpec
ResolveRecord folds the three tiers into one spec. recipe and cfg may be nil.
func (RecordSpec) Describe ¶
func (s RecordSpec) Describe(path string) string
Describe renders the one-line run-report summary for the recording plan.
func (RecordSpec) FrameExt ¶
func (s RecordSpec) FrameExt() string
FrameExt is the file extension for the spec's capture format.
func (RecordSpec) ResolvePath ¶
func (s RecordSpec) ResolvePath(outputDir, recipeName string, startedAt time.Time) string
ResolvePath returns the absolute video path for a run. An empty Path defaults to <outputDir>/recordings/<recipe>-<timestamp>.mp4. A relative Path is anchored under outputDir; an absolute one is used as-is. A Path naming a directory (trailing separator, or no file extension) keeps the default filename inside it.
startedAt is passed in rather than read from the clock so the caller can stamp the frames directory and the video with the same instant.
type RunBudget ¶
type RunBudget struct {
AutoContinue bool
AutoApprove bool // run tool calls without the approval dialog
MaxIterations int // total steps ceiling → takeover
MaxDuration time.Duration // wall-clock ceiling → takeover
StepInterval int // steps per leg (checkpoint/auto-resume cadence)
MaxContextTokens int // cumulative token ceiling (budget.size) → takeover
TakeoverWhen int // % of every ceiling consumed before the takeover leg starts
// Model / Effort are the resolved executor seat (recipe > config; empty →
// session defaults). FinalizerModel/FinalizerEffort are the finalizer
// seat; empty falls back to Model/Effort downstream.
Model string
Effort string
FinalizerModel string
FinalizerEffort string
// Floor is the resolved takeover-floor policy (recipe > config > built-in),
// applied by WithTakeoverFloor. Zero value → built-in floor.
Floor TakeoverFloor
}
RunBudget is the effective auto-continue budget for a run.
func (RunBudget) SplitForTakeover ¶
SplitForTakeover partitions the budget into the task share (TakeoverWhen% of each ceiling) and the takeover leg's reserved remainder. All three ceilings — steps, duration, and cumulative tokens — split proportionally into separate fresh allowances that sum back to the full budget (token accounting resets when the takeover leg starts, so its share is a clean fresh budget, not headroom). StepInterval and AutoContinue are shared unchanged.
func (RunBudget) WithTakeoverFloor ¶
WithTakeoverFloor raises a thin takeover share to a comfortable minimum, using the budget's resolved Floor policy (recipe > config > built-ins: trigger <1m / <50 steps / <0.3 book → floor 5m / 100 steps / 1 book). If the share is below ANY trigger threshold, every axis is lifted to at least its floor — axes already above their floor are kept as-is, and a share above all trigger thresholds is returned unchanged. The wrap-up leg is trusted to finish even when that means exceeding the nominal recipe budget: a clipped finalizer loses the run's gathered work, which costs more than the overshoot.
type StepConfig ¶
type StepConfig struct {
Interval int `yaml:"interval,omitempty"`
MaxIterations int `yaml:"max_iterations,omitempty"`
MaxDuration string `yaml:"max_duration,omitempty"`
AutoContinue *bool `yaml:"auto_continue,omitempty"`
AutoApprove *bool `yaml:"auto_approve,omitempty"`
// Model / Effort override the executor's model and effort for this
// recipe's main legs (seat 1 of 3; empty → session defaults). Effort is
// low|medium|high|xhigh|max.
Model string `yaml:"model,omitempty"`
Effort string `yaml:"effort,omitempty"`
Budget *BudgetConfig `yaml:"budget,omitempty"`
}
StepConfig is the run-loop budget nested under `step` in a recipe. A long run auto-continues past each Interval-sized leg until it finishes or a ceiling is hit (MaxIterations total steps, MaxDuration wall-clock, or budget.size total tokens), then the budget's takeover prompt (if set) wraps up gracefully.
step:
interval: 50 # steps per leg (checkpoint/auto-resume cadence)
max_iterations: 300 # total steps ceiling
max_duration: 20m # wall-clock ceiling (Go duration string)
auto_continue: true # default true; resume past each leg until a ceiling
auto_approve: true # default true; run tool calls without the approval dialog
budget: {size, watch: {takeover_when, takeover_prompt}}
func EffectiveStepDef ¶
func EffectiveStepDef(step *StepConfig, loop *LoopConfig) *StepConfig
EffectiveStepDef returns the config-level per-pass defaults with the same aliasing rule as the recipe: `automation.<kind>.loop.do` wins over `automation.<kind>.step`.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store manages the on-disk recipe directory for one automation kind.
func NewStore ¶
NewStore opens the web-kind store — the original constructor, kept so existing callers/tests are untouched. Equivalent to NewStoreFor(workDir, KindWeb).
func NewStoreFor ¶
NewStoreFor anchors the recipe directory for one automation kind under workDir (the same anchor the browser-profile store uses): .rysh/automations/<kind-subdir>, one child per ##auto subcommand (webs/tasks/agents/humanoids/codes). For the web kind, a legacy .rysh/web-automations directory is migrated (renamed) in place the first time a store is opened on that workDir.
func (*Store) List ¶
func (s *Store) List() []*Automation
List returns all recipes sorted by name. Unparseable files are skipped.
func (*Store) Load ¶
func (s *Store) Load(name string) (*Automation, error)
Load reads one recipe by name.
func (*Store) ResolveOutputDir ¶
func (s *Store) ResolveOutputDir(a *Automation) string
ResolveOutputDir returns the absolute directory where a recipe's results are saved. Precedence:
output_dir empty → <recipe-dir>/<sanitized-name>/results (default) output_dir relative → anchored under the recipe directory output_dir absolute → used as-is
The result is exposed to the prompt as {{output_dir}} (created before the run) and is the folder `##auto web results <name>` lists.
func (*Store) Save ¶
func (s *Store) Save(a *Automation) error
Save writes (or overwrites) a recipe file.
type TakeoverFloor ¶
type TakeoverFloor struct {
TriggerIterations int
TriggerDuration time.Duration
TriggerTokens int
Iterations int
Duration time.Duration
Tokens int
}
TakeoverFloor is the resolved takeover floor: when the reserved takeover share is below ANY Trigger* threshold, every axis is raised to at least Iterations/Duration/Tokens.
type WatchConfig ¶
type WatchConfig struct {
TakeoverWhen int `yaml:"takeover_when,omitempty"`
TakeoverPrompt string `yaml:"takeover_prompt,omitempty"`
// Model / Effort override the model/effort for the FINALIZER (takeover)
// leg only (seat 2 of 3) — the mechanical wrap-up can run on a cheaper
// model. Empty → fall back to the step's Model/Effort, then defaults.
Model string `yaml:"model,omitempty"`
Effort string `yaml:"effort,omitempty"`
Floor *FloorConfig `yaml:"floor,omitempty"`
}
WatchConfig is the budget watcher nested under `step.budget.watch`: it monitors budget consumption and hands the run over to TakeoverPrompt once TakeoverWhen% of the budget is consumed.
TakeoverWhen is the consumption threshold (default DefaultTakeoverWhen): 90 → the task runs against 90% of each ceiling; when it hits that mark it stops and TakeoverPrompt takes over with the remaining 10% as a fresh sub-budget. TakeoverPrompt supports {{args}} / {{output_dir}}.
type WhileConfig ¶
type WhileConfig struct {
Enabled *bool `yaml:"enabled,omitempty"`
MaxIterations int `yaml:"max_iterations,omitempty"`
MaxDuration string `yaml:"max_duration,omitempty"`
Budget string `yaml:"budget,omitempty"`
// Model / Effort select the UNTIL-JUDGE's model and effort (seat 3 of 3;
// empty → session defaults). A cheap fast model usually suffices for the
// strict YES/NO evaluation.
Model string `yaml:"model,omitempty"`
Effort string `yaml:"effort,omitempty"`
Prompts *LoopPromptsConfig `yaml:"prompts,omitempty"`
}
WhileConfig is the outer semantic loop nested under `loop.while`. Where the do-step's iterations are MECHANICAL (tool-use steps toward finishing the prompt once), the while-loop's iterations are SEMANTIC: whole passes over the goal, repeated until prompts.until is judged fulfilled.
Enabled is the master switch: false runs the do-step exactly ONCE with no while constraint applied (no redistribution either); omitted defaults to true when the block carries an until prompt. MaxDuration and Budget are TOTALS for the whole loop, governed by the per-axis policy in ResolveWhileWith (redistribute across passes when they exceed the per-pass do value; ignored when they don't). Budget takes p/b/s units (ParseBudgetSize).