Skip to content

jaysonmulwa/nilvar

Repository files navigation

nilvar

Python 3.11+ Tests Coverage mypy ruff License: MIT Open In Colab

Event-sourced campaign ledger for AI-driven scientific discovery — 11 trajectory analytics from the Bayesian optimization and self-driving lab literature.

Part of the Deep Science Tooling series: production-grade CLI tools for scientific research.

Why

AI-driven discovery loops (materials, chemistry, drug candidates) generate and screen candidates per iteration. The loop is fragile in ways current tooling can't surface: surrogate-model exploitation, diversity collapse, repeated failed experiments, and poor budget visibility.

Existing tools record independent runs. A discovery campaign is a causal loop (propose → test → learn → decide). The questions that matter — is the surrogate decoupling from reality? is the search collapsing? — are functions of the trajectory, not properties of current state. nilvar models the campaign as a causal process and mines it for discovery-health signals.

How it compares

Researchers already track experiments with other tools. nilvar occupies a different niche: it models the campaign as a causal trajectory, not a collection of independent runs.

Tool What it tracks Where nilvar differs
MLflow / W&B Model metrics, hyperparameters, individual experiment runs nilvar tracks the full propose-test-learn-decide loop across iterations; its analytics detect trajectory-level pathologies (proxy-gaming, diversity collapse) that per-run trackers cannot surface
Electronic lab notebooks Lab procedures, observations, results in human-readable form nilvar is machine-readable, event-sourced, and hash-chained for tamper detection; events are structured data, not prose
Spreadsheets / AirTable Ad-hoc campaign tracking, manual entry No immutability guarantees, no audit trail, no built-in analytics; a shared spreadsheet cannot prove it was not edited after the fact
Custom scripts Whatever you build nilvar ships 11 trajectory analytics grounded in the Bayesian optimization literature, plus counterfactual replay, out of the box
Ax / BoTorch / Dragonfly Surrogate models and acquisition functions (the optimization engine) nilvar does not replace the engine; it sits alongside it, recording what the engine proposed and what reality returned, then auditing the gap

nilvar is complementary to most of these. It ingests predictions from your surrogate model of choice and records outcomes from your lab or simulation, then mines the trajectory for signals that no individual tool above is designed to catch.

Table of Contents


A. Getting Started

Install

pip install -e ".[all]"

This installs the CLI, API server, analytics (FAISS), trajectory charts, and YAML ingest support.

Individual extras
Extra What it adds
cli Typer CLI (nilvar command)
api FastAPI server + uvicorn
analytics FAISS for vector similarity search
viz Matplotlib trajectory charts
ingest YAML file ingest support
dev Tests, linting, type checking
# Core only (event store + SDK + numpy-backed analytics)
pip install -e .

# Development
pip install -e ".[dev]"

Quick Start

# Initialize a campaign
nilvar init --campaign-id perovskite-opt --goal "maximize bandgap" --budget-cap 50000.0

# Ingest candidate data from a file
nilvar ingest --file candidates.json

# Record a validated lab result
nilvar record-result --candidate-id CsPbI3 --value 1.68 --uncertainty 0.05 --iteration 1

# Generate a campaign report
nilvar report
nilvar report --format html --output report.html

# Run analytics
nilvar counterfactual --policy greedy --top-k 1
SDK Quick Start (notebooks/scripts)
from nilvar.sdk import CampaignClient

client = CampaignClient("my_campaign.db")
client.create_campaign("perovskite-opt", "maximize bandgap", 50000.0)

# Your surrogate proposes candidates
client.propose("CsPbI3", {"Cs": 1, "Pb": 1, "I": 3}, 1.73, "v1.0", iteration=1)

# Lab results come back
client.start_experiment("CsPbI3", cost=5000.0, iteration=1)
client.record_result("CsPbI3", value=1.68, uncertainty=0.05, iteration=1)

# Check for proxy gaming (Goodhart's Law)
verdict = client.proxy_gaming()
print(verdict.verdict)  # "OK" or "GAMING"

# Check for diversity collapse
verdict = client.diversity()
print(verdict.verdict)  # "OK" or "COLLAPSING"

# Full campaign report
print(client.report())

See examples/basic_campaign.py for a complete 3-iteration campaign, or examples/notebook_demo.ipynb for an interactive Jupyter walkthrough. You can also run the notebook directly in Google Colab — no local install required.

