Skip to content

Releases: factorysemantics/factorysemantics-mes

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 15 Oct 08:29
4b4d96d

0.3.1

Reference. What this release does, what it does not, and what is simulated.

Released: 2026-10-15. Previous: 0.3.0, on PyPI 2026-10-12.


What 0.3.1 is

Four pull requests on top of 0.3.0,
#170 to
#174, every
one about the first hour on somebody else's PC. 0.3.0 proved the plant starts
from pipx install on Windows; this release is what happens when it does not.

The first run on a Windows PC recovers itself

Three things went wrong in the first twenty minutes for the first person to
install 0.3.0 on Windows. All three are fixed:

py -m fsmes demo bottling        # when `fsmes` is "not recognized"
py -m fsmes demo bottling status # when nothing answers on 8010
py -m fsmes demo bottling stop   # when the plant is half dead
  • py -m fsmes works. pip install puts fsmes.exe in a Scripts folder a
    default Python install does not put on PATH. Every command works this way now,
    and a plant started this way starts its child processes this way, so PATH
    never has to be fixed first.
  • status names what died. A plant records what each of its five processes
    is for, so status says which are alive and which is gone by role — the
    dashboard and API, the OPC server, the reader, the simulated floor, the log
    sink — prints the path of the gone one's log, and ends with the command to
    type next. A healthy plant prints its dashboard URL.
  • stop clears a half-dead plant, with no flag. When one process dies and
    the rest keep running, nothing answers and nothing can be started over the
    top. stop ends the survivors and says what it found; a start on top of a
    silent plant is refused in the same words instead of failing at bind five
    times.

One page for that first hour:
run it on your own box.

A simulated plant shaped like your own

0.3.0 could write a file describing your plant's shape and do nothing with it:

fsmes skills unpack plant-from-your-mes .\assistant   # offline, out of the wheel
# the three commands in assistant\SKILL.md, by hand on the MES box -> plant-shape.toml
fsmes demo from-shape .\plant-shape.toml --name acme

Your machines, routings, stop mix, order sizes and shifts, in six seconds, with
the one number the file cannot carry asked for on the way (the space between
each pair of machines). fsmes shape plant writes the pack without starting it.

One folder any assistant can walk you through

fsmes-assistant.zip is attached to this release, and
fsmes skills unpack assistant ./assistant writes the same folder out of an
installation with the network off. It is plain markdown you hand to whatever AI
assistant you already use — in a chat window, with no access to your machine.
Four stages: install until fsmes demo bottling answers; a plant shaped like
yours; this one beside your own MES in shadow mode, ending at the scorecard; and
an honest page on why "can I switch my old MES off?" is still answered no. It
never asks you for plant data: you run the commands and paste what was
printed.

fsmes doctor: one file you can paste to anybody

fsmes doctor --out doctor.md: one command, one Markdown file with the
version, the OS, how it was installed, every plant in this home with each process alive or gone, the ports and whether they
answer, and the last 40 lines of every process log with every value replaced by
its shape
— a tag becomes AAAAAA_AAA_AAA, an order AA-AAAA-#, a measurement
<float>. For a plant in shadow mode a second half says how long it has been
watching, how much of that window it saw, the kept scorecards as one row each,
and what is not built toward a real switch-over. The file is read back by the
shape file's own leak check and is not written at all if any value got
through
. It starts nothing, stops nothing, and nothing sends it anywhere:
one file to paste when something is wrong.

What 0.3.1 does not do

  • It has not run a real plant. Not one, still. Every number in this
    repository comes from a simulated line and says so where it appears.
  • No real SQL Server has run the shape queries. That path is proven on this
    project's own fixtures and on Windows in CI, nowhere else.
  • Shadow mode has never run beside a real MES, so the doctor file's second
    half has only ever described simulated plants.
  • A real switch-over is not built — the old MES off, this one booking for
    real, writing to PLCs, carrying the old history. The folder says so.
  • You still need Python 3.12 or newer, the real obstacle on a locked-down
    plant PC. Nothing here removes it.
  • Everything 0.3.0 does not do, it still does not do: 0.3.0.

