Skip to content

docs(research): rvagent Hermes-class harness architecture (metaharness + ruflo integration) - #752

Merged
ruvnet merged 25 commits into
mainfrom
claude/rvagent-harness-architecture-k8yfqn
Aug 2, 2026
Merged

docs(research): rvagent Hermes-class harness architecture (metaharness + ruflo integration)#752
ruvnet merged 25 commits into
mainfrom
claude/rvagent-harness-architecture-k8yfqn

Conversation

@ruvnet

@ruvnet ruvnet commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Research answering: how can rvagent be implemented more like the Hermes harness, integrated with ruvnet/metaharness and ruvnet/ruflo, to create the best SOTA harness?

Adds docs/research/rvagent-hermes-harness/ (4 docs, ~666 lines), synthesized from four parallel investigations: web research on NousResearch/hermes-agent, and deep code audits of crates/rvAgent/, ruvnet/metaharness, and ruvnet/ruflo.

Contents

  • README.md — thesis and ecosystem fit: rvagent as the Rust execution kernel, metaharness as the Darwin evolution/eval/governance layer, ruflo as the coordination plane (memory, hooks, MCP, routing, swarm).
  • 01-findings.md — Hermes architecture + benchmark evidence (harness choice swings SWE-bench pass@1 by 12.5–27.4 pts; the learning loop yields ~40% faster repeat tasks); rvAgent current-state audit: production-grade A2A/MCP/security vs a scaffold-grade loop with 4 blocking defects (tool schemas never sent to the model, middleware pipeline unwired, three incompatible type systems, stub subagents/parallelism/streaming); metaharness and ruflo capability maps with the loop-shaped hole both currently outsource to Claude Code.
  • 02-target-architecture.md — event-streaming loop, cache-first prompt tiers, compaction with lineage, four-layer memory (backed by ruflo's .swarm substrate with ADR-323 provenance), trajectory→SKILL.md synthesis with Darwin/GEPA-style evolution via the policy-genome seam, real subagents + A2A as the federation layer, explicit optional-both-ways integration contracts (ADR-150/256 invariants).
  • 03-roadmap.md — phases P0 (foundation repair) → P4 (evolution/SOTA claims) with falsifiable exit gates, six proposed ADRs, and risk register.

Notes

  • Docs only; no code changes.
  • Grounded in the existing ADR lines: ADR-093..107/139/159 (rvAgent), ADR-256/260/266/271 (metaharness/Darwin), ADR-211/252 (agent memory), ruflo ADR-150/323.

🤖 Generated with claude-flow

https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F


Generated by Claude Code

claude and others added 25 commits August 1, 2026 15:08
…rness + ruflo integration

Research synthesis from four parallel investigations (Hermes harness web
research; code audits of crates/rvAgent, ruvnet/metaharness, ruvnet/ruflo):

- Findings: Hermes architecture/benchmarks, rvAgent's 4 blocking loop
  defects vs its production-grade A2A/MCP/security layers, metaharness
  Darwin/flywheel apparatus, ruflo coordination-plane seams
- Target architecture: event-streaming loop, cache-first prompt tiers,
  compaction with lineage, 4-layer memory, skill synthesis learning loop,
  explicit rvagent<->ruflo and rvagent<->metaharness integration contracts
- Phased roadmap (P0 foundation repair -> P4 evolution/SOTA) with
  falsifiable exit gates and six proposed ADRs

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…ness (P0.2, P0.4)

Roadmap: docs/research/rvagent-hermes-harness/03-roadmap.md Phase 0.

- rvagent-core: add ToolDefinition; ChatModel::complete/stream and
  StreamingChatModel::stream_chunks now take the active tool set;
  ToolExecutor::definitions() advertises schemas to the loop
- rvagent-core graph: tool errors feed back to the model as tool results
  instead of aborting the loop; parallel branch now uses
  parallel_execute_limited (JoinSet + semaphore, bounded by new
  GraphConfig::max_parallel_tools) instead of sequential awaits;
  per-turn usage aggregated from provider metadata and logged
- rvagent-backends anthropic: tools field on ApiRequest; usage and
  stop_reason attached to response message metadata
- rvagent-backends gemini: full function-calling support
  (functionDeclarations, functionCall parts -> ToolCalls with synthesized
  IDs, tool results as functionResponse parts with name recovery); usage
  metadata
- rvagent-cli: CliToolExecutor advertises builtin tool schemas
- tests: tool serialization, usage metadata, error feedback, parallel
  failure isolation, Gemini function-call roundtrips

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…ine (P0.1, P0.3)

Roadmap: docs/research/rvagent-hermes-harness/03-roadmap.md Phase 0.

P0.1 — one canonical type system:
- rvagent-core is now the only source of AgentState/Message/ToolCall/
  TodoItem/TodoStatus/RunnableConfig; middleware's duplicate definitions
  deleted (new types.rs re-exports core + keeps middleware envelopes)
- rvagent-subagents' HashMap-based AgentState alias replaced with the
  typed core AgentState (ADR-103 A1), preserving isolation semantics
- ToolMessage gains optional tool_name; graph pushes tool results with
  the tool name so sanitizer middleware can label output
- middleware Tool trait aligned with core ToolDefinition
  (input_schema(), definition())

P0.3 — middleware actually runs:
- Middleware::before_agent/wrap_model_call now async; sync/async
  duplicate hooks (abefore_agent/awrap_model_call/AsyncModelHandler)
  deleted; MiddlewarePipeline drives the async chain so real HTTP model
  calls can run inside it; RetryMiddleware uses tokio sleep
- new PipelineModel<M: ChatModel> adapter runs modify_request + the
  wrap_model_call chain around any inner model
- rvagent-cli invoke_agent and rvagent-acp prompt now build the default
  pipeline from configured middleware names and run all model calls
  through it (previously zero middleware executed on these paths)

Net -242 lines. Verified: full rvAgent workspace test sweep green.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
- README: 10-crate map (adds rvagent-mcp, rvagent-a2a); parallel-tools
  claims now describe the actual bounded-concurrency JoinSet behavior;
  removed unverified 5-10x and HNSW O(log n) semantic-search claims
  (hnsw middleware is an experimental hash-embedding placeholder)
- architecture.md: adds mcp/a2a to the dependency graph and an explicit
  current-gaps note (no SSE streaming, stubbed subagent spawning,
  placeholder hnsw embeddings)
- middleware doc comments: drop inherited 150x-12,500x claim

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Findings from a security review of the Phase 0 diff:

- MEDIUM: a panicking tool aborted the entire agent process. Tool args
  are model-controlled and indirectly attacker-influenced via prompt
  injection, so any panic on crafted input was a DoS. Each parallel tool
  call now runs in a nested spawn; JoinError becomes a normal tool-error
  result on the model's recovery path. Regression test added.
- MEDIUM: RetryMiddleware treated empty content as a transient error.
  Now that tool schemas are actually sent, tool-call-only turns
  legitimately have empty text and would be retried up to max_retries
  (4x token cost, valid responses discarded). Empty content now only
  counts as an error when tool_calls is also empty.
- MEDIUM: the Gemini API key was passed in the URL query string, where
  it lands in reqwest error Displays (which are logged and now also
  surface into the middleware chain) and proxy logs. Moved to the
  x-goog-api-key header.
- LOW: synthesized Gemini call IDs (index+name) collided across turns,
  letting one turn's tool result satisfy another turn's call in id-keyed
  middleware. IDs now use a process-wide atomic counter; functionResponse
  name resolution prefers the recorded tool_name over the id fallback.

Verified safe, no change needed: tool errors go through the same
tool_result_sanitizer as success output on both CLI and ACP paths;
HITL-denied tool calls stay denied through PipelineModel reattachment;
COW state gives executors immutable snapshots (no torn reads);
max_parallel_tools bounds concurrency; advertised tool schemas are
static literals with no config or secret material.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…confinement

Adds the Phase 0 exit-gate test: an end-to-end verification that wires the
real builtin tool registry, the real filesystem backend, and the real
AgentGraph loop against a scripted model. Only the provider network call is
faked, so the gate fails if the loop, the schemas, or the tools regress.

Writing the gate surfaced two problems.

1. The filesystem backend applied no path confinement. Tool-supplied paths
   were passed straight to std::fs, so a model-issued read_file or write_file
   with an absolute path (or ../ traversal) escaped the working directory
   entirely. Paths from the model are untrusted input and were treated as
   trusted. LocalFsBackend now resolves every path against a fixed root:
   lexical .. normalization first, then canonicalization of the deepest
   existing ancestor so a symlink inside the root cannot bridge out, then a
   containment check. Escaping reads and writes are refused before touching
   the filesystem.

2. Two of the gate's own assertions passed vacuously — a wrong parameter name
   produced "file_path is required", which satisfied a loose contains("error")
   check while never exercising the boundary under test. The confinement
   assertions now require the specific refusal message.

The backend moves from the CLI binary into rvagent-tools so the gate exercises
the shipped code path rather than a lookalike copy; the CLI now consumes it
(-336 lines of duplication).

Covers: schemas advertised on every turn including after tool results, real
side effects on disk, tool errors feeding back as results without aborting the
loop, unknown tools reported not fatal, parallel execution preserving call
order and agreeing with sequential, usage metadata aggregation, and
confinement holding through the full loop.

Tests: 94 unit + 10 e2e in rvagent-tools; rvagent-cli/core/middleware/acp green.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Five-area research sweep (benchmarks, harness techniques, competing
architectures, long-horizon context, self-improving harnesses) with the
roadmap corrections each finding implies.

Findings that contradict the current roadmap:

- Compaction: simple observation masking matches or beats LLM summarization
  at ~half the cost, and summarization inflates trajectories 13-15% by
  destroying stopping signals. Phase 1.3 and Phase 2.5 both bet on
  summarization, as does the shipped default middleware pipeline.
- Subagents: Phase 1.6's CoW fork/merge + CRDT join is parallel-writer
  architecture, the one multi-agent pattern with strong negative evidence for
  coding. The patterns with production evidence are a fresh-context reviewer
  and a read-only context-gatherer.
- Learning loop: the 2026 literature turned against trajectory-learning
  memory. Controlled baselines show agent self-memory underperforming plain
  retrieval, and a documented inverted-U where utility drops below no-memory.
  "SONA on the default path" needs a gate and a permanent control arm.
- Positioning: three major 2026 harnesses are already Rust (Codex CLI, Grok
  Build at ~844k LOC open-sourced 2026-07-15, Goose). "Fast Rust harness" is
  not a differentiator. Two positions remain open: a stable embeddable
  library API with open governance, and deterministic replay.
- Phase 4's SWE-bench-Verified gate targets a saturated benchmark whose
  noise band exceeds the effects we would claim.

Missing from the roadmap entirely: programmatic tool calling (the only
context strategy positive across all models tested), and most of the
reliability floor (observation-window management, loop detection, environment
bootstrap, persisted thinking) where the largest measured deltas live.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
… progress

A stuck agent repeats the same call forever, and max_iterations does not fix
that — it only makes it more expensive. The loop now fingerprints each tool
call by (name, args) and refuses it once it has repeated consecutively past a
threshold (default 3), substituting an actionable message that tells the model
to change approach rather than an opaque error.

Evidence: the harness technique literature ranks loop/stuck detection among
the highest value-per-effort items, identifying repeated near-identical
actions as the clearest failure signal in agent trajectories, and notes
explicitly that raising iteration caps does not help.

Counting is consecutive rather than windowed, deliberately. An agent that
re-runs the same check between edits is doing legitimate work; a windowed
counter would refuse it. Only an unbroken run of identical calls trips the
detector. Alternating cycles are not caught — max_iterations remains the
backstop, and the limitation is documented on the type.

Refused calls still emit exactly one tool result each, in the model's original
call order, so the provider's tool_use/tool_result pairing stays in sync.

Tests cover: refusal after threshold with execution actually stopping,
differing args not treated as a loop, interleaved re-runs not refused (the
false-positive case that drove the design), disabling via threshold 0, and
fingerprint stability across call ids and JSON key order.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Five ADRs converting the 2026 SOTA sweep into decisions. Three of them
reverse or constrain standing bets in the roadmap.

ADR-273 Harness Reliability Floor. Sequences work by measured reliability
impact. The cleanest ablation available moves +54.3 points, almost entirely
from patch-apply failures dropping 69.1% -> <1.5%: the dominant wins are
mechanical, not reasoning. Defines seven floor mechanisms and an explicit
not-doing list (few-shot, ungrounded reflection, embedding code index,
context beyond ~128k, learned components).

ADR-274 Context Management: masking over summarization. Reverses Phase 1.3,
Phase 2.5/ADR-252, and the shipped default pipeline. Observation masking
matches or beats LLM summarization at ~half cost, and summarization inflates
trajectories 13-15% by destroying stopping signals. Adds addressable recall,
programmatic tool calling (the only context strategy positive on every model
tested), verbatim invariant re-injection against documented governance decay,
and per-model-tier capability gating.

ADR-275 Subagent Topology: single writer with auxiliary intelligence.
Reverses Phase 1.6's CoW fork/merge + CRDT join, which is parallel-writer
architecture -- the one multi-agent pattern with strong negative evidence for
coding. Adopts fresh-context reviewer (deliberately no shared context) and
read-only context-gatherer on a cheap model tier. Keeps JoinSet/semaphore
concurrency; drops the mergeable state type entirely.

ADR-276 Learning Loop: gating, trust tiers, measurement. Constrains "SONA on
the default path". Ships feature-gated off with the measurement apparatus as
a precondition: paired lift, previously-solved regression rate, permanent
memory-off control arm, plain-retrieval baseline, confound controls.
Immutable episodic storage, delta-only consolidation, trust tiers with
structural enforcement that untrusted-derived memories cannot influence
permission decisions, and sequential-test promotion instead of greedy accept.

ADR-277 Positioning, Protocols and Claims. Invalidates the premise that a
Rust harness is itself differentiating -- Codex CLI, Grok Build (~844k LOC,
open-sourced 2026-07-15) and Goose are already Rust. Repositions on the two
open gaps: a stable embeddable library API with open governance, and
deterministic replay reported as action-match rate rather than
reproducibility, since hosted inference is not reproducible even at
temperature 0. Mandates MCP 2026-07-28 migration, makes ACP first-class, and
withdraws the SWE-bench-Verified exit gate as a claim target.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Implements the default context strategy from ADR-274. Old tool observations
are replaced with compact placeholders before history is sent to the model;
reasoning steps and actions pass through verbatim.

Masking is a projection, not a mutation: AgentState::messages stays the
complete append-only log, and each placeholder carries the tool_call_id as a
recall handle, so elided content remains addressable. Placeholders name the
tool and the elided byte count so the model can tell what it is missing.

Also caps individual tool results at write time (ADR-273 3.3) with an explicit
truncation marker, in both the parallel and sequential execution paths. An
uncapped tool result can consume the context window in a single call.

Writing the end-to-end test for the cap surfaced two defects in read_file's
line formatter:

- Lines over the length limit were truncated silently, leaving the model
  believing it had seen the whole line. Now marked with the omitted byte count.
- The truncation sliced at a raw byte offset, which panics when a multi-byte
  character straddles it. A UTF-8 file with a long non-ASCII line would have
  crashed the tool. Now walks back to a character boundary.

The existing formatter test asserted the silent-truncation behaviour, so it
was updated rather than left encoding the bug; added coverage for the
char-boundary cases.

Tests: 12 e2e (2 new: masking reaches the model with the full log preserved,
oversized output capped), plus unit coverage for masking, truncation, and
multi-byte safety. 38 suites green across the five rvagent crates.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…memory -> policy

Studied ruvnet/metaharness (@metaharness/flywheel@0.1.7, ADR-226/228/236) for
reusable self-learning. Three consequences.

1. Do not build a promotion apparatus. The flywheel already implements what
   ADR-276 3.4 specified, more rigorously: a frozen conjunctive gate with a
   SHA-256 fingerprint proving it did not move, a holdout PLUS a frozen anchor
   never optimized against, Ed25519 receipts, independent replay verification,
   and a lineage DAG that re-bases on the promoted winner. It is deliberately
   host-agnostic -- Policy = Record<string,string> with Proposer/Evaluator as
   the only seams -- so there is no adapter impedance. Offline, so no Rust port.

2. Shift self-learning investment from memory to policy. These are different
   objects with opposite evidence: policy text (GEPA-style) is the best-evidenced
   optimizer in the 2026 sweep, while episodic memory accumulation shows an
   inverted-U and confound-sized gains. RuVector's weight sits on the memory
   side. ADR-276's gating is not repealed; new effort moves to policy evolution.

3. Adopt noopRate as a score axis. The default gate requires it to strictly
   improve -- a policy earns promotion by making the executor commit more, not
   just score higher.

Two internal nulls are now binding. ADR-226: a read-only frontier advisor
produced zero marginal gold-scored resolves at 5.4x cost while genuinely firing
(33 advisories, 3 vetoes) -- independently corroborating the +0.4pp/5.8x figure
already cited from the public literature. ADR-236: the flywheel mechanism was
proven end-to-end on real SWE-bench and still produced no compounding lift
because the base solver was too weak, confirming that a promotion engine cannot
rescue an unreliable loop and that ADR-273's reliability-floor-first ordering
is correct.

ADR-275 3.1 is amended accordingly: the fresh-context reviewer is downgraded
from adopted to gated. It was written on Cognition's production data without
considering ADR-226. ADR-226 gave its advisor the full transcript where this
reviewer sees only the diff, so it does not refute the design -- but it is the
null the reviewer must beat before reaching the default path. 3.2 is unaffected
and strengthened.

Also identifies an upstream contribution: the flywheel gate is single-shot, so
many generations against one holdout is uncontrolled multiple testing -- the
regime where PACE measured 30-42% false commits. Frozen conjunctive gate AND
anytime-valid sequential test is strictly stronger than either.

Closes a gap where ADR-271 did not reference the flywheel at all.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…, ADR-274)

