A lightweight, event-driven multi-agent framework for embodied AI systems
Project description
FastMind 🧠
A lightweight, event-driven framework for building embodied AI agents with dual-loop architecture (LLM + VLA).
Features
- Dual-Loop Architecture: LLM slow loop (planning, reasoning) + VLA fast loop (real-time control) run concurrently in one session
- Signal & Event: Two parallel communication primitives —
Eventfor discrete messages,Signalfor continuous high-frequency data - FastAPI-like Decorators: Familiar
@app.agent,@app.tool,@app.vla,@app.vla_action,@app.signalsyntax - State Graph: Build agent workflows like flowcharts with nodes, edges, and conditional routing
- Event-Driven: Asyncio-based, zero polling, high-performance async execution
- Human-in-the-Loop: Interrupt and resume sessions for human approval
- Perception Loops: Native support for sensors, timers, and external triggers
- Action Channel Routing: VLA outputs map to multiple action executors via channel names (N:M)
- Session Isolation: Multi-user support with isolated state per session
- Lightweight: ~8000 lines core, no big dependencies
Installation
pip install fastmind
Quick Start
LLM Agent
from fastmind import FastMind, Graph, Event
from fastmind.contrib import FastMindAPI
app = FastMind()
@app.agent(name="chat_agent")
async def chat_agent(state: dict, event: Event) -> dict:
state.setdefault("messages", [])
state["messages"].append({"role": "user", "content": event.payload.get("text", "")})
state["messages"].append({"role": "assistant", "content": "Hello!"})
return state
graph = Graph()
graph.add_node("chat", chat_agent)
graph.set_entry_point("chat")
app.register_graph("main", graph)
async def main():
api = FastMindAPI(app)
await api.start()
await api.push_event("user_001", Event("user.message", {"text": "Hello!"}, "user_001"))
await api.stop()
import asyncio
asyncio.run(main())
LLM Agent with Tool Calling (ReAct)
from fastmind import FastMind, Graph, Event, ToolNode, Tool
from fastmind.contrib import FastMindAPI
app = FastMind()
@app.tool(name="get_weather", description="Get weather for a city")
async def get_weather(city: str) -> str:
return f"{city} is sunny, 20°C"
async def chat_agent(state: dict, event: Event) -> dict:
state.setdefault("messages", [])
state["messages"].append({"role": "user", "content": event.payload.get("text", "")})
# Simulate LLM deciding to call a tool
if "weather" in event.payload.get("text", "").lower():
state["tool_calls"] = [
{"id": "call_1", "function": {"name": "get_weather", "arguments": '{"city": "Beijing"}'}}
]
else:
state["messages"].append({"role": "assistant", "content": "I can check weather for you!"})
return state
tool_node = ToolNode(app.get_tools())
def has_tool_calls(state: dict, event: Event) -> str:
return "tools" if state.get("tool_calls") else None
graph = Graph()
graph.add_node("agent", chat_agent)
graph.add_node("tools", tool_node)
graph.add_conditional_edges("agent", has_tool_calls, {"tools": "tools", None: "__end__"})
graph.add_edge("tools", "agent")
graph.set_entry_point("agent")
app.register_graph("main", graph)
VLA Agent (NPC Control)
from fastmind import FastMind, Graph, Event, ActionSpace
from fastmind.contrib import FastMindAPI
app = FastMind()
# High-frequency sensor signal (30fps, zero-copy)
@app.signal(name="vision", interval=1/30)
async def npc_vision():
return {"frame_id": 1, "objects": []}
# VLA fast loop (30Hz, time-driven, bypasses graph)
@app.vla(name="navigation", frequency=30.0)
async def navigation_vla(state, signal_bus):
vision = signal_bus.read("vision")
goal = state.get("llm", {}).get("goal", "idle")
action = [0.5, 0.0, 0.0] # mock: move forward
return {"body": action}
# Action executor receives routed action vector
@app.vla_action(name="body", action_space=ActionSpace(3))
async def body_executor(action):
await game_engine.move(action[0], action[1], action[2])
# LLM slow loop (event-driven)
@app.agent(name="npc_brain")
async def npc_brain(state, event):
if event.type == "user.message":
state.setdefault("llm", {})["goal"] = "go_to_castle"
return state
graph = Graph()
graph.add_node("brain", npc_brain)
graph.set_entry_point("brain")
app.register_graph("main", graph)
Core Concepts
| Concept | Description |
|---|---|
| State | Per-session dict shared across all loops |
| Event | Discrete messages (user input, LLM response) — queued, push-based |
| Signal | Continuous data (camera frames, joint angles) — last-value cache, pull-based |
| Graph | LLM workflow topology (nodes + edges) |
| @app.agent | LLM reasoning node, event-driven |
| @app.vla | VLA inference node, time-driven, runs on its own scheduler |
| @app.vla_action | Action executor, receives VLA output via channel name |
| @app.signal | High-frequency sensor source, writes to SignalBus |
| @app.perception | Low-frequency sensor source, yields Events (existing) |
| Action Channel | Named bus that routes VLA output to executors (N:M mapping) |
Architecture
Session
├── SignalBus ← high-frequency data (zero-copy)
├── LLM Task (_run) ← slow loop, event-driven
│ └── Graph: Agent → Tool → ...
├── VLA Task (_vla_scheduler) ← fast loop, time-driven
│ ├── @app.vla inference
│ ├── Action Channel routing
│ └── @app.vla_action execution
└── State (Blackboard)
├── llm/: goal, plan, messages
└── vla/: actions, status, memory
Examples
| Example | Description |
|---|---|
| simple_chat.py | Basic chat |
| simple_chat_with_tool.py | Tool calling (ReAct) |
| streaming_chat.py | Real-time streaming |
| human_in_loop.py | Human approval workflow |
| perception_loop.py | Sensor processing |
| drone.py | Timer-based perception |
| companion_bot.py | Multi-agent conversation |
| humanoid_robot.py | Multi-tool robot control |
| sleep_assessment.py | Multi-state HITL flow |
| comprehensive_assistant.py | Full-featured assistant |
| npc_vla.py | VLA dual-loop NPC (new) |
python -m fastmind.examples.npc_vla
API Reference
FastMindAPI
api = FastMindAPI(app)
await api.start()
await api.push_event(session_id, event)
async for ev in api.stream_events(session_id): ...
# New VLA/Signal API methods:
frame = api.read_signal(session_id, "vision") # read signal
api.write_signal(session_id, "gps", data) # write signal
signals = api.list_signals(session_id) # list signals
api.pause_vla(session_id) # pause VLA loop
api.resume_vla(session_id) # resume VLA loop
await api.stop()
Changelog
v0.2.1
- Refactor:
_merge_state从全量替换改为update,防止节点返回部分 state 时丢失未涉及的 key - Refactor: 输出队列从
asyncio.Queue替换为EventBuffer(只追加环形缓冲区 + 游标读取),stream_events支持多消费者并行独立消费
v0.2.0
- Major: New VLA dual-loop architecture —
@app.vlafor high-frequency inference (time-driven),@app.vla_actionfor action execution via channel routing,@app.signalfor zero-copy sensor data (parallel to Event) - Major: Dual-loop Session — VLA fast loop runs concurrently with LLM slow loop, communicates via shared State (Blackboard pattern), N:M action channel mapping
- New Feature:
FastMindAPI.read_signal()/write_signal()/list_signals()/pause_vla()/resume_vla() - Bug Fix: Fixed
_save_checkpointcrash on unpicklable state objects (now uses_safe_deepcopywith graceful fallback) - Bug Fix: Fixed
human_in_loopcheckpoint pickle error - Bug Fix: Fixed VLA action executor error isolation (one executor crash no longer blocks other actions in same tick)
- Reliability: Added 20 VLA stress/reliability tests (long-running, error recovery, concurrent access, multi-session, pause/resume cycles, override cycles)
Citation
If you use FastMind in your research, please cite:
@misc{xie2026fastmind,
title = {FastMind: A Framework-Centric Architecture for Dual-Loop Embodied Intelligence},
author = {Xie, Fujin},
year = {2026},
doi = {10.6084/m9.figshare.32692677},
url = {https://doi.org/10.6084/m9.figshare.32692677}
}
License
GPL-3.0 License — see LICENSE for details.
Author
xiefujin email:490021684@qq.com
Copyright (c) 2024-2026 xiefujin 490021684@qq.com. Licensed under GNU GPLv3.
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 fastmind-0.2.6.tar.gz.
File metadata
- Download URL: fastmind-0.2.6.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c1ebc1541478bba16fd343256366c44fb66c6dbf35873f91575863d5671def5
|
|
| MD5 |
137b5b476e5d09adcf6941764c65ff76
|
|
| BLAKE2b-256 |
f195bd644587621037b4394ed943ddbe63f6815db2471196690a872b8156b8d0
|
File details
Details for the file fastmind-0.2.6-py3-none-any.whl.
File metadata
- Download URL: fastmind-0.2.6-py3-none-any.whl
- Upload date:
- Size: 41.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5be37e6db0f2c1f77cfe76799f036075e04f559585d4fd969a61f12e00674aa
|
|
| MD5 |
6b762b6f9aab8fe33e839fd7bc965181
|
|
| BLAKE2b-256 |
ea96f99006ff676f981baa149cb71aa08f66d206e2bca56a1d96661b13dbb583
|