Skip to main content

Cloud project collaboration architecture

The current V4 UI source of truth is /Users/hongyu9/Downloads/wework-delivery-v4-TODO.pen. Implement the interaction from that design instead of deriving page layout from this document.

Goal​

A cloud project is the shared collaboration and storage boundary for a team. Members may select the same cloud project as the default destination of their own local projects, execute work in Wework, and submit selected conversations, files, and Markdown as editable deliveries.

A cloud project is not the existing Project model:

  • Project is a user-owned local execution workspace containing device, path, Git, and runtime configuration.
  • CloudProject is a shared aggregate containing membership, TODOs, shared files, and a MinIO namespace.
  • Local projects owned by different members may independently select the same cloud project; the cloud project stores no reverse link.
  • One TODO may link to many Wework Tasks, while one Task may process at most one active TODO at a time.

Client reuse boundary​

Execution configuration and waiting states​

Project settings keep collaboration organization separate from runtime resources:

  • Collaboration members presents project participants in the order Agents β†’ Project members β†’ Collaboration groups. A collaboration group is a reusable organization whose members and leader may be humans or Agents. Workflow stages belong to the collaboration group.
  • Automatic processing defines trigger rules only. Issue creation, Tag changes, external events, or schedules route work to a project member, Agent, or collaboration group. A rule never binds a device.
  • Execution environments manages the project's authorized device pool. Agent creation does not select a device. An idle Executor pulls Runs that match its project, Agent, and collaboration-group permissions.

A Mention never changes the assignee; explicitly mentioning an available agent can trigger comment execution. Assignment, Mention, Subscription, and Run have independent semantics. Explicit assignment changes ownership and creates a Run when the target is an Agent or collaboration group.

Model identity and provider options remain opaque dictionaries and are not subject to API field case conversion. Device presence comes from connection heartbeats. If model or workspace configuration is missing, the execution remains in waiting_runtime and uses the unified runtime-configuration entrypoint; a device is bound only after an Executor successfully claims the Run.

Successful planning and assignment by the manager does not mean the Issue is complete. Parent steps and child details display the child's execution state. Missing model or workspace configuration keeps an execution in waiting_runtime; Configure and continue completes that existing execution's profile. If no authorized Executor can claim it, the Run remains queued. Backend does not push work according to capacity and does not pre-bind a device to an Agent. Completing runtime configuration does not change project or Agent defaults and preserves manual approval requirements. Workflow progress counts steps only after acceptance.

Assigning a robot as the owner immediately moves the Issue into the in-progress column and creates its Run. When that Run is waiting_runtime for a missing model or workspace, the board card and the detail page must show the requires-configuration reason instead of only the column, and must not leave that wait silent behind "in progress".

Wegent Web replaces the former Inbox entry with Collaboration and directly reuses the Backend APIs for cloud projects, board Issues, comments, attachments, shared files, members, and execution records. Web and Wework do not maintain a second domain model or API surface.

Cross-client types, API clients, copy, test contracts, and host-independent React components live in packages/collaboration. CollaborationApp is the sole primary interface for cloud projects in both Web and Wework. It owns the project home, board, Issue details, comments, attachments, files, members, runs, and project settings; the clients must not maintain parallel cloud-collaboration pages.

Web supplies Next.js routing, notifications, and external-link behavior through a host adapter. Wework uses the same adapter to inject local project storage and a Desktop tools entry. Only local projects, terminals, device execution, AI orchestration, and other behavior that depends on Electron, the local filesystem, or the local executor may enter the desktop-specific workspace. New portable behavior must land in the shared package first instead of being copied into both hosts and synchronized later.

The shared package owns business state, field structure, and interaction contracts, but it must not duplicate a host's existing design system. Dialogs, tabs, selects, inputs, and primary actions should be injected through an explicit host adapter; the shared package keeps only a neutral default for host-independent use. Hosts pass brand colors through semantic CSS variables instead of hard-coding Web or Wework colors in shared components. The shared board sizing chain must preserve min-width: 0, min-height: 0, and vertical flex constraints so horizontal overflow remains inside the board scroll container instead of placing a page-level scrollbar above the remaining content.

The Collaboration members settings page uses a wide content container because Agents, project members, and collaboration-group forms need multi-column space; ordinary settings pages retain the default narrow container. Member and responsibility columns in collaboration-group details must use shrinkable minmax(0, ...) tracks, with min-width: 0 and truncation on text nodes. Fixed minimum track widths must not push form controls outside the panel, and heading actions remain on one line.

