Skip to content

feat(space): make documents the authoring surface, split Workspace from Space (专项作战人机协作区) #558

Open

Description

@PeterGuy326

Preflight

Summary

Today a user defines work through forms: "新建项目" is a modal with fields, "目标" is a field editor, tasks live in a board. That reads as a database admin tool, not as a place where a human and a set of agents work together. The proposal is to make documents the primary authoring surface, and to introduce a two-level naming system — Workspace (工作区) and Space (专项作战人机协作区) — that matches the existing on-disk model instead of fighting it.

Three concrete asks:

  1. Work should be defined in a beautified, structured Markdown document, not a modal form. The agent drafts a first pass; the human confirms and edits in place. One file, two renderings (document view ↔ structure view) — never two copies of the data.
  2. Projects and the inbox are not peers of the org. They should live inside a space: Workspace > Space > Files.
  3. When the human is the blocker, there must be one place to approve and one place to accept. These are two distinct acts and must not be merged.

Evidence from the current tree

Claim File Finding
"项目管理" and "目标" are already the same thing apps/desktop/renderer/src/projects/ProjectManagementModule.tsx 19 lines; it is an alias shell over GoalsModule. No separate project domain.
The repo already thinks in files package.json:8 Self-described as "file tree is the org chart".
Owner is org-level, not a position field packages/shared/src/org-tree.ts:60,66 OrganizationFile.owner: string points at the root role (reportTo === null).
Work items and execution facts are distinct packages/shared/src/goals.ts:31-49 GoalWorkItem is the human plan; GoalTaskExecution is projected from turn records.
Approval has a full domain model packages/shared/src/approvals.ts:88-114 ApprovalRecord with source, policy, decisions, execution phase.
Acceptance has no model packages/shared/src/goals.ts:26 Only a review status bit; nothing records who accepted which criterion, or when.
Progress is already modelled packages/shared/src/turn-progress.ts:12-79 5 fixed phases, STALE_PROGRESS_MS = 30_000.

The key asymmetry: approval is fully modelled, acceptance is not. That is why "我在卡点上" currently has nowhere to land.

Naming

The existing vocabulary overloads "空间" / "space". Split it:

  • Workspace (工作区) — the top-level container. One organization + one file root. Maps 1:1 onto the existing workspace.v1alpha1 manifest. One workspace is open at a time.
  • Space (专项作战人机协作区, shown compressed as 协作区 in the rail) — a folder inside a workspace where one objective is fought. Hierarchy: Workspace > Space > Files.

This follows the data model instead of inventing a parallel one: a workspace already has exactly one organization, and a space is just a folder.

Document-driven collaboration

A space is a folder. Its spine is one Markdown file (e.g. 目标.md) that is the single source of truth:

# 目标:<title>

## 验收标准
- [ ] <criterion>

## 任务
| 任务 | 状态 | 负责岗位 | 截止 |
| --- | --- | --- | --- |
| ... | ... | ... | ... |
  • The human writes prose freely, anywhere in the document.
  • The agent parses fixed H2 headings to extract structure. No custom DSL, no front-matter schema to maintain.
  • The agent's output is written back into the same folder, so prose and structured rows stay in one artifact.
  • The document view and the structure view are two renderings of one file. Editing a row in the structure view edits the document; editing the document updates the structure. There is never a second copy.

Who is the owner

  • owner comes from OrganizationFile.owner, not from a per-space field. It points at the root position.
  • Every space shows its participants (which agents took part) and marks one owner visually.
  • Goal / project-management / progress reuse existing components rather than new bespoke ones.

Acceptance vs approval

Two lanes, deliberately not merged:

  • Approval — belongs to the workspace/org. Reuses the existing ApprovalRecord. Triggered when an action needs permission (e.g. a read_only position requests network). Answered by the operator.
  • Acceptance — belongs to the space. Has no domain model today. Triggered when the human is the blocker on a deliverable. The human ticks criteria one by one, with an optional evidence path per criterion; "accept" stays disabled until every criterion is ticked; "reject" carries a note.

Hard constraint carried over from goals.ts: GoalWorkItem and GoalTaskExecution never overwrite each other. An agent may only push an item to review; only a human acceptance may write done.

Inbox

The inbox is a cross-cutting view, not a container:

  • "协作区待办" — items that have a home; clicking jumps back to the space.
  • "组织待办" — items that belong to no space; no jump target.

Proposal — the acceptance.v1 contract

The only genuinely new domain object this needs. Shape mirrors goal.v1 conventions (fail-closed validators, allowlisted keys, closed enums, bounded text):

export const ACCEPTANCE_SCHEMA_VERSION = "acceptance.v1" as const;