For a realistic 5-iteration perovskite campaign that exercises every CLI command (CSV ingestion, failures, counterfactuals, reports), see the smoke test fixtures. The accompanying analytics guide explains how proxy gaming happens, what corrections exist, and how counterfactual replay quantifies the cost.


B. Architecture

Every campaign fact is recorded as an immutable, hash-chained event. The write side appends events; the read side folds them into disposable projections that can be rebuilt from scratch at any time. New analytics can be added retroactively over the full history — no migrations needed.

  WRITE SIDE                                              READ SIDE

  ┌──────────────────────────────────────┐   ┌─────────────────────────────────────────────────┐
  │                                      │   │                                                 │
  │  ┌─────────────────────────────────┐ │   │  ┌─────────────────────────────────────────────┐│
  │  │ Commands (CLI / SDK / API)      │ │   │  │ Projectors (rebuild from seq 0)             ││
  │  │                                 │ │   │  │                                             ││
  │  │ • propose    • record-result    │ │   │  │ • timeline (per-candidate event history)    ││
  │  │ • start-exp  • record-failure   │ │   │  │ • negledger (failure ledger by class)       ││
  │  │ • select     • reject           │ │   │  │ • budget (cumulative spend vs cap)          ││
  │  │ • close-iter • update-model     │ │   │  │ • iterations (per-iteration summary)        ││
  │  └───────────────┬─────────────────┘ │   │  └──────────────────────┬──────────────────────┘│
  │                  │                   │   │                         │                       │
  │  ┌───────────────▼─────────────────┐ │   │  ┌──────────────────────▼──────────────────────┐│
  │  │ Handler (Pydantic v2)           │ │   │  │ Analytics (numpy / FAISS)                   ││
  │  │                                 │ │   │  │                                             ││
  │  │ • validate schema               │ │   │  │ • proxy-gaming     • calibration drift      ││
  │  │ • content-hash dedup            │ │   │  │ • diversity        • Pareto front            ││
  │  │ • causal link (caused_by)       │ │   │  │ • discovery rate   • reproducibility         ││
  │  │ • hash-chain append             │ │   │  │ • model learning   • acquisition efficiency  ││
  │  └───────────────┬─────────────────┘ │   │  │ • regret           • explore/exploit         ││
  │                  │                   │   │  │ • data quality      • counterfactual replay   ││
  └──────────────────┼───────────────────┘   │  └─────────────────────────────────────────────┘│
                     │                       └─────────────────────────────────────────────────┘
                     ▼                                          ▲
            ┌────────────────────────────┐                      │
            │ EVENT STORE (SQLite)       │      fold from seq 0 │
            │                            │──────────────────────┘
            │ • append-only, immutable   │
            │ • SHA-256 hash-chained     │
            │ • content-hash dedup       │
            │ • WAL mode for concurrency │
            └────────────────────────────┘
  • Event sourcing: every event is immutable, causally-linked, and hash-chained (SHA-256)
  • CQRS: write side (append facts) and read side (fold trajectories) are separate
  • Replay: any analytic can be added later and rebuilt over full history — no migrations
  • Snapshotting: projections checkpoint at seq N and replay only from N+1 forward
  • Three surfaces, one handler: CLI, SDK, and API all wrap the same CommandHandler — agent, script, and human hit the same validation and dedup logic

Tech Stack

Layer Technology
Language Python
Schemas Pydantic
Event store SQLite
Hash chain SHA-256, linear tamper-evident chain
CLI Typer
API FastAPI
Analytics NumPy FAISS
Charts Matplotlib
Type checking mypy
Linting Ruff

Event Model

10 event types covering the full propose → test → learn → decide cycle:

Event Stage Role
CampaignCreated Setup Initialize campaign with goal and budget
CandidateProposed Propose Surrogate prediction captured at proposal time
ExperimentStarted Test Budget committed before outcome is known
ResultRecorded Test Validated measurement with uncertainty
FailureRecorded Test Classified failure (6 classes: synthesizability, stability, out_of_spec, non_novel, measurement_error, process_error)
BudgetConsumed Test Cumulative cost tracking
ModelUpdated Learn Surrogate retrained — resets proxy-gaming window
CandidateSelected Decide Decision with rationale and causal links
CandidateRejected Decide Decision with rationale and causal links
IterationClosed Decide Marks iteration boundary