When chat messages enter a collaboration space, the Backend creates immutable message snapshots from a source Task the current user is authorized to access. The target may be a new Issue or a comment on an existing Issue. Clients must not write chat text directly as if it were a trusted snapshot.

Domain relationships​

CloudProject
β”œβ”€β”€ ResourceMember(resource_type=CloudProject)
β”œβ”€β”€ ShareLink(resource_type=CloudProject)
└── LoopItem
β”œβ”€β”€ LoopItemTaskBinding
β”‚ └── TaskResource
β”‚ └── Project (local execution workspace)
└── Delivery
└── DeliveryAsset

Data ownership​

DataSource of truth
Cloud projects, members, TODOs, task links, delivery metadataBackend MySQL
Local paths, devices, Git, execution configuration, and default project-space referenceDevice-local Codex project state
Shared files, Markdown, conversations, and delivery snapshotsMinIO/S3
AI access to cloud dataMCP authorized by the Backend

Objects are isolated by the cloud project's public ID:

projects/{cloud-project-public-id}/
shared/
loop-items/{loop-item-id}/
deliveries/{delivery-id}/
markdown.md
chat.json
manifest.json
files/

Delivered artifacts support atomic updates with optimistic version checks. An update writes new objects under a distinct edits/{revision} prefix within the project delivery, then commits the title, object references, asset list, and version together. Failed commits preserve the original content; successful commits clean up superseded objects.

Data model​

CloudProject​

cloud_projects stores the shared project and never stores local runtime configuration.

id, public_id, project_key, name, description
created_by_user_id, storage_prefix, next_item_number
status, version, created_at, updated_at

Local-project default space​

A local Codex project may store one { projectStore, projectId } default project-space reference. The reference belongs to device-local project state, never enters the Backend, and creates no reverse index on the project space. A new conversation may override or clear the default before its first message is sent.

LoopItem​

The existing loop_items table stores cloud TODOs. cloud_project_id references cloud_projects, and sequence_number produces display identifiers such as WEG-18.

The initial fixed workflow is:

inbox β†’ pending β†’ in_progress β†’ in_review β†’ completed

Completed TODOs may be reopened into in_progress. Updates carry a version value and use optimistic locking.

Issue dispatch and Executor ownership​

A board Issue can be assigned to a project member, an Agent, or a collaboration group. Manual UI, API, automation, and AI-manager entry points create the same root-Issue dispatch intent. They do not start a Runtime or manage device capacity.

Backend owns only durable root-Issue state and presentation: assignee, queue, claim, lease, status, activity, comments, attachments, and deliveries. It validates project, Agent, collaboration-group, and device permissions, exposes task-scoped board MCP tools, and accepts explicit status, comment, and delivery updates from an Executor or human. Backend does not choose an idle device, push work to devices, track Executor capacity, or run a collaboration group's manager loop, member fan-out, concurrent batch, or barrier.

Local and cloud Executors use the same claim protocol and board data model. When an Executor has a free execution slot, it pulls a root Issue that the current device is authorized to run, claims it atomically, starts the Runtime, and renews the lease. Multiple authorized devices compete for different Runs; one Run has exactly one owner while its lease is valid. Another Executor may recover an expired lease, and an Executor that lost its lease can no longer write results.

Three assignment loops​

For a direct human assignment, Backend sends in-app and connected IM notifications that open the original Issue without creating a personal task. The assignee enters the result and submits it for review on that Issue. Optional AI assistance creates a linked Runtime Task whose Delivery returns as a draft without submitting or accepting the Issue. A collaboration-group human assignment changes the owner to that person. They can create personal tasks from a notification, the Issue action, or a reply under their assignment activity. Acknowledgements remain comments. Each task retains humanAssignmentId and related identity; a real Delivery from any task bound to that assignment can release its barrier. The coordinator resumes when all required round results arrive.

For a direct Agent assignment, an Executor claims the root Issue and starts a Runtime session for the assigned Agent. Results are persisted as activity and delivery evidence; success moves the root Issue to in_review for user confirmation. Backend never creates or owns the internal Runtime session. Setting a robot owner is itself what moves the root Issue into the in-progress column and creates the Run; the owner does not have to drag the Issue into a processing column first.