Environment bootstrap (ADR-273 3.5). A cheap factual workspace snapshot is
injected into the system prompt before the loop starts, so the agent does not
spend its first turns discovering cwd, project kind, test command, and branch.
Filesystem-only -- no process spawning -- so it is safe to run unconditionally.

Two deliberate restraints. build_ok is None unless a caller checks it, and an
unchecked build renders nothing at all rather than reading as passing; a guess
here would be worse than silence because the agent would trust it. And a
truncated directory listing announces how many entries it dropped, since a
partial list that looks complete invites the agent to conclude a file is absent.

Compaction invariants (ADR-274 2.2). Safety constraints and task statements
erode through successive compaction cycles with no failure signal -- a
documented mechanism, not a jailbreak. InvariantSet re-emits a small set of
rules byte-identical after each compaction; they are never inputs to a
summarizer and never masked. Re-inserting an id replaces rather than
accumulates, so an updated objective cannot leave a stale copy in force.

The prompt composition lives in core as EnvironmentSnapshot::augment_prompt
rather than at the CLI call site: rvagent-cli is a binary crate, so anything
assembled there is unreachable from a test. This keeps the seam covered.

Tests: 160 core unit tests (was 143), including unchecked-build silence,
announced truncation, byte-stable invariant round-trips across repeated
renders, and no-op augmentation on an empty workspace. 38 suites green,
clippy clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Belongs with 9d11dbc, which added tempfile as a dev-dependency of
rvagent-core for the environment-bootstrap tests. The lockfile update was
missed because that commit staged only crates/rvAgent.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…274)