Every event is hash-chained (SHA-256) for tamper-evidence and content-hash deduped for idempotent ingest.

Project Structure

src/nilvar/
├── events/        # Event envelope, catalog, failure taxonomy (WRITE SIDE)
├── store/         # SQLite append-only event store (WRITE SIDE)
├── commands/      # Command handler with validation + dedup (WRITE SIDE)
├── projections/   # Projector framework + core projections (READ SIDE)
├── analytics/     # 11 trajectory analytics (READ SIDE)
├── decisions/     # Counterfactual replay, pluggable policies
├── api/           # FastAPI (one API surface)
├── sdk/           # Python SDK for notebooks/scripts
├── cli/           # Typer CLI
└── report/        # Markdown + HTML report generators

C. Analytics

nilvar's differentiators — trajectory analytics that CRUD experiment trackers structurally cannot provide.

BO = Bayesian optimisation — the closed-loop surrogate-driven search that nilvar records.

Analytic What it detects Signal Field relevance
Proxy gaming Surrogate predictions rising while validated results stay flat Goodhart's Law in action All BO
Diversity collapse Candidate proposals contracting to a narrow region Exploration dying All BO
Discovery rate Cost per validated hit flatlines; zero-result iterations Campaign exhaustion All BO
Calibration drift Model uncertainty estimates diverge from observed errors Broken explore/exploit Materials, drug discovery
Pareto front Multi-objective hypervolume stops expanding Tradeoff surface stalled Materials, drug discovery
Reproducibility Replicate measurements show high variance (CV) Noisy labels Materials, drug discovery
Model learning MAE fails to improve across surrogate retrains Surrogate plateaued All BO
Acquisition efficiency Predicted rankings don't match actual outcomes (Spearman rho) Wrong candidate ordering All BO
Cumulative regret Gap between best-known and selected values grows linearly Non-converging acquisition All BO
Explore/exploit ratio Proposals consistently cluster at one extreme of the exploitation spectrum Imbalanced search All BO
Data quality Anomaly rate per iteration exceeds threshold (z-score outliers) Instrument/reagent drift Materials, drug discovery
Counterfactual replay Would a different policy have found the winner sooner? Decision quality All BO

Trajectory Charts

Install the viz extra for matplotlib-based trajectory visualisation:

pip install -e ".[viz]"

Charts are embedded automatically in HTML reports (nilvar report --format html). In notebooks, use the SDK plotting helpers (try them in Colab):

fig = client.plot_proxy_gaming()             # predicted vs validated divergence
fig = client.plot_diversity()                # candidate spread per iteration
fig = client.plot_budget()                   # cumulative burn vs cap
fig = client.plot_discovery_rate()           # results/failures + cost efficiency
fig = client.plot_calibration()              # coverage rate over iterations
fig = client.plot_pareto()                   # hypervolume progress
fig = client.plot_reproducibility()          # per-candidate CV for replicates
fig = client.plot_model_learning()           # MAE across model versions
fig = client.plot_acquisition_efficiency()   # Spearman rank correlation
fig = client.plot_regret()                   # per-iteration + cumulative regret
fig = client.plot_explore_exploit()          # exploitation ratio trend
fig = client.plot_data_quality()             # anomaly rate per iteration

Diversity Computation

Diversity spread is computed as mean pairwise Euclidean distance across candidate feature vectors. numpy is a core dependency — vectorised O(n^2) broadcasting replaces the pure-Python loop. For campaigns with large candidate pools, install the analytics extra for FAISS approximate nearest-neighbor acceleration.

Counterfactual Replay

Replays the recorded campaign through alternate decision policies (greedy or random) and compares: when did each policy find the best candidate, and at what cost? This requires the full causal sequence of what was known at each decision point — a capability CRUD structurally cannot provide.


D. Compliance Alignment

nilvar's event-sourcing architecture aligns with scientific data integrity standards by design. These properties are structural consequences of event sourcing, not bolted-on features.

