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 immutable delivery snapshots.

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. Manual assignments and automatic processing resolve a device from this pool when a Run is claimed.

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; the device itself is bound when the Run is claimed.

Successful planning and assignment by the coordinator 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 device currently satisfies authorization and capacity constraints, the Run remains queued for device claim instead of requiring a device binding on the Agent. Completing runtime configuration does not change project or Agent defaults and preserves manual approval requirements. Workflow progress counts steps only after acceptance.

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/

Finalized delivery prefixes are immutable. Later tasks may only read or copy them.

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.

Board execution by Bots and Agents​

A board assignee is either a project member or a project Bot (ProjectChatAgent). A Wegent Agent (Kind(kind=Team)) is runtime configuration for that Bot, not an assignee: the user creates a Bot in the board, selects Wegent as its execution environment, and binds one runnable Team. The binding lives in the Bot's existing metadata_json; no table is created.

Automation execution connection graph​

Every edge has one owner:

EdgeSole responsibilityCurrent code owner
Entry β†’ assignmentValidate member/Bot and persist assigneeloop_items/service.py, external_provider.py
Assignment β†’ execution truthCancel the old attempt and create a new oneloop_item_executions/service.py
Automation β†’ runtime activationActivate the new execution after assignment commitproject_automation_execution.py
Project archive β†’ automation cleanupDisable and soft-delete every rule and clear its next trigger in the project-archive transactioncloud_projects/service.py, project_automations.py
Wework activationLocal device pull or cloud consumer claimrobot_queue_tasks.py, Wework local puller
Settings β†’ device total concurrencyPersist and immediately apply each scheduler limit through authenticated Runtime RPC; slot_used/slot_max are capacity projections onlydevices.py, runtime_rpc_service.py, Rust runtime.settings.*
Wegent activationCreate Task/Subtask by execution ID and enter Team pipelineboard_team_execution.py, project_automation_tasks.py
Wegent board MCP injectionBackend detects board execution from native Task labels and injects the Backend MCP URL plus task-scoped authentication into the same ExecutionRequest used by ChatShell and Executor; it must not depend on a caller-owned temporary booleanexecution/request_builder.py, mcp_server/server.py
Backend board MCP β†’ domain servicesExpose the canonical local-Space-MCP tool names and operate through existing Backend CloudProject, LoopItem, file, attachment, delivery, and assignment services; never invoke the Wework local stdio MCPmcp_server/tools/wework_space.py and the corresponding domain services
Wework local MCPStarted only by the Wework Runtime for local project-space and local-path capabilities; it must not replace the remote board MCP injected for a Wegent Runtimeexecutor/src/task_runtime/mcp.rs
Board execution β†’ all runtime inputsLocal, cloud, and Wegent share one visible user input containing canonical IDs, the task URI, and the Bot execution prompt; the runtime reads task content through MCPloop_item_executions/profile.py, board_team_execution.py
Wegent terminal β†’ execution truthProject terminal state after strict identity checksboard_team_completion.py
Wegent comment β†’ native continuationResolve the exact backend_task_id from the reply target and create Subtasks in the same Task; project the result only to that reply without rewriting the terminal executionboard_team_continuation.py, project_automation_tasks.py
Wegent user stop β†’ cancellation intentPersist Task CANCELLING and execution cancel_requested, then send the Runtime cancellation commandchat_namespace.py, board_team_completion.py
Runtime cancellation ACK β†’ terminal truthAfter process-stop confirmation, persist Task/Subtask CANCELLED and project board cancelled through the unified terminal projectorRust executor, status_updating.py, board_team_completion.py

The Backend board MCP exposes the complete Backend cloud-board domain surface: get_current_context; space list/create/update; board-item list/search/create/get/update/reorder; assignment candidates and assign; provider comments; space-file list/read; item-attachment list/upload/read/delete; and delivery list/read. The remote MCP transfers file contents as inline text or Base64 and never accepts a Runtime-local file path. DingTalk AI Table dynamic field/record tools remain a Wework-local provider route and must not be faked when no Backend provider service exists.

The 2026-08-15 queue defect was a missing edge: HTTP assignment invoked Wegent activation, while an automation manager's internal assignment only created a queued execution. Consequently claimed_at and backend_task_id stayed empty, and device consumers correctly ignored records whose execution_environment=wegent. The fix must add the automation-to-runtime-activation edge. It must not send Wegent rows to a Wework device consumer or infer execution from the queue UI.

Automation assignment and execution sequence​

