Skip to content

[Feature]: Switching an Agent's runtime provider orphans its Feishu app and forces a new one #96

Description

@serenakeyitan

User problem

An Agent's runtime provider is immutable by design ("immutable Computer/provider
binding", README). The only way to move an Agent from Codex to Claude Code is to
suspend it, delete it, and create a new one. That is a reasonable identity model,
but on Feishu it has a consequence that is not part of any stated design goal:

Changing an Agent's runtime provider orphans a Feishu app in the customer's
tenant and forces the registration of a brand-new one.

The chain is entirely mechanical:

  1. agent update exposes --display-name, --model, --reasoning-effort,
    --instructions, --allowed-tool, and --max-duration-ms. There is no
    --provider, and the Web Execution choices panel has no provider control
    either. Both are correct given the immutability rule.
  2. Deleting the old Agent disables its IM binding in the same transaction
    (agent-service.ts:529, disableImBindingInTransaction). This is deliberate
    and asserted by an integration test (im-binding.test.ts:4032).
  3. imBindings.agentId is never updated anywhere in the repository, so the
    binding cannot follow the Agent.
  4. The new Agent therefore has no binding. FeishuSetupService resolves
    existing via #currentForAgent(agentId) (setup-service.ts:119), which
    returns nothing for a new Agent.
  5. reauthorize — the one intent that reuses existingAppId — requires that
    existing be present (setup-service.ts:123, else
    FEISHU_REAUTHORIZATION_REQUIRES_BINDING). So the only reachable intent is
    create, and existingAppId is passed as undefined
    (setup-service.ts:138).
  6. DefaultFeishuRegistrationGateway then calls registerApp with
    createOnly: false and an appPreset named after the new Agent's display
    name (setup-service.ts:130), producing a new Feishu app.

The user-visible cost of trying a different runtime is therefore: a dead app left
in the Feishu tenant, a new app registered, a new QR scan, and re-adding the new
bot to every group it was in. Session continuity is lost as well, which is
expected — the orphaned tenant app is not.

Desired outcome

Changing an Agent's runtime provider should not require re-authorizing IM.

The asymmetry is already visible in the existing model:
FeishuSetupIntentSchema = ["create", "reauthorize", "replace"] covers replace
the app, keep the Agent
. The reverse — keep the IM identity, rebuild the Agent
underneath it
— has no representation.

Concretely, one of:

  • a supported way to re-point an existing, active binding at a different Agent of
    the same Team (an explicit rebind operation, revision-fenced like other Agent
    mutations); or
  • a provider change performed in place as a new Agent revision, keeping the
    Agent id and therefore the binding; or
  • if neither is desired, surface the real cost before the destructive step —
    today nothing warns that deletion will orphan a tenant-level Feishu app.

Alternatives considered

  • Leave as is. Defensible while Codex is the only runnable provider, since
    nobody has a reason to switch. [Feature]: Make the Claude Code provider actually runnable #53 changes that: once Claude Code runs, "try
    the other provider" becomes a normal thing to do, and every attempt costs a
    tenant app.
  • Document it. Does not help — the orphaned app is a side effect in the
    customer's Feishu tenant, not in OpenTag, so there is nothing the operator can
    clean up from OpenTag afterwards.
  • Delete the Feishu app on unbind. Rejected: OpenTag should not delete
    tenant-level assets it did not exclusively own, and the app may have been added
    to groups by other people.

Notes

Related to #53 (making Claude Code runnable) — that issue is what turns this from
theoretical into routine. Not a duplicate: #53 is about a provider that cannot
run, this is about the cost of moving between providers that can.

Observed on main @ 2875047 while switching a working Feishu-bound Codex Agent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions