~/projects
Multi-Agent Formation Control (LangGraph)
Repo: mohd-vasim/ai-engineering → mas-formation-control
Five drones over a wheat field, holding a grid formation while a single tree stands in their path. A naive agent loop would treat the whole mission as one opaque LLM call chain — swarm size, formation choice, physics, and compliance review all blurred together. This project is the opposite: an explicit six-node LangGraph StateGraph where LLM decisions happen in exactly two nodes (planning and verdict), and every deterministic step — simulation, analysis, persistence — is a named, inspectable, typed node.
The Six-Node StateGraph
START → assess_mission → plan_swarm (Gemini) → dispatch_simulation → analyze_telemetry → persist_telemetry (SQLite) → generate_verdict (Gemini) → END
assess_mission— logs field dimensions, mission brief, and obstacle list.plan_swarm— Gemini returns a structuredSwarmDispatchPlan(3–8 drones,line/v_shape/grid, spacing 8–12 m, justification). Falls back to a deterministic 5-drone grid if the model is unavailable or fails.dispatch_simulation— runs the decentralized physics simulation: 160 steps at 0.1 s, leaderDrone_Aat constant 2.5 m/s survey speed, followers steering from local sensing only.analyze_telemetry— reduces 160 steps to three metrics (max deviation, final formation error, min clearance) and renders the trajectory plot.persist_telemetry— writes the run to SQLitedata/telemetry.db(run ID, formation, spacing, metrics, timestamp).generate_verdict— Gemini audits the mission against a 5-stage FDS compliance rubric, returning a structuredFDSVerificationVerdict. The fallback verdict applies the same thresholds programmatically (clearance > 3 m, final error < 2 m).
All state flows through a typed MissionState TypedDict — no globals, no hidden channels.
Decentralized Control Laws
Each follower drone carries only its neighbor ID, its designated offset from that neighbor, and local sensing. No drone knows the full swarm state:
- Formation tracking — proportional correction toward
neighbor_position + offset(, tolerance 0.2 m). - Obstacle avoidance — within
radius + 3 mof an obstacle, a repulsion force plus a tangential term bends the path into an arc; avoidance forces outweigh formation error 7.5:1 while dodging. - Peer yielding (self-organization) — any peer within 4.5 m contributes a push-away force, guaranteeing inter-agent clearance.
- Speed invariant — every velocity update clamps to 5 m/s in code, not by prompt.
Concretely: What a Run Looks Like
The planner reads a 100 m × 80 m wheat field with one tree obstacle at (20, 40) and decides the swarm geometry; the simulation executes it; the analysis plots per-drone trajectories against the tree hazard along with formation error over time (tolerance and safety lines drawn in); SQLite records the run; and the verdict node reports all five FDS stages as pass/fail with an executive summary. Every LLM call, tool call, and trace is autologged to a Databricks MLflow experiment — with a deliberate payload optimization: per-step logs carry only instantaneous position snapshots, and full trajectories are appended once at the end, so traces stay linear instead of quadratic.
Tech Stack
- LangGraph
StateGraph+InMemorySavercheckpointer — orchestration and short-term thread memory with step-by-step replay. - Google Gemini (
gemini-3.5-flash-lite) — structured-output swarm planning and FDS compliance verdicts via Pydantic contracts. - Pydantic v2 —
SwarmDispatchPlan,FDSStageVerdict,FDSVerificationVerdict; every model output validated at the boundary. - SQLite — persistent cross-session mission history, queryable with
pandas.read_sql(). - MLflow on Databricks — automatic tracing of all LLM calls and tool invocations.
How to Run
git clone https://github.com/mohd-vasim/ai-engineering.git
cd ai-engineering/mas-formation-control
uv sync
# .env
GEMINI_API_KEY=...
MLFLOW_TRACKING_URI=databricks
MLFLOW_EXPERIMENT_ID=3192447675404693
DATABRICKS_HOST=...
DATABRICKS_TOKEN=...Run the notebook notebooks/formation_control_poc_v2.ipynb top to bottom. Without a Gemini key the graph still completes every mission using deterministic fallbacks — an LLM outage degrades the run, it never crashes it.
Status
Working end-to-end. The graph has been run repeatedly, with each mission accumulating rows in data/telemetry.db for cross-run comparison. Next steps I'd want:
- Durable checkpointer —
InMemorySaverdies with the process; a Postgres or file-backed checkpointer would make missions resumable across restarts. - Multi-obstacle missions — the course is currently a single static tree; dynamic obstacles would exercise the adaptation stage harder.
- Leader failover — the formation follows
Drone_A; designating a second leader enables formation continuation if the lead drone is lost. - Verdict post-processing —
mission_okis inserted as a placeholder; backfilling it from the verdict closes the loop between the DB and the compliance report.