Repository navigation
feat(space): make documents the authoring surface, split Workspace from Space (专项作战人机协作区) #558
Description
Preflight
- I searched existing issues and PRs; no duplicate. Closest prior art is feat(workspace): make user goals the spine of multi-agent collaboration #222 (goals as the collaboration spine) and feat(memory): Obsidian-style live Markdown notes in Memory & Collaboration #511 (obsidian-style live Markdown), both referenced below.
- I read
AGENTS.md/ contributing conventions for this repository. - I can describe the change without touching the engine contract.
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:
- 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.
- Projects and the inbox are not peers of the org. They should live inside a space:
Workspace > Space > Files. - 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.v1alpha1manifest. 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
ownercomes fromOrganizationFile.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. aread_onlyposition 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 andpassed: true.decision === "rejected"requires a non-emptynote.criteriaIndexvalues 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-closedvalidateAcceptanceRecord. - 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
-
validateAcceptanceRecordrejects anaccepteddecision that is missing a criterion or has onepassed: false. -
validateAcceptanceRecordrejects arejecteddecision with an empty note. -
validateAcceptanceRecordrejects duplicatecriteriaIndexvalues. -
validateAcceptanceRecordrejects unknown top-level keys and an unsupportedschemaVersion. -
packages/shared/src/index.tsre-exports the new module. -
pnpm testfor the shared package passes.
Activity
PeterGuy326 commentedon Oct 1, 2026
Contract landed in #559 (branch
feat/workspace-space-docs):packages/shared/src/acceptance.tsplus 17 tests.Scope of that PR is deliberately narrow — the
acceptance.v1shape 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:
OrganizationFile.owner(the root position), shown visually; participants list which agents took part.ProjectManagementModule.tsxis a 19-line alias shell overGoalsModule— "项目" and "目标" are already the same data.ApprovalRecord(workspace/org), acceptance usesacceptance.v1(space).Reference prototype and full spec live in
work/space-prototype/(v3.html,SPEC-v3-workspace-space.md).PeterGuy326 commentedon Oct 1, 2026
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.v1contract 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.doneback into the document, so the verdict lives where the agent reads it. An agent can move a task toreview, but only a human acceptance writesdone.## 任务section.Shell wiring is intentionally minimal:
spacejoins theactiveModuleunion, one rail item, one render branch, plus the locale keys.rail.spaceis the only hard registration point andi18n.test.tsxcovers it.Evidence:
space-doc17/17,space-acceptance-gate11/11,space-module5/5,i18n7/7,tsc --noEmitclean for bothpackages/sharedand 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.
fix(space): type-check server tests under noUncheckedIndexedAccess
fix(space): narrow verdict index access for noUncheckedIndexedAccess
fix(packaging): ship the new shared modules in the runtime allowlist
fix(packaging): ship acceptance.js in the runtime allowlist
fix(space): move the spine filename into the catalogs and register th…
fix(space): import shared values by subpath so the renderer can bundle
feat(space): acceptance.v1 contract for human acceptance verdicts (#559)