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:
BrainState— membrane potentials, sparse synaptic weights (scipy.sparseCSR), timestamp, JSON save/loadFlyHarness.step(obs) -> action— encode →ModelBackend.tick→ decode (default backend is the in-process LIF/rate circuit)Encoder/Decoder—typing.Protocolcontracts (optionalBaseEncoder/BaseDecoderABCs; no string class-name checks)load_connectome— load a circuit subset from a local sparse edge list (.npz/.csv) intoBrainStateModelBackend— 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 throughModelBackend— using the harness, not a harness feature. A dump you run locally, not a Google-hosted sim or the FlyWire website - Not
caveclientor 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 —
BioSimRouteris a harness-side port; biorouter is an optional local HTTP process (stdlib,127.0.0.1by 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 outputPOST /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); optionalneuron_idsfor 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)— oneFlyHarness.stepharness_reset()— zero potentials and the clockharness_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)
| File | Size | Uploaded | |
|---|---|---|---|
| fly_harness-0.5.0.tar.gz | 49.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|