Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f8dcda7
Consolidate design docs, exclude from mkdocs
yiyixuu Aug 6, 2026
11afd32
Add lighting primitives
yiyixuu Aug 6, 2026
b05b4c4
Decouple trail colour from lighting
yiyixuu Aug 6, 2026
0640162
Ignore .DS_Store and Claude Code state
yiyixuu Aug 6, 2026
1695b97
Consolidate the lighting modules and trim their comments
yiyixuu Aug 6, 2026
b40e049
Snapshot each look where its formation has arrived
yiyixuu Aug 6, 2026
256299b
Correct the docstrings that still said snapshots freeze at t_start
yiyixuu Aug 6, 2026
d741b69
Pin the degeneracy fixtures' float noise instead of assuming it
yiyixuu Aug 6, 2026
291803a
add preset
yiyixuu Aug 6, 2026
f5363c6
Give render.py a command line
yiyixuu Aug 6, 2026
d67bcfa
Add a bench calibration tool for the lighting palette and decks
yiyixuu Aug 10, 2026
4f48a99
Drop the synthetic 100-drone table, activate the 20 real drones
yiyixuu Aug 10, 2026
067f883
Initialize rclpy before DroneSwarm, and drop the Choreographer depend…
yiyixuu Aug 10, 2026
450cfdb
Cut comments and docstrings back to a supplement
yiyixuu Aug 10, 2026
6e3d1fa
Return the flight volume to the lab box
yiyixuu Aug 10, 2026
8ff4b2b
Teach the prompt the compact drone_ids spec, scaled to the active swarm
yiyixuu Aug 10, 2026
7fd608c
Cut lighting.toml's comments back to a supplement
yiyixuu Aug 10, 2026
403c353
Give reasoning models effort instead of temperature
yiyixuu Aug 10, 2026
3e3f67f
Show what the model is doing while it thinks
yiyixuu Aug 10, 2026
2590105
added upper/lower selector, also enabled LLM to put different colors …
yiyixuu Aug 13, 2026
e41ab68
Point the stage axis at the audience's right, not into the room
yiyixuu Aug 17, 2026
5d03721
Add gold, silver and violet, and a fade that moves colour in time
yiyixuu Aug 18, 2026
715d5da
Show the whole exchange live, and fix the review findings
yiyixuu Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,19 @@ ros_ws

openai_api_key.sh

.DS_Store

.cursor
# Claude Code: personal settings and runtime state (worktrees, locks, caches).
# Un-ignore a specific path if you ever want to share project config with the
# team, e.g. `!.claude/settings.json` — note `.claude/*` form is required for
# that, since git will not descend into a directory ignored as `.claude/`.
.claude/
docs/ui-previews
# Internal design docs: local-only, never published. docs/ is also the mkdocs
# docs_dir, so these are excluded from the site build in mkdocs.yml too.
docs/specs/
docs/superpowers
docs/plans/
test-results
playwright-report
web/dist
Expand Down
8 changes: 8 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
site_name: SwarmGPT - Learning Systems Lab

# docs/ doubles as the mkdocs source and the home of local-only design docs
# (docs/specs, docs/plans -- both gitignored). Exclude them so a local build or
# `mkdocs gh-deploy` from a dirty checkout cannot publish them.
exclude_docs: |
specs/
plans/

nav:
- Home: index.md
- Getting Started and Installation: getting_started.md
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ maturin = ">=1.13.3,<2"

[tool.pixi.tasks]
api = { cmd = "python swarm_gpt/launch.py", description = "Serve the SwarmGPT browser API and built frontend", depends-on = ["web-build"] }
render = { cmd = "python swarm_gpt/render.py", description = "Render a saved preset to a video (--help for flags, --list for presets)" }
web-dev = { cmd = "npm --prefix web run dev", description = "Serve the browser frontend with Vite" }
web-build = { cmd = "npm --prefix web run build", description = "Build the browser frontend" }

Expand Down
23 changes: 15 additions & 8 deletions swarm_gpt/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from pydantic import BaseModel, Field

