Skip to main content

Tensnap Python Bindings

Python bindings for the TenSnap protocol v0.3 runtime.

Installation

Requires Python 3.10+ and websockets>=13.0. The server uses the websockets.asyncio API introduced in websockets 13.0.

pip install tensnap

Quick Start

import asyncio

from tensnap import (
    SimulationScenario,
    agent,
    agent_layer,
    chart,
    env,
    grid_layer,
    params,
)


@agent(x="position[0]", y="position[1]")
class Bird:
    def __init__(self, bird_id: int, position: tuple[int, int]):
        self.id = bird_id
        self.position = position


@grid_layer()
@agent_layer("birds")
@env(id="main")
class Aviary:
    def __init__(self):
        self.width = 20
        self.height = 10
        self.birds = [Bird(1, (2, 3)), Bird(2, (4, 5))]

    def step(self) -> None:
        for bird in self.birds:
            x, y = bird.position
            bird.position = (x + 1, y)

    @chart("population", "Population")
    def population(self) -> int:
        return len(self.birds)


@params(include=["speed"])
class Config:
    speed = 1.0


scenario = SimulationScenario(port=8765)
model = Aviary()
config = Config()

scenario.add_all(model)
scenario.add_all(config)


async def main() -> None:
    await scenario.register_model_handler(model_step=model.step)
    await scenario.run()


if __name__ == "__main__":
    asyncio.run(main())

tensnap.bindings is the unified attach/readback surface for Python bindings. Use it for decorators such as env, grid_layer, and agent_layer, and for readback helpers such as environment_binding, layer_bindings, and bindings.

Layer and item binding fields now support these common forms:

  • omitted fields, None, or auto() for one-time same-name discovery from the class or initialized instance
  • selector strings such as x="pos[0]" or item_iterable_projector="agents" when the source path differs from the output field or layer id
  • literal values such as coord_offset="float" or icon="circle"; arbitrary strings still default to selectors unless they match a known literal value, CSS-like string such as "#3498db", or JSON-like string
  • explicit helpers such as attr("pos[0]"), value("red"), auto(), and skip()
  • direct callables such as size=lambda agent: agent.radius

None/auto() field discovery is resolved once when instances initialize or when layer_bindings(instance) materializes an instance-specific binding, so fields assigned in __init__ are picked up without scanning during projection:

from tensnap import agent, agent_layer, grid_layer, value


@agent(x="position[0]", y="position[1]", color=value("red"))
class Bird:
    def __init__(self, bird_id: int, position: tuple[int, int]):
        self.id = bird_id
        self.position = position


@grid_layer()
@agent_layer("birds")
class Aviary:
    def __init__(self):
        self.width = 20
        self.height = 10
        self.birds = [Bird(1, (2, 3))]

SimulationScenario registers the renderer-driven built-in actions start, step, and reset during construction. The initial synchronized state is always time 0, and the first simulated tick emitted by start or step is 1. For most targets, use scenario.add_all(target) to register available environment/layer, chart, and action bindings. Parameters are opt-in with @params(...) or an explicit config.

register_model_handler(model_init=None, model_step=None, model_reset=None) lets you keep reset distinct from init. If model_reset is omitted, the default handler falls back to model_init.

Reset reconciles declarations with strict CRUD, deletes the previous agent set before creating reset state, clears chart history, and preserves stable trajectory configuration so the renderer can apply on_reset.

add_all(..., dry_run=True) and the targeted add_* dry-run modes report registry ids without mutating the scenario. Mesa lifecycle helpers use this to unregister and rebuild model-owned registrations cleanly.

Trajectory layers expose length, width, color, z_index, on_agent_delete, on_state_sync, and on_reset as first-class decorator arguments. See the Python API reference for their lifecycle semantics.

Model-owned mappings, matrices, and indexed columns can use map_agent_layer, matrix_agent_layer, and indexed_agent_layer without creating visual agent wrappers. Built-in layer metadata goes in direct decorator keywords; custom metadata goes in metadata={...}. Source layers also accept direct color, icon, and size shortcuts. See the Python API reference for the conflict rules, projection forms, and restore behavior.

Monitors use explicit create/update/delete protocol operations. Scene checkpoint callbacks work with model data only: provide both checkpoint_capture and checkpoint_restore, and the binding infers the wire encoding. Pass restore=None to @scene_restore(...) for checkpoint-only models. Every connection starts with a simulator_info handshake containing stable model identity, per-instance identity, schema compatibility, and declared capabilities.

For Mesa models, prefer BoundModelReinitializer from tensnap or tensnap.bindings.lifecycle. Use bind_kwargs(...) to expose constructor keyword arguments as resettable parameters when needed. When a constructor kwarg and a model-owned parameter publish the same source field during register_model(), the model parameter keeps UI ownership and the reinitializer aliases its reset value from the model field instead of registering a duplicate kwarg parameter. configure_reinit(...) now applies the default Mesa cleanup automatically when no explicit cleanup callback is provided.

Projection ownership

Standalone built-in item projectors return independent nested snapshots. Eligible automatic layer scans can compare with previous owned snapshots and reuse equal read-only nested fields before materializing changed values; this does not guarantee outer-record identity. Custom callbacks keep safe publication copying and may explicitly declare independent output to avoid it. Layer options control projector and publication copying separately: disabling publication copying does not cancel an owning projector. Configure policies before use, and treat published records as read-only. See the shared ownership contract and focused benchmark commands for the language-specific options, overrides, and source-value rules.

Examples

Example simulations are located in the repository root:

  • examples/python/ - Standard Python examples (flock, hk, sirs)
  • examples/python_mesa/ - Mesa-based examples (cgol, sugarscape, mushroom)

Documentation

Full documentation: https://github.com/billstark001/tensnap

License

See LICENSE file in the repository root.

IP family

See the networking guide for auto (default), ipv4, and ipv6 policies, IPv6 URLs, and shared ephemeral ports.

Metadata

Release files for tensnap 0.3.3

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

Source distribution (sdist)

Source distribution for tensnap 0.3.3
File Size Uploaded
tensnap-0.3.3.tar.gz 137.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tensnap 0.3.3
File Interpreter ABI Platform
tensnap-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 254.6 kB

Release files / tensnap-0.3.3.tar.gz

Download URL tensnap-0.3.3.tar.gz
Size 137.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5b7e2bb240a8f47c3cd33ad4cc00125bde8c36823ca1b991c1090db82cff64d0
BLAKE2b-256 checksum
How to use checksums
e3eaad3155316b368aa6916f706e394b2cbc6c9f5075ae2f620f4582a83e785f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / tensnap-0.3.3-py3-none-any.whl

Download URL tensnap-0.3.3-py3-none-any.whl
Size 117.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e697fb14c5dfa84cd3ecbf520cfeec1257e0c9b09ac182b04974315a94169495
BLAKE2b-256 checksum
How to use checksums
57dc807493a565381b2a62417360ce26d39076a061861dc96fe2addaeb950b0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.3 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.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