What is simulated

Everything that produces a number, as in 0.3.0: a deterministic line simulator
in src/fsmes/sim/, replayed over a real OPC UA server. A plant from your shape
file is shaped like yours; none of its readings are yours.

Upgrading from 0.3.0

pipx upgrade factorysemantics-mes   # or pip install --upgrade
fsmes --version                     # 0.3.1

No migrations. Nothing here changes the database schema, so a plant that
runs on 0.3.0 runs on 0.3.1 with no init-db step; d4e8a1c93b70 is still
head. The procedure, and 0.2.0's, is on
upgrading between versions.


Every change in this release: CHANGELOG.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 00:29
122e5d5

0.3.0

Reference. What this release does, what it does not, and what is simulated.

Released: 2026-10-13. Previous: 0.2.0, on PyPI 2026-09-17.


What 0.3.0 is

A hundred and seventeen pull requests,
#38 to
#163.
The reason to install it rather than read about it is the first paragraph.

A plant on your machine, in two commands

pipx install factorysemantics-mes
fsmes demo bottling

Those two lines, on Linux, macOS or Windows, bring up the six-station ACME
bottling line: an eight-hour shift replayed over a real OPC UA server, a
simulated floor booking lots and naming its stops, and the operator screens
reading it. It prints where the plant is — http://127.0.0.1:8010/dashboard —
and an account to sign in with. fsmes demo bottling stop puts it down;
fsmes demo machining is a second plant on 8020.

0.2.0 could not do this. The plant packs were in the repository and not in the
wheel, so fsmes demo bottling from a PyPI install had no plant to run. They
are in the package now, byte for byte the ones the lab runs, and the wheel grew
3 % for it. First start copies the pack out of the package and generates the
shift from its seed — about 8 seconds on Linux, about 21 on Windows on
GitHub's runners — and after that it starts straight away. Everything it writes
goes to one per-user directory (%LOCALAPPDATA%\fsmes on Windows,
~/.local/share/fsmes on Linux, ~/Library/Application Support/fsmes on
macOS, or wherever FSMES_HOME says); nothing under the installed package is
written to.

A CI job does exactly this on Ubuntu and on Windows on every pull request —
installs the built wheel into an empty directory, starts the plant, signs in,
checks the specifications are there, the OPC port answers, the floor is
watching and a lot has been counted, then stops it and checks both ports came
back. It is the first job in this project's history that starts a plant on
Windows, and it found three faults nothing else could: the Windows console's
code page mangling what the CLI printed, in two places.

You need Python 3.12 or newer on the machine. On a locked-down plant PC
that is the real obstacle, and nothing in this release removes it.

Quality you can click

The SPC tab draws the charts the plant's own specifications ask for: an
individuals-and-moving-range chart where a characteristic is measured one piece
at a time, an X̄/R chart where it is measured n at a time, and a p chart
where an inspector counts a lot rather than measuring it. Western Electric
rules 1 to 5 are drawn and recorded — one row per firing, with the window it
judged — and which of them raise a hold is the plant's choice: rules 1 to 4 do
by default and the moving-range rule does not, because a plant that has run a
year without a moving-range chart should not wake up to a queue of holds about
readings it already lived through. A p chart draws rule 1.

Click a point and a panel opens beside the chart with the reading, the rules
that fired on it, the gauge that took it, the machine's state and tags at that
moment, and the rest of the line. Three more things on the floor screens became
clickable in this release, each opening the records behind what you clicked
rather than a new screen:

  • a point on a trend graph — the tag's reading, the machine, and what else
    was happening;
  • a block on a machine's state timeline — what the stop was, how long, why
    and who said so, the job that was on either side of it, and whether a
    maintenance order covers it (planned work is not a breakdown);
  • a lot on a p chart — how many pieces were looked at and how many were
    sent back, by defect.

A non-conformance is now worked rather than merely closed: open → under
review → dispositioned → closed, where the disposition is use_as_is,
rework, scrap or return and requires a reason. "Closed" never said what
happened to the material, which is the question a non-conformance exists to
answer.

