DEV Community

Rock
Rock

Posted on Fully Autonomous

Upgrading from 4.10 to 4.11: Migrating After the Built-in Runners Were Removed

Scenario

A 4.10.11 instance runs one pipeline with the built-in local-agent Runner, reached by a bot through a legacy routing rule.

Point the same data directory at 4.11.0: tables, data and the old pipeline survive, yet the pipeline is unusable — its Runner points at something 4.11 no longer has and the preview says it needs a plugin.

One sentence carries this article: the upgrade has two different paths. The schema migrates automatically through Alembic at boot; Runner configuration moves only through a migration API that requires your explicit confirmation.

Before the upgrade: the 4.10.11 instance with its built-in-Runner pipeline

How it works in 4.11

Path one: the schema migrates automatically at boot

4.11 starts from the 4.10.11 data directory and migrates Alembic to head on boot. Measured (2026-10-16): alembic_version moved from 0023_bot_scoped_sessions to 0037_metadata_text — 14 revisions — and the table count became 50. Boot to the first HTTP 200 took about 80 seconds; slow is not stuck, so do not restart early.

The key log sequence (measured (2026-10-16), data/logs/):

[INFO] : Initializing database type: sqlite...
[INFO] : Alembic migrations completed at head.
[INFO] : Loading bots from db...
[INFO] : Loading pipelines from db...
[WARNING] : Pipeline stage ConversationMessageTruncator is not registered; skipping
[INFO] : Loading knowledge bases from db...
Enter fullscreen mode Exit fullscreen mode

That WARNING is the line most easily misread as a failed migration: per source src/langbot/pkg/pipeline/pipelinemgr.py:838 the record carries a 4.10.11-era stage name that 4.11's registry lacks, so it is skipped and loading continues — startup is not blocked, and the pipeline still completes an end-to-end request (see Verify).

Bot routing migrates automatically too: 4.11 drops use_pipeline_name / use_pipeline_uuid / pipeline_routing_rules from bots and adds event_bindings and plugin_processors. Per source src/langbot/pkg/persistence/alembic/versions/0009_migrate_event_bindings.py:1-5, bindings keep the original pipeline UUIDs and the migration never creates Agent rows or copies Pipeline runner configuration; .../0011_drop_legacy_bot_routing.py drops those columns, and .../0035_pipeline_received_only.py narrows message.* to message.received, as the live database shows.

So: Alembic decides what tables and routes look like. It does not turn a built-in Runner into a plugin Runner.

Path two: Runner configuration moves only with your confirmation

The Runner path goes through a dedicated migration API, whose comment is blunt:

"""User-confirmed migration only; deliberately not API-key/MCP accessible."""
Enter fullscreen mode Exit fullscreen mode

(source src/langbot/pkg/api/http/controller/groups/pipelines/migration.py:1)

Both endpoints need USER_TOKEN: GET /api/v1/pipelines/_/migration/preview (Permission.RESOURCE_VIEW, migration.py:17) and POST /api/v1/pipelines/_/migration/execute (Permission.RESOURCE_MANAGE, migration.py:26).

Measured preview (2026-10-16): one legacy pipeline, legacy_runner: "local-agent", state needs_plugin with blocker plugin_missing; target plugin langbot-team/LocalAgent 0.2.1 (target_runner_id: plugin:langbot-team/LocalAgent/default), planner_version: "5", changed_paths such as ai.runner.runner and ai.runner.id, and warnings including local.box_state_reset and migration.output_policy_copied (these are the ones recorded in the measurement; the API returns the full list).

Migration preview after upgrading: the legacy pipeline needs a plugin, target plugin langbot-team/LocalAgent 0.2.1

Confirmation is enforced in the protocol, not just the UI: an unconfirmed POST /execute {"items":[]} returns HTTP 400 confirmation_required (source src/langbot/pkg/api/http/service/pipeline_migration.py:131-133); only a request carrying confirmed gets through, for example {"all":true,"confirmed":true,"install_plugins":true} (.../pipeline_migration.py:420-429).

Execution archives the old configuration: one row lands in pipeline_migration_snapshots holding the whole pre-migration config as source_snapshot, plus source_fingerprint / target_fingerprint (sha256) and state: active (measured (2026-10-16): 3053 bytes).

Note: http_bot is not new in 4.11 (http_bot.py is byte-identical in 4.10.11 and 4.11.0, 27029 bytes), so receiving messages there is preservation, not a new feature.

Hands-on steps

0. Copy the data directory

The upgrade happens to the data directory; copy it and leave the original untouched:

cp -a /path/to/410/data /path/to/411-upgrade/data
Enter fullscreen mode Exit fullscreen mode

1. Start with the 4.11 images

Two containers: core and a plugin runtime for Runner plugins.

docker network create lang7-411

docker run -d --name lang7-411-core --network lang7-411 --restart on-failure -e TZ=Asia/Shanghai \
  -e BOX__ENABLED=false -e SPACE__DISABLE_TELEMETRY=true -p 15400:5300 -p 15480-15485:2280-2285 \
  -v "$PWD/deploy/411/data:/app/data" rockchin/langbot:v4.11.0