Verify-after-write (ADR-273 3.1). LocalFsBackend now reads back every write
and edit, comparing bytes against what was requested. std::fs::write returning
Ok means the syscalls succeeded, not that the bytes are on disk and readable --
a full filesystem, a quota, a racing writer, or an unusual mount can all
produce a successful-looking write whose content differs. Reporting success
there is the worst outcome, because the agent proceeds believing the edit
landed and every later step rests on a false premise. That is the failure class
that dominates harness ablations. Costs one read per write, negligible against
a model round trip. Both failure messages tell the model to re-read the file
rather than just reporting that something went wrong.

Addressable recall (ADR-274 3.2). Masking is now non-destructive in practice,
not just in principle: a `recall` tool dereferences an elided observation by
the recall id carried in its placeholder. It is served by the loop from the
full message log rather than by a ToolExecutor -- executors do not have the
log, and reserving the name in the loop means a workspace tool cannot shadow
it. Advertised exactly when masking is active, since a masked observation the
model cannot dereference is worse than no masking at all.

Tests: 14 e2e (2 new -- recall round-trips content that was elided from the
model's view, and an unknown recall id is actionable rather than fatal), plus
backend coverage for verified write/edit, content mismatch, unreadable target,
and empty/multibyte content. 38 suites green, clippy clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
The shipped default pipeline used LLM summarization as its compaction
strategy, which ADR-274 decided against. Until now every run contradicted an
accepted decision.

Summarization is now gated behind PipelineConfig::enable_summarization, off by
default, alongside the existing SONA/HNSW opt-ins. It is removed from the CLI's
DEFAULT_MIDDLEWARE (11 -> 10). Observation masking in the agent loop is the
default; measured comparisons put simple masking at or above LLM summarization
on solve rate at roughly half the cost, and show summarization inflating
trajectories 13-15% by destroying the stopping signals an agent uses to notice
it has finished.

The middleware is kept and still constructible by name, so the fallback is
real rather than nominal. Its trigger drops from 0.85 to 0.75 of the token
budget when enabled: context degrades well before the nominal limit, and
compacting at 85% leaves too little headroom to be selective rather than
desperate.

Reading the implementation while demoting it confirms the direction. Its
summarize() keeps only truncated Human messages and discards all AI reasoning
and tool results outright -- considerably lossier than a summarizer that at
least attempts to preserve decisions and unresolved issues.

Two tests now assert the decision rather than the old shape: summarization is
absent from DEFAULT_MIDDLEWARE, and enabling it adds exactly one middleware
while remaining resolvable by name.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…lizers

db411ee added a field to PipelineConfig but only ran `cargo test`, which does
not build bench targets. `cargo clippy --all-targets` caught it; the commit
went out anyway because the command chained the commit after an echo rather
than gating on the lint result.

Verified with `cargo clippy --workspace --all-targets`.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
ADR-277 §5.1 withdrew the SWE-bench-Verified gate without a replacement. This
supplies one, and the research that produced it killed the obvious candidate:
SWE-bench Pro was retracted by OpenAI on 2026-07-08 after an audit flagged
27.4% of its 731 public tasks broken automatically and 34.1% by five
independent human reviewers. Two benchmark retractions in six months is the
context every claim now lands in. Also ruled out: SWE-Lancer (archived),
Aider polyglot (frozen since Nov 2025), LiveCodeBench (model benchmark, not
harness-sensitive), OSWorld (self-reported, meeting-gated verification), and
bare GAIA (30-50 point spread on identical tasks from scaffolding alone).

Four conjunctive gates:

1. Terminal-Bench 2.1 at >=78.0% on a mid-tier model, 5 trials, CI half-width
   <=1.5pp, team-verified. Deliberately mid-board: 78% on a mid-tier model is
   a stronger result than 84% on a frontier model, and claiming the top would
   be overreach given CI widths.
2. The actual harness claim -- >=+4.0pp over Terminus 2 on an identical model,
   CI-disjoint, across >=3 models and >=2 vendors, with variance decomposition
   separating harness- from model-induced effects. Absolute pass rate is a
   joint model x harness measurement; only the fixed-model delta is ours.
   Requires publishing the falsification point where the delta vanishes.
3. Cost-normalized Pareto. Terminal-Bench publishes no cost column and HAL has
   paused submissions, so no operating cost-normalized agentic-coding board
   exists -- uncontested ground, and the natural claim for a Rust harness.
4. SWE-rebench on the rolling window, where memorization is structurally
   impossible, to prove the result is not terminal-specific or contaminated.

Adds required caveat language for every published number and a data-hygiene
note: SEO aggregators are publishing figures that do not appear on primary
boards, using real model names.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
… (ADR-273)

"Error: old_string not found" is the highest-frequency tool failure in an
editing agent and is nearly useless on its own: the model already believed the
string was there, so restating that it isn't gives it nothing to change. It
then retries a near-identical call, which is precisely the input condition for
loop detection.

The edit path now works out WHY the match failed and says so, checking causes
in order of real-world frequency: CRLF-vs-LF line endings, whitespace
(indentation, trailing spaces, tabs-vs-spaces), case, and finally the nearest
line in the file echoed back verbatim so the model can copy the real text.
When nothing is close it says that plainly and tells the agent to read the
file rather than assume its contents.

No new tool. edit_file already implements str_replace semantics, and ADR-273
3.4 makes the 8-15 tool budget something to defend rather than spend -- the
published reproductions show the ergonomics are what move the number, not the
tool's existence.

Writing the end-to-end test corrected a wrong assumption: omitting leading
indentation does NOT fail, because matching is substring-based. The failure
that actually happens is supplying the WRONG indentation. Both the e2e test
and a misleading unit-test comment were fixed rather than left describing a
scenario that cannot occur.

Tests: 111 unit + 15 e2e in rvagent-tools, including CRLF, tabs-vs-spaces,
case, nearest-line, multibyte safety, and a guard that a trivially short
needle does not produce a confident wrong guess.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
The Rust half of the flywheel seam: the thing being evolved (PolicyGenome) and
the thing being measured (Score). rvAgent does not implement a promotion
engine -- @metaharness/flywheel already provides the frozen fingerprinted gate,
holdout plus never-optimized-against anchor, Ed25519 receipts, replay
verification, and lineage DAG.

PolicyGenome is shaped as the flywheel's Policy = Record<string, string> so
there is no adapter impedance, and is backed by a BTreeMap so serialization is
deterministic -- iteration order feeds gate fingerprints and replay, and a
nondeterministic order would make identical genomes hash differently. It maps
onto real config: max_iterations, parallel_tools, max_parallel_tools,
loop_repeat_threshold, keep_last_observations, max_tool_result_bytes, plus two
text levers for the prompt layer.

Unknown levers are rejected rather than silently skipped. A mutation to a lever
the harness does not apply produces a run identical to baseline, which the
optimizer would score as "no effect" and burn generations on.

Score projects runs onto the gate's four axes, including noopRate -- the
non-obvious clause the default gate requires to strictly improve. A run that
reports success while committing nothing counts as a no-op, so a policy cannot
earn promotion by making the agent talk rather than act.

Fixes a real bug at the JS seam found while testing: cost-per-win with zero
wins was f64::INFINITY, which serde serializes as JSON null, and the gate's
`candidate.costPerWin > baseline.costPerWin` evaluates `null > n` as false in
JavaScript -- so a policy that won nothing would silently PASS the cost clause.
Verified by running the gate's own logic against both encodings: null does not
fire the clause, the finite sentinel does. Now uses f64::MAX as an explicit
COST_PER_WIN_NO_WINS constant, with a test asserting no axis ever serializes
non-finite.

Tests: 17 policy tests including deterministic serialization, unknown-lever
rejection, a guard that KNOWN_LEVERS cannot drift out of sync with apply_to,
and the non-finite serialization guard. 174 core tests green.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…DR-275)

