Runner Migration
Runner migration guide.
Migrate your organization from the default TypeScript worker runtime to the Go agent runner by opting in a single SQL flag. The transition is zero-downtime, per-org, and fully reversible without a code deploy.
Overview
Every dispatched session carries a resolvedProfile.runner discriminator:
| Value | Runtime | Default |
|---|---|---|
"ts" | Legacy TypeScript Donmai workers via dispatchWork | Yes - all orgs start here |
"go" | Go agent runner (Wave 6 Phase F daemon) polling the global queue | Opt-in per org |
Both routes share the same Redis session JSON shape. Workers claim work based on resolvedProfile.runner in the queued payload. An org can be switched back to "ts" instantly without any in-flight disruption - sessions already claimed by a runner complete on that runner; the flag only affects new dispatches.
flowchart LR
A[Dispatch trigger] --> B[resolve-model.ts]
B --> C{organizations.features\nrunner_default?}
C -->|"go"| D["resolvedProfile.runner = 'go'"]
C -->|"ts" or absent| E["resolvedProfile.runner = 'ts'"]
C -->|DB error / missing row| E
D --> F[Redis queue\nQueuedWork JSON]
E --> F
F --> G{Worker poll}
G -->|runner = go| H[Go daemon\nrensei host run]
G -->|runner = ts| I[TypeScript\nDonmai worker]The resolver is failure-closed. A missing org row, a malformed feature flag, or a database error all fall back to "ts". No org is silently moved to the Go runner.
Prerequisites
Before opting an org into the Go runner, confirm both of the following:
-
The Go daemon is provisioned and healthy for that tenant. In the platform Admin, go to Workers and verify at least one worker row shows
runtime: gowith a recent heartbeat (within the last 2 minutes). -
The Go binary version is ≥ v0.5.0. The agent-runner interface ships at that version. Check with:
rensei host status
# or inspect the worker's version field in /admin/workersThe status output includes a version field. If the daemon reports a version below 0.5.0, run brew upgrade rensei && rensei host install before proceeding.
Opt in an org
Apply the following SQL against the platform's primary Postgres. Replace <ORG_ID> with the target org's organizations.id:
-- Opt the named org into the Go runner.
-- The resolver reads features.runner_default and stamps
-- resolvedProfile.runner = "go" on every dispatched session.
UPDATE organizations
SET features = jsonb_set(COALESCE(features, '{}'::jsonb),
'{runner_default}', '"go"', true),
updated_at = NOW()
WHERE id = '<ORG_ID>';
-- Verify the write took effect.
SELECT id, slug, features -> 'runner_default' AS runner_default
FROM organizations
WHERE id = '<ORG_ID>';The change takes effect on the next dispatched session for that org - no restart, no redeploy.
Verify the flip
After opting in, trigger a session for the org and check two places:
- Recent sessions table - confirms the session was created after the flag change:
SELECT id, status, created_at, organization_id
FROM agent_sessions
WHERE organization_id = '<ORG_ID>'
ORDER BY created_at DESC
LIMIT 5;- Daemon logs - look for a
claimedevent on the matchingsessionIdin the Go daemon's log output:
rensei host logs -n 50The dispatch node also logs the runner field on every call; check Vercel runtime logs for src/lib/nodes/action/agent.dispatch_to_queue/backend.ts and look for "runner":"go" in the structured output.
Roll back an org
To revert a single org to the TypeScript runtime, remove the feature flag:
-- Option A: remove the key entirely (treated as default "ts")
UPDATE organizations
SET features = features - 'runner_default',
updated_at = NOW()
WHERE id = '<ORG_ID>';
-- Option B: explicitly set back to "ts"
UPDATE organizations
SET features = jsonb_set(features, '{runner_default}', '"ts"', true),
updated_at = NOW()
WHERE id = '<ORG_ID>';Either form takes effect on the next dispatch. In-flight sessions that were already claimed by the Go runner complete normally - they are not interrupted.
Roll back the entire feature
If you need to revert the runner opt-in mechanism entirely across all orgs:
- Revert the platform code changes that introduced the runner opt-in mechanism.
- The
runnerfield disappears from the resolved profile shape, and every dispatch falls back to thedispatchWorkroute. - In-flight Go-runner sessions complete on their own daemon. New sessions return to TypeScript workers automatically.
No SQL cleanup is required - the features.runner_default column is ignored if the resolver code is absent.
Inspect the current runner for any org
The resolved profile is stamped onto the Redis QueuedWork JSON at dispatch time. Use any of these methods to verify which runner an org is currently using.
Method 1 - Query the feature flag directly:
SELECT id, slug, features -> 'runner_default' AS runner_default
FROM organizations
WHERE slug = '<org-slug>';
-- NULL or absent means "ts" (default). '"go"' means opted in.Method 2 - Vercel runtime logs:
Open Vercel → your deployment → Functions → filter by agent.dispatch_to_queue. Each dispatch emits a structured log entry:
{
"event": "dispatch",
"sessionId": "ses_...",
"resolvedProfile": {
"runner": "go",
"authMode": "host-session"
}
}Method 3 - Admin session inspector:
In the platform Admin, open any recent session for the org and expand Resolved Profile - the runner field is shown directly.
Method 4 - Go daemon claim log:
If the Go daemon successfully claimed the session, its log will contain a claimed event for the matching sessionId:
rensei host logs -n 100 | grep '"event":"claimed"'Source references
| File | Role |
|---|---|
platform/src/lib/model-profiles/types.ts | ResolvedProfile.runner field definition |
platform/src/lib/worker-fleet/runner-default.ts | Org feature-flag reader (failure-closed) |
platform/src/lib/worker-fleet/resolve-model.ts | Profile resolver that stamps runner |
platform/src/lib/nodes/action/agent.dispatch_to_queue/backend.ts | Dispatch node - branches on runner |
Related
- Local sandbox provider - configuring daemon workers for local execution
- Worker fleet migration - broader on-prem fleet upgrade context
- Auth modes -
host-sessionandlocalmodes pin to local-capacity pools - CLI: host daemon -
rensei host daemonlifecycle commands