Standard How nilvar aligns What's not covered
ALCOA+ (data integrity) Attributable (actor field), Contemporaneous (ts + server-side recorded_at), Original (append-only log + raw_data_ref), Accurate (hash chain + unit fields), Legible (nilvar export evidence bundle) Enduring (archival/WORM), Available (access controls)
21 CFR Part 11 (FDA electronic records) Immutable audit trail with tamper-evident hash chain E-signatures, access controls, system validation (organizational controls beyond a local-first tool)
FAIR (scientific data) Findable (campaign URN), Accessible (REST API), Interoperable (JSON schema), Reusable (license, provenance, replay from seq 0) Persistent DOIs, standard ontologies, JSON-LD export
GAMP 5 (pharma system validation) Category 5 classification with validation pack: IQ/OQ/PQ protocols, traceability matrix Executed validation is the adopting organization's responsibility

nilvar provides the technical substrate for compliance; the adopting organization layers identity, access controls, SOPs, and validation execution on top.

GAMP 5 validation pack: docs/gamp/ contains the system classification, traceability matrix, and executed IQ/OQ/PQ protocols with actual results (53 steps passed, 16 Docker-dependent steps deferred to CI). Organizations adopting nilvar in GxP environments should re-execute these protocols in their target environment.

Standards assessed and documented as not applicable: EU GMP Annex 11, ICH GCP E6, HIPAA, GDPR, ISO 27001/SOC 2, ISO 42001/AI governance, export controls. See docs/compliance_reference.md for rationale on each.


E. Reference

CLI

nilvar --version          # print version and exit
nilvar --verbose ...      # enable debug logging
nilvar --quiet ...        # suppress info, show warnings/errors only

Write commands:

# Initialize a campaign database
nilvar init --campaign-id perovskite-opt --goal "maximize bandgap" --budget-cap 50000.0

# Ingest events from JSON/CSV/YAML
nilvar ingest --file data.json

# Record a validated result
nilvar record-result --candidate-id CsPbI3 --value 1.68 --uncertainty 0.05 --iteration 1

# Record a failure
nilvar record-failure --candidate-id CsPbBr3 --failure-class synthesizability --iteration 1 --detail "Precursor unavailable"

Read commands:

# Campaign report (markdown, HTML, or JSON)
nilvar report
nilvar report --format html --output report.html
nilvar report --format json

# Verify hash chain integrity (shows event breakdown, actors, time range)
nilvar audit
nilvar audit --output json

# Export portable evidence bundle for auditors
nilvar export --output bundle.json

# Counterfactual replay
nilvar counterfactual --policy greedy --top-k 1
nilvar counterfactual --policy random --seed 42
SDK Reference
from nilvar.sdk import CampaignClient

client = CampaignClient("campaign.db")

Write operations:

Method Description
client.create_campaign(id, goal, budget_cap) Initialize a new campaign
client.propose(candidate_id, features, predicted_value, model_version, iteration) Record a surrogate prediction
client.start_experiment(candidate_id, cost, iteration) Commit budget before outcome
client.record_result(candidate_id, value, uncertainty, iteration) Record a validated measurement
client.record_failure(candidate_id, failure_class, iteration, detail) Record a classified failure
client.select(candidate_id, rationale, model_version, iteration) Select a candidate with rationale
client.reject(candidate_id, rationale, model_version, iteration) Reject a candidate with rationale
client.update_model(model_version, iteration, summary) Record surrogate retrain
client.close_iteration(iteration, summary) Mark iteration boundary

Read operations:

Method Description
client.report(format="markdown") Campaign report ("markdown" or "html")
client.iterations() Per-iteration summary with candidates, results, costs

Analytics:

Method Returns Signal
client.proxy_gaming(window=3) ProxyGamingVerdict Surrogate predictions rising while validated results flat
client.diversity(window=3) DiversityVerdict Candidate proposals contracting to narrow region
client.discovery_rate() DiscoveryRateVerdict Cost per hit flatlines; zero-result iterations
client.calibration(window=3) CalibrationVerdict Uncertainty estimates diverge from observed errors
client.pareto(objectives=None) ParetoVerdict Multi-objective hypervolume stops expanding
client.reproducibility(cv_threshold=0.3) ReproducibilityVerdict Replicate measurements show high variance
client.model_learning(window=3) ModelLearningVerdict MAE fails to improve across retrains
client.acquisition_efficiency(window=3) AcquisitionEfficiencyVerdict Predicted rankings don't match actual outcomes
client.regret(window=3) RegretVerdict Gap between best-known and selected grows
client.explore_exploit(window=3) ExploreExploitVerdict Search balance too extreme
client.data_quality(window=3) DataQualityVerdict Anomaly rate exceeds threshold

Counterfactual replay:

