You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status: accepted product direction. Phase 1 is ready to implement. Phase 2 is gated on a one-off data migration.
Summary
OpenTag's management surface is administered by one person. Sharing happens in Feishu and Slack, where colleagues talk to an Agent without an OpenTag Account. A user installs Claude Code or Codex on their own laptop, creates one or more Agents on it, and connects each Agent to one app in their IM tenant. Nobody else needs access to the management surface to benefit from those Agents.
Under that definition Workspace has no work left to do. It was designed as a team boundary: a set of people who completely trust one another to manage the same Agents and machines. That population does not exist in the product as defined.
This issue retires Workspace in two stages. Phase 1 removes it from every user-facing and API surface without touching storage. Phase 2 folds workspaces into users and drops the admin-grant and invitation tables. Between them sits a one-off data migration that is a hard precondition for Phase 2, not an optional cleanup.
The end state is four layers: Account → Computer enrollment → Agent → IM binding.
This ownership spine does not preclude shared durable context. Context Tree access is orthogonal to Account, Computer enrollment, Agent, and IM binding ownership. Agents owned by different Accounts may eventually be authorized to read or write the same portable Git-hosted Context Tree without joining a shared OpenTag management scope.
A Context Tree is not a Workspace or Project. It holds durable, reviewable decisions and context, not Agent ownership, runtime state, Tasks, ordinary working files, credentials, or Secrets.
#125 established that Workspace "remains the collaboration, authorization, and data-isolation scope, but it stays implicit when an Admin can access only one," and that it is "still necessary whenever one Account administers multiple independent trust scopes."
This issue supersedes that conclusion. The multi-trust-scope case is not the product: an Account administers its own Agents and nothing else. What #125 called progressive disclosure becomes permanent removal.
Everything else #125 established stands and is a precondition for this work — machine credentials scoped to one enrollment, account tokens barred from the Runtime WebSocket, cross-scope non-disclosure, and creation identity as audit metadata rather than an authorization rule.
Why this is changing
The two populations do not both need a boundary
#125 already separated Admins from IM Users. What it did not resolve is that the Admin population is one person per set of Agents. A boundary that groups people is only worth its cost when more than one person is inside it.
The schema confirms the separation is complete: sessions holds no user reference and there is no representation of an IM sender anywhere in the schema. The people using an Agent are invisible to the account model by design. Removing Workspace therefore cannot affect them.
Workspace's responsibilities do not survive the definition
Workspace carries four things today. Under one-Account-per-scope, three disappear outright rather than moving somewhere else:
Authorization boundary — admin grants and invitations. With exactly one administrator there is nothing to grant. requireAdmin degrades to "does this belong to you".
Isolation boundary — becomes account isolation, which is already implied by the account.
Unique naming — Agent names become unique per Account.
Enrollment scope — the only one that needs a new host, and users can hold it.
The enrollment case was the apparent blocker, because an enrollment must exist before an Agent is created and therefore could not hang off the Agent. It resolves by hosting enrollments on the Account, which is available at signup. An earlier objection — that an account-scoped credential would serve several Agents and widen its blast radius — does not hold: co-located Agents already share one Codex home and one Claude Code home, because CODEX_HOME and CLAUDE_CONFIG_DIR are resolved once per daemon process. The isolation that objection tried to protect does not exist a layer down.
The concept taxes every decision
Every feature discussion has to answer "is this Workspace-level or Agent-level" before it can start, and the answer is always the same once there is one Account per Workspace. That question disappears with the concept.
Current state
Measured on main at c2556e2, and against staging on 2026-08-26.
The production data is the shape being retired: one Workspace holding 8 Agents across 5 Computers with 5 equal Admins. A second, auto-provisioned Workspace on the same Account holds nothing.
Phase 1 — remove Workspace from every surface
No schema change. Reversible. Existing data is untouched, so current Admins keep every grant they have; they simply cannot add more.
The honest outcome is narrower than "OpenTag becomes single-administrator": new Accounts get the full self-administered model, and existing shared Workspaces stop growing. Existing users see exactly what they see today.
Delete
Layer
Item
Server
api/invitations.ts — both routes (52 lines)
Server
services/invitations/ (32 lines)
Server
api/workspaces.ts — list admins, revoke admin, create Workspace
Workspace entry and Workspace switcher in the account menu
CLI
opentag admin group — list, invite, revoke (88 lines)
Shared
Invitation and admin contracts, and their HTTP path constants
Keep
establishDefaultWorkspaceForNewAccount — Phase 1 depends on it to create the now-invisible Workspace at signup. Note this reverses feat(web): create Teams explicitly instead of provisioning them at sign-in #44, which made Team creation explicit; auto-creation is required once the concept is hidden, because a user cannot explicitly create something they never see.
bootstrapAdminInTransaction — the single grant row is what marks a Workspace as belonging to its Account.
The requireAdmin family. Call sites are unchanged; only the meaning narrows.
Add
A Computers entry in the account menu, pointing at /agents/computers. Self-administration raises the frequency of enrolling a machine — every user now provisions their own — and the only current paths to that page are recovering an already-offline Agent (PR fix(web): let an offline Computer be reconnected from its Agent #166) or opening the New Agent flow.
An assertion that an Account holds at most one Workspace. This holds naturally once creation is gone; the assertion prevents regression.
Preflight
Deleting the Workspace switcher strands anything held in a second Workspace. The switcher renders only when me.workspaces.length > 1. The reporter's second Workspace was verified empty, but other Accounts were not. Run before deleting:
selectu.email, count(*) as workspaces,
sum(case when exists(select1from agents a
wherea.workspace_id=g.workspace_idanda.status<>'deleted')
then 1 else 0 end) as with_agents
from workspace_admin_grants g join users u onu.id=g.user_idwhereg.revoked_at is nullgroup byu.emailhavingcount(*) >1;
Proceed only when every with_agents is at most 1. Also check for outstanding invitations, which the deletion will strand:
selectcount(*) from admin_invitations
where accepted_at is nulland revoked_at is nulland expires_at > now();
Accepted costs
Revoking an administrator becomes a SQL operation. The five existing grants on the shared Workspace remain and have no product surface for removal. Acceptable while OpenTag is internal.
A colleague who needs access to an existing Agent has no product path. Under self-administration this is the intended answer: they create their own Agent.
Data migration — precondition for Phase 2
A one-off operational task, deliberately not built as a product capability.
workspaces cannot be folded into users while one Workspace holds five administrators and eight Agents belonging to different people. The fold requires each Workspace to correspond to exactly one Account.
The split is pure data and needs no schema change. In one transaction: create a Workspace and grant per Account, then update workspace_computers.workspace_id and agents.workspace_id together so the composite foreign key holds throughout. Agent ids, IM bindings, session history, and machine credentials all survive, because im_bindings references agent_id and session_placements references the enrollment id, and neither changes.
Two things to decide before running it:
Naming. Agent name uniqueness is scoped to the Workspace. After splitting, two Accounts may each hold an Agent named reviewer, and merging back later collides with agents_workspace_name_active_unique. If reunification is even plausible, adopt a prefixed convention during the split; the cost is zero now and non-trivial later.
Shared machines. A Computer whose Agents belong to more than one person must be enrolled once per Account. The model supports it — same computerId, several enrollments — but it requires running computer connect again on that host. debian-devbox carries two Agents and is a candidate.
Phase 2 — remove Workspace from storage
Fold workspaces into users; drop workspace_admin_grants and admin_invitations; rehost the enrollment and Agent foreign keys on the Account; retire the CLI --workspace option. Invisible to users, who stopped seeing Workspace in Phase 1.
Most of the 2431 occurrences are a mechanical rename. The risk is concentrated in the migration itself, and this is a cutover of the same magnitude as #161.
Recommendation: do not schedule a dedicated release for it. Once Phase 1 lands, what remains is an inert table with one row per Account and a foreign key that always resolves to the same Account. Carry it until the next change that already touches the schema, and fold this in there.
When Workspace would have to come back
Some plausible triggers have cheaper answers and should not be met by restoring Workspace: sharing a single Agent with one colleague is a per-Agent collaborator list, and handing an Agent to someone else is an ownership-transfer operation.
A grouping layer is genuinely required when:
One grant must cover many Agents. The signal is noticing that the same authorization is being repeated across Agents.
A shared machine needs an owner.debian-devbox already fits: it is nobody's laptop, and under self-administration no Account owns it, so patching and access have no responsible party.
Billing or quota applies to an organization rather than a person.
An audit or compliance boundary spans people.
A shared execution lifecycle or policy spans independently owned Agents. Shared Tasks, working files, Secrets, or one lifecycle that must survive any participating Agent may justify a new grouping entity. That entity must be designed from the concrete requirement and must not inherit the retired Workspace model by default.
Agent-to-Agent routing or shared Context Tree access does not by itself require Workspace to return. Sparse interaction can use Agent-scoped capabilities, and durable shared knowledge can remain an independently authorized Context Tree.
If a future requirement is genuinely Workspace-shaped, restoring it is cheaper than removing it was. Removal collapses foreign keys onto users; restoration inserts a layer above users with a one-to-one mapping already in place, and by then the requirement is known rather than speculative.
Non-goals
Introducing a Project, Collaboration, Agent Group, or other shared execution-resource container. Shared durable context remains a separate Context Tree concern; a broader grouping model requires a concrete future use case.
Retire Workspace as a product and domain concept
Status: accepted product direction. Phase 1 is ready to implement. Phase 2 is gated on a one-off data migration.
Summary
OpenTag's management surface is administered by one person. Sharing happens in Feishu and Slack, where colleagues talk to an Agent without an OpenTag Account. A user installs Claude Code or Codex on their own laptop, creates one or more Agents on it, and connects each Agent to one app in their IM tenant. Nobody else needs access to the management surface to benefit from those Agents.
Under that definition Workspace has no work left to do. It was designed as a team boundary: a set of people who completely trust one another to manage the same Agents and machines. That population does not exist in the product as defined.
This issue retires Workspace in two stages. Phase 1 removes it from every user-facing and API surface without touching storage. Phase 2 folds
workspacesintousersand drops the admin-grant and invitation tables. Between them sits a one-off data migration that is a hard precondition for Phase 2, not an optional cleanup.The end state is four layers: Account → Computer enrollment → Agent → IM binding.
This ownership spine does not preclude shared durable context. Context Tree access is orthogonal to Account, Computer enrollment, Agent, and IM binding ownership. Agents owned by different Accounts may eventually be authorized to read or write the same portable Git-hosted Context Tree without joining a shared OpenTag management scope.
A Context Tree is not a Workspace or Project. It holds durable, reviewable decisions and context, not Agent ownership, runtime state, Tasks, ordinary working files, credentials, or Secrets.
Relationship to #125
#125 established that Workspace "remains the collaboration, authorization, and data-isolation scope, but it stays implicit when an Admin can access only one," and that it is "still necessary whenever one Account administers multiple independent trust scopes."
This issue supersedes that conclusion. The multi-trust-scope case is not the product: an Account administers its own Agents and nothing else. What #125 called progressive disclosure becomes permanent removal.
Everything else #125 established stands and is a precondition for this work — machine credentials scoped to one enrollment, account tokens barred from the Runtime WebSocket, cross-scope non-disclosure, and creation identity as audit metadata rather than an authorization rule.
Why this is changing
The two populations do not both need a boundary
#125 already separated Admins from IM Users. What it did not resolve is that the Admin population is one person per set of Agents. A boundary that groups people is only worth its cost when more than one person is inside it.
The schema confirms the separation is complete:
sessionsholds no user reference and there is no representation of an IM sender anywhere in the schema. The people using an Agent are invisible to the account model by design. Removing Workspace therefore cannot affect them.Workspace's responsibilities do not survive the definition
Workspace carries four things today. Under one-Account-per-scope, three disappear outright rather than moving somewhere else:
requireAdmindegrades to "does this belong to you".userscan hold it.The enrollment case was the apparent blocker, because an enrollment must exist before an Agent is created and therefore could not hang off the Agent. It resolves by hosting enrollments on the Account, which is available at signup. An earlier objection — that an account-scoped credential would serve several Agents and widen its blast radius — does not hold: co-located Agents already share one Codex home and one Claude Code home, because
CODEX_HOMEandCLAUDE_CONFIG_DIRare resolved once per daemon process. The isolation that objection tried to protect does not exist a layer down.The concept taxes every decision
Every feature discussion has to answer "is this Workspace-level or Agent-level" before it can start, and the answer is always the same once there is one Account per Workspace. That question disappears with the concept.
Current state
Measured on
mainat c2556e2, and against staging on 2026-08-26.workspacesworkspacesown columnsname,displayName,setupCompletedAtThe production data is the shape being retired: one Workspace holding 8 Agents across 5 Computers with 5 equal Admins. A second, auto-provisioned Workspace on the same Account holds nothing.
Phase 1 — remove Workspace from every surface
No schema change. Reversible. Existing data is untouched, so current Admins keep every grant they have; they simply cannot add more.
The honest outcome is narrower than "OpenTag becomes single-administrator": new Accounts get the full self-administered model, and existing shared Workspaces stop growing. Existing users see exactly what they see today.
Delete
api/invitations.ts— both routes (52 lines)services/invitations/(32 lines)api/workspaces.ts— list admins, revoke admin, create Workspaceworkspace-admin-access.ts—listAdmins,listAdminsConfig,createInvitation,previewInvitation,acceptInvitation,revokeAdmin,lockAccountForGrantWrite,requireWorkspaceHeadroom,createWorkspaceWithAdmin/workspace,/workspaces/new,/invites/:tokenopentag admingroup — list, invite, revoke (88 lines)Keep
establishDefaultWorkspaceForNewAccount— Phase 1 depends on it to create the now-invisible Workspace at signup. Note this reverses feat(web): create Teams explicitly instead of provisioning them at sign-in #44, which made Team creation explicit; auto-creation is required once the concept is hidden, because a user cannot explicitly create something they never see.bootstrapAdminInTransaction— the single grant row is what marks a Workspace as belonging to its Account.requireAdminfamily. Call sites are unchanged; only the meaning narrows.Add
/agents/computers. Self-administration raises the frequency of enrolling a machine — every user now provisions their own — and the only current paths to that page are recovering an already-offline Agent (PR fix(web): let an offline Computer be reconnected from its Agent #166) or opening the New Agent flow.Preflight
Deleting the Workspace switcher strands anything held in a second Workspace. The switcher renders only when
me.workspaces.length > 1. The reporter's second Workspace was verified empty, but other Accounts were not. Run before deleting:Proceed only when every
with_agentsis at most 1. Also check for outstanding invitations, which the deletion will strand:Accepted costs
Data migration — precondition for Phase 2
A one-off operational task, deliberately not built as a product capability.
workspacescannot be folded intouserswhile one Workspace holds five administrators and eight Agents belonging to different people. The fold requires each Workspace to correspond to exactly one Account.The split is pure data and needs no schema change. In one transaction: create a Workspace and grant per Account, then update
workspace_computers.workspace_idandagents.workspace_idtogether so the composite foreign key holds throughout. Agent ids, IM bindings, session history, and machine credentials all survive, becauseim_bindingsreferencesagent_idandsession_placementsreferences the enrollment id, and neither changes.Two things to decide before running it:
reviewer, and merging back later collides withagents_workspace_name_active_unique. If reunification is even plausible, adopt a prefixed convention during the split; the cost is zero now and non-trivial later.computerId, several enrollments — but it requires runningcomputer connectagain on that host.debian-devboxcarries two Agents and is a candidate.Phase 2 — remove Workspace from storage
Fold
workspacesintousers; dropworkspace_admin_grantsandadmin_invitations; rehost the enrollment and Agent foreign keys on the Account; retire the CLI--workspaceoption. Invisible to users, who stopped seeing Workspace in Phase 1.Most of the 2431 occurrences are a mechanical rename. The risk is concentrated in the migration itself, and this is a cutover of the same magnitude as #161.
Recommendation: do not schedule a dedicated release for it. Once Phase 1 lands, what remains is an inert table with one row per Account and a foreign key that always resolves to the same Account. Carry it until the next change that already touches the schema, and fold this in there.
When Workspace would have to come back
Some plausible triggers have cheaper answers and should not be met by restoring Workspace: sharing a single Agent with one colleague is a per-Agent collaborator list, and handing an Agent to someone else is an ownership-transfer operation.
A grouping layer is genuinely required when:
debian-devboxalready fits: it is nobody's laptop, and under self-administration no Account owns it, so patching and access have no responsible party.Agent-to-Agent routing or shared Context Tree access does not by itself require Workspace to return. Sparse interaction can use Agent-scoped capabilities, and durable shared knowledge can remain an independently authorized Context Tree.
If a future requirement is genuinely Workspace-shaped, restoring it is cheaper than removing it was. Removal collapses foreign keys onto
users; restoration inserts a layer aboveuserswith a one-to-one mapping already in place, and by then the requirement is known rather than speculative.Non-goals
Related
workspace-<uuid>and are never user-chosen.