For a collaboration-group assignment, the Executor still claims only one root Issue. All later coordination stays inside that Executor:

  1. The Executor starts a fresh manager session. Project collaboration rules and the optional workflow are visible in that turn, and the manager reads the Issue and eligible members through board MCP tools.
  2. The manager can update the root Issue state with an optional comment through MCP, or submit one round of assignments. Every assignment contains a task title, an assignee, and a workflow stage when the project defines a workflow.
  3. Dispatch updates the root Issue owner. The Executor starts isolated member Runtime sessions concurrently and notifies human members, waiting for their linked personal-task Delivery.
  4. The Executor waits for every required Agent outcome and human Delivery. A member that cannot start must have a recorded failed outcome.
  5. Once all outcomes arrive, the Executor starts a fresh manager session with the complete round results. The manager evaluates them, assigns another round, or explicitly updates the Issue through MCP. AI-only, human-only, and mixed batches use the same flow; delivery itself does not mean acceptance.

Every manager turn is a fresh Runtime session, and member sessions are isolated. A later round receives only structured assignments, results, and deliveries. It never reuses a member conversation, and Backend never guesses the manager's next action.

Collaboration-group execution sequence​

Implementation and review must preserve these invariants:

  1. Backend owns only the root Issue queue, claim, lease, status, and presentation projections. There is no Backend manager loop, member fan-out, barrier, or automatic continuation after completion.
  2. Local and cloud execution use the same claim protocol. A device is selected by a successful Executor pull, never by Backend push or reported-capacity scheduling.
  3. The Executor that claims the root Issue creates every collaboration-group manager and member session. Backend provides only MCP domain operations and persistence.
  4. Agent assignments in a round may run concurrently in separate Runtime sessions. A round containing human work waits for every required outcome and Delivery before resuming its coordinator.
  5. A manager assigns one round at a time. Every completed round returns to a fresh manager session; Backend never submits an Issue for review just because its members finished. Native Runtime defaults can resume a manager without a nonempty model configuration.
  6. Human Delivery is a first-class round result. A collaboration-group human delivery wakes the manager without asking Backend to start or maintain a manager loop.
  7. Activity records show real events: who assigned which task to which Agent or member, execution results, Deliveries, and manager state changes. UI copy must not replace a child-task title with the root Issue title.
  8. Backend board MCP and Wework's local Space MCP share domain semantics but keep separate authentication and transport boundaries. Remote MCP never accepts a Runtime-local file path.

LoopItemTaskBinding​

loop_item_task_bindings stores the historical many-to-many relationship between a TODO and concrete Wework Tasks. A runtime Task is identified by task_user_id + device_id + task_id, because a locally executed Task may not exist in the Backend tasks table; backend_task_id is only an optional index. Unlinking sets unlinked_at so execution provenance remains auditable.

The Wework local runtime classifies bindings as system or user. Every runtime task must retain one system binding to default-work-items. The current UI maintains at most one additional user binding, while the storage model can be extended to multiple user bindings later. Task-to-issue lookup prefers a user binding and falls back to the system binding. Runtime status, title, and archive synchronization update only the system binding; unlinking a user-selected board can soft-delete only the user binding and must not remove the system binding. The My tasks board reads only system issues for currently unarchived runtime tasks. It neither aggregates issues from other project spaces nor shows historical system issues whose runtime tasks have left the Task inventory.

Delivery​

deliveries and delivery_assets store delivery metadata. The nullable Delivery.source_task_binding_id points to a verified TODO/Task binding for local delivery and is null when a TODO is completed directly in the cloud UI.

Authorization​

Reuse resource_members and share_links with a new CloudProject resource type.

RoleReadEdit TODOs/filesManage membersArchive project
ReporterYesNoNoNo
DeveloperYesYesNoNo
MaintainerYesYesYesNo
OwnerYesYesYesYes

Every TODO, delivery, file, and MCP request resolves the caller's cloud-project role first. Inaccessible resources return 404 to avoid disclosing their existence.

Service boundaries​

cloud_projects/  projects and members
loop_items/ TODOs, state transitions, and Task bindings
delivery/ editable deliveries
cloud_files/ mutable shared files
mcp_server/tools/delivery.py authorized AI access to cloud references

Delivery services do not own TODO CRUD. LoopItem services do not access MinIO directly. MCP never holds or returns S3 credentials.

