Skip to main content

fly-harness

v0.5.0 — a sparse neural harness, not a fly-brain simulation.

Formula: Agent = Model + harness. The harness docks to a running sim through a stable contract (ModelBackend only). FlyWire / MaleCNS files are weight dumps, not that contract. The harness does not dock a specific model.

The public contract is:

  1. BrainState — membrane potentials, sparse synaptic weights (scipy.sparse CSR), timestamp, JSON save/load
  2. FlyHarness.step(obs) -> action — encode → ModelBackend.tick → decode (default backend is the in-process LIF/rate circuit)
  3. Encoder / Decoder — typing.Protocol contracts (optional BaseEncoder / BaseDecoder ABCs; no string class-name checks)
  4. load_connectome — load a circuit subset from a local sparse edge list (.npz / .csv) into BrainState
  5. ModelBackend — swappable Model port: tick (encoded input → neural output), reset, n_neurons, model_id

The 24-neuron touch-reflex loop is a test/demo fixture. It is not the model, and it is not a scaled-down FlyWire brain.

What this is not

  • Not a 140k-neuron FlyWire connectome, upload, or dense 140k×140k matrix
  • Not an arbitrary-scale whole-brain runtime in the core (memory is bounded by the loaded subset). Third-party Models such as flybrain (MaleCNS v1.0, 166,700 neurons, not 160k "parameters") attach through ModelBackend — using the harness, not a harness feature. A dump you run locally, not a Google-hosted sim or the FlyWire website
  • Not caveclient or a built-in biomechanics engine — FlyGym/NeuroMechFly is an optional body extra, not the core product
  • Not an MCP microkernel or FastAPI service — MCP is an optional protocol extra, not the core product
  • Not OpenRouter-the-company, a marketplace, billing system, hosted cloud gateway, or public bio-sim catalog — BioSimRouter is a harness-side port; biorouter is an optional local HTTP process (stdlib, 127.0.0.1 by default)
  • Not a consciousness/upload claim

Install

Python 3.10+ with numpy and scipy. Core install stays numpy/scipy-only:

pip install -e ".[dev]"

FlyGym is optional:

pip install -e ".[flygym]"

MCP is optional (stdio server via FastMCP):

pip install -e ".[mcp]"

biorouter is optional and stdlib-only (no FastAPI). The extra exists so it stays an extension of this repo, not a second product or GitHub repository:

pip install -e ".[biorouter]"

flybrain is optional. The extra is an adapter so biorouter can list flybrain.malecns and the example can use that third-party Model. The harness does not dock a specific model. Core stays model-agnostic and does not import flybrain. Default tests do not download ~/fly-data:

pip install -e ".[flybrain]"

The step loop

from fly_harness import BrainState, FlyHarness
from fly_harness.protocols import Decoder, Encoder

harness = FlyHarness(state, encoder, decoder)
result = harness.step(observation)
action = result.action

step encodes the observation into a length-n_neurons current vector, advances the docked Model one tick, and decodes an action from the neural output.

Default construction still wraps the in-process LIF/rate dynamics (InProcessLifBackend). FlyHarness.step(obs) -> action is the same public call.

BrainState can be snapshotted independently of the harness:

state.save("brain.json")
restored = BrainState.load("brain.json")

Docking modes

Two ways to attach a running Model. This package is the port, not a hosted catalog of sims.

1. Direct — one backend per vendor / deployed sim

from fly_harness import DirectBioSimBackend, FlyHarness

backend = DirectBioSimBackend(n_neurons=8, model_id="vendor.fake")
harness = FlyHarness(encoder=encoder, decoder=decoder, backend=backend)
result = harness.step(observation)

DirectBioSimBackend docks to one running sim. The in-process FakeDeployedSim is enough for tests. A thin HTTP client is available if you already host a sim (url=..., bodies include a model field). There are no FlyWire / CAVE / neuPrint clients and no 140k dense matrices.

2. Router — one entry, select by model id

from fly_harness import BioSimRouter, DirectBioSimBackend, FlyHarness, UnknownModelError