Implements the subagent boundary as a tool that spawns an isolated context and
returns a String. Not peer agents with a message bus, shared mutable state, or
a mergeable state type.

The signature is the architecture. A subagent returns text and nothing else --
no state update, no file handle, no mergeable value -- so a caller cannot wire
one up as a concurrent writer even by accident. SubagentRequest carries only a
role and a prompt, with no parent-state field by construction, so a subagent
cannot reach the parent's conversation, files, or todos.

This is what replaces Phase 1.6's CoW fork/merge + CRDT join. A CRDT can merge
two edits to the same file without textual conflict; it cannot make the result
coherent. Making the writer singular removes that entire failure class rather
than managing it.

Two roles, both read-only. may_write() and inherits_parent_context() are
encoded as methods returning false rather than assumed, so adding a role that
violates either is a visible decision rather than an oversight -- with tests
asserting the invariant directly.

Gatherer runs on the cheap tier by design: a frontier model in a read-only slot
measured +0.4pp at 5.8x cost, corroborated by metaharness ADR-226's 5.4x null.
Reviewer is documented as GATED rather than adopted, per ADR-278 §7, with
ADR-226 named as the null it must beat.

Summary returns are capped at the boundary rather than trusted: a subagent
exists to reduce what reaches the parent's context, so one returning its whole
transcript has inverted its own purpose.

