AI chatbot and chat assistant for your Factorio server: ask a question in game chat and the AI answers in chat from your live save. Where things are, production rates, research, what changed since you died. Multiplayer, every team and force, follow-up questions. Bring your own key: Claude, GPT, DeepSeek, Gemini or any LLM on OpenRouter. Runs with a small agent program beside the server.
Providing the player with new tools or adjusting the game interface, without fundamentally changing gameplay.
Version: 1.1.0
Date: 2026-10-25
Info:
- Runs on Factorio 2.1, nothing else changed in the mod. Servers still on Factorio 2.0 keep
1.0.9, which stays on the mod portal.
Version: 1.0.9
Date: 2026-10-11
Features:
- A new answer shape, art: ASCII art, diagrams and layout sketches print exactly as drawn,
spaces kept, in the monospace font, on lines of their own. Up to 12 lines of 60 characters.
Bugfixes:
- Asked for ASCII art, the bot sent a bulleted list in the chat font with every run of spaces
flattened, so nothing lined up.
- Table columns after an icon sat a character and a half to the right: an icon is about two
and a half characters wide in the monospace font, and was counted as one.
Version: 1.0.8
Date: 2026-10-11
Features:
- watching_force_v1: a mod that lets players watch another force's base can name the force
being watched. A question asked while watching is told whose base "here" is, and checks both
the asker's force and the watched one.
- item_rate says when an item was last made: with nothing made in the asked window, it adds
the next longer window that saw any.
Bugfixes:
- Asked "are we making repair packs here?" while watching another team's surface, the bot
searched only the asker's machines and said nothing on that surface made any.
- A machine that made 48 repair packs in the hour and stalled a minute ago was reported as not
making any.
Version: 1.0.7
Date: 2026-10-11
Features:
- home_force_v1: a mod that moves players onto another force for a while, a spectator force
or a lobby, can name the force they belong to. A question is then about that force, and the
model is told the force the player is watching from.
- list_forces takes names=true, adding what players call each force, so a question about the
team names themselves is answered from the real names.
Bugfixes:
- A member spectating another team who asked "how about our team?" was answered for the
spectator force.
- Asked about the teams' names, the model judged force names and said a named team had no
name.
Version: 1.0.6
Date: 2026-10-11
Features:
- recipe_needs: what it takes to make something, worked out from the force's own unlocked
recipes. Every crafted item per minute with its machine and machine count, the raw inputs,
and belts of each, for several targets in one call. An unknown name comes back with
did_you_mean, up to five real names: red-science-pack is answered with
automation-science-pack. Ore, coal, stone, water and crude oil stay raw once Space Age
recipes can make them: a raw row says raw = "mined" and names asteroid crushing, ice
melting, coal synthesis or scrap recycling as alternatives.
- The bot's voice is a setting, aab-personality: blank leaves it to the operator's config, off
turns it off, anything else describes how the bot should sound. Each question carries the
value it was asked with. Admins change it in game with /aab-personality, and reset hands it
back to the operator's config.
Changes:
- Tables line up. Every row of a table answer is set in a monospace font the mod now declares
(aab-mono, the game's own mono face at the chat size), each column padded to its widest
cell, numbers right-aligned, a rule under the header, so the column boundaries hold from the
first row to the last.
- locate_player takes nearby=N: the closest built things within N tiles of the player's
character, nearest first, with the distance and a gps each, so "what is Bob standing next
to" is one read of one small circle, never a walk over the surface. all=true answers for
every connected player in one call.
- Answers may run twice as long, 320 characters a cell, and every cap matches the service's.
A line the renderer has to cut ends with an ellipsis instead of stopping mid-word.
- on_answer carries the question's scope and private, so a mod relaying answers elsewhere can
keep a private one inside its channel.
- Every chat line in events.jsonl says who heard it, as scope and private: a chat privacy
mod's answer when one gives it, the game's own rule when none does. A plain line reaches the
speaker's force alone, so it is private to that force once a second force has players.
Update the service with the companion: an older service ignores these fields.
- The game's rule keeps a private line under its force name only on a server with no chat
privacy mod. Where one is installed, a force may have passed from one group to another, so
a line no such mod answers for, or one a failing mod may have kept private, still reaches
the speaker's force alone but is kept under global, where nobody reads it back. A question
asked with the chat prefix on it reads public history alone.
- The first line of every session in events.jsonl is a session event with a map id. The id is
made once per map, from the map seed, the tick and a random number, and kept in the save, so
the service can tell a new map from an older save of the same one.
- The status reply carries the same map id, so the service knows the map before any event is
written. On a save from before map ids, a status that comes first makes the id.
- An artifact that would print nothing, its title and every line, item or cell blank, is
refused as bad_artifact, so a question is never closed with a blank chat line.
Bugfixes:
- A sprite tag naming something the game has no sprite for printed raw, brackets and all: the
model wrote [img=entity.biter] for a kind of enemy that has no prototype of that name, and
planet.* is not a sprite path at all. Every such tag is now repaired: a planet path is read
as its space location, a name in the wrong class finds its own (item.crude-oil is a fluid),
a plural or a capital is respelled, and a name players give a science pack, circuit or belt
finds the item they mean, so [img=item.red-transport-belt] shows the fast-transport-belt
icon. Failing all of that the tag prints as its plain words, so "[img=entity.biter] attacks"
reads "biter attacks" instead of losing its subject. A clickable tag ends at its plain name.
- A question asked with the chat prefix went to everyone while its chat line reached the
speaker's force alone: with two populated forces and no chat privacy mod, its text was
public in history and its answer printed to the whole server. It now takes its line's scope
and stays inside that force. Questions asked with /ask are unchanged.
- With aab-answer-audience set to asker, an answer whose asker had left printed to the whole
server. It now prints to nobody, and the log says so.
- A chat scope provider's tag or label, or a force label, past its length cap was cut by bytes
and could end inside a character. Each is now cut on a character boundary.
- A chat scope provider's key past 64 bytes was cut, so two keys sharing their first 64 bytes
became one audience and read each other's private history. A longer key is now refused, and
its answer is kept private under a key no question reads with.
- A chat line was logged public when one chat scope provider failed and another answered
global, though the one that failed may have kept it private. The line now falls to the
game's own force rule unless a provider answered private, and a question asked with the
chat prefix follows its line. Questions asked with /ask are unchanged.
- A chat scope provider answering private = 1 or "true" was read as public. Anything but false
or nil now reads as private, the way the service reads it.
- The question event in events.jsonl logged the whole question text while the question kept
its first 400 bytes. The event now logs the same 400 bytes.
- A chat scope provider's tag past 200 bytes could be cut inside a rich-text tag, and the
answer's first line printed raw. The cut now drops a torn tag and closes an open colour or
font, all within the 200 bytes.
Modding:
- chat_scope_v1 is asked about every chat line the events file records as well as every
question. Return nil when you do not know, and the game's force rule applies: a line it makes
private is kept where no question reads it back. Return global only when the text really
reached everyone. A private key is read back for as long as the service keeps history, so
it must name one audience: add a generation if names are reused.
- chat_scope_v1: a key is opaque, compared whole, at most 64 bytes. A longer key is refused,
not cut, and its answer read as private under global.
- chat_scope_v1: private is read as private unless it is false or nil. A provider that errors
no longer counts as nil for a chat line: another provider's global answer then gives way to
the game's force rule. A scope handed to ai-agent-bridge-v1.ask is read the same way.
Version: 1.0.5
Date: 2026-09-16
Changes:
- sweep: one tool for every "which team has the most" question. Name a metric and, if it
needs one, a subject; get one row per force, largest first, ranked by the service with the
leader and its margin on top. metric is a closed list the tool's own description carries, and
an unknown one answers with a card for each rather than an error. axis groups the rows by
surface, space platform, player or force-and-surface instead of by force. Every metric
delegates to a tool that already exists, so nothing is measured twice, and a sweep the
companion would have to cut is refused whole rather than ranked over whoever survived.
- sweep gained six more metrics, each delegating to a tool this mod already ships rather than
measuring anything a second time: item_made and item_rate (how much of one item a force has
made, ever or right now), pollution and evolution (per surface, and per force for
evolution), and robots and networks (logistic robots and logistic networks a force has,
summed over its surfaces by default). logistics_summary takes an additive contents=false so
robots and networks never pay for a network's item contents, the one expensive read that
tool does.
- Four new lookups, and a sweep metric for each: kills and built (how many enemies a force has
killed and lost, how many entities it has built and mined back, summed over every surface
unless one is named, with a largest-first breakdown of the kinds), fluid_rate (production and
consumption of one fluid, per minute, the fluid version of item_rate), and trains (how many
trains a force has, and how many are moving or under manual control right now). Each takes
all=true the same way rockets and entity_count already do, so "which team has killed the
most" or "how many trains does each team have" is one call.
- list_surfaces says which space platform a surface is, who owns it, where it is stopped and
what it is doing, so "which space ship" questions no longer guess an owner force by force.
- entity_count takes all=true and answers for every force that has players in one call, one row
each, largest count first, so "how many solar panels does each team have" is one lookup and
not one per team. Leave surface out and it reports each force's total everywhere from the
counter the engine already keeps, which costs nothing whether a server runs 4 teams or 40.
Name a surface when the answer has to be about that one place, and that path counts one pass
per force and refuses rather than half-answering if even that would be too many.
- entity_count's all=true also takes per_surface=true, breaking each force's count down by
surface instead of totalling it: one row per team-and-place that actually has some, empty
combinations left out, platform rows carrying the same owner, location and state columns
list_surfaces does. This is what makes "which space ship has most thrusters, where is it"
one call instead of a many-round search.
- Team names print in the colour the team mod gave them. A labels provider may colour a label,
the way a team mod colours its teams, and the companion used to strip every tag from it, so
a team name never once printed in its colour. The colour and font tags a label carries are
kept now, and a label the model copied out of a tool result prints in the colour too.
- Player names print in the player's own chat colour, the one the game prints their chat in.
No team mod is involved: every player has one, and the companion reads it when it renders.
- A space platform row, from sweep or from entity_count's per_surface, carries who owns the
ship, where it is stopped, and a gps at its hub on its own surface, so "which ship has the
most, where is it" is one row and one click.
- entity_count's per_surface walk skips a force that owns none of the entity anywhere, one
O(1) read instead of a pass per surface, and counts a platform for its owner only. On a
30-surface, 16-team save the thrusters question cost 480 passes and 1.26 seconds on the
game thread; it is a few dozen passes now.
- Agent program: the OpenRouter upstream host can be pinned, providers and allow_fallbacks
under model in aab.yaml. The hosts behind one model differ by about three times in answer
time and the prompt cache is per host, so a pinned host keeps answers fast and the cache
warm.
- Agent program: every answer is read back the way the player sees it and logged, and the log
says so when a second agent program is answering the same server.
Bugfixes:
- A line clipped in the middle of a colour tag printed raw, tags and all. The mod and the agent
program both close the span they cut now.
- top_items sorted its rows on the rounded rate, a number for a whole rate and a text for a
fraction, and Lua throws comparing the two, so "what do we make most" answered with an error
whenever one item's rate happened to be whole beside another's that was not. Sorted on the
raw rate now, rounded only on the way out.
- list_forces hid the asking force whenever nobody had joined it. Forces nobody has ever joined
are left out as empty slots since 1.0.3, and on a server with no one connected that is every
force, the asker's included: "what forces are there" answered with none. The asker's own
force is always listed now, whoever has joined it.
- list_forces counted the engine's own enemy, neutral and player forces as empty team slots,
so "how many teams" reported three slots more than the server had, and a listing with
include_empty=true let the model count them again for itself. empty counts only team slots
now, either way, and the engine's rows carry engine=true so they are never mistaken for one.
- list_surfaces returned 20 rows by default, sorted by name, and a space platform is named
platform-N, so every platform sorted after every planet and fell off the end. On a server
with 25 surfaces the reply held no platform at all, which is why "which space ship has most
thrusters" guessed a platform name before it had seen one and then spent a round listing
again with a limit. The default is the cap now.
- list_surfaces said nothing about who owned a platform or where it was stopped, so seeing a
name in the list still left a 5-platform, 14-force cross product to search by hand. A
platform row now carries platform, owner, location and state. scheduled_for_deletion is a
tick count, not a boolean, so a platform mid-deletion reads as an ordinary row rather than a
half-built one.
- find_entities carried no cost tier, so the trace that started this release answered a
counting question with fifteen position walks and zero entity_count calls. find_entities is
tier 2 now; entity_count, which asks the engine for a number instead of walking positions,
stays tier 1.
- rockets with all=true sorted its rows by force name before cutting them to the row cap, so a
server with more forces than the cap discarded the biggest launcher first, on the one tool
whose purpose is naming the biggest. Largest first now, force name only as a tiebreak.
Version: 1.0.4
Date: 2026-09-14
Changes:
- list_players takes all=true and answers for every player on every force in one call, so
"who is online" is one lookup, not one per force.
- rockets takes all=true and answers for every force that has players or has launched a rocket
in one call, so "how many rockets has each team launched" is one lookup, not one per force.
Item lists stay a single-force answer.
- The tools op, which reads the whole catalog at once, is capped at 32 KB rather than 8 KB,
the room the manifest op has always had. The companion's own catalog had grown to within
13 bytes of the old cap, where one more sentence of tool description would have turned
every catalog read into a refusal.
Bugfixes:
- find_entities named a product or recipe but no type scanned every entity the force owns on
the surface, capped at 2000, which on a busy server was belts and inserters discarded by the
filter before a crafting machine was ever reached. "Where are we making red ammo" answered
that no machine made it on a server with six. A search naming a product or recipe but no
type now asks for the three crafter types instead.
Version: 1.0.3
Date: 2026-09-11
Changes:
- list_forces leaves out forces that have never had a player, the team slots a scenario mod
makes in advance and the engine's own, unless asked with include_empty; the reply counts
what it left out. An agent asked about "each team" no longer sees twenty empty slots.
- current_research takes all=true and answers for every force that has players or is
researching in one call, so "what is each team researching" is one lookup, not one per
force.
Version: 1.0.2
Date: 2026-09-11
Changes:
- A force's total production no longer depends on guessing its surface: production_since
takes the surface as optional and, without one, counts every surface the force has made
the item on, with a per-surface breakdown. Since tick 0 the count is exact, from the
game's own lifetime counters.
- The production tools say plainly which is a rate and which is a total, so "how much has
team 2 made" is answered with a total and "how fast" with a rate.
- Every lookup accepts a force's display name where it expects a force name, "Team 02" for
team-2, resolved through the team mod's labels, so an agent that read a name in a tool
result and passed it back still reaches the right force.
- A comparison with a blank column name prints a letter in its place rather than nothing.
Version: 1.0.1
Date: 2026-09-11
Changes:
- Compare teams on their own clocks: on a Multi-Team Support server the agent now reads
each team's online and elapsed hours from the team mod, and the game clock is named as
the server's clock, so "how am I doing compared to Team Ace" compares fairly.
Bugfixes:
- Fractions (rates, progress, evolution, hours) reached the model as fifty-digit numbers,
because the game's JSON writer prints a non-integer double at full precision. They now
leave as short decimals, "1.79", and whole numbers stay numbers.
Version: 1.0.0
Date: 2026-09-11
Features:
- Ask the AI in chat: /ask a question, or a chat prefix of your choice, and the answer prints
back in chat a few seconds later, pulled from the live save at that moment.
- Follow-up questions share a session. /ask new starts over, /ask #name opens a named session
others can join, /ask sessions lists what is open.
- Any player may ask about any force. Team chat privacy and team names come from mods that
have them (Multi-Team Support), so a team in team-only chat gets private answers.
- Answers with item icons, clickable item and recipe tags when you ask about a thing itself,
and map pings you can click for "where" questions, ghosts included.
- Seventeen lookups on the live game: forces, players, surfaces, research and the queue,
production rates and totals since a tick, logistic networks, entity counts, evolution,
pollution, rockets, game time, where entities are, where a player is; plus history
(deaths, joins, chat, research, rockets) from the event log.
- Other mods add their own lookups by exposing one function; the mod never needs to know them.
- Safety: the rpc command runs for RCON only, asking is rate limited per player and per
server, refusals are private, every lookup is a bounded read.
- The mod is the game side of an open protocol over RCON. The agent program on GitHub uses
your own OpenRouter or Anthropic key and any model you pick; anyone can write another.