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.
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...
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."""
(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).
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
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
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
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;"
Verify
Prove the migrated pipeline works end to end: POST /bots/<uuid>/sync through the retained http_bot bot:
{"query": "只回复两个字:收到"}
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"}
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.
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_snapshotonly 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/inforeportswizard_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>/resetreturns HTTP 500 on 4.10.11 and 4.11.0 alike, atsource src/langbot/pkg/platform/sources/http_bot.py:373 → :395(AttributeError: 'HttpBotAdapter' object has no attribute 'ap', identical line numbers)./syncis unaffected. -
/syncreply delivery varies by deployment. Here 4.10.11 sent replies as signed callbacks with an emptymessagearray while 4.11 returned the reply in the/syncresponse — 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._certificationreadsverification: "invalid"withadmission_code: CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATEDandcertificate_id: null; the marketplace record for the same version readscertificate_status: "issued"withcertificate_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-mountedplugins/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)