Delivery transaction​

  1. Create a draft Delivery and write its Markdown and optional conversation object.
  2. Upload assets in bounded chunks and record size and SHA-256 metadata.
  3. finalize locks the Delivery and LoopItem and validates that the source Task is still linked to the TODO.
  4. Write manifest.json.
  5. In one database transaction, mark the Delivery delivered and update current_delivery_id. Initial human-assignment delivery follows round-settlement rules; editing a delivery never settles the round again.
  6. If the database commit fails, remove the new manifest while keeping the draft retryable.

PATCH /api/v1/deliveries/{id} is implemented by the Rust Backend. It accepts multipart metadata and file_0, file_1, and subsequent file parts. Metadata includes title, version, markdown, chat, retained_asset_ids, and file descriptors. New and retained assets must have unique relative paths; references to assets from other deliveries are rejected. Existing creation, read, and initial publication endpoints retain their current ownership.

API​

/v1/cloud-projects
/v1/cloud-projects/{id}/members
/v1/cloud-projects/{id}/members/{user_id}
/v1/cloud-projects/{id}/files
/v1/cloud-projects/{id}/folders
/v1/cloud-projects/files/{file_id}
/v1/cloud-projects/{id}/loop-items
/v1/loop-items/{id}
/v1/loop-items/{id}/tasks
/v1/loop-items/{id}/start-task
/v1/loop-items/{id}/deliveries
/v1/deliveries/{id}
/v1/cloud-work-items/my-work
/v1/runtime-tasks/loop-item

Create boards and tasks with a personal API key​

Users can call the two creation endpoints with a personal API key while preserving the existing authorization and board-state rules. Both X-API-Key: wg-... and Authorization: Bearer wg-... are supported, and browser JWT authentication remains valid. Service keys cannot create boards or tasks as a user.

Create a board:

curl -X POST 'https://<host>/api/v1/cloud-projects' \
-H 'Content-Type: application/json' \
-H 'X-API-Key: wg-<personal-api-key>' \
-d '{
"project_key": "OPS",
"name": "Operations board",
"description": "Created through the API"
}'

Create a task with the board id returned by the previous request:

curl -X POST 'https://<host>/api/v1/cloud-projects/<project-id>/loop-items' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer wg-<personal-api-key>' \
-d '{
"title": "Check cloud execution state",
"description": "Keep the board as the source of truth",
"priority": "high",
"tags": ["api"]
}'

Task creation still passes through board membership authorization, status-definition validation, provider routing, and automation rules. If status is omitted, the task enters the board's inbox state. An unknown status returns 422, while an inaccessible private board returns 404 under the resource-hiding policy. These are create operations, not PUT upserts; callers should determine the outcome of an earlier POST before retrying to avoid duplicates.

Creation and updates use separate endpoints rather than PUT upsert. Shared files support folder creation, upload, rename/move, short-lived access, and recursive deletion. A move copies MinIO objects first, commits metadata, and only then removes the old objects; failed moves clean up newly copied objects.

When Wework adds a new runtime task to a cloud project space, it composes the existing primitives: create a LoopItem, then bind the runtime task; when execution status changes, read the task context and update the linked TODO. The Backend intentionally has no aggregate tracking endpoint dedicated to that orchestration. This allows the desktop app and Backend to be released independently while the stable TODO-creation, task-binding, and optimistic-locking APIs preserve the same behavior. The desktop app deduplicates concurrent association requests for the same runtime task and reuses a created TODO after a temporary binding failure to avoid duplicate cards.

The Wework Composer encodes cloud projects, directories, files, TODOs, and deliveries as atomic cloud:// references. Tasks carrying cloud-project context receive the Delivery MCP, and resolve_cloud_reference authorizes and resolves every reference in Backend so neither clients nor AI receive S3 credentials. The TODO board refreshes periodically while visible, while writes continue to use version optimistic locking for concurrent collaborators.

Delivery sequence​

  1. Add CloudProject, membership authorization, and local-project bindings.
  2. Move LoopItem ownership to CloudProject and add the state machine and optimistic locking.
  3. Add Task bindings and start-a-task-from-TODO.
  4. Migrate delivery authorization, source Task references, and MinIO paths.
  5. Add shared files and the cloud workspace MCP.

Project members and comment execution​

Agent configuration visibility controls whether members can select an Agent. Collaboration on an already authorized Issue uses project permissions. A Developer can reply without gaining access to an Executor's personal devices, models, or credentials.