router = BioSimRouter(
    {
        "vendor.a": DirectBioSimBackend(n_neurons=8, model_id="vendor.a"),
        "vendor.b": DirectBioSimBackend(n_neurons=8, model_id="vendor.b"),
    },
    model_id="vendor.a",
)
harness = FlyHarness(encoder=encoder, decoder=decoder, backend=router)
harness.step(observation)
router.use("vendor.b")
harness.step(observation)

# unknown model_id raises UnknownModelError (this is a port, not a marketplace)

BioSimRouter is OpenRouter-shaped: one entry, a model / model_id field, unknown ids fail clearly. Optional remote_url= forwards that model field to biorouter (HttpModelBackend). This repo does not ship a hosted marketplace.

You can also pass a backend positionally: FlyHarness(backend, encoder, decoder).

biorouter

OpenRouter routes existing LLMs. biorouter routes existing deployed biological simulation models. Same idea (model id), different substrate.

This extra is not the harness, not FlyWire dumps, and not a marketplace. It is a local HTTP process (127.0.0.1 by default) that already-deployed harness clients call: HttpModelBackend, DirectBioSimBackend(url=...), BioSimRouter(remote_url=...). Stdlib http.server only — no FastAPI, no billing, no cloud gateway.

pip install -e ".[biorouter]"   # empty extra; core install is enough to run the CLI
biorouter
# or
python -m fly_harness.router --host 127.0.0.1 --port 8765

Endpoints (JSON; bodies/query include an OpenRouter-shaped model field):

  • POST /tick — {"model": "...", "input": [...]} → neural output
  • POST /reset — {"model": "...", "potentials": null | [...]}
  • GET /status?model=...

Default in-process registry: the 24-neuron LIF fixture (fly-harness.in-process-lif) plus FakeDeployedSim ids (fake.deployed, fake.deployed.gain). If fly-harness[flybrain] is installed and MaleCNS files are already on disk, biorouter also lists flybrain.malecns (OpenRouter-shaped provider id). Unknown / missing flybrain → HTTP 404 / skip. Never downloads MaleCNS.

from fly_harness import DirectBioSimBackend, FlyHarness

backend = DirectBioSimBackend(
    url="http://127.0.0.1:8765",
    model_id="fake.deployed",
    n_neurons=8,
)
harness = FlyHarness(encoder=encoder, decoder=decoder, backend=backend)
harness.step(observation)

FlyHarness.step(obs) -> action is unchanged. This extra is not a 140k FlyWire runtime and does not download MaleCNS.

Example: FlyHarness.step with a third-party flybrain Model

Formula: Agent = Model + harness. flybrain (source) is a third-party Model (MaleCNS v1.0 LIF, 166,700 neurons, 25.6M connections). Google/Janelia released a dump, not a hosted sim. Wiring it is using the harness (ModelBackend), not a harness feature — the core stays model-agnostic. An optional extra holds the adapter so biorouter can list flybrain.malecns and this example can run. Not Google-hosted, not 160k parameters, not consciousness.

from fly_harness import FlyHarness
from fly_harness.flybrain import (
    FlyBrainBackend,
    FlyBrainInjectEncoder,
    FlyBrainReadoutDecoder,
)

backend = FlyBrainBackend.from_installed(download=False)  # files must already be on disk
harness = FlyHarness(
    encoder=FlyBrainInjectEncoder(backend.n_neurons, channels={"loom": (0, 1)}),
    decoder=FlyBrainReadoutDecoder(backend.n_neurons),
    backend=backend,
)
result = harness.step({"loom": 0.8})

Default example uses an in-process FakeFlyBrain (32 neurons). --real only runs if flybrain imports and MaleCNS files are already present; it will not download them:

python examples/flybrain_loop.py
python examples/flybrain_loop.py --real
# or
python -m fly_harness.demo.flybrain_loop
fly-harness-flybrain-demo

Example: biorouter through FlyHarness.step

OpenRouter routes existing LLMs; biorouter routes existing deployed bio-sims. This example starts a local stdlib server (or attaches with --url), docks HttpModelBackend / DirectBioSimBackend(url=...), and calls FlyHarness.step. It uses the 8-neuron FakeDeployedSim fixture — not FlyWire / MaleCNS, not a marketplace.

