Skip to content

Migration Guide: v0.3 → v0.4

A short checklist for upgrading from Machina v0.3.x to v0.4.0. Six changes can break existing code or deployments; everything else is additive or a fix.

Breaking Changes

1. Writes need confirmation by default

What changed: Agent(confirmations=...) now defaults to True (the confirmations: YAML key mirrors it). Mutating tool calls — create_work_order, execute_workflow — require a human confirmation before they execute.

Impact on programmatic callers: with confirmations on and no confirmer supplied, a write is fail-safe — it is not executed. The agent proposes the action and waits for a follow-up confirming message (the two-turn flow). If you drive the agent programmatically and expect autonomous writes:

# Opt out of the confirmation gate (restores the 0.3 behaviour):
agent = Agent(..., confirmations=False)

# Or in machina.yaml:
# confirmations: false

# Or keep the gate and supply a synchronous confirmer:
async def confirmer(prompt: str) -> bool:
    ...  # render prompt, return the user's decision
await agent.handle_message_full(text, confirmer=confirmer, user_id="...")

trigger_workflow is a deliberate direct-execution path guarded by sandbox only; it is not gated by confirmations.

Known limitations:

  • Anonymous senders cannot confirm (fail-safe withhold). On a shared async channel, the two-turn confirmation binds the pending write to the sender's user_id. If the channel cannot supply a trusted sender identity (empty user_id), the write is withheld — not stored and not executed — so an unidentified participant can never confirm it.
  • Pending confirmations expire. A pending write is only valid for the immediate follow-up; it expires after one hour (_PENDING_ACTION_TTL_SECONDS), so a much-later bare "ok" is treated as a fresh message, not a stale confirmation.
  • Email sender identity is NOT authenticated. The email channel derives chat_id and user_id from the unauthenticated, spoofable From: header (no DKIM/SPF/allowlist). Two-turn write confirmation over email therefore provides no integrity guarantee — a spoofed sender could confirm a write. For write-confirmation use, gate the email connector behind sandbox mode or a verified-sender allowlist, or avoid confirmations over email.

2. Reinstall the [docs-rag] extra

The Chroma vector store now comes from langchain-chroma (and the optional HuggingFace embedder from langchain-huggingface). pip does not remember extras, so upgrade with the extra named:

pip install -U "machina-ai[docs-rag]"     # or [docs-rag-pro]

A bare pip install -U machina-ai leaves langchain-chroma missing; the document store then falls back to keyword search and logs a warning naming the missing package.

3. EntityResolver.resolve_best removed; ResolvedEntity needs a confidence

resolve_best had no production callers and is gone — use resolve() and read the ranked list. ResolvedEntity.confidence is now required:

from machina.agent.entity_resolver import ResolvedEntity

# v0.3
entity = ResolvedEntity(asset)
# v0.4
entity = ResolvedEntity(asset, confidence=1.0, match_reason="exact_id")

4. Custom connectors: declare failure-mode catalogs

The runtime no longer reads a private _failure_modes attribute. A connector that serves a failure-mode catalog must declare the capability and implement the public method:

from typing import ClassVar

from machina.connectors.capabilities import Capability
from machina.domain.failure_mode import FailureMode


class MyCmmsConnector:
    capabilities: ClassVar[frozenset[Capability]] = frozenset(
        {Capability.READ_ASSETS, Capability.READ_FAILURE_MODES}
    )

    async def read_failure_modes(self) -> list[FailureMode]:
        ...

5. MCP bearer tokens: at least 32 characters

The MCP server refuses to start when a token in MACHINA_MCP_TOKENS_JSON (or the legacy MACHINA_MCP_TOKENS) is shorter than 32 characters. Generate tokens with openssl rand -hex 32. In practice this affects only new setups: the streamable-HTTP transport could not serve tool calls in v0.3.x.

6. simulated_sensor and cli are no longer YAML connector types

Agent.from_config builds connectors from the one registry machina describe lists. Construct SimulatedSensorConnector in Python and pass it in connectors=[...] (see examples/reference/predictive_pipeline/agent.py); declare the CLI under channels: — or leave channels: out, since the CLI is the default.

Behaviour Changes to Check

  • MCP HTTP binds 127.0.0.1 by default. Inside a container, or behind a proxy on another machine, pass --host 0.0.0.0. Requests whose Host (or Origin) is not in mcp.allowed_hosts / mcp.allowed_origins are rejected; the defaults cover loopback names only, so list your public host name when you put a proxy in front. See MCP Auth.
  • /health without a token returns only {"status": "healthy"}; connector names, sandbox mode and version need a valid token.
  • Citations in AgentResponse.text are renumbered [1]..[N] by first appearance, and AgentResponse.citations follows that display order.
  • diagnose_failure results carry a numeric confidence, matching_indicators and recommended_actions per entry, capped at 5; empty results always include a note.
  • Custom workflows that bound {analyze_alarm.primary_code} into a work order should switch to {analyze_alarm.failure_mode_for_write}, the confidence-gated accessor the built-in alarm_to_workorder now uses.
  • get_maintenance_schedule is offered to the LLM only when a connector declares read_maintenance_plans, and returns the plans (no due dates).
  • Workflow write detection no longer counts set inside asset, dataset or offset, so an action such as cmms.read_assets is no longer treated as a write; upsert now is. If a custom action is a write but its name does not look like one, mark the step is_write=True so sandbox mode intercepts it.
  • Excel/CSV and SQL settings are validated: unknown keys are refused by name. The SQL connector's read_write mode no longer declares update_work_order, which it cannot perform.
  • Excel/CSV and SQL writes: Excel updates rewrite only the changed cells and refuse fields no column maps, and reads before connect() raise ConnectorError. A work-order row with an invalid value (an unknown status) is now skipped with a warning; in v0.3 it made connect() fail. SQL creates need id in insert_columns to stay idempotent; a unique constraint on the ID column guards against other writers.

New (No Migration Needed)

  • machina mcp serve — the MCP server from the machina CLI.
  • ${VAR:-default} placeholders in YAML configs.
  • Asset.aliases — curated per-plant synonyms, matched like the asset name.
  • Answerable disambiguation — reply to "which asset?" by ID, name or ordinal.
  • Capability.READ_FAILURE_MODES on the Generic CMMS, SQL and Excel/CSV connectors.
  • YAML auth / pagination blocks for the Generic CMMS, SAP PM and Maximo connectors, and YAML-buildable Excel/CSV and SQL connectors.
  • LLM token usage and cost recorded on agent.tracer.

Deprecations

  • Raw-string capabilities (list[str] declarations, raw strings passed to find_by_capability) still work in v0.4, with a DeprecationWarning on the latter. Their removal moves from v0.4 to v0.5 — declare frozenset[Capability].
  • MACHINA_MCP_TOKENS remains deprecated; use MACHINA_MCP_TOKENS_JSON.

Checklist

  • [ ] Upgrade with your extras: pip install -U "machina-ai[litellm,docs-rag]" (add mcp, excel, sql, … as you use them)
  • [ ] Programmatic agents that write: set confirmations=False or pass a confirmer
  • [ ] Code constructing ResolvedEntity: pass confidence=
  • [ ] Custom connectors with a failure-mode catalog: declare READ_FAILURE_MODES and implement read_failure_modes()
  • [ ] MCP over HTTP: tokens of 32+ characters, --host and allowed_hosts for containers and proxies
  • [ ] Custom workflows: failure_mode_for_write, is_write=True where needed
  • [ ] YAML configs with type: simulated_sensor or type: cli under connectors:: move them to Python or channels:
  • [ ] Run your tests