fly-harness
v0.4.0 — a sparse neural harness, not a fly-brain simulation.
Formula: Agent = deployed bio-sim Model + harness. The harness docks to a running sim through a stable contract. FlyWire / MaleCNS files are weight dumps, not that contract.
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 (memory is bounded by the loaded subset)
- 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]"
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). Unknown model → HTTP 404 → harness UnknownModelError.
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: 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 extras, no MuJoCo. 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
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.4.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.4.0.tar.gz | 42.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fly_harness-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 89.4 kB
Release files / fly_harness-0.4.0.tar.gz
| Download URL | fly_harness-0.4.0.tar.gz |
|---|---|
| Size | 42.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2b2d654e69f1effb1ff3e38376bafb56c0fe5d91bfe8ddf01d4b5cf50efe3c68
|
|
BLAKE2b-256 checksum How to use checksums |
45d41cd285faa6e7099cbf75ab5182d708046d5bca56c92af1177200607e9cf8
|
| 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.4.0-py3-none-any.whl
| Download URL | fly_harness-0.4.0-py3-none-any.whl |
|---|---|
| Size | 46.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
812ce6e3f11d922005ce70d45da239c5dc82a3ff619889090a031dedd52e23f6
|
|
BLAKE2b-256 checksum How to use checksums |
070f28f596fdbc7655a905e53aca86816f112779b2adf7352f5285baa2af9d50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|