python examples/biorouter_loop.py
# or, after install:
python -m fly_harness.demo.biorouter_loop
fly-harness-biorouter-demo
# attach to a process you already started:
biorouter --host 127.0.0.1 --port 8765
python examples/biorouter_loop.py --url http://127.0.0.1:8765

Load a sparse connectome

load_connectome maps global neuron ids (any integers) onto contiguous local indices and builds a CSR weight matrix of shape (n_subset, n_subset).

from fly_harness import FlyHarness, load_connectome

loaded = load_connectome("tests/fixtures/mini_circuit.npz")
# loaded.state.weights is scipy.sparse CSR, shape (n_subset, n_subset)
# loaded.neuron_ids maps local index -> global neuron id
# loaded.id_to_index maps global id -> local index

harness = FlyHarness(loaded.state, encoder=..., decoder=...)
result = harness.step(observation)
fly-harness-loader-demo
# or
python -m fly_harness.demo.loaded_circuit

Supported files:

  • NPZ — arrays pre, post, weight (int ids + float weights); optional neuron_ids for the circuit subset
  • CSV — header pre_id,post_id,weight

Pass an explicit subset with neuron_ids=[...] or neuron_ids_file="ids.txt" (one id per line). Only synapses whose pre and post are both in the subset are kept.

Fixture: 24-neuron reflex

Shipped only so the contract is executable without an external connectome file. Left touch biases a right turn (and the reverse). Do not treat this as a biological circuit.

fly-harness-reflex-demo
# or
python -m fly_harness.demo.reflex
from fly_harness import FlyHarness
from fly_harness.demo import ReflexDecoder, ReflexEncoder, build_reflex_connectome
from fly_harness.demo.codec import TouchObservation

state = build_reflex_connectome()
harness = FlyHarness(state, ReflexEncoder(), ReflexDecoder())
result = harness.step(TouchObservation(touch_left=1.0))
print(result.action)  # ReflexAction(turn=1, forward=..., brake=...)

Optional body: FlyGym / NeuroMechFly

This is an extension, not the core product. It does not ship a 140k-neuron brain, does not rewrite FlyGym, and does not run MuJoCo unless you install the extra.

FlyGymEncoder / FlyGymDecoder map NeuroMechFly observations (joint angles, contact forces) and actions (joint targets, per-leg adhesion, optional muscle/tendon commands) onto FlyHarness.step. They work on dicts — no MuJoCo import. FlyGymHarnessEnv is a thin wrapper around an env you construct with FlyGym.

from fly_harness import FlyHarness
from fly_harness.demo.connectome import SENSORY_INDICES, build_reflex_connectome
from fly_harness.flygym import FlyGymDecoder, FlyGymEncoder, FlyGymHarnessEnv

state = build_reflex_connectome()  # fixture circuit, not FlyWire
harness = FlyHarness(
    state,
    FlyGymEncoder(state.n_neurons),
    FlyGymDecoder(state.n_neurons),
    sensory_indices=SENSORY_INDICES,
)
# env = your FlyGym / NeuroMechFly simulation (not constructed here)
body = FlyGymHarnessEnv(env, harness)
obs, info = body.reset()
result = body.step()
# result.action.as_env_dict() -> {"joints": ..., "adhesion": ...}

Gymnasium FlyGym (flygym-gymnasium, installed by the extra; import name flygym or flygym_gymnasium) takes env.step({"joints", "adhesion"}). FlyGym 2.x Simulation is duck-typed via set_actuator_inputs / set_leg_adhesion_states when you pass actuator_type.

Optional protocol: MCP

This is an extension pack, not the microkernel. BrainState / FlyHarness stay numpy/scipy-only and do not import MCP. The default server steps the 24-neuron reflex fixture, not a 140k FlyWire brain.

pip install -e ".[mcp]"
# stdio (default FastMCP transport) — point an MCP host at this command:
fly-harness-mcp
# or
python -m fly_harness.mcp

Tools (no live client required to unit-test the handlers):

  • harness_step(touch_left, touch_right) — one FlyHarness.step
  • harness_reset() — zero potentials and the clock
  • harness_status() — neuron count, timestamp, package version

