Chat with Claude agents from inside Factorio. Agents plan builds as ghost entities, reason about throughput, rails, fluids and belt lanes, point at things with in-world annotations — and everything they know or plan lives in declarative state files that stay in sync with the game world in both directions, automatically.
MIT licensed. macOS and Linux.
Streaming chat with an agent, in game:
Agents mark and explain plans with in-world annotations instead of editing your factory:
When a decision is yours to make, the agent asks with a form:
┌───────────────────────────── game client (your normal profile, isolated mods)
│ multiplayer
┌───────────▼───────────┐ RCON (poll / push / tool) ┌──────────────────────────────┐
│ Factorio dedicated │◄──────────────────────────────►│ agent daemon (bun) │
│ server + mod (Lua) │ │ │
│ · chat GUI, multi- │ │ · one Claude Agent SDK │
│ chat, forms, │ │ session per chat (resumed │
│ status bars │ │ across restarts) │
│ · world tools │ │ · state watcher: file edits │
│ · change journal │ │ auto-apply + verify │
│ · staleness cells │ │ · world→file sync (journal) │
│ · annotations │ │ · lane analyzer, watchdogs, │
└───────────────────────┘ │ idle reaper │
└───────────┬──────────────────┘
│ spawns, cwd =
┌───────────▼──────────────────┐
│ agent-workspace/ │
│ · factorio-tool CLI │
│ · .claude/skills (docs the │
│ agent reads at start) │
│ · lib/rails.js (engine- │
│ derived rail router) │
│ · state/ ← THE source of │
│ truth for plans & factory │
└──────────────────────────────┘
The core idea: files are the interface.
- A construction is a JSON file in
agent-workspace/state/: metadata,as_of_tick, and aghostsarray (with per-belt lane intents, machine recipes, train schedules, stop names). Saving the file deploys it: the daemon watchesstate/, applies changes within seconds, and writes the full result back into the file as averifyfield — placements, failures with reasons, removals, lane warnings, staleness refusals. - The world syncs back into the files continuously: the mod journals every
build/removal/rotation (player, bots, any chat) and the daemon files each
change into whichever state file claims that entity (build progress counters,
removal sync-logs, rotation updates) or into
state/world/region_X_Y.jsonmirrors for unclaimed territory. - Staleness is enforced: layouts declare the tick of the scan they were planned against; if the target area changed since (16-tile dirty-cell tracking, ghosts included), the apply is refused with instructions to re-read, update, re-apply.
- Agents can only add ghosts, mark, and point: deconstruction marks are restricted to natural clutter, upgrade marks to researched same-footprint tiers, and everything the player must do by hand is annotated in-world (entity-attached rings + labels that vanish when you do the thing, boundary boxes, map pins) — never bare coordinates.
The in-game mod provides multi-chat (each chat = an independent SDK session with persistent memory), streamed responses, AskUserQuestion forms, permission prompts, per-chat status bars with elapsed timers, and notification pings when an agent finishes or needs input while its window is closed.
The daemon provides: session resume across restarts (transcripts), a stall
watchdog (hung API streams get interrupted), an idle reaper (sessions stop
after 5 idle minutes, resume on the next message), API timeouts, reasoning
effort control, and permission policy (factorio-tool pipelines and
/tmp+workspace writes are pre-approved; anything else prompts in-game).
Agent-facing tooling (./factorio-tool --help for the full list):
observation (scan_area with statuses/contents/lanes/fluid segments,
get_occupancy tile grids with reserved-tile marking, trace_belt,
trace_entity with fluid ports, find_entities, find_resources map-wide,
get_power, get_rates, check_train_stop), planning (place_ghosts
file-based/declarative/idempotent, lint), marking (mark_deconstruction,
mark_upgrade), pointing (annotate, list_annotations,
clear_annotations) — all fog-of-war-gated to what the players have charted.
- Factorio 2.0 (full game for the client; the same binary runs the
server). Auto-detected at:
- macOS:
/Applications/factorio.app - Linux:
~/factorio/bin/x64/factorioor the Steam library paths - anything else:
FACTORIO_BIN=/path/to/factorio
- macOS:
- bun (runs the daemon and CLI), node (layout generation), jq (the agents' analysis workhorse), python3 (used by some scripts).
- Claude Code CLI, logged in — the Agent SDK uses its credentials.
git clone <this repo> && cd factorio-planning-agent
( cd daemon && bun install )Optional: put a save at server-data/saves/starter.zip to use an existing
world as the base (--fresh resets back to it); otherwise a new world is
generated. Mod-list enables Space Age + Quality + Elevated Rails by default —
edit scripts/enable-mod.mjs if your save differs.
scripts/run.sh # server + daemon + game client; quitting the game stops everything
scripts/run.sh --headless # server + daemon only (Ctrl-C stops)
scripts/run.sh --fresh # reset the world (reseeds from saves/starter.zip if present)In game: click the Claude button (top-left). Create/rename/delete chats in the window header; each chat is an independent agent. Type a request — Enter sends (¶ toggles multi-line mode), ■ interrupts. Watch it scan, plan, drop a boundary box, and fill the area with ghosts for you or your bots to build.
Ops (all safe while the game runs):
scripts/start-daemon.sh # restart just the daemon (sessions resume with memory)
scripts/reload-mod.sh # mod changes: sync + bounce server; reconnect in the client
scripts/rcon-cli.ts # ad-hoc: bun scripts/rcon-cli.ts '/silent-command rcon.print(game.tick)'Daemon knobs (env for start-daemon.sh / run.sh):
| var | default | effect |
|---|---|---|
AGENT_MODEL |
session default | pin a model, e.g. claude-sonnet-4-6 for snappier turns |
AGENT_EFFORT |
medium |
reasoning effort: low…max |
API_TIMEOUT_MS |
180000 |
hung API streams error out and retry |
STALL_MS / SPIN_MS |
240000/300000 |
watchdog: silence / thinking-without-progress before interrupt |
IDLE_STOP_MS |
300000 |
idle sessions stop (resume on next message) |
RCON_PORT / RCON_PASS / GAME_PORT |
27015/caludeagent/34197 |
networking |
- Mod iteration:
reload-mod.shbounces the server only; clients re-readcontrol.luaon rejoin (seconds). Prototype-stage changes (data.lua,info.json) need a full client restart. Keep the mod version stable across reloads — clients match versions at launch;init_storage()runs at every entrypoint instead of relying on version-triggered migrations. - Factorio RCON returns exactly one response packet per command (verified to
100 KB); commands must be single-line;
/silent-commandruns in the level context so mod state is only reachable viaremote.call; Factorio Lua is 5.2 (no\u{}escapes);game.reload_mods()/reload_script()are silent no-ops on dedicated servers. - The rail connection graph in
agent-workspace/lib/rails.jsand the belt lane rules indaemon/lanes.tswere derived empirically by probing the engine — regenerate the same way if a game update changes rail geometry. - Logs:
server-data/{server,daemon,client,create}.log; verify failures for malformed JSON land inagent-workspace/state/_verify.log.


