yams/AGENTS.md
Trevon b67c6374af
docs(agents): require ResourceGovernor admission for daemon background work (#288)
Codifies the rule after the 2026-10 incident where p2p and topology work
pinned the CPU of a memory-constrained host. Lists the components that do
not consult the governor yet.

Signed-off-by: trvon <me@trevon.dev>
2026-10-14 08:32:53 -06:00

321 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: YAMS repo supplement — conventions, structure, and repo-scoped rules
argument-hint: "[TASK=<description>] [MODE=<engineering|bug-bounty>] [PHASE=<start|checkpoint|complete>]"
---
# AGENTS.md (Repo Supplement)
Operating model (retrieval contract, execution loop, metadata, handoff,
ask-first list): `docs/prompts/PROMPT-eng-codex.md`. C++ design/TDD/assertion/
profiling flow: `docs/prompts/PROMPT-yams-cpp-workflow.md`. This file holds
only what is specific to this repository.
## Repo Intent
Engineering on YAMS itself, plus scoped non-destructive bug-bounty research.
Dogfood YAMS as the distributed memory across sessions, agents, and handoffs —
`yams grep`/`search`/`graph` before local discovery, always (see the retrieval
contract in the prompt). The graph holds document, path, version, entity, and
semantic-neighbor relations; code-symbol nodes and `calls`/`defined_in` edges
were removed in v0.20, so answer "who uses X" with `yams grep`.
## Development Memory
Store development audits, plans, decisions, and handoffs in YAMS with task/source/
phase metadata, not as new local Markdown documents. Keep repository instructions
here; keep executable tests and raw benchmark artifacts in their normal locations.
Before removing a local note after ingestion, hydrate its returned hash and verify
the content. An add acknowledgement is not a successful read-back. If hydration
fails, report the blocker and preserve the only recoverable copy until it is safe
to remove; do not describe the migration as complete.
## Mobile Host Consumers
When YAMS is embedded in a mobile app, treat `libyams_mobile` as a local corpus
SDK, not an inference or account SDK. Keep host UI focused on explicit import,
library, retrieval, result inspection, and confirmation-gated deletion. Do not
make a paid entitlement, account, or remote service a prerequisite for reading,
exporting, or deleting a person's local corpus.
Before asserting mobile support, record the exact native artifact, ABI version,
device/OS, and architecture slice. A host-only unit test proves lifecycle logic;
it does not prove that an Android `.so` or iOS XCFramework loads on hardware.
Keep physical-device claims separate and reproducible.
## CLI Philosophy
- Clean default behavior over feature flags and one-off env vars.
- Add an env knob only when it materially improves usability/debugging/perf.
- Auto-select behavior when safe rather than exposing another switch.
- Remove temporary diagnostics once product behavior is clear.
## Tuning Surfaces
- Tuning lives in typed config, not env vars: new search/topology/daemon knobs
go on the relevant config struct (`SearchEngineConfig`, `TopologyConfig`, …)
with a TOML key resolved through `ConfigResolver`
(`include/yams/daemon/components/ConfigResolver.h`).
- Benches/tests/callers set the typed field directly or write a per-test
`config.toml` (`DaemonHarnessOptions::configPath`) — never a fresh `YAMS_*`
env var. Existing `YAMS_SEARCH_TOPOLOGY_*` / `YAMS_EMBED_*` overlays are
test-only; keep them working, do not grow them.
- Reaching for `std::getenv` for a tunable is the warning sign.
## Repo Structure (Start Here)
- CLI entry points: `src/cli/` · services: `src/app/services/`
- Search (grep/semantic): `src/search/` · metadata/indexing: `src/metadata/`
- Storage engines: `src/storage/` · vector DB/embeddings: `src/vector/`
- Daemon client/server/components: `src/daemon/` · MCP: `src/mcp/`
- Tests: `tests/` · vendored libs: `third_party/` (own git repos)
## Conventions (Condensed)
- Formatting is mechanical: `.clang-format` (LLVM base, 4-space, 100-col).
- Naming: functions/vars `camelCase`; types `PascalCase`; constants
`kPascalCase`; members trailing `_`; files `snake_case`.
- `Result<T>` for fallible operations, explicit propagation.
- Public/installable headers include other public YAMS headers through the canonical
`<yams/...>` include root. This validates installed-SDK layout and keeps includes
stable when headers move. Use quoted relative includes only for private,
non-installed implementation headers or explicit test/bootstrap exceptions.
- Prefer `YAMS_HAS_*` gates from `include/yams/core/cpp23_features.hpp`.
## Engineering Quality Loop (C++)
1. Design the seam first: observable behavior, boundary, smallest testable seam.
2. Red before green: failing Catch2 test through public/repo-stable APIs;
characterize existing behavior before refactors.
3. Assertions: recoverable failures stay in `Result<T>`; `YAMS_ASSERT`/
`YAMS_PRECONDITION`/`YAMS_POSTCONDITION` for always-on invariants;
`YAMS_DCHECK` for debug-only consistency; expressions side-effect free.
4. Refactor under green tests; no broad rewrites without seam/test evidence.
5. Measure before optimizing: workload + KPI + baseline, change one thing,
re-measure. `YAMS_*` profiling macros only with a clear question.
## Build Setup
- Configure/build through the repo script; it resolves Conan + Meson and prefers
Clang: `./setup.sh Debug` (also `Release`, `Profiling`, `Fuzzing`).
- Build dirs are chosen per type: `Debug` -> `build/debug` (the unit-test lane;
`.clangd` points here), `Release` -> `build/release`, `Profiling` ->
`build/profiling`, `Fuzzing` -> `build/fuzzing`. Override with `YAMS_BUILD_DIR`.
The legacy `builddir` name is still accepted by some helper scripts.
- Debug enables TSAN by default; pass `--no-tsan` for faster local iteration.
- Tests are opt-in outside Debug. If `meson compile -C <dir> <target>` reports
`target not found`, that dir was configured without tests; reconfigure with
`meson setup --reconfigure <dir> -Dbuild-tests=true --wrap-mode=default`.
- One writer per build dir; never run parallel `meson compile` invocations.
## Testing Conventions
- Catch2 v3; files end `_catch2_test.cpp`; live in `tests/unit/<subsystem>/`;
registered in `tests/meson.build`; targets named without `_exe`.
- Compile: `meson compile -C build/debug -j4 <target>` — never run parallel
meson compiles (build-dir lock contention).
- Run: `build/debug/tests/<target>` or `meson test -C build/debug <name>`.
- Enable tracked hooks with `git config core.hooksPath .githooks`. The default
pre-push hook runs the blocking Linux+macOS CI gate, not coverage.
Run coverage explicitly with `YAMS_PREPUSH_COVERAGE=1 git push`; it dispatches
`scripts/run-local-coverage.sh`, builds `build/coverage`, prefers ccache and
a fast linker override when available, runs the unit-suite fast path by
default, and prints a `gcovr` text summary for pushed C/C++ / Meson changes.
Opt into integration coverage with `YAMS_COVERAGE_INCLUDE_INTEGRATION=1`.
Use `YAMS_PREPUSH_GATE_SELF_TEST=1 git push` to prove hook wiring without the
expensive build/test work. Temporary bypasses: `YAMS_SKIP_PREPUSH_CI_GATE=1 git push`
or `YAMS_SKIP_COVERAGE_HOOK=1 YAMS_PREPUSH_COVERAGE=1 git push`.
- Optimize test **overlap and suite shape**, not away correctness. Keep a fast
default correctness lane, but move stress/soak, migration, heavy log-capture,
and multi-second wait tests into explicit slow suites or binaries instead of
deleting them.
- Avoid repeated expensive bootstrap in broad suites when bootstrap is not the
behavior under test: temp DB + pool + migration + daemon/KG setup should be
reused, pre-seeded, or split into narrower binaries where safe.
- Prefer deterministic synchronization (`promise`/`future`, latches, condition
variables, observable state transitions) over fixed sleeps and long polling
loops. If polling is unavoidable, keep deadlines tight and justify them.
- Non-log-assertion tests should clamp log volume; verbose daemon/service logs
belong in focused diagnostics/logging tests, not the default fast TSAN path.
- Split monolithic Catch2 binaries when one executable bundles many unrelated
files and blocks `meson test` parallelism; preserve behavior coverage while
improving failure locality and runtime.
- `-DYAMS_TESTING=1` gates `testing_*` helpers in production headers.
- RAII guards for global state (`ProfileGuard`, `EnvGuard`, `HwGuard`); reset
atomics/overrides between cases.
- DynamicCap sentinel: `UINT32_MAX` = unset (use it when resetting, not `0`).
## Benchmarks & Experiments (local xplan)
The xplan harness is optional, gitignored local tooling and is not present in a
clean clone. When it is available, daemon KPI and **search-engine**
quality/latency ablations use it; do not invent parallel multi-arm shell
matrices. Organized default-system numbers: `docs/benchmarks/index.md`.
Per-run detail (gitignored):
`build/benchmarks/<plan>/<stamp>/` (`REPORT.md`, `ablation.md`, `metrics.csv`).
### Entry points
```bash
python3 tests/benchmarks/xplan/runner.py self-test
python3 tests/benchmarks/xplan/runner.py list-plans
python3 tests/benchmarks/xplan/runner.py download-beir # scifact + nfcorpus
python3 tests/benchmarks/xplan/runner.py run <plan> --build-dir build/release
python3 tests/benchmarks/xplan/runner.py report build/benchmarks/<plan>/<stamp>
python3 tests/benchmarks/xplan/runner.py compare <dirA> <dirB>
```
| Surface | Path |
|---------|------|
| Default-system numbers | `docs/benchmarks/index.md` |
| Harness (how it works) | `tests/benchmarks/xplan/README.md` |
| Plans / workers | `tests/benchmarks/xplan/plans/`, `…/workers/` |
| Artifacts | `build/benchmarks/**` (gitignored) |
Quality plans default to **BEIR scifact**. Do not rank search levers on
`dataset=synthetic` (ingest/load throughput only). Prefer
`build/release/tests/benchmarks/retrieval_quality_bench` for quality runs.
### Search engine measurement loop
The primary optimization surface is **`src/search/`** (`SearchEngine`, fanout,
fusion, rerank, topology assist). Topology is one optional assist path — not the
whole loop. Engine expansion (new stages, seams, fusion/rerank knobs) must be
driven by this loop, not intuition alone.
1. **Name the lever** — one typed `SearchEngineConfig` / topology field (or a
tested seam extract). No new product `YAMS_*` env tunables.
2. **Pick a plan by question:**
| Question | Plan |
|----------|------|
| Which hybrid component carries quality? | `search_component_ablation` |
| Topology-off control | `search_product_component_ablation` (`repeats=3`) |
| Current default vs topology off vs traced shadow | `search_shadow_cost` (`repeats=3`) |
| Vector fusion weight | `search_vector_weight_ablation` (`repeats=3`) |
| Vector weight multi-corpus gate | `search_vector_weight_0_20_multicorp` (`repeats=3`) |
| Graph-vector fusion weight | `search_graph_vector_weight_ablation` (`repeats=3`) — **parked for product defaults**: only meaningful with topology assist; SCIENTIFIC/no-KG gates force `graphVectorWeight=0` |
| Tiered execution on/off | `search_tiered_ablation` (`repeats=3`) |
| Multi-corpus transfer gate | `search_product_nfcorpus_gate` (`repeats=3`) |
| Compact engine overhead set | `subsystem_overhead` |
| Pipeline / leg stages | `leg_stage_ablation` (`repeats=3`) |
| Rerank off vs replace vs blend | `simeon_rerank` / `simeon_rerank_beir` |
| Rerank blend weight multi-corpus | `search_rerank_blend_multicorp` (`repeats=3`) |
| Per-query RR class analysis | `analyze_query_class.py` on arm `debug.jsonl` |
| Routed vs global ANN at equal budgets | `topology_routing_budget_ablation` (`repeats=3`) |
| Topology construction purity | `topology_purity_validate` (`repeats=3`) |
| Ingest / load / repair / ops KPIs | `ingest_pipeline`, `retrieval_load`, … |
3. **Decision-grade runs use `repeats>=3`.** Bare Δ with `repeats=1` is exploratory.
Summary marks: `*` clears pooled stdev, `~` within noise, unmarked = n=1.
4. **Read quality × cost** — primary columns include MRR (and friends) **and**
`search_latency_ms_p50` (plus expansion/path rates when topology is on).
Mechanism rates from `debug.jsonl` (e.g. medoid vs seed-neighbor path,
`topology_vector_seeds_added_*`) land in metrics via `retrieval_quality`.
5. **Compare stamps** with `runner.py compare` before claiming a win. Do not
rebuild `build/release` mid-run (poisons arms). xplan skips meson compile when
the bench binary already exists; force with `YAMS_BENCH_FORCE_BUILD=1`.
6. **Product default** is **hybrid_assist + shadow**; topology-disabled hybrid is
the explicit no-topology control. Shadow can perform counterfactual work, and
stage tracing adds exact-control work: compare untraced timings separately.
Experimental engine paths must beat the default hybrid on quality without a large
latency regression, or stay opt-in / parked.
7. **Catch2 first** for seams and unit behavior (`tests/unit/search/`); xplan
is for multi-arm KPI and default-system ranking — not a substitute for TDD.
### Workers (short)
`retrieval_quality` → BEIR quality + engine/topology counters (`retrieval_quality_bench`).\
`ingestion_e2e` / `retrieval_load` / `repair_ability` / `ops_timeline` → daemon KPIs 2–5.
Ablation mapping: `workers/ablation.py` (search weight gates, ingest flags, topology
mode/expansion). Harness-only: `YAMS_BENCH_*`. Do not grow product `YAMS_*` for axes.
### Plan hygiene
- Active topology decision plans: `topology_purity_validate`,
`topology_routing_budget_ablation`.
- Product-path engine plans: `search_product_component_ablation`,
`search_vector_weight_ablation`, `search_vector_weight_0_20_multicorp`,
`search_tiered_ablation`, `search_product_nfcorpus_gate`,
`search_rerank_blend_multicorp`, `search_product_clean_baseline`.
- Parked product levers (do not use for promote/kill of defaults):
`search_graph_vector_weight_ablation` (topology-only), path/tag/metadata/entity
weight knobs on SCIENTIFIC (capability-gated to 0).
- Superseded multi-arm topology plans: `tests/benchmarks/xplan/plans/archive/`
(historical only; not for promote/kill).
- No new multi-arm shell scripts; wrappers under `scripts/` or
`tests/benchmarks/scripts/` only.
- Prefer sanitizer builds for intermittent bench crashes:
`build/asan-api|tsan-api|ubsan-api` + `YAMS_BENCH_SKIP_BUILD=1`.
## Branches, PRs & Promotion
- Feature PRs target `experimental`; `main` only moves through the automated
"promote experimental to main" draft PR, which lists everything on
`experimental` not yet on `main` (not just the latest change).
- Stacked PRs (a PR whose base is another PR's branch): merge bottom-up. Merge the
child into the parent branch *before* merging the parent, or retarget the child
to `experimental` first. A child merged into a parent branch that was already
merged lands only on that branch and silently misses `experimental`.
- After merging a stack, verify nothing was stranded:
`git cherry origin/experimental origin/<branch>` must print no `+` lines.
- Submodule (`third_party/*`) PRs merge with a merge commit or rebase, never
squash, and land before the YAMS PR that bumps the pointer, so the pinned SHA
stays reachable from the submodule's default branch.
- Merge method by PR kind:
- Feature PRs into `experimental`: squash is fine.
- Promotion PRs (`experimental` → `main`) and back-merges (`main` →
`experimental`): **merge commit only**. A squash copies the changes but not
the commits, so Release Candidate rejects the next candidate with
"candidate must contain the complete base history".
- After every change to `main` (a promotion, a release PR, or a direct commit),
back-merge `main` into `experimental`. Check with
`git log --oneline origin/experimental..origin/main`, which must print nothing.
- Promotion PR bodies must not contain `BEGIN_COMMIT_OVERRIDE`. Release Please
applies the override to every commit in the PR and repeats it in the notes.
- To pin a version (for example, when a `feat` would otherwise bump the minor
version), add a `Release-As: X.Y.Z` footer to a commit on `main`.
## Patterns To Reuse (High Signal)
- `TuneAdvisor`: static inline atomics, relaxed reads for advisory knobs.
- `InternalEventBus`: typed channels keyed by string names.
- `ResourceGovernor`: check admission before heavy work.
- `profiling.h`: `YAMS_*` macros, no-op without Tracy.
- Daemon async paths: Boost.Asio coroutines (`boost::asio::awaitable<…>`).
## Background Work & Resource Governor
YAMS must run on memory-constrained, non-Apple hosts. In October 2026 the
daemon pinned a weak machine's CPU with p2p and topology work.
- Daemon code that starts a thread, posts a job, spawns a coroutine or arms a
periodic timer checks `ResourceGovernor` first: `canAdmitWork()`,
`admitDeferrable(DeferrableWork::…)` or its cap in `ScalingCaps`. Admission
is not enough for long jobs: a rebuild or scan that runs for seconds re-checks
pressure between batches and yields. No free-running loops.
- Worker counts come from `TuneAdvisor` or the governor, not
`hardware_concurrency()`.
- Idle means no active non-health requests and empty queues. An open
connection is not activity.
- Background work stops through a bounded, checked drain. A `void stop()` that
returns while work is still running is not a shutdown.
- A new or changed background path needs a KPI on the constrained lane
(`setup.sh Profiling`, Linux x86, pinned cores and RAM): idle CPU and RSS
over time with the feature on. Profile before and after. Code reading is not
evidence of a fix.
- Gaps as of 2026-10: `BackgroundTaskManager`, `p2p_transport` (accept thread,
one thread per session, 5 s reconnect loop) and `WriteCoordinator` do not
consult the governor. Topology rebuilds and semantic maintenance are admitted
through `admitDeferrable` but then run to completion with no pressure check
inside `TopologyManager::runRebuild`. Fix these before adding background work.
## Bug-Bounty (Repo Use)
Scoped targets only; read-only/low-impact validation first; no persistence,
destructive payloads, or exfiltration; redact secrets before `yams add`; index
evidence as short reproducible notes (`source=evidence`).
## Keep This File Lean
Generic agent behavior belongs in `docs/prompts/PROMPT-eng-codex.md`. No
session-specific skill catalogs here.