Tests: 182 core tests (was 174), including the single-writer invariant across
all roles and the summary budget.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
The Evaluator is where all rvAgent meaning lands on the flywheel's four
host-agnostic Score axes, which makes it the trust boundary for every
downstream guarantee the gate provides.

Rust side: RunOutcome is now serializable with the JS-side field names, since
the Evaluator boundary is a process boundary rather than a function call. Adds
item_id so an aggregator can detect a missing or duplicated item instead of
silently averaging fewer runs, and a round-trip test pinning the wire names --
renaming one silently would make every aggregated Score wrong rather than
failing loudly.

JS side (scripts/rvagent-flywheel-evaluator.mjs): aggregation, lever
validation, and an injected runItem so the seam is testable without spawning
real agent runs.

Three refusals are deliberate, each preventing a silently-wrong Score rather
than a crash:

- Zero runs score as maximally bad, never as a clean sweep.
- A dropped item throws instead of shrinking the denominator, which would
  inflate every axis.
- A policy naming an unapplied lever throws, because such a mutation produces
  a run identical to baseline that the optimizer reads as "no effect".

COST_PER_WIN_NO_WINS is duplicated as Number.MAX_VALUE with the reason stated
in both files: Infinity serializes to JSON null, and the gate reads null > n as
false, so a zero-win policy would silently pass the cost clause.