Handlers are importable without FastMCP:

from fly_harness.mcp import HarnessSession

session = HarnessSession()
print(session.step(touch_left=1.0)["action"])

Example: protocol + body through the harness

This proves optional MCP and FlyGym extras can attach to the same FlyHarness.step. It uses the 24-neuron reflex fixture. It is not a 140k FlyWire or ~166k MaleCNS upload, and it does not rewrite FlyGym or the MCP pack.

python examples/mcp_flygym_loop.py
# or, after install:
python -m fly_harness.demo.composed
fly-harness-composed-demo

Default run uses an in-process body stub (no MuJoCo). --try-flygym attempts a real NeuroMechFly sim and falls back to the stub. --serve starts the optional FastMCP stdio server on that composed session (pip install -e ".[mcp]"):

python examples/mcp_flygym_loop.py --serve

An MCP host that calls harness_step(touch_left, touch_right) therefore steps the harness and applies the decoded joint/adhesion command to the body.

Tests

GitHub Actions on main and pull requests runs pip install -e ".[dev]" then pytest — no FlyGym/MCP/flybrain extras, no MuJoCo, no MaleCNS download. Skip/mock tests in those extras still pass. The biorouter extra is stdlib-only, so its tests run in that same core CI.

Default local suite does not need MuJoCo. The FlyGym smoke test skips unless flygym imports and a NeuroMechFly sim can start:

pytest
# with the FlyGym extra, still skip-friendly if MuJoCo/display is missing:
pytest -q
# FlyGym smoke only:
pytest tests/test_flygym_adapter.py -k smoke

Model-backend docking (default LIF, fake deployed sim, router model_id switch, unknown-id error) is core and needs no extras:

pytest tests/test_backend.py tests/test_harness.py

The optional biorouter process starts a stdlib server and ticks through HttpModelBackend / FlyHarness (still no FastAPI, no extras required):

pytest tests/test_router.py
# after install:
biorouter --help
python -m fly_harness.router --help

The biorouter example is in-thread by default (no live biorouter daemon). Attaching to a real process is skip-gated on BIOROUTER_URL:

pytest tests/test_biorouter_example.py
python examples/biorouter_loop.py

Router tests can register a fake flybrain-shaped backend under flybrain.malecns (no extra, no download). Default CI without flybrain/data omits that id (HTTP 404). Real flybrain smoke skips unless the extra is installed and ~/fly-data already has files (CI does not download):

pytest tests/test_router.py tests/test_flybrain_adapter.py
python examples/flybrain_loop.py
# optional, only with extra + on-disk MaleCNS:
# python examples/flybrain_loop.py --real

MCP tests mock FastMCP and do not start a live MCP client. They pass without fly-harness[mcp]. With the extra, a factory smoke checks FastMCP constructs (still no stdio client):

pip install -e ".[mcp]"
pytest tests/test_mcp_extension.py

The composed MCP+FlyGym example is also mock/skip: no MuJoCo and no live MCP client.

pytest tests/test_composed_example.py
python examples/mcp_flygym_loop.py

License

MIT — see LICENSE.

Release files for fly-harness 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fly-harness 0.5.0
File Size Uploaded
fly_harness-0.5.0.tar.gz 49.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fly-harness 0.5.0
File Interpreter ABI Platform
fly_harness-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 105.4 kB

Release files / fly_harness-0.5.0.tar.gz

Download URL fly_harness-0.5.0.tar.gz
Size 49.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a6d045dfaacd10d153cbd156792f51a2b1fae1c9ec1be54aeed0939e9fe0edce
BLAKE2b-256 checksum
How to use checksums
045fee0e12a21f6208da930f1f97e6c15e9d68298cf0f866546f2da76255b51f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / fly_harness-0.5.0-py3-none-any.whl

Download URL fly_harness-0.5.0-py3-none-any.whl
Size 55.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
466e7c1059c604264fb2a77977cfa0b11fda15f42661a7305b3150044a8b90e8
BLAKE2b-256 checksum
How to use checksums
973e85464d53589f4e130294af380c1973e2cb13f1c77816d204fe0a97bb1ec3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

This release

0.5.0 This release

2 release files

0.4.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page