Important
This repository is archived. Development has moved to
ditto-assistant/ditto-subnet,
with the maintained starter kit in
miners/dittobench-starter-kit.
Use the monorepo for current documentation, issues, and pull requests.
An agent and memory harness for DittoBench, the benchmark on Bittensor subnet 118 (SN118). Miners run an agent that the validator probes with tool-calling and memory-recall cases. You earn by being more correct than other miners. Latency is reported but not scored. A case that exceeds its per-case timeout scores 0.
| Repository | Purpose |
|---|---|
ditto-subnet |
Miner CLI, validator worker, and on-chain integration |
ditto-platform |
Public API, dashboard, submission coordination, and score ledger |
ditto-screener |
Platform-operated submission build and health gate |
ditto-harness |
Reference memory and agent harness library |
dittobench-starter-kit |
Miner starter project and local practice loop |
dittobench-api |
Validator scoring engine and hosted practice API |
dittobench-datagen |
Deterministic dataset generator and judge-free grader |
This repository is the maintained Rust reference implementation, not a Rust
requirement. Competition admission is language-neutral: submit any project that
has a Dockerfile at the tarball root and serves the documented HTTP contract.
Python, TypeScript/JavaScript, Go, Java, C/C++, or any other containerized
implementation is equally valid. The screener builds the image once; validators
load and run that exact content-addressed image.
The kit is a working baseline plus the full local eval loop (tool calling +
memory + speed) running locally against an embedded Turso (SQLite-family)
database with native vector search inside the
ditto-harness crate.
OpenRouter-backed models inherit its app attribution:
HTTP-Referer: https://heyditto.ai and X-OpenRouter-Title: Ditto.
It mirrors Ditto's production memory retrieval pipeline and ships the real ranking models as weights:
- Vector candidate pool over the seeded memories (cosine on 768-dim embeddings).
- Composite scoring (V2): 7 signals (semantic, linear + exponential recency,
subject frequency, subject semantic match, session continuity, neighbor density)
fused by weights from a weight-predictor MLP (
fixtures/models/mlp-weights.bin, the production architecture retrained on embeddinggemma, which predicts the fusion weights + scale from the query embedding + 17 aux features). - Cross-encoder rerank: a TinyBERT-L2 cross-encoder
(
fixtures/models/cross-encoder.onnx, ONNX viaort) reranks the top-20 pool and fuses with composite rank via Reciprocal Rank Fusion (k=60, ceWeight=0.7).
It also ships a self-contained seed user: a coherent slice of LongMemEval (a public long-term-memory QA benchmark) with subjects already synced.
There are two repositories, and it helps to be exact about the split.
- dittobench-starter-kit (this repo) is the Rust reference you can submit. You edit it, then
run
cargo run -- submitto package the whole crate as a tarball. The screener builds that tarball and validators score the resulting exact image. This kit is both a local test rig and an example submission. A different language may implement the same container/HTTP contract without this crate. - ditto-harness is a dependency, not a copy inside this repo. It is Ditto's
production memory and agent engine, pinned to one commit in
Cargo.toml. Cargo downloads it when you build. No harness source is checked into this kit, so there is nothing here to edit inside it, and you do not submit it.
The dividing line: ditto-harness knows nothing about DittoBench, the validator, or scoring. It is a generic memory and agent library, the same one Ditto runs in production. Every benchmark-aware line is in this kit.
- The engine (ditto-harness): how memories are stored, retrieved, ranked, and reasoned over. The memory store and vector database, the retrieval and ranking pipeline, the agent loop, and the model and embedder clients. It exposes its pieces as slots: it takes an embedder, a ranking-weight predictor, and a reranker, and does not care where they come from.
- The kit (this repo): everything benchmark-specific, plus your work. It fills
those slots with concrete pieces and your weights (
src/baseline.rs), speaks the validator protocol (/health,/seed,/run), ships the tool catalog and the seed user, runs the local practice loop, and is what you customize and submit.
So the loop is: edit this kit, submit this kit. You tune the engine by what you hand it from the kit (your prompt, your reranker, your weights, your retrieval config), including the retrieval algorithm itself, which moves your score most. You rarely edit ditto-harness directly; see Changing the retrieval algorithm for how far the kit reaches and the one case that needs a fork.
src/baseline.rs is the natural entry point most changes start from. It wires
the pieces together (database, embedder, model, retrieval, tools) and is marked
with EXTENSION POINT comments where you plug in your work. It is a starting
point, not a boundary: nothing restricts you to this one file. You can add your
own modules, edit any other file, pull in new crate dependencies, and change the
Dockerfile. The only hard requirement is that docker build . succeeds and
the resulting image serves the protocol (see What you're free to change and
The fixed interface).
This is what miners optimize, and it is the only thing that moves your score. You
make the harness remember and act better. These levers are wired in
src/baseline.rs, which is where most work starts, but you are not confined to
that file: you can edit, add, or restructure any file in the crate and submit the
whole thing (see What you're free to change). The three levers:
- Memory retrieval: from a user's history, find the exact past facts that answer a question. This is the harder half of the score.
- Tool use: pick the right tool for a request, and no tool when none fits.
- Orchestration: the prompt and control flow that turn a question into a correct answer.
See How to optimize for the exact knobs, Per-question-type levers for the mechanism behind each scored question type, and Changing the retrieval algorithm for how far the retrieval lever reaches.
Two things are held constant on purpose and never move your score: the model (the validator serves one frozen model and ignores yours) and latency (measured, not scored). See What isn't scored, and why. You do not compete on model choice or hardware.
Why this is the competition: Ditto's product is memory, an assistant that recalls what you told it across sessions. DittoBench scores that exact capability on Ditto's real production retrieval stack, not a stand-in, so a harness that scores higher is a better memory system, and strong work can flow back into the product. The composite score is half tool accuracy, half memory recall.
The loop is: edit this kit, test locally, submit this kit. In order:
- Edit
src/baseline.rs: change the prompt, the retrieval config, the reranker, the ranking weights, or the tools (the levers above). - Test retrieval, fast and offline:
cargo run -- mem-eval --k 10reports recall@k with no LLM (needs only Ollama for embeddings). Run this after any change to retrieval, weights, or the reranker. - Test the full agent:
cargo run -- evaluate(fixed inputs, best for iterating) orcargo run -- practice(rotating inputs). Watch the composite, the per-category tool means, and the slowest cases. Run this after any change to the prompt or tools. - Rehearse against the real validator (optional, recommended before you submit): serve your harness and drive it from the playground Submit tab. This is the only local run with a fresh random dataset per submission, and the only one that exercises Tier B/C seeding. See Hosted practice.
- Package:
cargo run -- submitbuildsdittobench-submission.tgzfrom your whole crate. - Go on-chain: register a hotkey on netuid 118 and upload with the eval fee. The screener builds your image and validators score it under the model lock. See Mining on SN118.
You never leave this repo for any of it. ditto-harness is fetched automatically when you build.
| File | What it is |
|---|---|
src/baseline.rs |
The agent you optimize. Wires DB + embedder + model + MLP predictor + reranker + harness. |
src/reranker.rs |
ONNX cross-encoder reranker, the production rerank stage, 1:1. |
src/seed.rs |
Loads the bundled LongMemEval seed user into the vector DB. |
src/protocol.rs |
The validator HTTP wire contract (see PROTOCOL.md). |
src/catalog.rs |
The Ditto tool catalog presented per case. |
src/datagen.rs |
Deterministic-per-seed dataset generator. |
src/scorer.rs |
Local score report (tool accuracy + memory + latency). |
src/bin/dittobench-miner.rs |
CLI: serve, playground, seed-user, mem-eval, evaluate, practice, submit. |
fixtures/seed-user/ |
The seed user: pairs + pre-synced subjects + subject graph + LongMemEval questions. |
fixtures/models/ |
Shipped weights: mlp-weights.bin (217K-param MLP) + cross-encoder.onnx (TinyBERT-L2 INT8) + BERT vocab. |
scripts/build-seed-user.py |
Regenerates the seed-user slice from the LongMemEval fixture (maintainers only, inputs not distributed). |
SETUP.md is the step-by-step guide for setting up this kit with
ditto-harness(the crate dependency), including Ollama and.env.
# 1. Pick a chat model for LOCAL practice. Default provider is OpenRouter and
# the default model is openai/gpt-oss-20b, matching benchmark v8. Canonical
# scoring overrides local model settings and serves the same locked model
# through ticket-scoped platform inference.
export OPENROUTER_API_KEY=sk-or-...
# (optional) export DITTOBENCH_MODEL=<any OpenRouter model id>
# ...or run fully locally with Ollama (edit the same .env file):
# DITTOBENCH_PROVIDER=ollama
# DITTOBENCH_MODEL=gpt-oss:20b
# ollama pull gpt-oss:20b
# ollama pull embeddinggemma
# cargo run -- ollama-check
# 2. Embeddings use Ollama's embeddinggemma (768-dim) by default. For memory
# cases you need it running locally:
# ollama serve
# ollama pull embeddinggemma
# 3. Load the seed user (one-time, embeds pairs + subjects), then practice.
cargo run -- seed-user # load the LongMemEval seed user
cargo run -- mem-eval --k 10 # retrieval recall over the seed user (no LLM)
cargo run -- evaluate # FIXED local submission test (static user + same questions)
cargo run -- practice --n 20 # ROTATING random dataset (anti-overfit, like the hosted validator)
# 4. Serve the harness for the validator.
cargo run -- serve --port 8080Local Ollama uses the canonical gpt-oss:20b chat model and embeddinggemma
embedder and requires no provider credential. Scored v8 runs use
the validator-injected, ticket-scoped platform inference and hosted embedding
routes; Ollama is not a scored fallback.
The interactive playground is a chat UI wired to a production-Ditto agent:
the v2 system prompt + persona + tool-use policy, the model set by
DITTOBENCH_MODEL (.env.example ships openai/gpt-oss-20b, the v8 scored model; set
google/gemini-3.1-flash-lite to mirror prod Ditto's model), the full tool
catalog, and real memory retrieval + cross-encoder rerank over the seed user. Action tools
(search_web, create_image, agent jobs, settings, …) return fake-but-plausible
results so you can exercise tool-calling without real integrations. Memory
tools are real and query the seed user.
cp .env.example .env # paste your OPENROUTER_API_KEY into .env
cargo run -- seed-user # one-time: load the dummy seed user
cargo run -- playground # open http://127.0.0.1:8088The UI shows the full tool catalog (every tool's description + JSON schema),
and after each turn a trace of the tool calls (args + fake results) and
the memories retrieved for that query. Try "search the web for…"
(search_web fires) or "how many postcards have I collected?" (memory
retrieval answers with ditto://memory/… citations). The Submit tab scores
your harness against the official hosted validator. See Hosted practice
below.
evaluate(local, fixed): scores your submission against the same inputs every run: the static seed user, the same bundled LongMemEval questions, and a fixed-seed tool set. Inputs are reproducible and model output is still stochastic.practice(local, rotating): re-rolls prompts per run, but from a small fixed template pool (10 memory facts). It varies wording, not substance, and never exercises the seeding tiers/waves.- Hosted validator: generates a fresh random v8 dataset per submission, like the on-chain SN118 validator, and is the only pre-chain rehearsal of the staged seeding waves and real question mix. Drive it from the playground's Submit tab (below). A
harness_urlpractice submission uses that harness's local model configuration; scored submissions use the ticket-bound platform routes.
Use evaluate to develop.
The hosted validator is available. The playground's Submit tab drives it:
- Serve your harness and expose it publicly so the validator can reach it:
cargo run -- serve --port 8080, then e.g.ngrok http 8080. - Set
DITTOBENCH_HARNESS_URLin.envto the public URL. .env.example ships the officialDITTOBENCH_API_URL. cargo run -- playground→ open the Submit tab and pick a run size. Canonical v8 scoring uses locked GPT-OSS-20B inference supplied through the platform's ticket-scoped route. The playground does not send a provider key. The local target continues to use only the model configuration of your already-running harness.
The active submission target is the uploaded tarball. Hosted harness_url
practice can drive any already-running implementation. Production v8 validators
never build miner repositories and never provide a GitHub credential to a build.
seed-user and mem-eval need only Ollama (embeddinggemma). No chat model
or API key is required. mem-eval runs the full
production pipeline (MLP weights + composite V2 + cross-encoder rerank) and
reports recall@k per LongMemEval question type, isolating retrieval quality
from the LLM. Keep the same DITTOBENCH_DB across seed-user and mem-eval.
cargo build and cargo test need no model or embedder, but the first
build needs network (the git dependency fetch, and the ort crate downloads
ONNX Runtime). Rebuilds are offline. Only practice/serve call out
to the model + Ollama at runtime.
The validator calls POST /run with a RunRequest (system prompt, user
input, available tools) and expects a RunResponse (final text, observed tool
calls, token usage, latency). Before memory questions it installs a haystack via
POST /seed. Full shapes in PROTOCOL.md.
The system_prompt field is an input, not a fixed prompt imposed on you. Your
harness decides what actually reaches the model: keep it, extend it with your own
tool-use and abstention policy, or replace it entirely. The prompt the agent runs
on is yours to control, the same as the retrieval config and the tools. The stock
baseline layers the production Ditto system prompt on top of this field; shaping
that is one of the three levers below.
The local submission lab validates a signed Ditto Memory Passport, converts it
to staged /seed requests, then puts a multi-turn chat UI in front of a
reviewed submission's /run endpoint. The target must implement GET /health,
POST /seed, and POST /run. The authoritative contract in
dittobench-api/pkg/protocol/protocol.go defines /seed and the optional
RunRequest.user_id used for graph isolation, mirrored in this kit's
PROTOCOL.md and baseline server. Use a reviewed adapter for an
older submission that predates those fields.
For the fastest manual review, start the browser workbench from a shell that already has the desired provider key:
python3 scripts/submission-workbench.pyIt opens http://127.0.0.1:4320. Drop the reviewed submission tarball, choose
OpenRouter or local Ollama, acknowledge local source execution, and
click Build, load & chat. The Ditto Memory Passport ZIP is optional: omit it
for a genuinely fresh blank-memory chat, or add it to experience the harness
against real Ditto memories. The same page shows preparation, safe extraction,
the Rust reference's Cargo release build, harness health, and memory
initialization progress before
turning into a multi-turn chat. Review another tarball stops the current
harness, removes its temporary files, and resets the page for the next
submission.
OpenRouter appears as available only when OPENROUTER_API_KEY is present in
the workbench process. Only the selected provider's key is passed to the child.
When a Passport is present, the default
quick-chat mode verifies the entire signed export but seeds only the first 100
seedable conversations so a reviewer can reach chat quickly; select Full
export for compatibility and long-tail retrieval testing. Neither mode
changes the original export. Without a Passport, the workbench sends an empty
/seed wave to create a fresh isolated user namespace before enabling chat.
The current browser workbench is a convenience for this Rust reference; other
languages can use the lower-level lab with a custom start command or call the
same /health, /seed, and /run endpoints directly. The workbench binds to
loopback, stores uploads in a private temporary
directory, displays the uploaded tarball's SHA-256, and never exposes memory
contents or counts in its status API. It is still intentionally not a code
sandbox: the acknowledged submission runs as your local account. Use a
disposable VM or container for hostile or unreviewed code.
The lower-level lab remains available for CI, digest-pinned runs, custom start commands, already-running harnesses, and Passport-only validation.
Validate the export before exposing its contents to a harness. By default the
lab verifies both the archive's Ed25519 signature and the signing key against
Ditto's production API key endpoint (https://api.heyditto.ai):
python3 scripts/submission-lab.py \
--passport ~/Downloads/ditto-passport.zip \
--check-onlyThen point it at an already-running, reviewed local harness. A base URL or its
/run URL is accepted:
python3 scripts/submission-lab.py \
--passport ~/Downloads/ditto-passport.zip \
--agent-url http://127.0.0.1:8080/run \
--port 4320The original cloud user ID is not reused: every run seeds a fresh random local
user unless --user-id is supplied. The lab advertises the current read-only
memory tool catalog and carries the last 20 messages (up to 10 user/assistant
exchanges) in system_prompt; a submission that ignores that field may still
behave as a single-turn agent.
The UI starts as soon as validation and harness health checks pass, reports a
seeding, ready, or error state, and rejects chat requests until every
staged seed wave has completed. Seed failures expose only a non-private error
category in /api/meta; memory IDs and backend response details stay out of the
browser surface.
For a faster subjective smoke test, pass --max-pairs N. The lab still verifies
the complete Passport signature, manifest, counts, graph references, and issuer
authority before selecting the first N seedable memories in signed export order.
Only subjects and links referenced by that sample are retained. The local status
reports memory_scope: bounded_sample without exposing a count or content. This
mode is useful for quickly experiencing a harness, but it is not a full-export
replay and must not be treated as a complete compatibility or quality verdict.
Omitting --max-pairs always seeds the full validated export.
For an offline real export, save a trusted base64url Ed25519 public key in a
file and pass --verification-key PATH. --trust-embedded-key proves only
archive integrity, not Ditto provenance, and is intended solely for synthetic
fixtures or explicit offline experiments. Staging and local backend exports can
use --verification-base-url https://staging-api.heyditto.ai (or a loopback
backend origin); the signed issuer remains https://heyditto.ai.
To start an audited submission tarball, pin the exact digest reviewed in Backroom and pass only the provider secret it actually needs:
python3 scripts/submission-lab.py \
--passport ~/Downloads/ditto-passport.zip \
--submission ~/Downloads/reviewed-submission.tar.gz \
--submission-sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
--allow-run-untrusted \
--pass-env OPENROUTER_API_KEY \
--seed-max-request-bytes 25165824 \
--agent-port 8081 \
--port 4321Important security boundaries:
- Passport files and responses are size-bounded; signed file coverage, counts, graph references, duplicate IDs, timestamps, and the issuer key are checked before seeding. Signed historical rows with neither prompt nor response are validated but omitted because the retrieval seed contract requires text.
- The lab accepts loopback harness URLs by default.
--allow-remote-agentmeans the real memory export will be uploaded to that remote service and requires HTTPS for the non-loopback URL. - Seed waves are limited by both pair count and serialized JSON size. Lower
--seed-max-request-byteswhen an adapter has a smaller request-body limit. A single pair and its linked subjects are never split across requests. - A child process gets a small build/runtime environment and no provider keys
unless named with
--pass-env. The selected model provider can still receive prompts and retrieved memory content during normal inference. - Digest pinning and safe extraction do not make miner code safe. The source process is not container-sandboxed: it runs as your local account and can access files and the network available to that account. Review it first and use a disposable VM/container for hostile or uncertain submissions.
- Gzip, bzip2, xz, and uncompressed tarballs are detected by their magic bytes
and read as a stream. One strict decompressed-byte limit covers tar headers,
padding, PAX/GNU metadata, file bodies, and trailing data; member-count and
declared file-size limits are also enforced before each body is extracted.
Conventional
.and./root layouts are normalized, while traversal, links, special files, duplicate names, case collisions, and file/directory collisions are rejected. - Default Cargo startup uses the extraction root when it contains
Cargo.toml, or one non-hidden top-level wrapper directory containingCargo.toml; missing or ambiguous layouts fail closed. An explicit--start-commandinstead runs from the extraction root so the lab never guesses a custom project's layout. - A source directory is mutable and cannot be pinned by this script. Prefer the
exact reviewed tarball plus
--submission-sha256for reproducible tests.
The starter harness and active validators serve benchmark version 8 only.
Every submission gets a
fresh procedural persona universe, and the composite is
0.5 × tool + 0.5 × memory. The wire contract lives in
PROTOCOL.md; the public scorer owns the versioned dataset and
grading contract.
BASELINES.md reports what the stock kit scores under the locked
model (the target to beat) and its weakest categories.
Memory is seeded in three tiers (see PROTOCOL.md POST /seed):
A prepared subjects, B raw pairs only (build your own subject index),
C staged waves interleaved with runs (upsert each
wave). The bundled harness reuses the production save_memory path in
seed.rs. Extending it to construct subjects when none are provided (Tier B) is
the highest-value change you can make.
The three levers from What you optimize, in detail. Each is wired in
src/baseline.rs, marked EXTENSION POINT, and free to spill into your own
modules. The chat model is locked (see What isn't scored, and why), so the levers
are retrieval, the prompt, and tools:
- Retrieval / memory: the production stack is wired and active, including the
weight-predictor MLP, composite V2, and the cross-encoder reranker
(
open_store). Tune it by retraining/swappingfixtures/models/mlp-weights.bin, swapping the cross-encoder ONNX, adjusting the RRFk/ceWeightinreranker.rs, or changingcandidate_pool_size/variant/limits. Measure withmem-eval(recall@k). Memory is the harder half of the composite and retrieval recall is the main bottleneck, so this is the highest-value scored lever. - System prompt: you own the prompt the agent runs on. The
system_promptin eachRunRequestis an input you can extend or replace; layer on a tool-use policy and abstention rules so the agent picks the right tool (and no tool when it shouldn't). - Tools: the baseline registers the per-case tool catalog and executes each non-memory call through the validator-provided endpoint. The returned value is fed back to the model and the validator grades the observed trajectory.
The stock harness allows 24 model turns so composed tasks can include retries, dependent calls, and useful exploration. This is a starter implementation guardrail, not a scoring cap: a correct trajectory is not penalized merely for using more than 15 tool calls, and miners may tune the harness turn bound.
Two things you might expect to tune do not affect your score: the model and latency. Both are held out on purpose.
Model. Every miner is scored on the same frozen model. A scored crate run builds
your image and serves it under the lock: the validator's relay overrides
DITTOBENCH_MODEL and serves GPT-OSS-20B through the platform inference relay regardless of what baseline.rs
sets (see Mining on SN118), so swapping the model changes only local practice
speed and cost. The benchmark measures the harness (memory, retrieval, agent
orchestration, tool selection), not the model. If model choice were scored, the
board would rank who can afford the strongest frontier model, not who built the
best agent, turning an open-source harness competition into a spend race. One
frozen open-weight model holds that variable constant, so score gaps reflect
harness quality and every miner is scored under identical, attestable conditions:
the ticket-bound route proves the locked model actually ran, so no one can
quietly swap in a stronger one. Keep the kit default
(openai/gpt-oss-20b) so practice tracks scoring.
Latency. Reported as median_ms on the leaderboard, never scored. It measures
hardware and model-provider speed, not harness quality, and it varies with
sandbox load, so two validators would compute different numbers, and the weight
fold requires every validator to derive the same score from the same public
ledger. Speed is bounded instead of ranked: a case that misses its per-case
timeout scores 0, and the tool-efficiency factor penalizes over-calling, so the
efficiency that reflects harness quality is captured without tying the score to
raw hardware speed.
Put your effort into the three levers above.
Run mem-eval after retrieval changes (recall@k, no LLM) and practice after
agent/tool changes (watch composite, per-category tool means, slowest cases).
Every memory question type maps to a concrete mechanism in this kit (or one you can add). Nothing is scored that lacks a lever; the lever is the capability being measured:
| Question type | Miner lever |
|---|---|
| single/multi-session, preference, knowledge-update | retrieval quality: composite signals, the subject index, recency handling (latest value wins) |
| temporal, trajectory, duration | timestamp arithmetic over the seeded pairs' timestamps. Order and elapsed time come from the transcript, not the model's guess |
| assistant-recall | store and index the ASSISTANT turns, not just user turns (the answer only ever appeared in an assistant reply) |
| aggregation, computed | mention counting across sessions; deliberately punishes naive dedup collapse of repeated topics |
| canary | a lexical/exact-match index. Embeddings represent random tokens (VK-… codes) poorly, so semantic-only retrieval misses them: a concrete, winnable gap the stock kit does not attempt |
| injection-resistance | a system-prompt guard: the frozen harness model complies with embedded overrides unless YOUR harness defends; a scored, discriminative surface the stock kit does not attempt |
| isolation | honor user_id scoping (already wired in the kit's store) |
| abstention, DRM lure (a related decoy that tempts a false recall) | confidence gating + the abstain wire flag. Decline when retrieval finds nothing (or finds only someone ELSE's value) instead of fabricating |
| contradiction | read the LATEST stance from memory: some opinions were reversed ("no longer do it") and some were not ("still love it"). Both answers occur under the same question surface, so the signal must come from retrieval |
The two rows that most separate a naive submission from a competitive one are canary (needs a lexical index) and injection (needs a prompt guard): both are scored on every run and the stock kit leaves them on the table.
The kit defaults to local Ollama embeddinggemma (768-dim) for a free,
self-contained loop. To make the ranker work in that space, the shipped MLP is
retrained on embeddinggemma (via the production training pipeline, on
LongMemEval).
On the bundled seed user this lifts retrieval from hit@10 0.90 → 0.96 vs the
Vertex-trained weights. The cross-encoder rerank is embedder-independent (it
scores raw text), so it is identical to production.
DittoBench v8 keeps this exact 768-dimensional OllamaEmbedder interface, but
the trusted validator gateway serves it with the reviewed OpenRouter profile
dittobench-v7-openrouter-pplx-embed-v1-0.6b-768-v1
(perplexity/pplx-embed-v1-0.6b, Perplexity only, no fallback). The harness
does not receive an OpenRouter key and cannot select a sibling model or
provider. Local practice and historical benchmark versions continue to use
embeddinggemma.
The v8 efficiency contract intentionally retains this embedding profile (whose
immutable revision name includes v7) and the existing MLP weights. A model or
dimension change requires a new profile and calibration.
If you switch build_embedder to a different embedder, retrain the MLP for
that space. The production training pipeline is not distributed, but the
artifact format is documented (fixtures/models/README.md
and the harness's mlp.rs), so you can train your own with any pipeline and
drop it in. To run the exact production stack, use Vertex text-embedding-005
- the production
model.bin.
Retrieval is the main lever on your score, so this is worth being clear about:
you can change the ranking algorithm itself, and for the most part you do it from
this kit, not by editing ditto-harness. The harness exposes the pipeline as
seams that baseline.rs already wires up:
- Reranker: it is a trait.
baseline.rsbuilds anArc<dyn Reranker>and injects it. Implement your own reranker in yoursrc/and pass it in, or swap the model infixtures/models/cross-encoder.onnx. - Fusion weights: the weight predictor loads your bytes
(
MlpPredictor::load_from_reader), so retrain and drop in your ownfixtures/models/mlp-weights.bin. - Raw retrieval:
store.db()exposes the database directly (candidate search, subjects, raw rows). You can bypass the built-in ranker entirely and score candidates with your own algorithm in your crate. - Candidate pool and variant:
CompositeSearchRequestexposescandidate_pool_size,variant, and limits. - New retrieval capabilities (a lexical index for canary codes, a subject index,
indexing assistant turns): add them in your
src/on top of the store API.
You only fork ditto-harness to edit its built-in composite scorer in place (its
internal signal math and candidate queries), which you can otherwise override or
bypass with the seams above. To fork: point the ditto-harness git URL and
rev in Cargo.toml at your fork. Editing a local clone has no effect on your
submission unless you repoint the dependency, because the build uses the pinned
commit. Any dependency needed by the submission build must be public or
vendored into the bounded artifact. Screeners deliberately provide no GitHub
credential or other build secret to submission-controlled Dockerfiles.
cargo run -- submit # packages dittobench-submission.tgz + prints next stepssubmit runs tar -czf dittobench-submission.tgz . (excluding target/,
.git, *.tgz, *.db, *.db-*, .env, .env.*, and it prints the exclusion
list). Never commit or package your .env. It may hold your local-practice
OPENROUTER_API_KEY, and the tarball is uploaded to the platform. You submit
the entire buildable project, with the Dockerfile at the tarball root:
Dockerfile,Cargo.toml,Cargo.lock(this Rust reference)src/, including your editedbaseline.rsand thedittobench-minerserverfixtures/, the ONNX models + seed data your harness loads at runtime
An alternate implementation instead packages its own normal project files—for
example Dockerfile + pyproject.toml, package.json, go.mod, or any other
build inputs. Cargo files and a src/*.rs layout are not part of admission.
The tarball is capped at 20 MiB. The shipped fixtures/models/ fit well under
that; if you bundle larger weights, keep the packaged tarball within the cap.
You are not submitting src/baseline.rs on its own, and you are not
submitting ditto-harness. ditto-harness is a pinned public git dependency
of your crate. The Docker build fetches it.
The screener builds your tarball in a credential-free rootless Docker boundary. Validators load the exact verified image, run it, then score it. A submission is only valid if it keeps this contract intact:
| Must hold | Why |
|---|---|
A Dockerfile at the tarball root |
It's the validator's Docker build context. |
docker build succeeds |
A pre-screen gate rejects submissions that don't build. |
The image serves GET /health, POST /seed, POST /run on :8080 |
The validator drives your harness over these (see PROTOCOL.md). |
POST /run returns a well-formed RunResponse |
The scorer grades tool_calls + final_text. A malformed body scores 0. |
Everything else is yours: baseline.rs holds the scored levers from How to
optimize (system prompt, retrieval knobs, tools) plus the model, which only
affects local practice; then any other src/ file, added crate dependencies,
your own fixtures/models/ weights, even the Dockerfile build steps.
Restructure the crate however you like, as long as docker build . still
produces a container serving that protocol on :8080.
Status: the hosted practice validator, on-chain submission path (
ditto upload, eval fee, scoring, weights), and the SN118 leaderboard are live today. Benchmark v8 is the only active scoring contract. It uses lockedopenai/gpt-oss-20binference and validator-observed tool execution.
-
Registration. You need a hotkey registered on subnet netuid 118 (
btcli subnet register --netuid 118) and TAO for the registration cost plus per-submission eval fees. -
Submission + fee. This kit's
submitonly packages the tarball. The on-chain upload happens throughditto upload(the miner CLI from the ditto-subnet repo), with your registered hotkey. The Backroom-controlled fee is denominated in TAO and is currently0.04 TAO(40,000,000 rao). The CLI shows the exact TAO amount before you confirm. TAO/USD conversion is reporting-only and does not affect the fee, admission, or payment validation.
The CLI saves a finalized payment receipt before upload so a failed upload can recover it for 24 hours without paying again. An abandoned unpaid reservation can be replaced after 15 minutes. Follow the ditto-subnet miner guide for the canonical upload, retry, and receipt-recovery commands.
-
The runtime contract. Scoring is one-shot from your uploaded tarball. You do not keep a server running. A screener builds your
Dockerfileonce; the scorer loads that exact image, starts it, and injects runtime model configuration for a trusted local broker plus a freshDITTOBENCH_DBpath. No validator or provider credential is placed in the harness container. The broker supplies only ticket-scoped v8 inference and locksDITTOBENCH_MODELto GPT-OSS-20B. The Docker host gateway is mapped so the defaultOLLAMA_BASE_URLresolves to the scoring host's Ollama, which serves the referenceembeddinggemmaembedder. If you use a different local embedder, bundle it in your image. The container has network egress to model providers (hardened deployments may restrict egress to an allowlist). Your harness must read all model config from env.Baseline::from_envalready does this, so keep that property if you rewrite it. -
Timeouts. 10 s for
/healthto come up, 60 s per/runcall (a case that misses it scores 0), 5 minutes per/seedwave. The table is in PROTOCOL.md. -
Run shape. An on-chain run is
run_size=full: on the order of 50 memory cases + 60 tool cases, with 2 staged seeding waves, a substantial Tier-B raw-pairs share, and a handful of isolation cases across separate user graphs. Exact counts can change withbench_version. -
Economics (king-of-the-hill). Competitive weight is distributed
65% / 14% / 10% / 7% / 4%to the champion and the next four distinct miners; everyone else earns nothing. The competitive vector takes 100% of the available miner emission, so nothing is burned while eligible miners exist (with no eligible miners, 100% is burned). A challenger dethrones the incumbent only after clearing the greater of a fixed0.007composite-point hysteresis and the statistical error band. Frombench_version6 on, that band shrinks smoothly once the incumbent exceeds0.60, keeping the crown contestable as scores approach the ceiling; a near-miss is settled by re-scoring both agents on shared seeds rather than dataset luck. Weights are recomputed from the public score ledger on every validator sweep. -
bench_version. Version 8 is activated and is the only version accepted by this starter harness. Every
/runrequest must include"bench_version": 8. -
Lifecycle. After upload your agent goes
uploaded → evaluating → scored, orscreening_failedif the Docker build or/healthfails (fix and resubmit). Scores land on the public score ledger and the SN118 leaderboard, with median latency,bench_version, and per-category stats. -
Anti-gaming. The dataset is procedurally regenerated per run from a fresh seed. Grading is deterministic (no judge to prompt-inject): emitting an embedded injection payload, surfacing another user's value, or naming a distractor value zeroes the case, and those events land in the run's public details. Malformed responses, timeouts, and build failures score 0. Observed tool execution (the validator runs your tool calls against its own mock endpoint) is mandatory on scored v8 tool cases; an unobserved observable case scores 0. Beyond per-case grading, the composite carries bounded integrity multipliers: a per-run canary nonce (bounded penalty for an honest miss, hard cap for leaking the decoy), a metamorphic-consistency factor over invariance families, and the tool-efficiency factor. All three are detailed in PROTOCOL.md and are pure functions of the published run details.
A fraction of every scored run's cases is also re-asked under a reproduce-under-transform audit. Those cases are ordinary graded cases: some are the same question in different wording, and some shift what is being asked (for example asking what a value was before the most recent change) so that the correct answer differs from the base case's. Which cases are audited and how they are transformed both derive from the dataset seed, and that seed comes from a block hash fixed after you submit, so neither is predictable at commit time.
Practically: answer the question that is actually in front of you. A harness
that dispatches on a question's exact surface form (a template fingerprint, a
lookup keyed to the original phrasing, or an answer precomputed for it) will
answer the base case and miss its transformed sibling, and that split is
reported as transform_robustness in the run details. A harness that genuinely
reads the conversation and recomputes its answer scores the same on both, so
there is nothing here to defend against beyond not hard-coding phrasings. The
run also publishes audit_case_count so you can see how many pairs the number
rests on.
-
Originality (duplicate detection). Before scoring, the platform compares your uploaded harness against every other miner's eligible submission across several dimensions: exact bytes, normalized source (comments, whitespace, and formatting stripped, so a reformat/recomment/file-rename does not hide a copy), lexical and language-aware structural fingerprints, the prompt/strategy text, and a semantic code-embedding vector. A runtime behavioral signal (your observed tool-call trajectory on a shared dataset seed) is not folded into this comparison today. An exact or trivially repackaged copy of another miner's agent is held for manual review and excluded from the ledger (zero weight) while held. The earlier upload wins by first-seen. In the softer "similar but not identical" band a hold requires agreement across multiple independent signals, so legitimate convergence is not penalized: building on this starter kit and the shared
ditto-harnessdependency is expected, and two miners independently arriving at similar prompts or structure is fine. Detection targets copying another miner's submission; shared use of the public baseline is expected. First-seen protects the original author over a later uploader. Forking a leaked crate and renaming its symbols does not earn. Differentiate on substance: change the prompt, retrieval, or tools (see How to optimize). The model is not a differentiator, since scoring locks every miner to the same frozen model. -
Hardware. The reference stack runs on CPU: Ollama
embeddinggemma(~1 GB), the TinyBERT ONNX reranker, embedded SQLite. 8 GB RAM is sufficient. No GPU is required unless you bundle a local LLM. -
Support. Open a GitHub issue on this repo.
- Do not overfit the local scorer. The local dataset generator is a simplified pool while the validator's persona universe rotates every run. See the blockquote in PROTOCOL.md.
- Arguments weigh as much as selection on-chain. The on-chain
deterministic tool grade is
0.4 name F1 + 0.4 argument F1 + 0.2 trajectory(see PROTOCOL.md). Only the local scorer is name-centric. - Latency is not scored (only reported as
median_ms); a case that misses its per-case timeout scores 0, and the tool-efficiency multiplier bounds over-calling on observed runs. The rationale is in What isn't scored, and why. Measure withpractice. - Do not key answers to a question's exact wording. A fraction of every scored
run is re-asked under an unpredictable rephrasing (and some transforms change
what is being asked, so the base case's answer becomes wrong). Surface-form
dispatch answers one and fails the other; see
transform_robustnessin the run details. - Memory needs the seed user loaded + Ollama embeddings. Run
seed-userfirst. Ifmem-evalreportsrecall@k: 0.000, see SETUP.md → Troubleshooting.
MIT (LICENSE). The ditto-harness dependency is also MIT-licensed.