docker run -d --name lang7-411-runtime --network lang7-411 --network-alias langbot_plugin_runtime --restart on-failure \
  -e TZ=Asia/Shanghai -e SPACE__DISABLE_TELEMETRY=true -v "$PWD/deploy/411/data/plugins:/app/data/plugins" \
  rockchin/langbot:v4.11.0 uv run --no-sync -m langbot_plugin.cli.__init__ rt
Enter fullscreen mode Exit fullscreen mode

Point -v at your copy and adjust names/ports. Wait about 80 seconds after boot for Alembic. Admin admin@langbot.local / LangBot411!demo (local demo value — replace it with your own).

2. Preview, then execute with confirmation

Unconfirmed first: {"items":[]} returns 400 confirmation_required. With confirmation, expect 200 carrying pipeline_uuids and task_id:

curl -s -X POST -H "Authorization: Bearer <USER_TOKEN>" -H 'Content-Type: application/json' \
  -d '{"all":true,"confirmed":true,"install_plugins":true}' \
  http://127.0.0.1:15400/api/v1/pipelines/_/migration/execute
Enter fullscreen mode Exit fullscreen mode

Task page after executing the migration: the target plugin is installed and the pipeline reaches its completed state

3. Confirm it landed

Expect one row with state = active, planner_version = 5:

sqlite3 /path/to/411-upgrade/data/langbot.db \
  "select state, planner_version, length(source_snapshot) from pipeline_migration_snapshots;"
Enter fullscreen mode Exit fullscreen mode

Verify

Prove the migrated pipeline works end to end: POST /bots/<uuid>/sync through the retained http_bot bot:

{"query": "只回复两个字:收到"}
Enter fullscreen mode Exit fullscreen mode

Signed with HMAC-SHA256 over "{timestamp}." + raw_body_bytes, prefixed sha256=; secret legacy-inbound-secret (local demo value — replace it with your own).

Expected response shape (measured (2026-10-16)):

{"message":[{"text":"收到","type":"Plain"}],"reply_to":"in_ef4b42dcd79444368c420a053b30f00e"}
Enter fullscreen mode Exit fullscreen mode

4.14 s, over: retained http_bot bot → event binding (message.received) → migrated pipeline (Runner plugin:langbot-team/LocalAgent/default) → local Ollama qwen3:0.6b.

The migrated pipeline's configuration page: the Runner now points at the plugin plugin:langbot-team/LocalAgent/default

Re-check: alembic_version = 0037_metadata_text and pipeline_migration_snapshots holds one row with state = active.

Limits and caveats

  • A snapshot is not a rollback button. source_snapshot only archives the pre-migration configuration — no public download, no restore UI (official docs docs/pipeline-migration-config-map.zh-CN.md) — export the database first.
  • Config migration is not lossless state migration. Remote sessions, history, Box files and form tokens are not imported; expect a state reset (official docs docs/pipeline-migration-config-map.zh-CN.md).
  • Post-upgrade operations are yours. GET /api/v1/system/info reports wizard_status: "none"; confirm workspace/account availability yourself (here the 4.10 credentials still logged in).
  • /reset's 500 is pre-existing on both versions, not a migration regression. POST /bots/<uuid>/reset returns HTTP 500 on 4.10.11 and 4.11.0 alike, at source src/langbot/pkg/platform/sources/http_bot.py:373 → :395 (AttributeError: 'HttpBotAdapter' object has no attribute 'ap', identical line numbers). /sync is unaffected.
  • /sync reply delivery varies by deployment. Here 4.10.11 sent replies as signed callbacks with an empty message array while 4.11 returned the reply in the /sync response — an observed difference whose cause we did not verify; go by your own responses.
  • The two certification records disagree, so both are reported. install_info._certification reads verification: "invalid" with admission_code: CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED and certificate_id: null; the marketplace record for the same version reads certificate_status: "issued" with certificate_id: cert:86f25832.... We report the discrepancy and claim no validated signature.
  • The planner document contradicts itself ("planner v4" early, "relaxed migration rules (planner v5)" later); the preview returns planner_version: "5".
  • Plugin artifacts land inside the runtime container at /app/data/plugin-runtime/artifacts/sha256/<digest>/code/; the host-mounted plugins/ directory stays empty (measured (2026-10-16)).
  • Platform capabilities: Support depends on the adapter and platform — check the support matrix first.

Summary and next steps

Start with the pinned migration document pipeline-migration-config-map.zh-CN.md — where each field goes — and the v4.11.0 release page, covering v4.10.11 → v4.11.0.

For how built-in Runners became plugins, continue with this series' Runner-plugins article. To skip self-hosting, check whether the cloud at space.langbot.app suits your scenario (availability depends on your deployment and account — check it yourself; no promise). Plugin sources: langbot-plugins.

Top comments (0)