Maintenance that reaches the right person

A maintenance order says which trade it needs, how much it matters, whether the
machine has to be stopped and when the job may be done. A dispatcher reads the
plant's own rules, the crew's skills and the roster, and gives the work to
somebody who can do it; a supervisor's screen shows what is assigned, what is
waiting and why. An SPC signal can raise a maintenance order, and a stop
covered by one reads as planned work on the timeline rather than as a
breakdown.

A chat on the AI tab that explains the chart

Ask the AI tab why a point is out of control and it answers from the plant's
own records, drawing the chart it is talking about. It needs
ANTHROPIC_API_KEY
— without one the tab says so and the chat is off; the
rest of the product is unaffected. It costs about $0.12 a question at
current prices, and the plant keeps a row per turn, with the tool calls, the
cost and the outcome, so what the AI did is a record like anything else.

The separate floor assistant — the one that walks an operator through a
screen — runs on a local model through Ollama. Without Ollama on the machine,
that assistant is off and says so. Neither is required to run a plant.

Every floor row knows its shift

A production booking, a state interval, a check and a non-conformance now each
say which shift they fell in, and /analysis/* can be asked for a shift
instead of a number of hours — so how did my shift go is answerable. Three
rules decide which shift a minute belongs to, and an instant no pattern covers
is not attributed rather than filed under the nearest shift.

The lab plays a whole shift, with a chain in it

The simulated bottling line replays an eight-hour shift rather than an hour,
and the shift has a four-link chain of cause in it that the product has to be
able to follow, from production through maintenance and a tag to quality: a
changeover on the filler, a chiller maintenance order raised and never started,
the chiller's outlet temperature climbing all afternoon with the product
reaching the filler walking 8.4 → 6.4 °C behind it, and — half an hour later —
fill-height samples a millimetre and a half low with low_fill going from the
second rarest defect to the commonest in the lots counted in that window. No
limit on the filler is broken at any point: 6.4 °C is an ordinary number for a
cold drink. It is scripted on purpose — that is what makes it a test — and the
MES is not told the answer.

The full record is
CHANGELOG 0.3.0.

What 0.3.0 does not do

  • It has not run a real plant. Not one, still. Every number in this
    repository comes from a simulated line and says so where it appears.
  • No plant PC in a real plant has run it. Windows is proven on GitHub's
    runner, which has no antivirus, no domain policy, no proxy and no firewall
    prompt. A real plant PC may add all four, and the first one to do so will
    find something.
  • The chat needs a key, and it is not free. No ANTHROPIC_API_KEY, no
    chat on the AI tab. No Ollama, no floor assistant. About $0.12 a question is
    measured on this project's own usage, not promised.
  • The AI answers from the records, and the records are simulated. It has
    never been asked a question about a real plant's data.
  • No SAP, Odoo, Oracle or NetSuite connector exists, and ERPNext Job Card
    confirmations are still not built. Order-level round trip against a real
    ERPNext v15.120.0 in CI is what exists.
  • No MQTT broker has been on the other end of either direction, so
    Node-RED, United Manufacturing Hub and Grafana are untested for the same
    reason.
  • PLC write-back is still a guarded recommendation queue, not a control
    loop, and that is deliberate.
  • The upgrade gate proves one plant's shape, on SQLite. Nobody has
    upgraded a populated PostgreSQL plant. Take the backup. See
    upgrading.

What is simulated

Everything that produces a number. The plant behind every screenshot, scored
run and CI check is a deterministic line simulator in src/fsmes/sim/,
replayed over a real OPC UA server. The bottling plant's eight-hour shift, its
fill-height samples, its chiller and its chain of cause are all scripted, and
the script's answer key is in labs/multiplant/bottling/. The
largest plant is an 83-station cutlery works that serialises every piece, and
it is the public demo.

Every chart in the product states its coverage, its totals and, where it is
drawing simulated data, that it is simulated. A KPI that cannot be computed
honestly comes back unknown with the reason attached — including, new in this
release, performance and OEE on a machine whose counted work will not fit
inside its run time, which used to print a figure above 100 %.

The ERPNext in CI is real software in a container, not a simulation, but it is a
clean instance seeded by the job — not anybody's books.

Compatibility is the claims table: every row
carries the date it was last checked and what was run on that date, and rows
nothing has ever run against say never.

Upgrading from 0.2.0

fsmes backup --out /mnt/nas/fsmes
# stop the agent and the API
pip install --upgrade factorysemantics-mes
fsmes db-status --pack packs/yourplant
fsmes init-db   --pack packs/yourplant
fsmes db-status --pack packs/yourplant
# start them again

Fifteen migrations, c8b1e40d7a92 to d4e8a1c93b70: twelve new tables and a
set of nullable columns. Two of them write to rows that already exist — the
shift backfill, and a 30-day warning window on every gauge — and both are
described, with what they assume, in
[upgrading betw...

Read more

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 10:20
ca3aca4

Thirty pull requests, #8 to #37, since 0.1.2 on 2026-09-08. All thirty were
opened by the maintainer, @kalwei; there are no
outside contributors to thank yet. Within each section the entries run in the
order a plant person would read them: the ERP and its connectors first, then
the pieces a first real plant needs, then plant packs and the fleet, then
packaging and CI. Read the Honesty section before upgrading a plant anyone
reads numbers off
— it holds every change to what a number, a file name or a
setting means, each with its migration line.

Added

  • The outbox is a domain event log. It carried ERP confirmations and
    nothing else, because the ERP sync read every pending row as one. It now
    selects the kinds its contract can parse, which leaves room for the plant
    events no ERP asked for: equipment state changes (the state entered,
    the reason, the state left and how long that had been open) and order
    holds and resumes
    , with the reason a hold always carries. Each is
    written in the same transaction as the fact it describes, so an event
    cannot exist without its fact or a fact without its event. The
    unified-namespace publisher picks them up with no change: a machine going
    down reaches
    umh/v1/<enterprise>/<site>/…/<machine>/_mes/equipment_state_change, and
    a hold reaches the site. MES_OUTBOX_DOMAIN_EVENTS=false keeps the log to
    what the ERP is owed. See the unified namespace. (#15)

  • fsmes erp setup and fsmes erp check. The ERPNext connector needs
    five custom fields on Work Order (custom_mes_synced,
    custom_mes_good_qty, custom_mes_scrap_qty, custom_mes_over_qty,
    custom_mes_lot). Until now the only thing that created them was a demo
    seeding script in labs/, which is not in the wheel — so nobody who
    installed the package could create them at all. The definitions moved into
    the package, fsmes erp setup creates any that are missing and is safe to
    run twice, and fsmes erp check says in words whether the URL, the
    credentials, all five fields and the company are each in order, exiting
    non-zero if the connector would not work. fsmes run-erp-sync runs the field check when it
    starts and says so on the console; it still starts, because an ERP that is
    briefly unreachable is not a reason to refuse to run. The labs/ seeder
    now uses the same definitions.
    See the ERPNext connector. (#16)

  • The ERPNext connector is tested against a real ERPNext. A new
    ERPNext (live) job stands up ERPNext v15.120.0 in containers pinned to
    image digests (labs/erpnext/), seeds the plant, and runs the whole round
    trip: a Work Order submitted in ERPNext, pulled by the connector,
    acknowledged, confirmed, and then checked by reading ERPNext's own
    documents back — the custom fields, produced_qty, one submitted
    Manufacture stock entry, the comment. A retried confirmation is asserted to
    leave exactly one stock entry. The old live test fetched orders, asserted a
    list, and was deselected by CI's own settings, so it had never run. The job
    is not on every pull request: it takes about three and a half minutes and
    runs only when the connector, its tests or its fixture change. See
    the ERPNext connector. (#17)

  • A connector contract, so the next ERP is not the first one all over
    again.
    The ERP port had three methods — fetch, acknowledge, confirm —
    and they said nothing about the three things that actually bit the
    ERPNext connector: what has to exist on the far side, how a write is
    proved to have landed, and how a person checks it before trusting it.
    Each was fixed in ERPNext-specific code, so Odoo or SAP or Oracle would
    have rediscovered all three. The port now carries requirements()
    (what this connector needs on the ERP side, as data a person can act on),
    setup() (create or verify it, idempotently) and check()
    (connectivity, credentials and requirements in plain language, non-zero
    when something is wrong). All three have defaults, so a transport that
    needs nothing — and a connector written against the older three-method
    port — keeps working. fsmes.integrations.erp.conformance ships
    inside the package: eight obligations any connector can be run against
    without vendoring this project's tests, each of them something a
    connector got wrong once. Every adapter that ships passes it.
    Writing an ERP connector is the page
    for the person who would write the next one, and decision record
    0020
    says what this project requires before it calls one supported. Odoo, SAP
    and Oracle are still not written; the contract and the suite exist, the
    connectors do not. (#18)

  • fsmes erp requirements. What the configured connector needs on the
    ERP side, and which of it the MES can create itself — the list to send
    whoever administers the ERP, who is usually not the person running the
    MES. (#18)

  • A unified-namespace publisher. fsmes uns publish relays the MES's
    own event stream — the transactional outbox the ERP connector already
    delivers from — to an MQTT broker as JSON, under an ISA-95 topic tree
    read out of the equipment model (umh/v1/<enterprise>/<site>/<area>/<line>/<machine>/_mes/<kind>).
    Broker, credentials, prefix and the enterprise and site names are
    settings; every rung between them comes from the plant's own equipment
    codes, at whatever depth it modelled them. Delivery is at-least-once with
    the outbox's retry, backoff and dead-letter behaviour, and every payload
    carries the event id a consumer dedupes on. The MQTT client is the new
    [mqtt] extra, so the core install does not grow a dependency;
    MES_UNS_MODE=log prints the whole namespace without a broker.
    fsmes uns topics and fsmes uns queue show the tree and the backlog.
    Off by default. See the unified namespace. (#11)

  • Shadow mode: MES_SHADOW=true. One setting that lets this MES watch a
    real plant and change nothing in it. It reads the OPC UA tags and books
    production exactly as it would in charge; every path by which it could
    reach past its own database is shut. No setpoint reaches a machine (the
    order-code write-back included, which was never approval-gated); no live
    ERP is contacted; nothing is published to a broker; the ERP file adapter
    reads its inbox and moves nothing, because that folder may be the
    incumbent's; the optional cloud model is refused, so the plant's numbers
    stay on the box; fsmes demo refuses to run a fake plant beside a real
    one. MES_ERP_MODE may only be off or file and MES_UNS_MODE only
    off or log — anything else refuses to start, in one sentence naming
    the variable. A mode you never set is settled rather than refused.

    Every outbound path in the package is listed in one place,
    fsmes/shadow.py, with what shadow mode does to each and why; a test
    walks each closed path and holds the refusal, and a second test scans the
    source for outbound primitives so a new path cannot be added without the
    register hearing about it. It shows on every screen as a bar that cannot
    be dismissed, in fsmes info, at GET /health and GET /shadow, in the
    MCP server's description and per plant in list_plants(), and in the
    audit trail as shadow.on / shadow.off at start-up. There is no runtime
    toggle: leaving shadow mode is a restart.
    See running beside an existing MES. (#21)

  • A second front door: events other systems tell this MES. The OPC agent
    is how the MES sees a plant; it is not how it learns why a machine stopped,
    what an inspector measured, or how many units somebody counted by hand.
    Those are typed into whatever system the plant already has, and until now
    none of it could reach the MES. fsmes.integrations.inbound.contract is
    three typed shapes — DowntimeLabel, QualityResult,
    ManualCount — each carrying who supplied it (source, a free name
    such as replay:incumbent-mes, plus a source_kind category), the
    supplier's own id (external_key), and when the supplier recorded it
    (recorded_at). fsmes.services.inbound writes them through the services
    that already own the rules, deduplicating on
    (source, kind, external_key), so the same file delivered twice changes
    nothing. It matters most for a shadow run: technicians label stops in the
    incumbent, and a shadow that never hears those shows an unlabelled stop
    where the incumbent shows a reason code, which reads as a data problem in
    the shadow when it is a plumbing problem. (#22)

  • The first inbound driver: files in a folder. fsmes inbound watch
    reads CSV — or the same rows as JSON — from one inbox per event type,
    through a column mapping that is your configuration and not code. No
    input file is ever deleted: it moves to processed/, or to rejected/
    when nothing could be taken from it, and a rejects report beside it states
    the file's totals and the first reason for each row refused. fsmes inbound check says whether the mapping and the folders are usable before
    any file exists. Feeding the MES what people typed
    elsewhere
    is the page. A SQL poller ...

Read more

v0.1.2

Choose a tag to compare

@github-actions github-actions released this 09 Sep 02:16
478f7f8

Fixed

  • pipx install factorysemantics-mes && fsmes demo did not run: the wheel
    carried no config/tag_map.json or config/line_layout.json, so the
    simulator had no line to run and nothing was ever booked. The four
    default config files now ship as package data, and the settings fall
    back to them when the relative path is absent (a path you set yourself is
    never replaced). The release workflow refuses a wheel without them.
  • The release workflow's SBOM step asked for an image tag that does not
    exist (#5); the docs workflow serialises pushes to gh-pages (#5).

FactorySemantics MES 0.1.1

Choose a tag to compare

@kalwei kalwei released this 09 Sep 01:30
ae8aac2

Fixed

  • The container image did not build: the Dockerfile copied pyproject.toml
    without LICENSE and NOTICE, and the build backend refuses metadata
    without the licence file. The 0.1.0 release reached PyPI but no image and
    no GitHub Release; this version carries the same code with the image built.
  • The Scorecard workflow pinned an action version whose image had moved
    registries (#3).
  • The cutlery walkthrough installer failed lint (#3).

0.1.0, same day: published to PyPI only; its release run failed in the image job before the GitHub Release step. 0.1.1 is the same code with the image built. The 0.1.0 notes:

The first public version. The code was developed privately from 2026-08-28
to 2026-09-07 in 34 pull requests; docs/history/private-era.md
lists them. Everything below is what a stranger gets on day one, and every
number in it comes from a simulated plant.

Added

  • Kernel: master data, routings, work orders, dispatch, execution with lot
    genealogy, an append-only audit trail, users and roles with capabilities.
  • OPC UA connectivity: tag maps from an engineering worksheet, opc-browse
    and opc-verify, a replay server for simulated lines, triggers as data, a
    write-back recommendation queue with three guards, and certificates of
    analysis at the end of the line.
  • Downtime and OEE with an explicit unknown segment; shift calendars.
  • Quality: inspections, holds, non-conformances, SPC with capability withheld
    when the data cannot support it, a gauge register with calibration.
  • Maintenance-lite, finite-capacity scheduling with promised dates,
    serialisation and genealogy at ten million pieces a day (the cutlery lab).
  • ERP: a typed per-operation contract with an outbox, a REST adapter, a
    B2MML-flavoured file adapter, and an ERPNext connector registered as a
    module (fsmes.modules entry point erpnext).
  • The agent surface: an MCP server with 85 tools that go through the HTTP API
    as the AGENT role, dry-run on every write, on_behalf_of and idempotency
    keys; the floor assistant in the operator UI; recorded walkthroughs; agent
    evals that ask whether an agent given only the tools can answer what the
    plant knows.
  • Operations: plants from a registry (fsmes plant), scored runs
    (fsmes score) against the simulator's scripted truth, sweeps, a Docker
    image and Compose file, systemd units for test and promoted environments.
  • Documentation site (MkDocs), decision records, security policy, code of
    conduct, governance.

Honesty

  • Counters book production by delta only; a counter falling toward zero is a
    reset and books nothing. OEE reports unknown rather than zero when the
    MES was not watching. Unlabelled downtime is reported as unlabelled. These
    are the house rules and they are tested.