What Is A Harness?
A harness is a small Rust crate under .riptide/harness/. Riptide builds it and runs it before the simulation starts. The harness creates concrete setup state: Anchor account bytes, SPL mints and token accounts, PDAs, oracle mocks, sibling CPI programs, and any external accounts your adapter observes.
Keep the boundary simple: the adapter declares the shape Riptide should drive and observe; the harness writes the bytes that make that shape boot before tick 0. If external dependency state must change between flows, move that behavior into a guided sim service instead of extending the harness or Riptide core.
Rule of thumb: if riptide lint passes but riptide adapt fails because accounts are zeroed, use a harness instead of adding protocol-specific setup to Riptide core.
Generate The Crate
riptide init --wizard can scaffold a TODO harness on the advanced path. In the default thin-init flow, /riptide-config decides whether one is needed. If the repo does not have one yet, generate it from the adapter:
riptide harness generate --adapter .riptide/adapters/<program>.tomlThen edit .riptide/harness/src/main.rs. The generated crate imports riptide_engine::harness and implements RiptideHarness::setup.
Common Setup
The harness context gives you helpers for the setup most Solana protocols need:
- Declared accounts — check adapter accounts with
ctx.require_declared_account. - Raw bytes — write shared or per-agent data with
ctx.set_shared_account_dataandctx.set_agent_account_data. - SPL state — create mints, shared vaults, and per-agent token accounts with
ctx.spl_mint,ctx.spl_token_account, andctx.agent_spl_token_account. - Addresses — derive and bind PDAs with
ctx.derive_pda,ctx.bind_shared_account, andctx.bind_agent_accounts. - Programs — load a sibling program with
ctx.load_program_from_so.
use riptide_engine::harness::{run_harness_cli, HarnessContext, RiptideHarness};
struct ProjectHarness;
impl RiptideHarness for ProjectHarness {
fn setup(&self, ctx: &mut HarnessContext<'_>) -> anyhow::Result<()> {
let authority = ctx.admin_pubkey();
let mint = ctx.spl_mint("mint", authority, 1_000_000_000, 6)?;
ctx.spl_token_account("vault", mint, authority, 500_000)?;
for agent_idx in 0..ctx.agent_count() {
let owner = ctx.agent_pubkey(agent_idx)?;
ctx.agent_spl_token_account("user_ata", agent_idx, mint, owner, 100_000)?;
}
Ok(())
}
}
fn main() -> std::process::ExitCode {
run_harness_cli(ProjectHarness)
}SPL Decoder Flow
The generic adapter can observe raw external account bytes through decoders. SPL Token account and mint support are presets over that generic layout layer, not protocol-specific integrations.
[accounts.vault_src]
kind = "shared"
space = 165
decoder = "spl_token_account"
[state_mapping]
"vault_src.amount" = "pool.reserve_src"
[observations]
"pool.reserve_src" = "uint"Pair that adapter mapping with harness-created SPL token account bytes. Riptide decodes vault_src.amount every tick and the normal state_mapping surface drives coverage checks and invariants.
Validate
Keep validation bounded while you are authoring setup. Build the harness and run one seed first:
cargo build --release --quiet --manifest-path .riptide/harness/Cargo.toml
riptide run --adapter .riptide/adapters/<program>.toml --harness .riptide/harness --seeds 1 --seed-root 1337Inspect the result before launching a larger sweep. The smoke should show successful mapped actions, nonzero or moving decoded observations, and passing coverage checks such as state_movement.
For larger runs, multiply agents × ticks × seeds before launching. A 1000-agent, 30-tick, 50-seed sweep schedules 1,500,000 planned events.
Agent Skill
The riptide-config skill can draft or repair this crate as part of the post-init setup loop. It reads the adapter, IDL, and source/tests, edits .riptide/harness/src/main.rs, then validates with a one-seed riptide run --harness smoke.