Verified the two implementations agree by running both over the same four
cases -- mixed (3.25), success-but-noop, zero-wins, and empty -- rather than
assuming a shared spec keeps them in sync.

Tests: 19 policy tests, 184 core tests green.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Answers two questions asked together. The answers turn out to be independent,
and the second matters far more.

C: no. Every capability commonly cited as C-only has a production Rust path in
2026 -- io_uring (pure-Rust crate), CUDA (cudarc dynamic loading), BLAS (faer
matches or beats OpenBLAS), AVX-512 (stable since Rust 1.89, FP16 since 1.94).
Two results are decisive rather than suggestive: zlib-rs is faster than zlib-ng
in C AND is the fastest WASM zlib, and Qdrant -- the closest analogue -- spent
two minor versions REMOVING its one C++ dependency, citing interop friction.

The most-cited pro-C evidence does not survive decomposition. SimSIMD/NumKong's
20-118x headline compares its f16 against GCC's f32; there is no f32-vs-f32
row. Same-width it is 1.15-2.1x. PDX (SIGMOD '25) beat SimSIMD and FAISS
hand-written kernels by 2.0x average using plain scalar C++ with no intrinsics,
purely via data layout, and NumKong loses bulk scoring by 1.85-3.04x for want
of a bulk API. Two expert C teams differ from each other more than Rust differs
from C.

Cost lands where RuVector is most exposed: 37 of 166 crates are WASM, and
wasm32-unknown-unknown has no C/C++ toolchain by design. Every C feature would
need reimplementing in Rust for the browser anyway. Miri also cannot execute
across an FFI boundary, which for a concurrent index is a real loss.

The one genuine gap -- f32 reduction reassociation, historically 8.4x -- closes
in Rust 1.98 on 2026-08-20 via float_algebraic. What remains blocked is
narrower than assumed: f16/bf16 arithmetic and ARM SVE/SME.

The finding that matters more than C: ruvector-sota-bench measures against
ann-benchmarks.com, which is deprecated and now redirects to VIBE, on
SIFT/GloVe/Deep -- exactly the datasets VIBE exists because they are no longer
representative. ADR-267 does not mention VIBE. Every SOTA claim we could make
today rests on an unmaintained artifact. Same failure mode as the withdrawn
SWE-bench-Verified gate in ADR-277.

Recall@k is itself under credible attack: 1/Ratio@k reaches equal downstream
quality with 1.86-9.36x fewer distance computations.

Program, ranked: retarget to VIBE + 1/Ratio@k (blocks everything else);
8-bit rotational quantization (>99% recall10@10, 4x compression, zero training,
days of work); SymphonyQG-class packed quantized graph; streaming-stable
quantization where every incumbent is weak and which is our actual workload;
adaptive filtered-query router; MUVERA FDE; CAGRA build via cuvs-sys.

Records two corrections to my own earlier survey: an initial pass wrongly
concluded there were no binary/hamming popcount kernels, having generalized
from one file across a 166-crate workspace -- ruvector-rabitq/src/scan.rs has
had AVX-512 VPOPCNTDQ all along. And the two research threads report
TurboQuant against different baselines; the ADR states both rather than
conflating them.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
…NOT adopted

Implements the PDX (SIGMOD '25) vertical/dimension-major layout that ADR-279
5.1 ranked first in the kernel build order, benchmarks it against the existing
row-major batch path, and records that it did not reproduce.

Measured on this host (AVX-512, both paths built with target-cpu=native):
PDX is 0.65-0.88x on cache-resident working sets (i.e. SLOWER) and only
1.09-1.29x when streaming. The paper reports ~2.0x.

The first benchmark run was invalid and is worth recording. It showed PDX
14-18x slower, because the row-major path runtime-dispatches to AVX-512 via
is_x86_feature_detected! while the new code compiled for baseline x86-64 with
SSE2. That was an ISA comparison presented as a layout comparison.

The corrected comparison is still confounded: the row-major path takes
Vec<&[f32]> built from Vec<Vec<f32>>, so it pointer-chases across 4096 separate
heap allocations, while PdxIndex is one contiguous buffer. The streaming win
may be allocation contiguity rather than vertical layout. A clean experiment
needs a contiguous row-major baseline.

Reading: at f32 these workloads are bandwidth-bound, so layout cannot help
much. That strengthens rather than weakens the case for 8-bit rotational
quantization (ADR-279 5 item 2) -- 4x less data to stream attacks the actual
bottleneck. Revisit PDX for quantized codes, where the working set may shrink
enough to become compute-bound.

The code is kept: it is correct, tested against a naive reference across block
boundaries and padding lanes, and is the natural substrate for the quantized
retry. It is not wired into any hot path.

Also documents why the existing batch_euclidean is not a bulk kernel -- its
TILE_SIZE chunking is decorative, since the inner call is identical to a flat
loop over single-pair distances.

Tests: 8 pdx tests including ragged-block rejection, oversized-block rejection,
partial-block padding, and cross-block-boundary correctness at dim=129.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019mt39sZNUecTJBBBqPRH9F
Co-Authored-By: claude-flow <ruv@ruv.net>
…, no-op HITL gate, Gemini request bugs

Code-review fixes on top of the harness work:

rvagent-tools (local.rs):
- Reject dangling-symlink path components and writes through any symlink;
  refuse writes through hard links (nlink > 1, unix) — closes workspace
  write-escape; module doc now states exact confinement guarantees
- Saturating offset+limit in read (u64::MAX no longer panics)
- Char-boundary-safe output truncation
- execute(): own process group, concurrent pipe draining (no >64KB
  deadlock), SIGKILL of the whole tree on timeout, child reaped
- glob/grep no longer follow symlinked dirs (loop + read-escape fix)

rvagent-middleware:
- middleware_by_name takes PipelineConfig; HITL built from interrupt_on
  with a conservative default gate (shell/file mutation) shared by
  build_default_pipeline — both construction paths now match
- HITL block message states reality (call dropped, how to configure)
- Unknown middleware names are hard errors instead of silent skips
- unicode_security/memory/skills/summarization by-name arms honor config
- Retry backoff capped at 60s; summarization preview char-boundary-safe
- masking truncate_tool_result respects caps smaller than the marker

rvagent-backends:
- Gemini: consecutive tool results grouped into one Content (parallel
  tool calls no longer 400); Candidate deserializes finishReason;
  missing candidates/content/parts produce descriptive errors instead of
  silent empty success; tool schemas sanitized to Gemini's OpenAPI
  subset (nullable arrays, format filtering, type-less fallbacks);
  parameterless tools omit parameters
- Anthropic: consecutive tool results grouped into one user message

rvagent-cli:
- Pipeline built via validated by-name path; RVAGENT_AUTO_APPROVE=1
  opt-out (stderr-visible) for unattended runs, fail-closed by default

Co-Authored-By: claude-flow <ruv@ruv.net>
@ruvnet

ruvnet commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Review + fix summary (automated multi-agent review)

Full adversarial review of the ~9.5k-line diff found 3 blockers and ~15 majors in the new rvAgent code; all blockers and the tractable majors are fixed in c20629136, verified by an independent verification pass with an empirical escape-testing harness. All 7 rvAgent crates + ruvector-core PDX tests green locally (700+ tests), fmt/clippy clean.

Blockers fixed

  1. local.rs symlink write escape — dangling symlinks let writes land outside the workspace root. Now: dangling-link components rejected, writes never travel through symlinks, hard links (nlink>1) refused on unix, glob/grep no longer follow symlinked dirs (also closed a read-escape). Verified against 12 attack vectors.
  2. HITL gate was a no-op — the CLI's by-name pipeline built the approval gate with an empty pattern list (approved everything). Now both construction paths share a conservative default gate (shell + file mutation); RVAGENT_AUTO_APPROVE=1 is the explicit, stderr-logged opt-out.
  3. Gemini parallel tool calls 400'd — one Content per functionResponse. Now grouped per model turn; plus finishReason deserialization, descriptive errors for safety-blocked/empty responses, and schema sanitization to Gemini's OpenAPI subset.

Also fixed

Panics on model-supplied input (read limit overflow, UTF-8 truncation in three places), execute() pipe deadlock >64KB + process-group kill + zombie reap, unknown middleware names now hard errors, sequential tool-call panic containment in graph.rs, max_iterations off-by-one, retry backoff cap.

Known follow-ups (deliberately deferred)

  • No approval UI exists — gated tool calls fail closed with an instructive message; a real approval prompt (TUI y/n) is the right next step.
  • witness sits outside hitl in the default order, so blocked calls aren't witnessed.
  • execute() timeout can still be held open by a child that calls setsid (reader join is unbounded).
  • Pipeline before_agent hooks run once per user turn, not per model call; MemoryMiddleware's AGENTS.md injection reads an extension slot nothing writes (latent pre-existing).
  • PDX: pub mod pdx is unconditional public API surface despite non-adoption; bench compares sqrt'd euclidean vs squared distance, inflating the headline ratio.

🤖 Generated with claude-flow

@ruvnet
ruvnet marked this pull request as ready for review August 2, 2026 15:39
@ruvnet
ruvnet merged commit 0efdbeb into main Aug 2, 2026
54 of 56 checks passed
@ruvnet
ruvnet deleted the claude/rvagent-harness-architecture-k8yfqn branch August 2, 2026 15:40
ruvnet added a commit that referenced this pull request Aug 2, 2026
Brings the ruvector npm package on main up to the 0.2.40 release content
(published from an unmerged branch: metaharness SDK/CLI/MCP surface, ONNX
embedder improvements, embedding provenance) and bumps to 0.2.41,
published post-PR-#752 with rebuilt NAPI binaries on main.

Co-Authored-By: claude-flow <ruv@ruv.net>
ruvnet added a commit that referenced this pull request Aug 2, 2026
…beddings, nightly research quality gate

- ADR-280: RVF durable self-contained metadata (rvf-types metadata.rs,
  runtime options/store/filter/safety_net/vector_slab wiring, crash-safety
  and durability integration tests)
- ADR-281: role-aware embedding APIs in ruvector-core (embeddings.rs,
  agenticdb.rs, error taxonomy across core/hailo/hailo-cluster, role test)
- ADR-282: nightly research quality gate (research-gate scripts + schemas,
  research-* GitHub workflows, CODEOWNERS, sota-bench metaharness harness
  with scorePolicy + darwin.ts, gate override + promote pipelines)

ADRs renumbered from 273-275 to 280-282 after PR #752 took 273-279.

Co-Authored-By: claude-flow <ruv@ruv.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants