A transient-by-default thinking surface for sketching and rearranging entities before structure hardens into the graph.
A workspace is the thinking surfaceSurfaceExperience DesignA place inside a screen, the features that occupy it, and the rule that decides who wins itView reference → where a product creator works through the graph: the canvas on which entities are sketched, connected, argued over, and rearranged before any of it is settled. Structure emerges on the workspace as a working draft, and only some of what happens there hardens into the product graph it produces.
Workspace in this sense is native to the Unified Product Graph and to Entopo, the tool built on it. The lineage is the canvas tradition: the infinite, spatial surfaces popularised by whiteboarding and diagramming tools, where ideas get room to spread out before they are filed. The Unified Product Graph takes that surface and makes it structured underneath, so the loose arrangement on the canvas is already typed graph data rather than free-floating shapes.
The reason to name the workspace as its own concept is the gap between thinking and committing. A product graph is a clean, queryable record of what a team has decided about its product. Thinking is messier: half-formed personasPersonaUserAn archetype representing a user segmentView reference →, edges drawn and undrawn, two competing strategies held side by side until one wins. The workspace is where that mess is allowed to live. It holds the in-progress state, the alternatives under consideration, and the spatial layout that helps a person reason, while the product graph holds what survived the reasoning. Separating the two keeps the graph trustworthy without making the thinking feel premature.
Skelton and Pais argue in Team Topologies that unmanaged cognitive load — the total weight of what a team must hold in mind to do its work — is the primary constraintConstraintStrategyA limit, requirement, or ceiling the product must respect, whether a self-imposed principle or an externally imposed boundaryView reference → on how effectively a team can move. The workspace addresses a version of that problem at the individual level: by giving a product creator a bounded surface to externalise competing ideas, partial structures, and discarded alternatives, it reduces the cognitive burden that would otherwise have to be carried mentally or scattered across notes and documents.
A founder opens a workspace to map a new featureFeatureProduct SpecificationA product capability or featureView reference →. She drops a persona, three jobsJobUserJob To Be Done: what the user is trying to accomplishView reference →, and a rough feature, and draws edges between them by hand. Two of the jobs turn out to be the same job seen twice, so she merges them on the canvas. She lays out two pricing options spatially, left and right, and sits with them for a week before deleting the one she rejects. Throughout, she is moving real entities around a surface, not doodling. When the shape settles, the decisionDecisionStrategyA recorded decision with context, rationale, and consequencesView reference → she reached gets recorded through the workspace, and the surviving personas and feature become part of the product graph. The discarded pricing option never enters the record, but the workspace remembers that the choice was made.
Kersten's Project to Product makes a related distinction at the organisational level: work organised as temporary projects — bounded, output-focused, disbanded on delivery — tends to destroy the continuity of understanding that a product requires. By that reading, the workspace occupies a deliberate middle position: it is allowed to be temporary and messy in the way a project is, but it feeds a persistent product graph rather than a final deliverable, so the continuity is preserved in the graph even when the workspace is discarded.
In the Unified Product Graph, the workspace is the surface a ProductStrategyThe product being created, the root of the graphView reference → is thought through, captured by productProductthinks inWorkspacehierarchy. It arranges the entities placed on it through workspace_arranges_node, and what it produces is captured by product_thinks_in_workspaceWorkspaceproducedany entitycausal, widened from the decision-only edge it began as, because a workspace produces personas and features as readily as it produces decisions. Modelling the workspace as a node, distinct from the product graph it generates, draws the line between the place structure emerges and the structure that survives. That separation is what lets the graph stay a clean source of truth while the workspace stays a forgiving place to think: neighbours such as workspace_produced_nodeview read from the committed graph, while the workspace feeds it.
A workspace and a compositionCompositionWorkspaceA published arrangement of graph entities that has an identity of its own: a gallery, a board, a deck. Membership can be derived by running its query, position is authored by a person, and both are stored because both are real.View reference → are two halves of one pair, and at 0.33.0 they were given the same two fields. member_query makes membership derived rather than placed: members arrive by running the query instead of by someone dragging them onto the canvas. presentation carries advisory rendering intent, which a consumer may ignore entirely and stay conformant. Both had been declared only on the published half, and that was the wrong place for them. A canvas is query-driven while it is being thought through, not at the instant somebody publishes it, so declaring the query only on the composition made it a fact invented at publish time rather than one recorded. A canvas with no member query is authored, which is what every workspace written before this is.
Both fields tightened at 0.34.0, and a canvas feels the change before a published view does, because a canvas is where a selection is still being argued with. presentation gains orphan_disposition, either root or hide, which finally lets an author say what should happen to a member the query admitted but the nest_by relation cannot reach. Absent means root, and that default is chosen so that ignoring the field is safe: a consumer knowing nothing about it draws every admitted member somewhere rather than dropping the ones it cannot place. The measurement behind the choice is blunt. On an imported tracker graph of 1,118 nodes and 3,685 edges, one tree admits 218 members and renders a single card under hide, because tracker-imported work hangs off cycle and project relations, which are reference-axis edges rather than nesting. A default that hid them would turn a full canvas into an empty one with nothing to say why.
The other change moves no bytes. A clause inside member_query is now a discriminated union on dimension, so a clause on the type axis carries entity types and a clause on any other axis carries strings. The JSON is identical, which means no workspace file written before this needsNeedUserA user need, pain, desire, or constraintView reference → migrating; what narrows is what a TypeScript author is allowed to write. The round-trip rule is untouched and still normative, because a union constrains authors and not the JSON arriving from a file. The clause list is authoritative, the named fields are its positive-only readable projection, and a tool that rewrites clauses into shorthand and back must refuse rather than silently drop a negation it has nowhere to put.
Type-specific fields on BaseNode
member_queryobjectWhen present, membership is DERIVED: members are produced by running this query rather than authored by placement. The clause list is authoritative and the named fields are a positive-only projection of it; since 0.34.0 a clause is a discriminated union on `dimension`, so the `type` axis carries entity types rather than free strings.
This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded.
presentationobjectAdvisory rendering intent for the layer as a whole: `group_by`, `sort`, `layout`, `nest_by`, and `orphan_disposition` (0.34.0, absent means `'root'`). A consumer may ignore it entirely and still be conformant, because every default it then applies is the safe one.
THE DESCRIPTION LISTS THE FIELDS ON PURPOSE. This property is `object` in the runtime property registry, so an agent reading `get_entity_schema` gets an opaque blob and this sentence. For an object-typed property the description IS the declared shape, which is why `check:editorial` hashes it (E.4, 0.34.0) and why a field added to `UPGViewPresentation` without a word here would be invisible to every gate and every agent at once.
visibilityenumWho can see this workspace
purposestringFree-text description. Pairs with the closed-enum `workspace_purpose`.
workspace_purposeenumWhat the workspace is for. Drives template suggestions and surfaces in workspace browsers.
`discovery` is persona, job and opportunity exploration. `planning` covers roadmap and decision sessions. `retrospective` is reflection on shipped work. `design` is experience or UI exploration. `research` organises study data and synthesis. `strategy` is high-level direction setting. `general` is the catch-all.
ownerstringWorkspace owner (handle or email). Display label; canonical owner is `team_owns_workspace` or `persona_owns_workspace`.
member_countnumberSnapshot count. `team_works_in_workspace` edges are the source of truth.
iconstringDisplay icon (emoji or icon name)
retentionenumRetention intent. Absent means `transient`.
A workspace is a free-form thinking space and is transient by default, so the spec makes both intents EXPRESSIBLE and takes no position on which reaches the file. Whether to write a transient canvas at all is a tool decision, and the recommended posture is not to: a scratch canvas nobody named has no business in a shared, git-tracked graph, where it lands in everyone's diff. Writing only `durable` workspaces is what keeps a canonical graph entity comfortable with a transient-by-default object.
canvasobjectOpaque canvas furniture: the parts of a canvas with no graph referent. Preserved verbatim on round-trip and never interpreted. Tool extension keys are namespaced `<tool>:<key>` with a colon (the rule since 0.31.0), and no consumer interprets a key it does not own.
The cut here is UPG principle P14 applied literally. Anything that is a REFERENCE TO A GRAPH NODE is an edge, which is why placed entities ride `workspace_arranges_node` and not this bag: a node id held as a scalar inside a blob is a foreign key in disguise, and a deleted node would leave a stale reference nothing can detect. Anything that is pure UI chrome with no graph referent stays here, because minting `annotation` and `frame` entity types would add catalog surface that is meaningless to every consumer outside the tool that drew it. WHY THE COLON, and why the rule is in the summary rather than buried here. An underscore key is indistinguishable from an ordinary property name, so a migration that targets namespaced keys cannot match it and no validator can detect one that should have been namespaced. That undetectability is why enforcement lives in the type and the documentation instead of in a check: a check would report clean on a bag full of underscore keys, which is worse than no check. Two conventions were already coexisting when this was ruled (`entopo_views` in a live writer, `entopo:view_blocks` in the contract test certifying preservation), which is how a cheap rule becomes a migration. PRESERVATION IS NOT PERMISSION TO RENDER. Preserving every byte says nothing about meaning: a field-measured canvas carried `excluded: true` tombstones, and a consumer that preserved them faithfully while rendering every entry showed the user images they had deleted.
idstringrequiredUnique identifier (UUID)
typeNodeTyperequiredDiscriminator for the entity type
titlestringrequiredDisplay name
descriptionstringOptional detailed description
statusstringLifecycle status
tagsstring[]Freeform tags for filtering
5 phases, initial: planning · template: OPERATIONAL
3 edge types connected to this entity.
product_thinks_in_workspaceorganization_thinks_in_workspaceproduct_area_thinks_in_workspace