mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
Clear the operations-layer alignment residuals (#1054)
Closes out the six residual items #1051 disclosed: terminology residue on non-authority surfaces, the eval command-surface fixture that failed when executed (#{3,4} -> #{3,5}), the vacuous retrieval-quality canary and the nightly job that ran it, the consumer-free dream config block and its exclusive helpers, the remaining knowledge-shaped writers moved to the scratch tier, and the MEMORY.md consumer audit. bin/ralph still resumes legacy .agents/ralph/ checkpoints so the documented backwards-compat contract holds without a migration; both paths and the outside-both refusal are now tested. Fresh author-distinct validation returned PASS with empty not_checked, after an earlier revision failed on a dangling nightly invoker and a back-compat test regression that were fixed and independently re-verified. Test-Removal-Reason: the dream config subsystem was deleted with its tests (operations-layer residuals)
This commit is contained in:
@@ -54,22 +54,6 @@ jobs:
|
||||
chmod +x tests/docs/validate-doc-release.sh
|
||||
./tests/docs/validate-doc-release.sh
|
||||
|
||||
retrieval-bench:
|
||||
name: Retrieval Quality Bench
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: '1.26.5'
|
||||
cache-dependency-path: cli/go.sum
|
||||
|
||||
- name: Run retrieval quality smoke
|
||||
run: bash evals/agentops-core/fixtures/retrieval-quality-smoke.sh
|
||||
|
||||
# The release path is otherwise exercised only by a real `v*` tag push, which
|
||||
# is how a retired .goreleaser.yml before-hook stayed broken for 20 days
|
||||
# (docs/audits/release-readiness-v3.3.0.md, "Process lesson"). The nightly run
|
||||
@@ -145,7 +129,7 @@ jobs:
|
||||
|
||||
summary:
|
||||
name: Summary
|
||||
needs: [cli-tests, static-validation, retrieval-bench, release-path-smoke, security-toolchain]
|
||||
needs: [cli-tests, static-validation, release-path-smoke, security-toolchain]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -160,7 +144,6 @@ jobs:
|
||||
echo "|-----|--------|"
|
||||
echo "| CLI tests | ${{ needs.cli-tests.result }} |"
|
||||
echo "| Static validation | ${{ needs.static-validation.result }} |"
|
||||
echo "| Retrieval bench | ${{ needs.retrieval-bench.result }} |"
|
||||
echo "| Release path smoke | ${{ needs.release-path-smoke.result }} |"
|
||||
echo "| Security toolchain | ${{ needs.security-toolchain.result }} |"
|
||||
echo ""
|
||||
@@ -168,7 +151,7 @@ jobs:
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Create issue on failure
|
||||
if: needs.cli-tests.result == 'failure' || needs.static-validation.result == 'failure' || needs.retrieval-bench.result == 'failure' || needs.release-path-smoke.result == 'failure' || needs.security-toolchain.result == 'failure'
|
||||
if: needs.cli-tests.result == 'failure' || needs.static-validation.result == 'failure' || needs.release-path-smoke.result == 'failure' || needs.security-toolchain.result == 'failure'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -179,7 +162,6 @@ jobs:
|
||||
|-----|--------|
|
||||
| CLI tests | ${{ needs.cli-tests.result }} |
|
||||
| Static validation | ${{ needs.static-validation.result }} |
|
||||
| Retrieval bench | ${{ needs.retrieval-bench.result }} |
|
||||
| Release path smoke | ${{ needs.release-path-smoke.result }} |
|
||||
| Security toolchain | ${{ needs.security-toolchain.result }} |
|
||||
|
||||
@@ -201,5 +183,5 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check for failures
|
||||
if: needs.cli-tests.result == 'failure' || needs.static-validation.result == 'failure' || needs.retrieval-bench.result == 'failure' || needs.release-path-smoke.result == 'failure' || needs.security-toolchain.result == 'failure'
|
||||
if: needs.cli-tests.result == 'failure' || needs.static-validation.result == 'failure' || needs.release-path-smoke.result == 'failure' || needs.security-toolchain.result == 'failure'
|
||||
run: exit 1
|
||||
|
||||
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Removed
|
||||
|
||||
- The consumer-free `dream:` config block (the retired overnight subsystem's
|
||||
settings; existing config files still parse and the key is ignored), the
|
||||
vacuous retrieval-quality canary and its smoke (every Go test it named was
|
||||
already deleted; the retrieval-comparison contract is now explicitly
|
||||
dormant and the blocking manifest-paths gate remains the live retrieval
|
||||
guard), and the dormant `check-pillar-coverage.sh` GOALS.yaml script.
|
||||
Remaining scratch-tier writers (`mine-all-sessions.sh`, `team-runner.sh`,
|
||||
`bin/ralph`) now write under `.agents/scratch/`.
|
||||
|
||||
- The `ao flywheel` command family (`status`, `compare`) and the whole live
|
||||
knowledge-flywheel product surface: `cli/internal/flywheelapp`, the
|
||||
flywheel-only metric helpers and types, the `flywheel` build-profile bit,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# ralph --branch feat/dark-mode "Add dark mode"
|
||||
# ralph --spec spec.md "Add dark mode"
|
||||
# ralph --dry-run "Add dark mode"
|
||||
# ralph --resume .agents/ralph/<slug>.checkpoint
|
||||
# ralph --resume .agents/scratch/ralph/<slug>.checkpoint
|
||||
# ralph --phase-timeout 900 "Big feature"
|
||||
|
||||
set -euo pipefail
|
||||
@@ -62,10 +62,14 @@ if [[ -n "$RESUME_FILE" ]]; then
|
||||
echo "Checkpoint file not found: $RESUME_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Validate checkpoint is under .agents/ralph/ and has expected header
|
||||
# Validate the checkpoint lives in a ralph checkpoint dir and has the
|
||||
# expected header. New checkpoints are written under .agents/scratch/ralph/;
|
||||
# the legacy .agents/ralph/ path stays resumable so checkpoints written
|
||||
# before the scratch-tier move still work (no migration required).
|
||||
case "$RESUME_FILE" in
|
||||
.agents/scratch/ralph/*.checkpoint|*/.agents/scratch/ralph/*.checkpoint) ;;
|
||||
.agents/ralph/*.checkpoint|*/.agents/ralph/*.checkpoint) ;;
|
||||
*) echo "Refusing to source checkpoint outside .agents/ralph/: $RESUME_FILE" >&2; exit 1 ;;
|
||||
*) echo "Refusing to source checkpoint outside .agents/scratch/ralph/ (or the legacy .agents/ralph/): $RESUME_FILE" >&2; exit 1 ;;
|
||||
esac
|
||||
if ! head -1 "$RESUME_FILE" | grep -q '^# Ralph checkpoint'; then
|
||||
echo "Invalid checkpoint file (missing header): $RESUME_FILE" >&2
|
||||
@@ -97,7 +101,7 @@ BRANCH="${BRANCH:-feat/ralph-$(date +%s)}"
|
||||
BASE_BRANCH="${BASE_BRANCH:-$(git branch --show-current)}"
|
||||
WORKDIR="${WORKDIR:-$(pwd)}"
|
||||
SLUG="${SLUG:-$(echo "$GOAL" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g' | head -c 40)}"
|
||||
RALPH_DIR=".agents/ralph"
|
||||
RALPH_DIR=".agents/scratch/ralph"
|
||||
RALPH_LOG="${RALPH_LOG:-$RALPH_DIR/$(date +%Y-%m-%d)-$SLUG.log}"
|
||||
CHECKPOINT_FILE="$RALPH_DIR/$SLUG.checkpoint"
|
||||
|
||||
|
||||
+25
-304
@@ -44,9 +44,6 @@ type Config struct {
|
||||
// Models settings
|
||||
Models ModelsConfig `yaml:"models" json:"models"`
|
||||
|
||||
// Dream settings for private overnight runs.
|
||||
Dream DreamConfig `yaml:"dream" json:"dream"`
|
||||
|
||||
// Compile settings for ao compile headless runtime preference.
|
||||
Compile CompileConfig `yaml:"compile" json:"compile"`
|
||||
}
|
||||
@@ -90,7 +87,6 @@ type RPIConfig struct {
|
||||
TmuxCommand string `yaml:"tmux_command" json:"tmux_command"`
|
||||
}
|
||||
|
||||
|
||||
// PathsConfig holds configurable paths for artifact locations.
|
||||
// Fixes G5: paths are now configurable, not hardcoded.
|
||||
type PathsConfig struct {
|
||||
@@ -149,71 +145,6 @@ type ModelsConfig struct {
|
||||
SkillOverrides map[string]string `yaml:"skill_overrides" json:"skill_overrides"`
|
||||
}
|
||||
|
||||
// DreamConfig holds settings for the private overnight Dream operator mode.
|
||||
type DreamConfig struct {
|
||||
// ReportDir is where overnight summaries and step artifacts are written.
|
||||
ReportDir string `yaml:"report_dir" json:"report_dir"`
|
||||
|
||||
// RunTimeout is the maximum duration for a single overnight run.
|
||||
RunTimeout string `yaml:"run_timeout" json:"run_timeout"`
|
||||
|
||||
// KeepAwake controls whether local runs should attempt keep-awake assistance.
|
||||
// Nil means "not explicitly configured".
|
||||
KeepAwake *bool `yaml:"keep_awake,omitempty" json:"keep_awake,omitempty"`
|
||||
|
||||
// Runners is the ordered list of Dream runtimes to use for multimodel work.
|
||||
Runners []string `yaml:"runners,omitempty" json:"runners,omitempty"`
|
||||
|
||||
// SchedulerMode is the chosen host scheduler mode (manual, launchd, cron, systemd).
|
||||
SchedulerMode string `yaml:"scheduler_mode,omitempty" json:"scheduler_mode,omitempty"`
|
||||
|
||||
// ScheduleAt is the preferred local time (HH:MM) for host-scheduled Dream runs.
|
||||
ScheduleAt string `yaml:"schedule_at,omitempty" json:"schedule_at,omitempty"`
|
||||
|
||||
// ConsensusPolicy controls how Dream Council synthesizes multiple runner outputs.
|
||||
ConsensusPolicy string `yaml:"consensus_policy,omitempty" json:"consensus_policy,omitempty"`
|
||||
|
||||
// CouncilRunnerTimeout caps how long each Dream Council runner lane may
|
||||
// run before its context is cancelled. Accepts Go duration strings
|
||||
// (e.g. "90s", "3m"). Empty means "use the built-in default".
|
||||
CouncilRunnerTimeout string `yaml:"council_runner_timeout,omitempty" json:"council_runner_timeout,omitempty"`
|
||||
|
||||
// CreativeLane controls whether Dream Council should request wildcard ideas.
|
||||
// Nil means "not explicitly configured".
|
||||
CreativeLane *bool `yaml:"creative_lane,omitempty" json:"creative_lane,omitempty"`
|
||||
|
||||
// LocalCurator configures a local Tier 1 curator such as Ollama/Gemma.
|
||||
LocalCurator DreamLocalCuratorConfig `yaml:"local_curator,omitempty" json:"local_curator,omitempty"`
|
||||
}
|
||||
|
||||
// DreamLocalCuratorConfig holds settings for a local Tier 1 Dream curator.
|
||||
type DreamLocalCuratorConfig struct {
|
||||
// Enabled controls whether the local curator should be treated as active.
|
||||
// Nil means "not explicitly configured".
|
||||
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
||||
|
||||
// Engine identifies the local engine. V1 supports "ollama".
|
||||
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
|
||||
|
||||
// OllamaURL is the Ollama API root used when Engine is "ollama".
|
||||
OllamaURL string `yaml:"ollama_url,omitempty" json:"ollama_url,omitempty"`
|
||||
|
||||
// Model is the Tier 1 model name.
|
||||
Model string `yaml:"model,omitempty" json:"model,omitempty"`
|
||||
|
||||
// WorkerDir is the local Dream worker directory.
|
||||
WorkerDir string `yaml:"worker_dir,omitempty" json:"worker_dir,omitempty"`
|
||||
|
||||
// VaultDir is the local knowledge vault written by the worker.
|
||||
VaultDir string `yaml:"vault_dir,omitempty" json:"vault_dir,omitempty"`
|
||||
|
||||
// HourlyCap is the worker's bounded autonomy limit.
|
||||
HourlyCap int `yaml:"hourly_cap,omitempty" json:"hourly_cap,omitempty"`
|
||||
|
||||
// AllowedJobKinds is the local curator queue allowlist.
|
||||
AllowedJobKinds []string `yaml:"allowed_job_kinds,omitempty" json:"allowed_job_kinds,omitempty"`
|
||||
}
|
||||
|
||||
// TierConfig holds model names for a tier.
|
||||
type TierConfig struct {
|
||||
// Claude is the Claude model name (opus, sonnet, haiku).
|
||||
@@ -288,7 +219,6 @@ const (
|
||||
// Default returns the default configuration.
|
||||
func Default() *Config {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
defaultKeepAwake := true
|
||||
return &Config{
|
||||
Output: defaultOutput,
|
||||
BaseDir: defaultBaseDir,
|
||||
@@ -318,13 +248,6 @@ func Default() *Config {
|
||||
},
|
||||
SkillOverrides: map[string]string{},
|
||||
},
|
||||
Dream: DreamConfig{
|
||||
ReportDir: ".agents/overnight/latest",
|
||||
RunTimeout: "8h",
|
||||
KeepAwake: &defaultKeepAwake,
|
||||
SchedulerMode: "manual",
|
||||
ConsensusPolicy: "majority",
|
||||
},
|
||||
Paths: PathsConfig{
|
||||
LearningsDir: ".agents/learnings",
|
||||
PatternsDir: ".agents/patterns",
|
||||
@@ -581,19 +504,6 @@ func applyEnv(cfg *Config) *Config {
|
||||
applyEnvStr(&cfg.RPI.BDCommand, "AGENTOPS_RPI_BD_COMMAND")
|
||||
applyEnvStr(&cfg.RPI.TmuxCommand, "AGENTOPS_RPI_TMUX_COMMAND")
|
||||
applyEnvStr(&cfg.Models.DefaultTier, "AGENTOPS_MODEL_TIER")
|
||||
applyEnvStr(&cfg.Dream.ReportDir, "AGENTOPS_DREAM_REPORT_DIR")
|
||||
applyEnvStr(&cfg.Dream.RunTimeout, "AGENTOPS_DREAM_RUN_TIMEOUT")
|
||||
if v, ok := getEnvBoolValue("AGENTOPS_DREAM_KEEP_AWAKE"); ok {
|
||||
cfg.Dream.KeepAwake = boolPtr(v)
|
||||
}
|
||||
if v, ok := getEnvBoolValue("AGENTOPS_DREAM_CURATOR_ENABLED"); ok {
|
||||
cfg.Dream.LocalCurator.Enabled = boolPtr(v)
|
||||
}
|
||||
applyEnvStr(&cfg.Dream.LocalCurator.Engine, "AGENTOPS_DREAM_CURATOR_ENGINE")
|
||||
applyEnvStr(&cfg.Dream.LocalCurator.OllamaURL, "AGENTOPS_DREAM_CURATOR_OLLAMA_URL")
|
||||
applyEnvStr(&cfg.Dream.LocalCurator.Model, "AGENTOPS_DREAM_CURATOR_MODEL")
|
||||
applyEnvStr(&cfg.Dream.LocalCurator.WorkerDir, "AGENTOPS_DREAM_CURATOR_WORKER_DIR")
|
||||
applyEnvStr(&cfg.Dream.LocalCurator.VaultDir, "AGENTOPS_DREAM_CURATOR_VAULT_DIR")
|
||||
if v := os.Getenv("AGENTOPS_COUNCIL_MODEL_TIER"); v != "" {
|
||||
if cfg.Models.SkillOverrides == nil {
|
||||
cfg.Models.SkillOverrides = make(map[string]string)
|
||||
@@ -630,7 +540,6 @@ func merge(dst, src *Config) *Config {
|
||||
mergeSearch(&dst.Search, &src.Search)
|
||||
mergeRPI(&dst.RPI, &src.RPI)
|
||||
mergeModels(&dst.Models, &src.Models)
|
||||
mergeDream(&dst.Dream, &src.Dream)
|
||||
mergePaths(&dst.Paths, &src.Paths)
|
||||
|
||||
return dst
|
||||
@@ -683,41 +592,6 @@ func mergeModels(dst, src *ModelsConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeDream merges Dream config fields.
|
||||
func mergeDream(dst, src *DreamConfig) {
|
||||
mergeStr(&dst.ReportDir, src.ReportDir)
|
||||
mergeStr(&dst.RunTimeout, src.RunTimeout)
|
||||
if src.KeepAwake != nil {
|
||||
dst.KeepAwake = boolPtr(*src.KeepAwake)
|
||||
}
|
||||
if len(src.Runners) > 0 {
|
||||
dst.Runners = append([]string{}, src.Runners...)
|
||||
}
|
||||
mergeStr(&dst.SchedulerMode, src.SchedulerMode)
|
||||
mergeStr(&dst.ScheduleAt, src.ScheduleAt)
|
||||
mergeStr(&dst.ConsensusPolicy, src.ConsensusPolicy)
|
||||
mergeStr(&dst.CouncilRunnerTimeout, src.CouncilRunnerTimeout)
|
||||
if src.CreativeLane != nil {
|
||||
dst.CreativeLane = boolPtr(*src.CreativeLane)
|
||||
}
|
||||
mergeDreamLocalCurator(&dst.LocalCurator, &src.LocalCurator)
|
||||
}
|
||||
|
||||
func mergeDreamLocalCurator(dst, src *DreamLocalCuratorConfig) {
|
||||
if src.Enabled != nil {
|
||||
dst.Enabled = boolPtr(*src.Enabled)
|
||||
}
|
||||
mergeStr(&dst.Engine, src.Engine)
|
||||
mergeStr(&dst.OllamaURL, src.OllamaURL)
|
||||
mergeStr(&dst.Model, src.Model)
|
||||
mergeStr(&dst.WorkerDir, src.WorkerDir)
|
||||
mergeStr(&dst.VaultDir, src.VaultDir)
|
||||
mergeInt(&dst.HourlyCap, src.HourlyCap)
|
||||
if len(src.AllowedJobKinds) > 0 {
|
||||
dst.AllowedJobKinds = append([]string{}, src.AllowedJobKinds...)
|
||||
}
|
||||
}
|
||||
|
||||
// mergePaths merges path config fields (G5: configurable paths, not hardcoded).
|
||||
func mergePaths(dst, src *PathsConfig) {
|
||||
mergeStr(&dst.LearningsDir, src.LearningsDir)
|
||||
@@ -855,23 +729,6 @@ func getEnvBool(key string) (bool, bool) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
// getEnvBoolValue returns the boolean value and whether the env var was set to
|
||||
// a recognized boolean literal.
|
||||
func getEnvBoolValue(key string) (bool, bool) {
|
||||
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
|
||||
case "true", "1", "yes", "on":
|
||||
return true, true
|
||||
case "false", "0", "no", "off":
|
||||
return false, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// resolveStringField resolves a string through the precedence chain.
|
||||
// Returns the resolved value and its source.
|
||||
func resolveStringField(home, project, env, flag, def string) resolved {
|
||||
@@ -901,51 +758,24 @@ func resolveStringField(home, project, env, flag, def string) resolved {
|
||||
return result
|
||||
}
|
||||
|
||||
func resolveStringSliceField(home, project []string, def []string) resolved {
|
||||
result := resolved{Value: append([]string{}, def...), Source: SourceDefault}
|
||||
if len(home) > 0 {
|
||||
result = resolved{Value: append([]string{}, home...), Source: SourceHome}
|
||||
}
|
||||
if len(project) > 0 {
|
||||
result = resolved{Value: append([]string{}, project...), Source: SourceProject}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResolvedConfig shows config values with their sources.
|
||||
//
|
||||
// The RPI* and Dream* fields are excluded from serialization (`json:"-"`):
|
||||
// no `ao rpi` or `ao dream` command exists, so `ao config --show` must not
|
||||
// render resolved values for subsystems a user cannot invoke. The fields
|
||||
// themselves are retained for now because callers outside this package
|
||||
// (e.g. cmd/ao tests) still reference them; deleting the underlying Config
|
||||
// struct sections is a separate pass.
|
||||
// The RPI* fields are excluded from serialization (`json:"-"`): no `ao rpi`
|
||||
// command exists, so `ao config --show` must not render resolved values for a
|
||||
// subsystem a user cannot invoke. The Dream block was removed with the
|
||||
// overnight subsystem; unknown `dream:` keys in existing config files still
|
||||
// parse and are ignored.
|
||||
type ResolvedConfig struct {
|
||||
Output resolved `json:"output"`
|
||||
BaseDir resolved `json:"base_dir"`
|
||||
Verbose resolved `json:"verbose"`
|
||||
RPIWorktreeMode resolved `json:"-"`
|
||||
RPIRuntimeMode resolved `json:"-"`
|
||||
RPIRuntimeCommand resolved `json:"-"`
|
||||
RPIAOCommand resolved `json:"-"`
|
||||
RPIBDCommand resolved `json:"-"`
|
||||
RPITmuxCommand resolved `json:"-"`
|
||||
ModelsDefaultTier resolved `json:"models_default_tier"`
|
||||
DreamReportDir resolved `json:"-"`
|
||||
DreamRunTimeout resolved `json:"-"`
|
||||
DreamKeepAwake resolved `json:"-"`
|
||||
DreamRunners resolved `json:"-"`
|
||||
DreamScheduler resolved `json:"-"`
|
||||
DreamScheduleAt resolved `json:"-"`
|
||||
DreamConsensus resolved `json:"-"`
|
||||
DreamCreativeLane resolved `json:"-"`
|
||||
DreamCuratorEnabled resolved `json:"-"`
|
||||
DreamCuratorEngine resolved `json:"-"`
|
||||
DreamCuratorOllamaURL resolved `json:"-"`
|
||||
DreamCuratorModel resolved `json:"-"`
|
||||
DreamCuratorWorkerDir resolved `json:"-"`
|
||||
DreamCuratorVaultDir resolved `json:"-"`
|
||||
DreamCuratorJobKinds resolved `json:"-"`
|
||||
Output resolved `json:"output"`
|
||||
BaseDir resolved `json:"base_dir"`
|
||||
Verbose resolved `json:"verbose"`
|
||||
RPIWorktreeMode resolved `json:"-"`
|
||||
RPIRuntimeMode resolved `json:"-"`
|
||||
RPIRuntimeCommand resolved `json:"-"`
|
||||
RPIAOCommand resolved `json:"-"`
|
||||
RPIBDCommand resolved `json:"-"`
|
||||
RPITmuxCommand resolved `json:"-"`
|
||||
ModelsDefaultTier resolved `json:"models_default_tier"`
|
||||
}
|
||||
|
||||
type resolved struct {
|
||||
@@ -961,23 +791,6 @@ type configFields struct {
|
||||
rpiRuntimeCommand, rpiAOCommand string
|
||||
rpiBDCommand, rpiTmuxCommand string
|
||||
modelsDefaultTier string
|
||||
dreamReportDir, dreamRunTimeout string
|
||||
dreamKeepAwake bool
|
||||
dreamKeepAwakeSet bool
|
||||
dreamRunners []string
|
||||
dreamSchedulerMode string
|
||||
dreamScheduleAt string
|
||||
dreamConsensusPolicy string
|
||||
dreamCreativeLane bool
|
||||
dreamCreativeLaneSet bool
|
||||
dreamCuratorEnabled bool
|
||||
dreamCuratorEnabledSet bool
|
||||
dreamCuratorEngine string
|
||||
dreamCuratorOllamaURL string
|
||||
dreamCuratorModel string
|
||||
dreamCuratorWorkerDir string
|
||||
dreamCuratorVaultDir string
|
||||
dreamCuratorJobKinds []string
|
||||
}
|
||||
|
||||
// extractFields pulls resolution-relevant fields from a Config.
|
||||
@@ -987,34 +800,16 @@ func extractFields(cfg *Config) configFields {
|
||||
return configFields{}
|
||||
}
|
||||
return configFields{
|
||||
output: cfg.Output,
|
||||
baseDir: cfg.BaseDir,
|
||||
verbose: cfg.Verbose,
|
||||
rpiWorktreeMode: cfg.RPI.WorktreeMode,
|
||||
rpiRuntimeMode: cfg.RPI.RuntimeMode,
|
||||
rpiRuntimeCommand: cfg.RPI.RuntimeCommand,
|
||||
rpiAOCommand: cfg.RPI.AOCommand,
|
||||
rpiBDCommand: cfg.RPI.BDCommand,
|
||||
rpiTmuxCommand: cfg.RPI.TmuxCommand,
|
||||
modelsDefaultTier: cfg.Models.DefaultTier,
|
||||
dreamReportDir: cfg.Dream.ReportDir,
|
||||
dreamRunTimeout: cfg.Dream.RunTimeout,
|
||||
dreamKeepAwake: cfg.Dream.KeepAwake != nil && *cfg.Dream.KeepAwake,
|
||||
dreamKeepAwakeSet: cfg.Dream.KeepAwake != nil,
|
||||
dreamRunners: append([]string{}, cfg.Dream.Runners...),
|
||||
dreamSchedulerMode: cfg.Dream.SchedulerMode,
|
||||
dreamScheduleAt: cfg.Dream.ScheduleAt,
|
||||
dreamConsensusPolicy: cfg.Dream.ConsensusPolicy,
|
||||
dreamCreativeLane: cfg.Dream.CreativeLane != nil && *cfg.Dream.CreativeLane,
|
||||
dreamCreativeLaneSet: cfg.Dream.CreativeLane != nil,
|
||||
dreamCuratorEnabled: cfg.Dream.LocalCurator.Enabled != nil && *cfg.Dream.LocalCurator.Enabled,
|
||||
dreamCuratorEnabledSet: cfg.Dream.LocalCurator.Enabled != nil,
|
||||
dreamCuratorEngine: cfg.Dream.LocalCurator.Engine,
|
||||
dreamCuratorOllamaURL: cfg.Dream.LocalCurator.OllamaURL,
|
||||
dreamCuratorModel: cfg.Dream.LocalCurator.Model,
|
||||
dreamCuratorWorkerDir: cfg.Dream.LocalCurator.WorkerDir,
|
||||
dreamCuratorVaultDir: cfg.Dream.LocalCurator.VaultDir,
|
||||
dreamCuratorJobKinds: append([]string{}, cfg.Dream.LocalCurator.AllowedJobKinds...),
|
||||
output: cfg.Output,
|
||||
baseDir: cfg.BaseDir,
|
||||
verbose: cfg.Verbose,
|
||||
rpiWorktreeMode: cfg.RPI.WorktreeMode,
|
||||
rpiRuntimeMode: cfg.RPI.RuntimeMode,
|
||||
rpiRuntimeCommand: cfg.RPI.RuntimeCommand,
|
||||
rpiAOCommand: cfg.RPI.AOCommand,
|
||||
rpiBDCommand: cfg.RPI.BDCommand,
|
||||
rpiTmuxCommand: cfg.RPI.TmuxCommand,
|
||||
modelsDefaultTier: cfg.Models.DefaultTier,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1027,16 +822,6 @@ type envFields struct {
|
||||
rpiRuntimeCommand, rpiAOCommand string
|
||||
rpiBDCommand, rpiTmuxCommand string
|
||||
modelsDefaultTier string
|
||||
dreamReportDir, dreamRunTimeout string
|
||||
dreamKeepAwake bool
|
||||
dreamKeepAwakeSet bool
|
||||
dreamCuratorEnabled bool
|
||||
dreamCuratorEnabledSet bool
|
||||
dreamCuratorEngine string
|
||||
dreamCuratorOllamaURL string
|
||||
dreamCuratorModel string
|
||||
dreamCuratorWorkerDir string
|
||||
dreamCuratorVaultDir string
|
||||
}
|
||||
|
||||
// loadEnvFields reads all resolution-relevant environment variables.
|
||||
@@ -1055,15 +840,6 @@ func loadEnvFields() envFields {
|
||||
ef.rpiBDCommand, _ = getEnvString("AGENTOPS_RPI_BD_COMMAND")
|
||||
ef.rpiTmuxCommand, _ = getEnvString("AGENTOPS_RPI_TMUX_COMMAND")
|
||||
ef.modelsDefaultTier, _ = getEnvString("AGENTOPS_MODEL_TIER")
|
||||
ef.dreamReportDir, _ = getEnvString("AGENTOPS_DREAM_REPORT_DIR")
|
||||
ef.dreamRunTimeout, _ = getEnvString("AGENTOPS_DREAM_RUN_TIMEOUT")
|
||||
ef.dreamKeepAwake, ef.dreamKeepAwakeSet = getEnvBoolValue("AGENTOPS_DREAM_KEEP_AWAKE")
|
||||
ef.dreamCuratorEnabled, ef.dreamCuratorEnabledSet = getEnvBoolValue("AGENTOPS_DREAM_CURATOR_ENABLED")
|
||||
ef.dreamCuratorEngine, _ = getEnvString("AGENTOPS_DREAM_CURATOR_ENGINE")
|
||||
ef.dreamCuratorOllamaURL, _ = getEnvString("AGENTOPS_DREAM_CURATOR_OLLAMA_URL")
|
||||
ef.dreamCuratorModel, _ = getEnvString("AGENTOPS_DREAM_CURATOR_MODEL")
|
||||
ef.dreamCuratorWorkerDir, _ = getEnvString("AGENTOPS_DREAM_CURATOR_WORKER_DIR")
|
||||
ef.dreamCuratorVaultDir, _ = getEnvString("AGENTOPS_DREAM_CURATOR_VAULT_DIR")
|
||||
return ef
|
||||
}
|
||||
|
||||
@@ -1085,25 +861,6 @@ func resolveVerbose(home, project configFields, env envFields, flagVerbose bool)
|
||||
return r
|
||||
}
|
||||
|
||||
func resolveBoolField(
|
||||
homeVal bool, homeSet bool,
|
||||
projectVal bool, projectSet bool,
|
||||
envVal bool, envSet bool,
|
||||
def bool,
|
||||
) resolved {
|
||||
r := resolved{Value: def, Source: SourceDefault}
|
||||
if homeSet {
|
||||
r = resolved{Value: homeVal, Source: SourceHome}
|
||||
}
|
||||
if projectSet {
|
||||
r = resolved{Value: projectVal, Source: SourceProject}
|
||||
}
|
||||
if envSet {
|
||||
r = resolved{Value: envVal, Source: SourceEnv}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Resolve returns configuration with source tracking.
|
||||
// Uses precedence chain: flags > env > project > home > defaults.
|
||||
func Resolve(flagOutput, flagBaseDir string, flagVerbose bool) *ResolvedConfig {
|
||||
@@ -1139,36 +896,6 @@ func Resolve(flagOutput, flagBaseDir string, flagVerbose bool) *ResolvedConfig {
|
||||
RPIBDCommand: resolveStringField(home.rpiBDCommand, project.rpiBDCommand, env.rpiBDCommand, "", "bd"),
|
||||
RPITmuxCommand: resolveStringField(home.rpiTmuxCommand, project.rpiTmuxCommand, env.rpiTmuxCommand, "", "tmux"),
|
||||
ModelsDefaultTier: resolveStringField(home.modelsDefaultTier, project.modelsDefaultTier, env.modelsDefaultTier, "", "balanced"),
|
||||
DreamReportDir: resolveStringField(home.dreamReportDir, project.dreamReportDir, env.dreamReportDir, "", ".agents/overnight/latest"),
|
||||
DreamRunTimeout: resolveStringField(home.dreamRunTimeout, project.dreamRunTimeout, env.dreamRunTimeout, "", "8h"),
|
||||
DreamKeepAwake: resolveBoolField(
|
||||
home.dreamKeepAwake, home.dreamKeepAwakeSet,
|
||||
project.dreamKeepAwake, project.dreamKeepAwakeSet,
|
||||
env.dreamKeepAwake, env.dreamKeepAwakeSet,
|
||||
true,
|
||||
),
|
||||
DreamRunners: resolveStringSliceField(home.dreamRunners, project.dreamRunners, []string{}),
|
||||
DreamScheduler: resolveStringField(home.dreamSchedulerMode, project.dreamSchedulerMode, "", "", "manual"),
|
||||
DreamScheduleAt: resolveStringField(home.dreamScheduleAt, project.dreamScheduleAt, "", "", ""),
|
||||
DreamConsensus: resolveStringField(home.dreamConsensusPolicy, project.dreamConsensusPolicy, "", "", "majority"),
|
||||
DreamCreativeLane: resolveBoolField(
|
||||
home.dreamCreativeLane, home.dreamCreativeLaneSet,
|
||||
project.dreamCreativeLane, project.dreamCreativeLaneSet,
|
||||
false, false,
|
||||
false,
|
||||
),
|
||||
DreamCuratorEnabled: resolveBoolField(
|
||||
home.dreamCuratorEnabled, home.dreamCuratorEnabledSet,
|
||||
project.dreamCuratorEnabled, project.dreamCuratorEnabledSet,
|
||||
env.dreamCuratorEnabled, env.dreamCuratorEnabledSet,
|
||||
false,
|
||||
),
|
||||
DreamCuratorEngine: resolveStringField(home.dreamCuratorEngine, project.dreamCuratorEngine, env.dreamCuratorEngine, "", ""),
|
||||
DreamCuratorOllamaURL: resolveStringField(home.dreamCuratorOllamaURL, project.dreamCuratorOllamaURL, env.dreamCuratorOllamaURL, "", ""),
|
||||
DreamCuratorModel: resolveStringField(home.dreamCuratorModel, project.dreamCuratorModel, env.dreamCuratorModel, "", ""),
|
||||
DreamCuratorWorkerDir: resolveStringField(home.dreamCuratorWorkerDir, project.dreamCuratorWorkerDir, env.dreamCuratorWorkerDir, "", ""),
|
||||
DreamCuratorVaultDir: resolveStringField(home.dreamCuratorVaultDir, project.dreamCuratorVaultDir, env.dreamCuratorVaultDir, "", ""),
|
||||
DreamCuratorJobKinds: resolveStringSliceField(home.dreamCuratorJobKinds, project.dreamCuratorJobKinds, []string{}),
|
||||
}
|
||||
// When a layer was read through the deprecated fallback path, attribute
|
||||
// its values to the path actually read so --show agrees with the
|
||||
@@ -1191,12 +918,6 @@ func (rc *ResolvedConfig) relabelSource(from, to Source) {
|
||||
&rc.RPIWorktreeMode, &rc.RPIRuntimeMode, &rc.RPIRuntimeCommand,
|
||||
&rc.RPIAOCommand, &rc.RPIBDCommand, &rc.RPITmuxCommand,
|
||||
&rc.ModelsDefaultTier,
|
||||
&rc.DreamReportDir, &rc.DreamRunTimeout, &rc.DreamKeepAwake,
|
||||
&rc.DreamRunners, &rc.DreamScheduler, &rc.DreamScheduleAt,
|
||||
&rc.DreamConsensus, &rc.DreamCreativeLane,
|
||||
&rc.DreamCuratorEnabled, &rc.DreamCuratorEngine, &rc.DreamCuratorOllamaURL,
|
||||
&rc.DreamCuratorModel, &rc.DreamCuratorWorkerDir, &rc.DreamCuratorVaultDir,
|
||||
&rc.DreamCuratorJobKinds,
|
||||
}
|
||||
for _, field := range fields {
|
||||
if field.Source == from {
|
||||
|
||||
@@ -2032,167 +2032,29 @@ func TestResolve_ModelsDefaultTier_EnvOverride(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_DreamDefaults(t *testing.T) {
|
||||
t.Setenv("AGENTOPS_CONFIG", "")
|
||||
for _, key := range []string{
|
||||
"AGENTOPS_OUTPUT", "AGENTOPS_BASE_DIR", "AGENTOPS_VERBOSE",
|
||||
"AGENTOPS_NO_SC",
|
||||
"AGENTOPS_RPI_WORKTREE_MODE", "AGENTOPS_RPI_RUNTIME",
|
||||
"AGENTOPS_RPI_RUNTIME_MODE", "AGENTOPS_RPI_RUNTIME_COMMAND",
|
||||
"AGENTOPS_RPI_AO_COMMAND", "AGENTOPS_RPI_BD_COMMAND",
|
||||
"AGENTOPS_RPI_TMUX_COMMAND",
|
||||
"AGENTOPS_FLYWHEEL_AUTO_PROMOTE_THRESHOLD",
|
||||
"AGENTOPS_MODEL_TIER", "AGENTOPS_COUNCIL_MODEL_TIER",
|
||||
"AGENTOPS_DREAM_REPORT_DIR", "AGENTOPS_DREAM_RUN_TIMEOUT", "AGENTOPS_DREAM_KEEP_AWAKE",
|
||||
} {
|
||||
t.Setenv(key, "")
|
||||
func TestLoadFromPath_ToleratesRetiredDreamKey(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
// The dream config block was retired with the overnight subsystem.
|
||||
// Existing user configs that still carry the key must load.
|
||||
content := `
|
||||
verbose: true
|
||||
dream:
|
||||
report_dir: .agents/overnight/latest
|
||||
local_curator:
|
||||
enabled: true
|
||||
`
|
||||
if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rc := Resolve("", "", false)
|
||||
if rc.DreamReportDir.Value != ".agents/overnight/latest" || rc.DreamReportDir.Source != SourceDefault {
|
||||
t.Fatalf("DreamReportDir = (%v, %v), want (.agents/overnight/latest, %v)", rc.DreamReportDir.Value, rc.DreamReportDir.Source, SourceDefault)
|
||||
cfg, err := loadFromPath(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("loadFromPath() with retired dream key errored: %v", err)
|
||||
}
|
||||
if rc.DreamRunTimeout.Value != "8h" || rc.DreamRunTimeout.Source != SourceDefault {
|
||||
t.Fatalf("DreamRunTimeout = (%v, %v), want (8h, %v)", rc.DreamRunTimeout.Value, rc.DreamRunTimeout.Source, SourceDefault)
|
||||
}
|
||||
if rc.DreamKeepAwake.Value != true || rc.DreamKeepAwake.Source != SourceDefault {
|
||||
t.Fatalf("DreamKeepAwake = (%v, %v), want (true, %v)", rc.DreamKeepAwake.Value, rc.DreamKeepAwake.Source, SourceDefault)
|
||||
}
|
||||
if rc.DreamScheduler.Value != "manual" || rc.DreamScheduler.Source != SourceDefault {
|
||||
t.Fatalf("DreamScheduler = (%v, %v), want (manual, %v)", rc.DreamScheduler.Value, rc.DreamScheduler.Source, SourceDefault)
|
||||
}
|
||||
if rc.DreamConsensus.Value != "majority" || rc.DreamConsensus.Source != SourceDefault {
|
||||
t.Fatalf("DreamConsensus = (%v, %v), want (majority, %v)", rc.DreamConsensus.Value, rc.DreamConsensus.Source, SourceDefault)
|
||||
}
|
||||
if rc.DreamCreativeLane.Value != false || rc.DreamCreativeLane.Source != SourceDefault {
|
||||
t.Fatalf("DreamCreativeLane = (%v, %v), want (false, %v)", rc.DreamCreativeLane.Value, rc.DreamCreativeLane.Source, SourceDefault)
|
||||
}
|
||||
if got, ok := rc.DreamRunners.Value.([]string); !ok || len(got) != 0 || rc.DreamRunners.Source != SourceDefault {
|
||||
t.Fatalf("DreamRunners = (%#v, %v), want (empty, %v)", rc.DreamRunners.Value, rc.DreamRunners.Source, SourceDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyEnv_DreamOverrides(t *testing.T) {
|
||||
cfg := Default()
|
||||
t.Setenv("AGENTOPS_DREAM_REPORT_DIR", "/tmp/dream")
|
||||
t.Setenv("AGENTOPS_DREAM_RUN_TIMEOUT", "10h")
|
||||
t.Setenv("AGENTOPS_DREAM_KEEP_AWAKE", "false")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_ENABLED", "true")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_ENGINE", "ollama")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_OLLAMA_URL", "http://127.0.0.1:11435")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_MODEL", "gemma4:e4b")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_WORKER_DIR", "D:\\dream")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_VAULT_DIR", "D:\\vault")
|
||||
|
||||
got := applyEnv(cfg)
|
||||
if got.Dream.ReportDir != "/tmp/dream" {
|
||||
t.Fatalf("Dream.ReportDir = %q, want /tmp/dream", got.Dream.ReportDir)
|
||||
}
|
||||
if got.Dream.RunTimeout != "10h" {
|
||||
t.Fatalf("Dream.RunTimeout = %q, want 10h", got.Dream.RunTimeout)
|
||||
}
|
||||
if got.Dream.KeepAwake == nil || *got.Dream.KeepAwake != false {
|
||||
t.Fatalf("Dream.KeepAwake = %#v, want false", got.Dream.KeepAwake)
|
||||
}
|
||||
if got.Dream.LocalCurator.Enabled == nil || *got.Dream.LocalCurator.Enabled != true {
|
||||
t.Fatalf("Dream.LocalCurator.Enabled = %#v, want true", got.Dream.LocalCurator.Enabled)
|
||||
}
|
||||
if got.Dream.LocalCurator.Engine != "ollama" {
|
||||
t.Fatalf("Dream.LocalCurator.Engine = %q, want ollama", got.Dream.LocalCurator.Engine)
|
||||
}
|
||||
if got.Dream.LocalCurator.OllamaURL != "http://127.0.0.1:11435" {
|
||||
t.Fatalf("Dream.LocalCurator.OllamaURL = %q, want http://127.0.0.1:11435", got.Dream.LocalCurator.OllamaURL)
|
||||
}
|
||||
if got.Dream.LocalCurator.Model != "gemma4:e4b" {
|
||||
t.Fatalf("Dream.LocalCurator.Model = %q, want gemma4:e4b", got.Dream.LocalCurator.Model)
|
||||
}
|
||||
if got.Dream.LocalCurator.WorkerDir != "D:\\dream" {
|
||||
t.Fatalf("Dream.LocalCurator.WorkerDir = %q, want D:\\dream", got.Dream.LocalCurator.WorkerDir)
|
||||
}
|
||||
if got.Dream.LocalCurator.VaultDir != "D:\\vault" {
|
||||
t.Fatalf("Dream.LocalCurator.VaultDir = %q, want D:\\vault", got.Dream.LocalCurator.VaultDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_DreamLocalCuratorEnv(t *testing.T) {
|
||||
t.Setenv("AGENTOPS_CONFIG", "")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_ENABLED", "true")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_ENGINE", "ollama")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_OLLAMA_URL", "http://127.0.0.1:11435")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_MODEL", "gemma4:e4b")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_WORKER_DIR", "D:\\dream")
|
||||
t.Setenv("AGENTOPS_DREAM_CURATOR_VAULT_DIR", "D:\\vault")
|
||||
|
||||
rc := Resolve("", "", false)
|
||||
if rc.DreamCuratorEnabled.Value != true || rc.DreamCuratorEnabled.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorEnabled = (%v, %v), want (true, %v)", rc.DreamCuratorEnabled.Value, rc.DreamCuratorEnabled.Source, SourceEnv)
|
||||
}
|
||||
if rc.DreamCuratorEngine.Value != "ollama" || rc.DreamCuratorEngine.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorEngine = (%v, %v), want (ollama, %v)", rc.DreamCuratorEngine.Value, rc.DreamCuratorEngine.Source, SourceEnv)
|
||||
}
|
||||
if rc.DreamCuratorOllamaURL.Value != "http://127.0.0.1:11435" || rc.DreamCuratorOllamaURL.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorOllamaURL = (%v, %v), want env URL", rc.DreamCuratorOllamaURL.Value, rc.DreamCuratorOllamaURL.Source)
|
||||
}
|
||||
if rc.DreamCuratorModel.Value != "gemma4:e4b" || rc.DreamCuratorModel.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorModel = (%v, %v), want env model", rc.DreamCuratorModel.Value, rc.DreamCuratorModel.Source)
|
||||
}
|
||||
if rc.DreamCuratorWorkerDir.Value != "D:\\dream" || rc.DreamCuratorWorkerDir.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorWorkerDir = (%v, %v), want env worker", rc.DreamCuratorWorkerDir.Value, rc.DreamCuratorWorkerDir.Source)
|
||||
}
|
||||
if rc.DreamCuratorVaultDir.Value != "D:\\vault" || rc.DreamCuratorVaultDir.Source != SourceEnv {
|
||||
t.Fatalf("DreamCuratorVaultDir = (%v, %v), want env vault", rc.DreamCuratorVaultDir.Value, rc.DreamCuratorVaultDir.Source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDream_ExtendedFields(t *testing.T) {
|
||||
dst := Default()
|
||||
src := &Config{
|
||||
Dream: DreamConfig{
|
||||
Runners: []string{"codex", "claude"},
|
||||
SchedulerMode: "launchd",
|
||||
ScheduleAt: "01:30",
|
||||
ConsensusPolicy: "majority",
|
||||
CreativeLane: boolPtr(true),
|
||||
LocalCurator: DreamLocalCuratorConfig{
|
||||
Enabled: boolPtr(true),
|
||||
Engine: "ollama",
|
||||
OllamaURL: "http://127.0.0.1:11435",
|
||||
Model: "gemma4:e4b",
|
||||
WorkerDir: "D:\\dream",
|
||||
VaultDir: "D:\\vault",
|
||||
HourlyCap: 20,
|
||||
AllowedJobKinds: []string{"ingest-claude-session", "lint-wiki", "dream-seed"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got := merge(dst, src)
|
||||
if want := []string{"codex", "claude"}; len(got.Dream.Runners) != len(want) {
|
||||
t.Fatalf("Dream.Runners = %#v, want %#v", got.Dream.Runners, want)
|
||||
}
|
||||
if got.Dream.SchedulerMode != "launchd" {
|
||||
t.Fatalf("Dream.SchedulerMode = %q, want launchd", got.Dream.SchedulerMode)
|
||||
}
|
||||
if got.Dream.ScheduleAt != "01:30" {
|
||||
t.Fatalf("Dream.ScheduleAt = %q, want 01:30", got.Dream.ScheduleAt)
|
||||
}
|
||||
if got.Dream.ConsensusPolicy != "majority" {
|
||||
t.Fatalf("Dream.ConsensusPolicy = %q, want majority", got.Dream.ConsensusPolicy)
|
||||
}
|
||||
if got.Dream.CreativeLane == nil || !*got.Dream.CreativeLane {
|
||||
t.Fatalf("Dream.CreativeLane = %#v, want true", got.Dream.CreativeLane)
|
||||
}
|
||||
if got.Dream.LocalCurator.Enabled == nil || !*got.Dream.LocalCurator.Enabled {
|
||||
t.Fatalf("Dream.LocalCurator.Enabled = %#v, want true", got.Dream.LocalCurator.Enabled)
|
||||
}
|
||||
if got.Dream.LocalCurator.Model != "gemma4:e4b" {
|
||||
t.Fatalf("Dream.LocalCurator.Model = %q, want gemma4:e4b", got.Dream.LocalCurator.Model)
|
||||
}
|
||||
if got.Dream.LocalCurator.HourlyCap != 20 {
|
||||
t.Fatalf("Dream.LocalCurator.HourlyCap = %d, want 20", got.Dream.LocalCurator.HourlyCap)
|
||||
}
|
||||
if got := strings.Join(got.Dream.LocalCurator.AllowedJobKinds, ","); got != "ingest-claude-session,lint-wiki,dream-seed" {
|
||||
t.Fatalf("Dream.LocalCurator.AllowedJobKinds = %q", got)
|
||||
if !cfg.Verbose {
|
||||
t.Fatal("loadFromPath Verbose = false, want true (sibling keys must still parse)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -512,8 +512,8 @@ func TestTrackChild_ConcurrentAccess(t *testing.T) {
|
||||
func TestMeasureOne_Skip_ExitCode77(t *testing.T) {
|
||||
// When a gate exits 77 (autotools skip convention), the goals runner
|
||||
// must classify the measurement as `skip`, not `fail`. This is the
|
||||
// quarantine-by-precondition path used by check-flywheel-compounding.sh
|
||||
// when the corpus is dormant — failing under "no signal" is a
|
||||
// quarantine-by-precondition path skip-aware gate scripts use when a
|
||||
// precondition is absent — failing under "no signal" is a
|
||||
// misclassification that artificially drags fitness scores.
|
||||
g := Goal{ID: "skip77", Check: "exit 77", Weight: 3}
|
||||
m := MeasureOne(g, time.Second)
|
||||
|
||||
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Removed
|
||||
|
||||
- The consumer-free `dream:` config block (the retired overnight subsystem's
|
||||
settings; existing config files still parse and the key is ignored), the
|
||||
vacuous retrieval-quality canary and its smoke (every Go test it named was
|
||||
already deleted; the retrieval-comparison contract is now explicitly
|
||||
dormant and the blocking manifest-paths gate remains the live retrieval
|
||||
guard), and the dormant `check-pillar-coverage.sh` GOALS.yaml script.
|
||||
Remaining scratch-tier writers (`mine-all-sessions.sh`, `team-runner.sh`,
|
||||
`bin/ralph`) now write under `.agents/scratch/`.
|
||||
|
||||
- The `ao flywheel` command family (`status`, `compare`) and the whole live
|
||||
knowledge-flywheel product surface: `cli/internal/flywheelapp`, the
|
||||
flywheel-only metric helpers and types, the `flywheel` build-profile bit,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# AgentOps Eval Architecture — v1
|
||||
|
||||
> **Status:** PROPOSED (Bo to ratify) · **Date:** 2026-08-04
|
||||
> **Terminology note (2026-08-07):** this dated proposal predates the
|
||||
> operations-layer alignment and keeps its original wording ("the operating
|
||||
> loop", "~150 skills"). The current product category and vocabulary live in
|
||||
> [`../contracts/ubiquitous-language.md`](../contracts/ubiquitous-language.md);
|
||||
> the catalog is 51 canonical skills. Read the decisions here against that
|
||||
> current frame.
|
||||
> **Inputs:** [skill-eval SOTA research](../research/skill-eval-sota-standards-2026-08.md) ·
|
||||
> [effectiveness evidence audit](../evals/agentops-effectiveness-evidence.md) · the in-tree eval
|
||||
> substrate (`cli/internal/eval/`, `cli/internal/evalsubstrate/`, `evals/`)
|
||||
|
||||
@@ -85,7 +85,13 @@ documents:
|
||||
|
||||
# MEMORY.md is a temporary machine-consumed projection, not an AgentOps
|
||||
# authority: its claims belong to their cited sources (docs, ADRs, session
|
||||
# records). Retire it only after its current consumers are audited.
|
||||
# records). Consumer audit (2026-08-07): two live consumers exist — the
|
||||
# corpus-delta eval harness (scripts/corpus-delta-harness.sh reads it as a
|
||||
# context surface in both A/B arms) and the coding-agent convention of
|
||||
# loading a root memory file. The former ao session memory writer is
|
||||
# removed, so the file is read-only surface. Retained; retire when the
|
||||
# harness stops reading it and the convention is served by a caller-owned
|
||||
# memory system.
|
||||
- path: MEMORY.md
|
||||
tracked: true
|
||||
class: machine-consumed-memory-projection
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Optional Lesson Format
|
||||
|
||||
Learn may analyze collections of durable verdicts after the operating loop has
|
||||
Learn may analyze collections of durable verdicts after an RPI traversal has
|
||||
finished. When a caller chooses to preserve a recurring pattern as a lesson, use
|
||||
one Markdown file under a caller-selected directory such as
|
||||
`.agents/learnings/`. Lessons are advisory evidence; they never change RPI
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
# Retrieval Comparison Contract
|
||||
# Retrieval Comparison Contract (dormant)
|
||||
|
||||
The former retrieval-bench CLI was retired from the production command spine.
|
||||
The deterministic decision gate for changing retrieval behavior is the
|
||||
repository-owned smoke below. It exercises the retained Go implementation
|
||||
directly, including named backend comparison over the same manifest, search
|
||||
root, and `k`.
|
||||
|
||||
## Command Surface
|
||||
|
||||
The canonical comparison smoke is:
|
||||
|
||||
```bash
|
||||
bash evals/agentops-core/fixtures/retrieval-quality-smoke.sh
|
||||
```
|
||||
|
||||
The smoke runs focused Go tests covering:
|
||||
|
||||
- train/holdout Precision@K and MRR
|
||||
- representative live-corpus coverage and stable top results
|
||||
- multi-backend comparison report shape
|
||||
- offline `agentic-rg`, `wiki-link-expand`, and `rerank-llamacpp` fallback behavior
|
||||
- retrieval quality thresholds
|
||||
|
||||
The smoke must run offline. The `rerank-llamacpp` case leaves
|
||||
`AGENTOPS_RETRIEVAL_RERANK_ENDPOINT` unset, proving the file-backed fallback
|
||||
rather than contacting a live model.
|
||||
> **Status (2026-08-07): dormant.** The retrieval-bench surface is fully
|
||||
> retired: the `ao eval bench` seat is deliberately unwired, the Go tests the
|
||||
> former comparison smoke named were deleted with their implementation, and
|
||||
> the smoke itself was removed once it could no longer execute anything
|
||||
> (a canary that runs zero tests is theater, not proof). The one live
|
||||
> retrieval guard is the blocking `always.retrieval-manifest-paths` gate,
|
||||
> which keeps the checked-in eval manifest's ground-truth paths resolvable.
|
||||
> The sections below are retained as the binding policy for any future
|
||||
> retrieval-backend revival; they govern nothing until such an
|
||||
> implementation exists and brings its own executable comparison smoke.
|
||||
|
||||
## Report Shape
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Dated plans, audits, releases, and archive material are historical evidence, not
|
||||
- [Producer-Defect Recurrence Contract](contracts/producer-defect-register.md)
|
||||
- [Generated skill projections](contracts/registry-as-derived.md)
|
||||
- [Release Notes Contract](contracts/release-notes.md)
|
||||
- [Retrieval Comparison Contract](contracts/retrieval-comparison.md)
|
||||
- [Retrieval Comparison Contract (dormant)](contracts/retrieval-comparison.md)
|
||||
- [Runtime Neutrality Contract](contracts/runtime-neutrality.md)
|
||||
- [Scenario → Test Linkage Contract](contracts/scenario-test-linkage.md)
|
||||
- [Scope Escape Report Template](contracts/scope-escape-report.md)
|
||||
|
||||
@@ -15,7 +15,7 @@ trap cleanup EXIT
|
||||
|
||||
top_count="$(rg -c '^### `ao ' "$DOCS_PATH")"
|
||||
sub_count="$(rg -c '^#### `ao ' "$DOCS_PATH")"
|
||||
all_count="$(rg -c '^#{3,4} `ao ' "$DOCS_PATH")"
|
||||
all_count="$(rg -c '^#{3,5} `ao ' "$DOCS_PATH")"
|
||||
|
||||
if [[ "$top_count" != "20" || "$sub_count" != "56" || "$all_count" != "89" ]]; then
|
||||
printf 'unexpected command heading counts: top=%s sub=%s all=%s\n' "$top_count" "$sub_count" "$all_count" >&2
|
||||
@@ -23,7 +23,7 @@ if [[ "$top_count" != "20" || "$sub_count" != "56" || "$all_count" != "89" ]]; t
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016 # literal backticks delimit generated Markdown command headings.
|
||||
mapfile -t commands < <(rg '^#{3,4} `ao ' "$DOCS_PATH" | sed -E 's/^.*`([^`]+)`.*/\1/')
|
||||
mapfile -t commands < <(rg '^#{3,5} `ao ' "$DOCS_PATH" | sed -E 's/^.*`([^`]+)`.*/\1/')
|
||||
|
||||
if [[ "${#commands[@]}" -ne 89 ]]; then
|
||||
printf 'unexpected command matrix size: %s\n' "${#commands[@]}" >&2
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
|
||||
(
|
||||
cd "$ROOT/cli"
|
||||
env -u AGENTOPS_RPI_RUNTIME go test -count=1 -timeout=120s ./cmd/ao -run '^(TestRetrievalBench_BuildReportTrainHoldoutAndSections|TestRetrievalBench_RealCorpusManifestHasTrainHoldoutCoverage|TestRetrievalBench_PrecisionAtK|TestBuildLiveReport_FixtureCoverage|TestBuildSearchEvalComparisonReport_JSONIncludesBackendMetrics|TestSearchEvalBackendAgenticRGPathTokenSearch|TestSearchEvalBackendWikiLinkExpand|TestSearchEvalBackendRerankLlamaCPPSkipsWhenEndpointUnset)$'
|
||||
)
|
||||
|
||||
echo "retrieval quality smoke passed"
|
||||
@@ -1,143 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"id": "agentops-core.retrieval-quality-gates",
|
||||
"name": "AgentOps Retrieval Quality Gates Canary",
|
||||
"description": "Offline public canary for real-corpus retrieval quality, search-backend comparison, and deterministic fixture coverage.",
|
||||
"practices": [
|
||||
"llm-eval-harness",
|
||||
"wiki-knowledge-surface"
|
||||
],
|
||||
"domain": "retrieval",
|
||||
"visibility": "public_canary",
|
||||
"tier": "deterministic",
|
||||
"owners": [
|
||||
"agentops"
|
||||
],
|
||||
"tags": [
|
||||
"canary",
|
||||
"offline",
|
||||
"retrieval",
|
||||
"knowledge",
|
||||
"dream"
|
||||
],
|
||||
"allowed_runtimes": [
|
||||
"shell",
|
||||
"static"
|
||||
],
|
||||
"environment": {
|
||||
"offline_required": true,
|
||||
"network": "forbidden",
|
||||
"scrub_env_prefixes": [
|
||||
"AGENTOPS_RPI_RUNTIME",
|
||||
"ANTHROPIC_",
|
||||
"OPENAI_"
|
||||
],
|
||||
"timeout_seconds": 120
|
||||
},
|
||||
"fixtures": [
|
||||
{
|
||||
"path": "fixtures/retrieval-quality-smoke.sh",
|
||||
"purpose": "Behavioral retrieval smoke covering quality thresholds, live-corpus ranking, and offline backend comparison.",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"scoring": {
|
||||
"aggregate_threshold": 1,
|
||||
"dimensions": [
|
||||
{
|
||||
"name": "correctness",
|
||||
"weight": 2,
|
||||
"threshold": 1,
|
||||
"critical": true
|
||||
},
|
||||
{
|
||||
"name": "learning_closure",
|
||||
"weight": 2,
|
||||
"threshold": 1,
|
||||
"critical": true
|
||||
},
|
||||
{
|
||||
"name": "process_adherence",
|
||||
"weight": 1,
|
||||
"threshold": 1
|
||||
},
|
||||
{
|
||||
"name": "artifact_quality",
|
||||
"weight": 1,
|
||||
"threshold": 1
|
||||
},
|
||||
{
|
||||
"name": "runtime_compatibility",
|
||||
"weight": 1,
|
||||
"threshold": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"baseline_policy": {
|
||||
"mode": "compare",
|
||||
"baseline_path": ".agents/evals/baselines/agentops-core.retrieval-quality-gates.baseline.json",
|
||||
"blocking_gate": "none"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"id": "retrieval-quality-smoke",
|
||||
"title": "real-corpus retrieval smoke passes",
|
||||
"kind": "command",
|
||||
"objective": "Keep train/holdout retrieval quality, live-corpus ranking, and offline backend comparison passing.",
|
||||
"runtime": "shell",
|
||||
"timeout_seconds": 120,
|
||||
"inputs": {
|
||||
"cwd": "../..",
|
||||
"shell": "bash evals/agentops-core/fixtures/retrieval-quality-smoke.sh"
|
||||
},
|
||||
"expectations": [
|
||||
{
|
||||
"type": "exit_code",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"type": "stdout_contains",
|
||||
"value": "retrieval quality smoke passed"
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
"correctness",
|
||||
"learning_closure",
|
||||
"runtime_compatibility"
|
||||
],
|
||||
"critical": true
|
||||
},
|
||||
{
|
||||
"id": "retrieval-real-corpus-fixtures",
|
||||
"title": "real-corpus retrieval fixtures preserve holdout labels",
|
||||
"kind": "artifact_check",
|
||||
"objective": "Keep the live retrieval fixture corpus large enough and explicitly labeled for holdout quality checks.",
|
||||
"expectations": [
|
||||
{
|
||||
"type": "artifact_contains",
|
||||
"target": "../../cli/cmd/ao/testdata/retrieval-bench-live/benchmark.json",
|
||||
"value": "\"split\": \"holdout\""
|
||||
},
|
||||
{
|
||||
"type": "artifact_contains",
|
||||
"target": "../../cli/cmd/ao/testdata/retrieval-bench-live/benchmark.json",
|
||||
"value": "\"best_id\""
|
||||
},
|
||||
{
|
||||
"type": "artifact_contains",
|
||||
"target": "../../cli/cmd/ao/testdata/retrieval-bench-live/security-toolchain-gate.md",
|
||||
"value": "Security Toolchain Gate"
|
||||
},
|
||||
{
|
||||
"type": "artifact_contains",
|
||||
"target": "../../cli/cmd/ao/testdata/retrieval-bench-live/architecture-surface-contract.md",
|
||||
"value": "Architecture Surface Contract"
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
"artifact_quality",
|
||||
"learning_closure"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -122,7 +122,7 @@ parse_spec() {
|
||||
AGENT_COUNT=$MAX_AGENTS
|
||||
fi
|
||||
|
||||
TEAM_DIR=".agents/teams/${TEAM_ID}"
|
||||
TEAM_DIR=".agents/scratch/teams/${TEAM_ID}"
|
||||
mkdir -p "$TEAM_DIR"
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ scripts/check-orchestration-contracts.sh
|
||||
scripts/check-outcomes-holdout-leak.sh
|
||||
scripts/check-paths-resolver-coverage.sh
|
||||
scripts/check-pawl-pre-push.sh
|
||||
scripts/check-pillar-coverage.sh
|
||||
scripts/check-product-freshness.sh
|
||||
scripts/check-proof-coherence.sh
|
||||
scripts/check-provenance-chain.sh
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-pillar-coverage.sh — Verify GOALS.yaml has goals for all 4 pillars.
|
||||
#
|
||||
# Post-GOALS.md migration (see CLAUDE.md §Agent Goals): GOALS.yaml is the
|
||||
# legacy YAML format. When it is absent but GOALS.md is present, skip with
|
||||
# exit 0 because the markdown narrative does not carry machine-readable
|
||||
# per-goal `pillar` fields; no downstream gate depends on this script today.
|
||||
set -uo pipefail
|
||||
|
||||
GOALS_FILE="${1:-GOALS.yaml}"
|
||||
|
||||
if [ ! -f "$GOALS_FILE" ]; then
|
||||
if [ -f "GOALS.md" ]; then
|
||||
echo "SKIP: GOALS.yaml not found; repo uses GOALS.md (post-migration). Pillar coverage is documented in the narrative, not machine-checkable here." >&2
|
||||
exit 0
|
||||
fi
|
||||
echo "ERROR: GOALS.yaml not found at $GOALS_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pillars=$(yq '.goals[].pillar' "$GOALS_FILE" | sort -u)
|
||||
|
||||
for p in knowledge-compounding validated-acceleration goal-driven-automation zero-friction-workflow; do
|
||||
if ! echo "$pillars" | grep -q "$p"; then
|
||||
echo "MISSING pillar: $p" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -27,7 +27,7 @@ set -uo pipefail
|
||||
|
||||
AO="${AGENTOPS_AO_BIN:-ao}"
|
||||
SESSIONS_DIR="${1:-$HOME/.claude/projects/-Users-bo-dev-agentops}"
|
||||
STORE_DIR=".agents/provenance"
|
||||
STORE_DIR=".agents/scratch/provenance-mine"
|
||||
STORE="$STORE_DIR/mine-events.jsonl"
|
||||
STATE_DIR="$STORE_DIR/state"
|
||||
SUMMARY="$STORE_DIR/mine-summary.json"
|
||||
|
||||
@@ -12,5 +12,4 @@ evals/agentops-core/distribution-install-update.json
|
||||
evals/agentops-core/eval-runner-engine-control-plane.json
|
||||
evals/agentops-core/rpi-behavior.json
|
||||
evals/agentops-core/release-security-gates.json
|
||||
evals/agentops-core/retrieval-quality-gates.json
|
||||
evals/agentops-core/runtime-smoke-matrix.json
|
||||
|
||||
@@ -48,9 +48,9 @@ JSONL
|
||||
run env AGENTOPS_AO_BIN="$AO_BIN" bash "$SCRIPT" "$SESS"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"mine-all"* ]]
|
||||
# The store exists, is gitignored-by-convention (.agents/provenance/), and holds
|
||||
# The store exists, is gitignored-by-convention (.agents/scratch/provenance-mine/), and holds
|
||||
# the mined per-inference events.
|
||||
store="$WORK/.agents/provenance/mine-events.jsonl"
|
||||
store="$WORK/.agents/scratch/provenance-mine/mine-events.jsonl"
|
||||
[ -f "$store" ]
|
||||
# Two tool_use blocks -> two tool_call events; the tool_result is filtered out.
|
||||
n="$(grep -c '"kind":"tool_call"' "$store")"
|
||||
@@ -62,7 +62,7 @@ JSONL
|
||||
@test "mine-all: re-run is incremental (idempotent) — already-mined events are not re-appended" {
|
||||
cd "$WORK"
|
||||
env AGENTOPS_AO_BIN="$AO_BIN" bash "$SCRIPT" "$SESS" >/dev/null
|
||||
store="$WORK/.agents/provenance/mine-events.jsonl"
|
||||
store="$WORK/.agents/scratch/provenance-mine/mine-events.jsonl"
|
||||
before="$(grep -c '"schema_version"' "$store")"
|
||||
# Second run over the SAME unchanged session: --state has advanced, so 0 new.
|
||||
run env AGENTOPS_AO_BIN="$AO_BIN" bash "$SCRIPT" "$SESS"
|
||||
@@ -75,7 +75,7 @@ JSONL
|
||||
@test "mine-all: writes a valid summary artifact with reconciled counts" {
|
||||
cd "$WORK"
|
||||
env AGENTOPS_AO_BIN="$AO_BIN" bash "$SCRIPT" "$SESS" >/dev/null
|
||||
summary="$WORK/.agents/provenance/mine-summary.json"
|
||||
summary="$WORK/.agents/scratch/provenance-mine/mine-summary.json"
|
||||
[ -f "$summary" ]
|
||||
run jq -e '.sessions_mined==1 and .new_events==2 and .store_total==2 and .failed==0' "$summary"
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -83,8 +83,8 @@ JSONL
|
||||
|
||||
@test "mine-all: a held lock makes a concurrent miner exit 2 (single writer)" {
|
||||
cd "$WORK"
|
||||
mkdir -p .agents/provenance
|
||||
mkdir .agents/provenance/.mine.lock # simulate another miner holding the lock
|
||||
mkdir -p .agents/scratch/provenance-mine
|
||||
mkdir .agents/scratch/provenance-mine/.mine.lock # simulate another miner holding the lock
|
||||
run env AGENTOPS_AO_BIN="$AO_BIN" bash "$SCRIPT" "$SESS"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"another miner holds the lock"* ]]
|
||||
@@ -116,7 +116,7 @@ STUB
|
||||
[ "$status" -eq 1 ] # exit reflects the failure (defect 1)
|
||||
[[ "$output" == *"(1 failed)"* ]]
|
||||
# The advanced state was rolled back (removed) — not left to skip never-stored events.
|
||||
[ ! -f "$WORK/.agents/provenance/state/sess-a.json" ]
|
||||
[ ! -f "$WORK/.agents/scratch/provenance-mine/state/sess-a.json" ]
|
||||
# Nothing was appended to the store.
|
||||
[ ! -s "$WORK/.agents/provenance/mine-events.jsonl" ] || ! grep -q . "$WORK/.agents/provenance/mine-events.jsonl"
|
||||
[ ! -s "$WORK/.agents/scratch/provenance-mine/mine-events.jsonl" ] || ! grep -q . "$WORK/.agents/scratch/provenance-mine/mine-events.jsonl"
|
||||
}
|
||||
|
||||
@@ -68,3 +68,42 @@ CKPT
|
||||
[[ "$output" != *"Budget:"* ]]
|
||||
[ ! -e "$TMP/does-not-exist" ]
|
||||
}
|
||||
|
||||
@test "checkpoints under the canonical scratch dir resume" {
|
||||
mkdir -p "$TMP/.agents/scratch/ralph"
|
||||
cat > "$TMP/.agents/scratch/ralph/resume.checkpoint" <<CKPT
|
||||
# Ralph checkpoint — source this to resume
|
||||
GOAL=Scratch\ tier\ resume
|
||||
BRANCH=feat/scratch-resume
|
||||
BASE_BRANCH=main
|
||||
WORKDIR=$TMP
|
||||
SLUG=resume
|
||||
SKIP_PRE_MORTEM=false
|
||||
SPEC_FILE=''
|
||||
RALPH_LOG=.agents/scratch/ralph/resume.log
|
||||
LAST_PHASE=pr
|
||||
CKPT
|
||||
|
||||
cd "$TMP"
|
||||
run env CODEX_BIN="$TMP/does-not-exist" "$RALPH" \
|
||||
--resume .agents/scratch/ralph/resume.checkpoint --dry-run
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Resuming after phase: pr"* ]]
|
||||
[ ! -e "$TMP/does-not-exist" ]
|
||||
}
|
||||
|
||||
@test "a checkpoint outside both ralph dirs is still refused" {
|
||||
mkdir -p "$TMP/elsewhere"
|
||||
cat > "$TMP/elsewhere/evil.checkpoint" <<'CKPT'
|
||||
# Ralph checkpoint — source this to resume
|
||||
GOAL=pwned
|
||||
CKPT
|
||||
|
||||
cd "$TMP"
|
||||
run env CODEX_BIN="$TMP/does-not-exist" "$RALPH" \
|
||||
--resume elsewhere/evil.checkpoint --dry-run
|
||||
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"Refusing to source checkpoint outside"* ]]
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ TMPDIR=$(mktemp -d)
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMPDIR" \
|
||||
"$REPO_ROOT/.agents/teams/test-run-001" \
|
||||
"$REPO_ROOT/.agents/teams/test-run-claude-001" \
|
||||
"$REPO_ROOT/.agents/teams/test-run-claude-crash"
|
||||
"$REPO_ROOT/.agents/scratch/teams/test-run-001" \
|
||||
"$REPO_ROOT/.agents/scratch/teams/test-run-claude-001" \
|
||||
"$REPO_ROOT/.agents/scratch/teams/test-run-claude-crash"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -54,8 +54,8 @@ assert_not_contains() {
|
||||
}
|
||||
|
||||
echo "=== Test: team-runner.sh dry-run ==="
|
||||
rm -rf "$REPO_ROOT/.agents/teams/test-run-001" \
|
||||
"$REPO_ROOT/.agents/teams/test-run-claude-001"
|
||||
rm -rf "$REPO_ROOT/.agents/scratch/teams/test-run-001" \
|
||||
"$REPO_ROOT/.agents/scratch/teams/test-run-claude-001"
|
||||
|
||||
# Test 1: Syntax check
|
||||
echo "Test 1: Syntax check"
|
||||
@@ -75,7 +75,7 @@ assert_contains "shows codex runtime" "Runtime: codex" "$OUTPUT"
|
||||
|
||||
# Test 3: Dry run produces report
|
||||
echo "Test 3: Report generation"
|
||||
assert_eq "report exists" "true" "$(test -f "$REPO_ROOT/.agents/teams/test-run-001/team-report.md" && echo true || echo false)"
|
||||
assert_eq "report exists" "true" "$(test -f "$REPO_ROOT/.agents/scratch/teams/test-run-001/team-report.md" && echo true || echo false)"
|
||||
|
||||
# Test 4: Sandbox level mapping
|
||||
echo "Test 4: Sandbox level mapping"
|
||||
|
||||
Reference in New Issue
Block a user