result = client.counterfactual_replay(policy="greedy", top_k=1)
print(f"Original found best at iteration {result['original_found_at_iteration']}")
print(f"Greedy would find it at iteration {result['alternate_found_at_iteration']}")
print(f"Iterations saved: {result['iterations_saved']}")
Parameter Description
policy "greedy" (exploitation baseline) or "random" (exploration baseline)
top_k How many candidates the alternate selects per iteration
seed RNG seed for random policy (ignored for greedy)

Replays the campaign through an alternate decision policy: did the alternate find the best candidate sooner or cheaper? Only compares paths through candidates that were actually tested.

API Reference

The API runs via Docker Compose (see Docker below). For local development:

pip install -e ".[api]"
uvicorn nilvar.api.app:app --reload

Interactive docs at http://localhost:8000/docs (Swagger UI).

Write endpoints:

Method Endpoint Description
POST /campaigns Create campaign
POST /candidates/propose Propose a candidate
POST /experiments/start Start experiment (commit budget)
POST /results Record validated result
POST /failures Record classified failure
POST /candidates/select Select candidate with rationale
POST /candidates/reject Reject candidate with rationale
POST /model/update Record surrogate retrain
POST /iterations/close Close iteration

Read endpoints:

Method Endpoint Description
GET /report?format=markdown Campaign report (markdown or html)
GET /timeline/{candidate_id} Per-candidate event timeline
GET /negledger Failure ledger
GET /budget Budget state
GET /iterations Per-iteration summary

Analytics endpoints:

Method Endpoint Description
GET /analytics/proxy-gaming?window=3 Proxy-gaming detector
GET /analytics/diversity?window=3 Diversity-collapse monitor
GET /analytics/discovery-rate Discovery rate analysis
GET /analytics/calibration?window=3 Calibration drift detector
GET /analytics/pareto?window=3 Pareto front progress
GET /analytics/reproducibility?cv_threshold=0.3 Reproducibility monitor
GET /analytics/model-learning?window=3 Model learning rate
GET /analytics/acquisition-efficiency?window=3 Acquisition efficiency
GET /analytics/regret?window=3 Cumulative regret
GET /analytics/explore-exploit?window=3 Explore/exploit ratio
GET /analytics/data-quality?window=3 Data quality / anomaly rate
GET /analytics/counterfactual?policy=greedy&top_k=1 Counterfactual replay

Operations:

Method Endpoint Description
GET /health Health check (returns {"status":"ok","events":"<count>"})
GET /audit Verify hash chain integrity
Docker

The API server runs via Docker Compose. No Python install required.

# 1. Copy the example config (optional — defaults work out of the box)
cp .env.example .env

# 2. Start the API
docker compose up -d

# 3. Verify it's running
curl http://localhost:8000/health
# {"status":"ok","events":"0"}

Configuration:

Variable Default Description
NILVAR_DB /data/nilvar.db SQLite database path inside the container
NILVAR_LOG_LEVEL INFO Python logging level
NILVAR_CORS_ORIGINS [] JSON array of allowed CORS origins

Set variables in .env (loaded automatically by Compose) or inline: NILVAR_LOG_LEVEL=DEBUG docker compose up. See .env.example for the full reference.

Data persistence: The SQLite database is stored in a Docker named volume (nilvar-data), mounted at /data inside the container. Data survives container restarts and image rebuilds.

# Back up the database
docker compose cp nilvar-api:/data/nilvar.db ./backup.db

Production: For deployments behind a reverse proxy, configure rate limiting and TLS at the proxy layer (nginx, Caddy, Cloudflare). The nilvar API is stateless HTTP.


F. Literature

nilvar's trajectory analytics are grounded in the Bayesian optimization, self-driving lab, and autonomous experimentation literature.

Foundational

Paper What it grounds
Srinivas et al. 2010, "Gaussian Process Optimization in the Bandit Setting: No Regret and Experimental Design" (ICML) Regret bounds, exploration-exploitation theory — foundational GP-UCB paper
Shahriari et al. 2016, "Taking the Human Out of the Loop: A Review of Bayesian Optimization" (IEEE) Comprehensive BO survey; explore-exploit tradeoff framework
Garnett 2023, Bayesian Optimization (Cambridge University Press) Textbook treatment of acquisition function evaluation and BO campaign design
Frazier 2018, "A Tutorial on Bayesian Optimization" Sample efficiency and regret definitions in BO context
Rasmussen & Williams 2006, Gaussian Processes for Machine Learning Surrogate model comparison and uncertainty quantification