from swarm_gpt.core import AppBackend
from swarm_gpt.utils import generate_default_colors
from swarm_gpt.utils.llm_providers import (
DEFAULT_OPENAI_MODEL_CHOICES,
PROVIDER_LABEL_OLLAMA,
Expand Down Expand Up @@ -239,13 +238,12 @@ def normalize_playback(sim_data: dict[str, Any], backend: AppBackend) -> dict[st
if states.shape[1] != num_drones:
raise ValueError(f"State drone count mismatch: {states.shape[1]} != {num_drones}")

colors = generate_default_colors(num_drones, limit=1.0)
bounds = backend.settings["axswarm"]
sample_rate = 0.0
if len(timestamps) > 1:
sample_rate = float(1.0 / np.median(np.diff(timestamps)))
return {
"schemaVersion": 1,
"schemaVersion": 2,
"audioUrl": _audio_url(backend.music_manager.song),
"audioOffset": backend.crop_window(backend.music_manager.song)[0],
"song": backend.music_manager.song,
Expand All @@ -254,7 +252,7 @@ def normalize_playback(sim_data: dict[str, Any], backend: AppBackend) -> dict[st
"states": states.tolist(),
"fields": {"pos": [0, 3], "quat": [3, 7], "vel": [7, 10], "angVel": [10, 13]},
"bounds": {"min": bounds["pos_min"], "max": bounds["pos_max"]},
"colors": colors.tolist(),
"lighting": backend.browser_cues(),
"sampleRate": sample_rate,
}

Expand All @@ -265,11 +263,20 @@ def _start_thread(job: Job, target: Any) -> None:
thread.start()


def _watch_backend(store: JobStore, job: Job) -> None:
"""Forward the backend's exchange events onto the job's socket as they happen.

The panel is a live transcript, so nothing is held back to the end: a rejected response and the
retry it triggers both reach the browser while the run is still going.
"""
job.backend.on_event = lambda event_type, payload: store.emit(job, event_type, payload)


def _run_initial_job(store: JobStore, job: Job, selection: str) -> None:
try:
_watch_backend(store, job)
store.emit(job, "thinking_started", {"selection": selection}, status="thinking")
messages = job.backend.initial_prompt(selection)
store.emit(job, "conversation", {"messages": messages})
job.backend.initial_prompt(selection)
store.emit(job, "safety_started", {}, status="filtering")
sim_data = _run_simulation_with_events(job.backend, store, job)
playback = normalize_playback(sim_data, job.backend)
Expand All @@ -295,9 +302,9 @@ def _run_refine_job(
if provider is not None and model_id:
job.backend.choreographer.configure_llm(provider, model_id)
store.emit(job, "llm_configured", {"provider": provider, "modelId": model_id})
_watch_backend(store, job)
store.emit(job, "thinking_started", {"refine": True}, status="thinking")
messages = job.backend.reprompt(message)
store.emit(job, "conversation", {"messages": messages})
job.backend.reprompt(message)
store.emit(job, "safety_started", {"refine": True}, status="filtering")
sim_data = _run_simulation_with_events(job.backend, store, job)
playback = normalize_playback(sim_data, job.backend)
Expand Down
22 changes: 3 additions & 19 deletions swarm_gpt/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
"""Core package for the swarm_gpt package.

This submodule contains the backend code for interfacing with the LLMs, AMSwarm, pybullet-drones,
and the crazyflies.
"""
"""Backend code for interfacing with the LLMs, AMSwarm, pybullet-drones, and the crazyflies."""

from typing import TYPE_CHECKING, Any

Expand All @@ -16,20 +12,8 @@
def __getattr__(name: str) -> Any:
"""Resolve the package's two entry points on first access (PEP 562).

Importing them eagerly made *any* ``swarm_gpt.core.<module>`` import pull in the choreographer,
which imports ``swarm_gpt.utils.music_analyzer``, which imports
``swarm_gpt.core.structured_output_schema`` -- a cycle that broke ``tools/analyze_songs.py``
with a partially-initialized-module ImportError. Nothing in that chain needs the choreographer;
only this convenience re-export did, so it is deferred until something asks for it.

Args:
name: Attribute being looked up on the package.

Returns:
The requested class.

Raises:
AttributeError: If the package has no such attribute.
Importing them eagerly made any ``swarm_gpt.core.<module>`` import pull in the choreographer
and reach ``structured_output_schema`` via ``music_analyzer`` -- a cycle.
"""
if name == "AppBackend":
from swarm_gpt.core.backend import AppBackend
Expand Down
Loading
Loading