tests/smoke-test.sh validates every next-work.jsonl entry against
docs/contracts/next-work.schema.md; the entry-level claim_status
enum is {available, in_progress, consumed}. Dream's
findings_router.go was opening brand-new entries with "pending",
which the schema doc explicitly says writers must emit as
"available" (§Lifecycle, §Enums). Today's Dream run appended such
a line, tripping smoke-test on PR #116.
- Flip the writer literal to "available".
- Patch the one offending line already on disk
(.agents/rpi/next-work.jsonl line 56).
- Extend TestRouteFindings_EmitsSchemaCompliantEnums to assert the
entry-level claim_status against a validNextWorkClaimStatuses
set so this exact regression is caught at test time, not push
time.
https://claude.ai/code/session_01FDsQrm2EbQ8ubcJcJren9N
Bumped direct coverage of CheckStaleReferences and CountUniqueFiles
from 0% to 100% by testing them against the quality-package API
rather than only through cmd/ao/doctor_test.go. Keeps regression
signal close to the function under test.
https://claude.ai/code/session_01FDsQrm2EbQ8ubcJcJren9N
Lines documenting command renames (`ao old` → `ao new`) intentionally
mention the deprecated form; treat them as doc, not stale usage. Fixes
the recurring warn from ao doctor on docs/CHANGELOG.md.
https://claude.ai/code/session_01FDsQrm2EbQ8ubcJcJren9N
mapSeverity could return "critical" but the next-work v1.3 severity
enum is strictly {low, medium, high} (see
docs/contracts/next-work.schema.md §Enums → Severity). A finding
frontmatter with severity: critical or severity: blocker would have
produced a next-work.jsonl line that the pre-push contract parity
gate rejects.
Collapse "critical" and "blocker" to "high" — they represent the
strongest signal the next-work contract supports, so the mapping is
lossless at that ceiling.
Also extend the cycle-2 schema guard test to check severity, and add
TestMapSeverity_CollapsesCriticalToHigh to lock in the full mapping
table (low/minor, medium/significant, high/major/critical/blocker,
default, case-insensitive, whitespace-tolerant).
Cycle 1 fixed the router's schema-invalid enum values; this adds a
guard test so the same drift cannot recur without a deliberate test
update.
TestRouteFindings_EmitsSchemaCompliantEnums hard-codes the Type and
Source enum sets from docs/contracts/next-work.schema.md and asserts
that RouteFindings emits values from them. If the schema doc genuinely
grows a new enum value, the test comment directs the reader to update
both the doc and this test together — same shape as other contract
parity checks in this repo.
Previously the only drift surface was validate-next-work-contract-parity.sh,
which runs at push time against the concrete .agents/rpi/next-work.jsonl
file. This test catches the drift at 'go test' time.
RouteFindings was emitting type="finding" and source="finding-router",
neither of which is in the next-work v1.3 enums (see
docs/contracts/next-work.schema.md). Every Dream run was tripping the
pre-push gate's next-work contract parity check, forcing an auto-revert.
Map the router output to the closest valid enums:
- type: "finding" -> "tech-debt"
- source: "finding-router" -> "council-finding"
Original finding provenance is still carried in SourcePath and the
finding file's own frontmatter; no information is lost for downstream
consumers (triage, swarm, flywheel).
Test updated to assert the new enum values explicitly.
Completes the sweep started in cycles 1-4. Applies the same byte-length
upper-bound fast-path to the four remaining rune-safe truncation sites:
- internal/search/search_cass.go TruncateContext
- internal/parser/parser.go Parser.truncate
- cmd/ao/contradict.go truncateSnippet
- cmd/ao/extract.go truncateForPrompt
All call sites now follow the same pattern: if len(s) <= max, return s
without allocating a []rune slice. Behavior is unchanged for inputs
whose byte length exceeds max (the rune-count branch still fires).
Applies the same fast-path introduced in cycles 1-3 to the remaining
call sites in goals/measure.go (truncateOutput) and pool/pool.go
(truncateAtWordBoundary). The existing measure.go comment explicitly
flagged the opportunity ("ASCII-only strings could use a len(s)
fast-path") — honoring that note.
Adds a multibyte regression case to truncateAtWordBoundary so the
rune-count fallback is exercised under the fast-path gate.
Third application of the byte-length-upper-bound fast path for
rune-safe truncation (after truncateForError and TruncateRunes).
When len(s) <= maxLen, the rune count cannot exceed maxLen either,
so the []rune allocation is skipped. Adds multibyte tests covering
all three branches: fast-path, rune-count fallback, truncation.
Same pattern as cycle 1 (cli/internal/parser/parser.go truncateForError):
byte length is an upper bound on rune count, so when len(s) <= cap we can
skip the []rune allocation. Adds a multibyte test that proves the
rune-count fallback still fires when byte length exceeds cap but rune
count fits, ensuring the fast-path does not change behavior.
When the input byte length already fits within maxLen, the rune count
cannot exceed it either (bytes >= runes). Short-circuit before the
[]rune allocation. Adds multibyte test cases proving both the fast-path
(bytes==runes), the rune-count fit case (bytes>maxLen, runes<=maxLen),
and the truncation case.
Closes finding: ag-yht / Add ASCII fast-path comment to truncate()
Adds cli/internal/shellutil package with:
- SanitizedBashCommand(ctx, script) — bash --noprofile --norc -c with
BASH_ENV and ENV stripped from the environment.
- SanitizedEnv(env) — exported for composition.
Wires two goals-package callsites through the helper:
- MeasureOne (cli/internal/goals/measure.go) — user-authored goal
check strings run via bash -c and could be silently rewritten by
aliases defined in BASH_ENV + shopt -s expand_aliases.
- AddGoal dry-run check (cli/internal/goals/commands.go).
New tests cover env filter correctness, flag shape, BASH_ENV stripping
from cmd.Env, context cancellation, and a regression guard that writes
a real BASH_ENV rcfile aliasing echo -> 'printf HIJACKED' and asserts
the alias does NOT leak into the sanitized command.
Consumes swarm-post-mortem-findings 'Sanitize agent shell environment
to bypass aliases'.
ao rpi serve --run-id <id> rejected legacy bare 8-hex run IDs (e.g.
0aa420a9, 3e298842) that still exist on disk at .agents/rpi/runs/<id>/,
forcing users to either pass them positionally (where they misroute
as goal strings and trigger a new orchestration instead of watching
the existing run) or not at all. Commit e8572ee8 (2026-03-05)
tightened ServeRunIDPattern to avoid false positives with short git
SHAs typed as positional goals, which was correct for the positional
branch but wrong for the explicit --run-id flag where user intent is
unambiguous.
Splits the regex:
- ServeRunIDPattern (positional, strict) — unchanged
- ExplicitServeRunIDPattern (flag path, permissive) — adds the
8-hex form: ^(rpi-[a-f0-9]{8,12}|[a-f0-9]{8}|[a-f0-9]{12})$
ClassifyServeArg flag branch and ValidateExplicitServeRunID now use
the permissive pattern. Error message updated to mention <8 hex>.
New cli/internal/rpi/serve_test.go covers flag-vs-positional
asymmetry, numeric/slug/UUID/invalid shapes, and a proper-superset
invariant. Existing positional classifier test unchanged.
Consumes 'Fix rpi serve --run-id classification for run IDs'
from v2.9.3..HEAD epic.
Real overnight runs repeatedly logged 'claude council timed out after
1m30s' while Codex completed successfully. The Claude lane's cold-start
plus structured-output envelope routinely lands in the 90-150s band, so
the hard-coded 90s runner cap was structurally too tight.
- dreamCouncilRunnerTimeout default: 90s -> 180s (evidence-backed).
- withDreamCouncilRunnerTimeout now takes a per-run configured
duration (zero = default); still clamps to parent deadline.
- overnightSettings.CouncilRunnerTimeout + new
resolveDreamCouncilRunnerTimeout helper plumb the duration through.
- config.go: new DreamConfig.CouncilRunnerTimeout field
(yaml: dream.council_runner_timeout) + merge wiring.
Adds 3 tests: config parse paths, default=180s invariant +
configured-wins + parent-deadline-wins, and a regression guard
asserting the runner lane deadline is >90s.
Consumes na-jox1 from .agents/rpi/next-work.jsonl (source_epic=na-22xi).
BuildCatalog previously serialized full Artifact structs (including
heavy Frontmatter map) for every excluded candidate. With >100
exclusions the JSON ballooned. Projects to ExcludedArtifactMeta
{id, title, confidence} on serialization; keeps excludedFull
in-memory for TopExcludedNearMiss(). Bumps CatalogSchemaVersion to 2.
BREAKING: catalog JSON consumers reading excluded_candidates.*
fields beyond id/title/confidence must update. Verified no
in-repo consumers affected.
Invalid YAML in ~/.agentops/config.yaml previously fell through silently
to auto-detect. Now warns 'Warning: invalid YAML in <path>: <err>' and
still falls back (backward compat). Missing files remain silent.
Adds TestLoadConfig_SurfacesYAMLErrorToStderr.
Users who have 'claude' on PATH but prefer Ollama (for privacy, cost,
or latency) were forced to export AGENTOPS_COMPILE_RUNTIME=ollama in
every shell. Add a persistent preference:
~/.agentops/config.yaml
compile:
preferred_runtime: ollama
Precedence (high → low):
1. --runtime flag
2. AGENTOPS_COMPILE_RUNTIME env var
3. compile.preferred_runtime config
4. auto-detect (claude binary → claude-cli)
5. empty (preflight error)
Tests cover all 5 precedence levels including a graceful fallback
when the config load itself errors.
Closes na-pmx1.9.
ao harvest previously reported only "Promoted N artifacts" and silently
dropped every deduped winner that fell below --min-confidence. Users
could not see how much signal the threshold was cutting or whether a
slight tune (0.5 → 0.45) would materially change the promotion set.
Catalog now carries ExcludedCandidates (sorted by confidence desc).
outputHarvestCatalog prints:
Excluded N candidate(s) under confidence threshold 0.50
Top near-miss candidates (closest to threshold):
0.48 promotable-learning-one (learning)
...
Tests:
- TestBuildCatalog_TracksExcludedCandidates: sort order, n-bounds, nil on n<=0
Closes na-pmx1.10.
The salvage pass added in the previous commit rescues frontmatter where
a scalar value contains ": " mid-string. Two tests that used that exact
shape as their "malformed" fixture now correctly succeed at extraction.
Switch those fixtures to tab-indented YAML, which is genuinely
unparseable and salvage-proof, so the "continues after malformed
frontmatter" and "malformed file becomes warning" assertions still
exercise the failure path.
Follow-up to na-pmx1.2.
Adds a retry pass to parseFrontmatter that quotes top-level scalar values
when the initial yaml.Unmarshal fails. Root cause on 2026-04-15:
unquoted `description:` or `name:` scalars containing a literal ": "
(like "Solution: layer the CA bundle") are read by yaml as nested
mappings and break the parse. Users had to hand-fix the file before
harvest would re-scan without warnings.
The salvage pass only rewrites lines at column 0 whose value is a plain
scalar (not already quoted, not a sequence/map marker, not a block
header), so valid documents are untouched.
Tests:
- TestParseFrontmatter_SalvagesMidValueColons (3 real-world fixtures)
- TestParseFrontmatter_ValidDocIsUnchanged (false-positive guard)
Closes na-pmx1.2.
Adds the three missing pieces that turn .agents/ into a proper
LLM wiki following the Karpathy pattern:
1. .agents/INDEX.md — canonical discovery surface cataloging all 202
learnings, plans, research, council reports, and wiki pages with
wikilinks. Auto-generated by scripts/generate-index.sh.
2. .agents/LOG.md — append-only chronological operation log. Entries
formatted as: YYYY-MM-DD HH:MM | actor | VERB | subject | [[wikilink]]
3. .agents/wiki/ directory structure:
- wiki/sources/ — tier 1 LLM session summaries (forge output)
- wiki/synthesis/ — cross-session insights
- wiki/concepts/ — promoted concept pages
- wiki/entities/ — promoted entity pages
Integration:
- ao search now scans wiki/sources/, wiki/synthesis/, wiki/concepts/
(3 new appendKnowledgeMarkdownSearch calls in search.go)
- ao forge --tier=1 writes to .agents/wiki/sources/ (was ao/sessions/)
- Dream post-loop hook + evolve dream sub-cycle write to wiki/sources/
- forge_tier1.go appends to LOG.md and INDEX.md after each session page
- wiki_log.go: AppendToLog + AppendToIndex helpers for all ingest paths
Obsidian-ready: open .agents/ as vault, INDEX.md is the home page,
wikilinks resolve to page navigation, graph view shows connections.
Two fixes for the 20260411T031037Z runaway Dream run that created
22K+ commit markers:
1. Hard safety cap of 100 iterations in RunLoop.shouldHalt(), even
when --max-iterations=0 (budget-bounded). This prevents iteration
count from going unbounded if the budget/timeout check fails to
fire. The cap is intentionally generous (typical runs use 1-5
iterations) but prevents the 10K+ explosion observed.
2. Batch recovery log: when recoverOvernightStart finds >20 stale
items, it summarizes as a single degraded entry ("cleaned up N
stale items") instead of appending one entry per action. This
prevents the 22K-entry degraded array that made the summary.json
44KB and obscured real issues.