Minimal signal processing engine for observable games
Project description
MetaSPN Engine
Minimal signal processing engine for observable games.
Zero game semantics. Pure signal flow. Maximum composability.
Philosophy
The MetaSPN Engine is a dumb pipe. It knows nothing about podcasts, tweets, G1-G6 games, or any domain-specific concepts. It only knows:
- Signals flow in (typed, timestamped, immutable)
- Pipelines process them (pure functions, composable)
- State accumulates (typed, versioned)
- Emissions flow out (typed, traceable)
Everything else is built on top through game wrappers.
Installation
pip install metaspn-engine
Quick Start
from dataclasses import dataclass
from datetime import datetime
from metaspn_engine import Signal, Emission, State, Pipeline, Engine
from metaspn_engine.transforms import emit_if, accumulate, update_state
# 1. Define your signal payload type
@dataclass(frozen=True)
class ScoreEvent:
user_id: str
score: float
# 2. Define your state type
@dataclass
class GameState:
total_signals: int = 0
running_total: float = 0.0
high_score: float = 0.0
# 3. Build your pipeline
pipeline = Pipeline([
# Count signals
accumulate("total_signals", lambda acc, _: (acc or 0) + 1),
# Track running total
accumulate("running_total", lambda acc, payload: (acc or 0) + payload.score),
# Update high score
update_state(lambda payload, state:
GameState(
total_signals=state.total_signals,
running_total=state.running_total,
high_score=max(state.high_score, payload.score)
) if payload.score > state.high_score else state
),
# Emit on high score
emit_if(
condition=lambda payload, state: payload.score > state.high_score,
emission_type="new_high_score",
payload_extractor=lambda payload, state: {
"user_id": payload.user_id,
"score": payload.score,
"previous_high": state.high_score,
}
),
])
# 4. Create engine
engine = Engine(
pipeline=pipeline,
initial_state=GameState(),
)
# 5. Process signals
signal = Signal(
payload=ScoreEvent(user_id="user_123", score=95.5),
timestamp=datetime.now(),
source="game_server",
)
emissions = engine.process(signal)
# 6. Check results
print(f"State: {engine.get_state()}")
print(f"Emissions: {emissions}")
Documentation
Full documentation is in the docs folder:
| Document | Description |
|---|---|
| Docs index | Entry point — overview and links to everything |
| Core concepts | Why the engine exists; signals, state, emissions |
| Quick start tutorial | Build your first game in ~15 minutes |
| Mental model | One-page architecture overview |
| Designing games | How to design new games; four questions, patterns |
| API cheatsheet | Quick reference for types and methods |
| Architecture · Data flow | Mermaid diagrams |
Examples: Podcast Game · Creator Scoring Game
Core Concepts
Signals
Immutable input events with typed payloads:
@dataclass(frozen=True)
class PodcastListen:
episode_id: str
duration_seconds: int
completed: bool
signal = Signal(
payload=PodcastListen("ep_123", 3600, True),
timestamp=datetime.now(),
source="overcast",
)
Pipelines
Sequences of pure steps that process signals:
pipeline = Pipeline([
step_one,
step_two,
step_three,
], name="my_pipeline")
# Pipelines are composable
combined = pipeline_a + pipeline_b
# Pipelines support branching
branched = pipeline.branch(
predicate=lambda s: s.payload.type == "podcast",
if_true=podcast_pipeline,
if_false=other_pipeline,
)
State
Mutable accumulated context:
@dataclass
class MyState:
count: int = 0
items: list = field(default_factory=list)
state = State(value=MyState())
state.enable_history() # Track state transitions
# State updates happen through pipeline steps
# using update functions
Emissions
Immutable output events:
emission = Emission(
payload={"score": 0.85},
caused_by=signal.signal_id, # Traceability
emission_type="score_computed",
)
Transforms
Built-in step functions for common operations:
from metaspn_engine.transforms import (
# Mapping
map_to_emission,
# State management
accumulate,
set_state,
update_state,
# Windowing
window,
time_window,
# Emissions
emit,
emit_if,
emit_on_change,
# Control flow
branch,
merge,
sequence,
# Utilities
log,
tap,
)
Building Game Wrappers
The engine is meant to be wrapped by game-specific packages:
# metaspn_podcast/game.py
from metaspn_engine import Signal, Pipeline, Engine
from metaspn_engine.protocols import GameProtocol
class PodcastGame:
"""Podcast listening game built on MetaSPN Engine."""
name = "podcast"
version = "1.0.0"
def create_signal(self, data: dict) -> Signal[PodcastListen]:
return Signal(
payload=PodcastListen(
episode_id=data["episode_id"],
duration_seconds=data["duration"],
completed=data.get("completed", False),
),
timestamp=datetime.fromisoformat(data["timestamp"]),
source=data.get("source", "unknown"),
)
def initial_state(self) -> PodcastState:
return PodcastState()
def pipeline(self) -> Pipeline:
return Pipeline([
track_listening,
compute_influence_vector,
update_trajectory,
emit_if_significant,
])
# Usage
game = PodcastGame()
engine = Engine(
pipeline=game.pipeline(),
initial_state=game.initial_state(),
)
for event in listening_events:
signal = game.create_signal(event)
emissions = engine.process(signal)
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Game Wrappers │
│ (PodcastGame, TwitterGame, CreatorScoring, etc.) │
│ │
│ - Define signal types │
│ - Define state shape │
│ - Build domain-specific pipelines │
│ - Handle game-specific logic │
└─────────────────────────────────────────────────────────────┘
│
implements GameProtocol
│
▼
┌─────────────────────────────────────────────────────────────┐
│ metaspn-engine (core) │
│ │
│ Signal[T] ──▶ Pipeline[Steps] ──▶ Emission[U] │
│ │ │
│ reads/writes │
│ │ │
│ State[S] │
│ │
│ - Type-safe signal flow │
│ - Pure function pipelines │
│ - Versioned state management │
│ - Traceable emissions │
└─────────────────────────────────────────────────────────────┘
Design Principles
- Zero Dependencies - The core engine has no external dependencies
- Pure Functions - All transforms are pure (state updates are explicit)
- Type Safety - Full generic type support for signals, state, emissions
- Composability - Pipelines compose, games compose, everything composes
- Traceability - Every emission traces back to its causing signal
- Testability - Given input + state, output is deterministic
Why This Exists
MetaSPN measures transformation, not engagement. But transformation can happen in many contexts:
- Podcast listening → G3 (Models) learning
- Tweet threads → G2 (Idea Mining) extraction
- Creator output → G1 (Identity) development
- Network connections → G6 (Network) building
Each context is a different game, but they all share the same underlying mechanics:
- Signals come in (things happen)
- State accumulates (context builds)
- Transformations occur (changes happen)
- Emissions go out (observable results)
This engine is the shared foundation. Game wrappers add the semantics.
Contributing
Contributions are welcome. See CONTRIBUTING.md for setup, running tests and checks, and how to submit changes. We also have a Code of Conduct and Security policy.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file metaspn_engine-0.0.1.tar.gz.
File metadata
- Download URL: metaspn_engine-0.0.1.tar.gz
- Upload date:
- Size: 24.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
786e7afc2f00b6ea83acbcaf44dcbf7c34ce144fcd7a1a1ff525845759674c39
|
|
| MD5 |
0d9aee2d4c037651283a25de1d000e66
|
|
| BLAKE2b-256 |
e2972d317cb32f4cde71794f7610bcfdd7229faaaa4077249cd1d6b917505f75
|
File details
Details for the file metaspn_engine-0.0.1-py3-none-any.whl.
File metadata
- Download URL: metaspn_engine-0.0.1-py3-none-any.whl
- Upload date:
- Size: 23.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
262039930f3982d3f62da642fb99939c12d2df50992611d7eba8310a8ca111af
|
|
| MD5 |
298b7080466e787d63526e9708b66469
|
|
| BLAKE2b-256 |
1518b3f881aeec0307c535ebce01c5d861a3af33bf002f711f638d9cff4ea343
|