export interface AcceptanceRecord {
  schemaVersion: typeof ACCEPTANCE_SCHEMA_VERSION;
  acceptanceId: string;
  spaceId: string;
  source: {
    positionId: string;
    turnId?: string;
    artifactPath?: string;
  };
  verdicts: Array<{
    criteriaIndex: number;
    passed: boolean;
    evidencePath?: string;
  }>;
  decision: "accepted" | "rejected";
  note?: string;
  decidedBy: string;
  decidedAt: string;
}

Invariants a validator must enforce:

  • decision === "accepted" requires every criterion index present and passed: true.
  • decision === "rejected" requires a non-empty note.
  • criteriaIndex values must be unique and non-negative.

Scope for this change

To keep the first PR reviewable, this only lands the contract + tests, not the UI:

  • New: packages/shared/src/acceptance.ts — the contract above plus a fail-closed validateAcceptanceRecord.
  • New: tests covering the invariants listed above.
  • Exported from packages/shared/src/index.ts.
  • No renderer changes yet; the space/doc UI lands in a follow-up once the contract is agreed.

Assumptions

  • A space is a folder under the workspace root, not a new persisted entity with its own manifest. If a space later needs durable metadata, it goes in a sidecar file inside the same folder rather than a new store.
  • Fixed H2 headings are sufficient structure for v1; a richer syntax can come later without breaking the document.
  • Acceptance is per-deliverable and append-only. Re-accepting after a change produces a new record rather than mutating the old one.

Acceptance

  • validateAcceptanceRecord rejects an accepted decision that is missing a criterion or has one passed: false.
  • validateAcceptanceRecord rejects a rejected decision with an empty note.
  • validateAcceptanceRecord rejects duplicate criteriaIndex values.
  • validateAcceptanceRecord rejects unknown top-level keys and an unsupported schemaVersion.
  • packages/shared/src/index.ts re-exports the new module.
  • pnpm test for the shared package passes.

Refs #222, #511.

Activity

  1. PeterGuy326 commented on Oct 1, 2026

    @PeterGuy326
    ContributorAuthor

    Contract landed in #559 (branch feat/workspace-space-docs): packages/shared/src/acceptance.ts plus 17 tests.

    Scope of that PR is deliberately narrow — the acceptance.v1 shape only, so we can agree the data model before any renderer work. The space UI (document view ↔ structure view, participants + owner, task board, progress, acceptance gate, inbox split) follows in a separate PR.

    Implementation plan for the UI PR, following this issue:

    • Space = a folder under the workspace root. No new persisted entity; the spine is one Markdown file whose fixed H2 headings carry the structure.
    • Document view ↔ structure view. Two renderings of one file. Editing a row updates the document and vice versa; there is never a second copy.
    • Participants + owner. Owner comes from OrganizationFile.owner (the root position), shown visually; participants list which agents took part.
    • Reuse, don't rebuild. Goals / project management / progress reuse existing components. Note ProjectManagementModule.tsx is a 19-line alias shell over GoalsModule — "项目" and "目标" are already the same data.
    • Acceptance vs approval stay two lanes: approval reuses ApprovalRecord (workspace/org), acceptance uses acceptance.v1 (space).
    • Inbox is a cross-cutting view, not a container: "协作区待办" has a jump target, "组织待办" does not.

    Reference prototype and full spec live in work/space-prototype/ (v3.html, SPEC-v3-workspace-space.md).

  2. PeterGuy326 commented on Oct 1, 2026

    @PeterGuy326
    ContributorAuthor

    PR opened for the UI: #560 (feat(space): document-driven collaboration space UI with acceptance gate).

    Scope of that PR, on top of the acceptance.v1 contract in #559:

    • space-doc.v1 — the Markdown contract for a space. Two fixed H2s (## 验收标准, ## 任务) carry structure. The parser is total (any string yields a result, missing sections come back empty), and the renderer is surgical: it rewrites only those two sections and preserves every other byte, so prose it does not understand survives a round-trip. That preservation property is the whole reason for not inventing a DSL — a human and an agent can both edit the file with no tooling.
    • Document view / structure view — two renderings of the same file. There is no second data model.
    • Acceptance gate — the mechanical half of the 验收 vs 审批 split. 验收通过 stays disabled until every criterion is ticked; 打回 requires a note; accepting writes done back into the document, so the verdict lives where the agent reads it. An agent can move a task to review, but only a human acceptance writes done.
    • Participants / owner — owner pinned first, derived from the existing position names.
    • Task board — five columns, driven by the ## 任务 section.

    Shell wiring is intentionally minimal: space joins the activeModule union, one rail item, one render branch, plus the locale keys. rail.space is the only hard registration point and i18n.test.tsx covers it.

    Evidence: space-doc 17/17, space-acceptance-gate 11/11, space-module 5/5, i18n 7/7, tsc --noEmit clean for both packages/shared and the renderer.

    Kept open deliberately — the contract may still move during review, and I would rather land follow-ups against an open issue than reopen a closed one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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