Review the sequence against these invariants, in order:

  1. LoopItem.assignee_agent_id is always the board Bot; the Wegent Team exists only in Bot configuration and execution team_id.
  2. Runtime activation occurs only after assignee and execution commit, so consumers can always read the execution.
  3. Wegent dispatch locks an exact execution_id and idempotently checks backend_task_id; native Task labels and the execution binding commit together while the lock is held, so it never guesses the latest task or releases the lock before binding.
  4. queued only means execution intent is durable. The UI cannot show running before a backend_task_id or Runtime acceptance event exists.
  5. Automation run, Bot execution, and native Wegent Task keep separate state boundaries. Only the unified projector may write board terminal truth after verifying every identity label; Runtime events and user stops both invoke it.
  6. Manual, API, scheduled, and AI-manager assignment converge on one runtime activator. New entry points must not copy dispatch logic.
  7. Failure to enqueue activation, or activation failure in the worker, must persist an explicit failed terminal state; an execution with no remaining consumer must never stay queued.
  8. A Wegent UI/API stop first writes only CANCELLING/cancel_requested. Both sides become CANCELLED/cancelled only after a Runtime ACK or trustworthy CANCELLED callback. Delivery failure cannot invent terminal truth, and the frontend must await and display the server ACK.
  9. All three runtimes use the same visible user input: canonical project_id, task_id, and execution_id, the task cloud:// URI, and the user-configured Bot execution prompt. The execution prompt never enters a Team/Ghost/Bot system prompt or hidden application context; MCP reads the latest task title, description, and state.
  10. A Wegent comment continuation resolves the native Task from the reply target's exact execution_id and backend_task_id, then revalidates the thread-bound project Bot and original execution Team. It never infers a session from the latest execution, a device runtime list, or frontend memory. Each turn creates Subtasks in the same Task, preserves the execution's terminal state, and uses the reply comment only as that turn's display projection. A native Task may have at most one pending or streaming continuation at a time so concurrent requests cannot overwrite the active Subtask label or cross-write projections.
  11. Whenever native Wegent Task labels identify a board execution or board automation, Backend injects the board MCP on every request build. ChatShell and Executor consume the same injection result, and continuations never depend on MCP state left in a previous container.
  12. The Backend board MCP and Wework's native local Space MCP are separate runtime boundaries. Backend owns the former with Task Token authentication; Wework Runtime starts the latter locally. They share canonical tool names and domain semantics but never fall back to or overwrite each other.
  13. The Task Token's task_id/subtask_id and native Task labels jointly scope the current board space. The model may operate on other items inside that space, but the current item, automation run, and execution identities are resolved by the server and are never guessed, and a Task Token cannot cross the current space boundary.
  14. A project cannot be archived while it has an active automation run; Settings provides the cross-project stop control. Project archival and rule cleanup then commit in one transaction. Every rule is disabled, soft-deleted, and stripped of its next trigger; schedule scans only select rules whose parent project remains active. Historical terminal runs remain as audit records and can never create new executions.
  15. Device-wide concurrency belongs to each Runtime scheduler and is separate from Bot concurrency. Settings updates each device through authenticated Runtime RPC; an offline device cannot report a fabricated saved result. The sum of device slot_max values is capacity display only and never becomes execution-state truth.

Wegent board comment continuation sequence​

loop_item_executions is the sole source of truth for board execution state, while native tasks/subtasks own Team-internal execution. backend_task_id and labels containing the execution, Subtask, and Team identities fence the two records together. Messages and activity rows are presentation projections and never override execution truth.

A board-originated reassignment or stop first moves board truth to cancel_requested when a process may exist, or cancelled when execution provably has not started, then routes cancellation to the device Runtime or native Team Task. A native Wegent stop atomically persists Task CANCELLING and board execution cancel_requested. Only after the Runtime actually stops and calls back may Task/Subtask become CANCELLED; the unified terminal event then advances the board execution to cancelled. A UI click or delivered HTTP request is not a substitute for Runtime ACK. A delayed worker must recheck board execution truth after claiming and cannot start a cancelled run.

Execution scope remains owned by the board Bot. agent_id determines queue columns, assignment history, and concurrency identity; team_id records only the actual Wegent runtime target. Different board tasks may still enter the native Team pipeline concurrently, where Team collaboration configuration controls internal parallelism.

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 immutable snapshot 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/ immutable delivery snapshots
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, complete the TODO, and update current_delivery_id.
  6. If the database commit fails, remove the new manifest while keeping the draft retryable.

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 and the comment thread's execution binding. A Developer can reply without gaining access to the executor's personal devices, models, or credentials.

  • A reply to an AI thread continues its original execution identity and session, even after the Issue is reassigned.
  • A new top-level comment uses an explicitly mentioned available agent, or the Issue's assigned agent. The existing execution queue starts an independent session 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. Execution identity and device configuration are resolved on the server. Comment/thread locks and existing response records prevent duplicate dispatch. Execution failures are surfaced without resending saved comments. A follow-up creates its own activity and does not inherit or reopen a completed automation run.

Focused checks cover hidden admin agents, independent root sessions, reassignment, duplicate requests, device rejection, 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.