A human assignment in a collaboration round exposes Reply and create a new task to its assignee. The acknowledgement is saved as a comment; the new personal task uses the round assignment instructions and retains its assignment binding.

  • A reply to an AI thread saves a reference to the original activity but does not require Backend to continue the old Runtime session. When execution is requested, it creates a new root-Issue Run for an Executor to claim.
  • Comments and replies mentioning only members, without an Agent mention, save the comment and notify authorized readers without starting or continuing the assigned Agent. Runner availability is irrelevant; the comment persists this intent so retries behave consistently.
  • Other new top-level comments use an explicitly mentioned available Agent, or the Issue's assigned Agent. The root-Issue queue creates an independent Run and preserves approval and configuration-waiting states.
  • Without an assigned or mentioned agent, the comment is saved without execution. Retrying that request after reassignment does not start AI unexpectedly.
  • Mentioning another agent still requires picker visibility. Comments do not change the Issue assignee.

Clients send project, Issue, saved comment, and attachment IDs through wework:project_chat:comment:execute; they do not send device configuration. Comment locks and existing response records prevent duplicate Runs. Execution failures are surfaced without resending saved comments. Backend persists only a claimable execution intent, and the claiming Executor creates the Runtime session.

Agent candidates follow visibility and use authority; device liveness, Issue runner affinity and model readiness do not hide candidates. New comments and queued session replies validate project, Issue and Agent use authority, then persist an execution request. Queue consumers reuse execution preparation to check runner affinity, models and runtime configuration. Conflicts fail the Run and update its AI activity while the saved user comment remains successful. Offline devices do not prevent admission: valid Runs wait for a runner to reconnect and claim them. Replies to unclaimed sessions also queue and resume the original task after it becomes ready. Manual approval remains mandatory, and claims revalidate current authority. The UI displays actual Run state rather than inferring Agent receipt from mention text.

Focused checks cover hidden admin Agents, independent root Runs, reassignment, duplicate requests, comment-only behavior, and cross-project, read-only, and other-author rejection. Desktop regression coverage belongs to the existing collaboration-shared-core scenario; E2E runs require an explicit request.

Reading project execution sessions​

Execution details and Issue conversations declare projectSession: { projectId, issueId } as their read context. HTTP and Socket.IO transcript requests share one authorization path: project Reporter access or higher, an existing Issue, an exact device/task match in an execution record or active TaskBinding, and a single original execution owner. Client workspace paths and Runtime Handles cannot expand this authority. Personal transcripts retain device ownership checks; project reads do not grant access to the owner's device catalog, model credentials, or other sessions.

Historical execution outcomes and transcript availability are independent. A read failure must not appear as empty history or confirmed executor idleness. Initial loading uses a skeleton. An offline executor remains an explicit error with retry; clients must not switch to a member's similarly named device. The existing automation regression includes member reads of admin-owned history. Focused unit tests run by default; E2E requires an explicit request.

HTTP transcript responses preserve the Runtime's turns, running, origin, historyUnavailable, and turnNavigation, matching the Socket.IO contract. Missing turns is a protocol error, not an empty-history fallback.

Project milestones​

Local and cloud projects with internally stored Issues support milestones. The Milestones project view manages a description, due date and manual open/closed state, and shows counts for Issues visible to the caller. Progress is completed Issues divided by linked Issues; empty milestones show 0%. Finishing an AI run does not complete an Issue.

Each Issue can reference one milestone in the same project. Parent and child Issues are assigned independently. Issue creation and details offer a milestone selector. Creating an Issue from milestone details preselects that milestone and still allows changing or removing the association; boards and tables support filtering, and tables support bulk association and removal. Deleting a milestone only removes its associations and preserves every Issue. Cloud milestone management requires Maintainer or Owner; association changes retain the existing Issue content-edit policy. Counters apply the existing Issue visibility rules.

Milestones reuse loop_items nodes with resource_type=milestone; Issue references live in metadata.milestone_id. Executor IPC and SQLite transactions own local persistence. Rust owns the new cloud milestone APIs, while existing Python Issue APIs create and update references. Association validation and deletion lock the same milestone row; updates and deletion check versions. No new table or migration is required. External GitHub, GitLab and DingTalk Issue providers are outside this feature's scope.

Archived completed Issues remain in both total and completed counts, and archived unfinished Issues remain in the remaining count. Archiving and restoration do not change delivery progress. The UI identifies the archived count and links milestone details to the existing archive.