Campaign-health signals

Paper Signal
Amodei et al. 2016, "Concrete Problems in AI Safety" (§3) Proxy gaming / Goodhart's Law — surrogate optimizes its own score, not reality
Kuleshov et al. 2018, "Accurate Uncertainties for Deep Learning Using Calibrated Regression" (ICML) Calibration drift — surrogate confidence intervals vs observed hit rates
Guo et al. 2017, "On Calibration of Modern Neural Networks" (ICML) Expected Calibration Error (ECE) definition
Foldager et al. 2023, "On the Role of Model Uncertainties in Bayesian Optimisation" (UAI) How miscalibration directly degrades BO performance
Zitzler & Thiele 1999, "Multiobjective Evolutionary Algorithms: A Comparative Case Study" (IEEE Trans. Evol. Computation) Hypervolume indicator for multi-objective Pareto front progress
Daulton et al. 2021, "Parallel Bayesian Optimization of Multiple Noisy Objectives with Expected Hypervolume Improvement" (NeurIPS) qNEHVI — multi-objective BO in BoTorch
Makarova et al. 2024, "Stopping Bayesian Optimization with Probabilistic Regret Bounds" Regret-based convergence and stopping criteria

Self-driving labs and autonomous experimentation

Paper Relevance
Abolhasani & Kumacheva 2023, "The rise of self-driving labs in chemical and materials sciences" (Nature Synthesis) Typed failure classification in closed-loop discovery
Granda et al. 2018, "Controlling an organic synthesis robot with machine learning" (Nature) Early SDL tracking measurement variance and reproducibility
Burger et al. 2022, "Autonomous Chemical Experiments: Challenges and Perspectives" (Accounts of Chemical Research) SDLs tracking replicate variance as system health
Kalinin et al. 2024, "Dual-GP active oversight framework" (npj Computational Materials) Real-time surrogate quality monitoring in autonomous experiments
Bartel et al. 2020, "A critical examination of compound stability predictions from ML formation energies" (npj Computational Materials) Stability vs performance tradeoffs in materials discovery

Materials and drug discovery

Paper Relevance
Bellamy et al. 2021, "Bias-free multi-objective active learning" (Nature Communications) Multi-objective BO applied to materials discovery SDLs
Warmuth et al. 2003, "Active Learning with SVMs in the Drug Discovery Process" (JCIM) Learning curves applied to drug discovery campaigns
Baker 2016, "1,500 scientists lift the lid on reproducibility" (Nature) The reproducibility crisis — why measurement quality matters
Settles 2012, Active Learning (monograph) Learning curves and model learning rate formalization

G. Performance

Benchmarked on a 1,000-event in-memory SQLite store (100 iterations, 10 candidates each). Numbers are median of 5 runs on a single core.

Operation Throughput Latency
Cold import (from nilvar) ~310 ms
Event writes (hash-chained appends) ~175 events/sec ~6 ms/event
Full replay (4 projectors from seq 0) ~55,000 events/sec ~18 ms / 1k events
Report generation (markdown) < 1 ms
Hash chain verification ~43,000 events/sec ~23 ms / 1k events

Replay and verification are I/O-bound on SQLite reads; in-memory stores hit higher throughput. Write throughput is dominated by per-event SHA-256 hashing and SQLite fsync.


H. Development & Contributing

make dev     # install with all dependencies
make check   # run full quality gate (lint + format + typecheck + test)

All available targets:

Command What it does
make help Show all available targets
make install Install core package
make dev Install with all dev dependencies
make check Run full quality gate (lint + format + typecheck + test)
make lint Run ruff linter
make format Check code formatting
make typecheck Run mypy strict type checking
make test Run tests (374 tests)
make coverage Run tests with coverage report (93%, 80% gate)
make audit Audit dependencies for vulnerabilities
make clean Remove build artifacts
bash fixtures/smoke_test/run_smoke_test.sh Run end-to-end smoke test (details)

See CONTRIBUTING.md for the full development workflow, architecture constraints, and commit format.

License

MIT

About

Event-sourced campaign ledger for AI-driven scientific discovery. It detects proxy-gaming, diversity collapse, and discovery flatlines.

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Languages