| Field | Value |
|---|---|
| RFC | 0059 |
| Title | A host.workspace capability — a versioned, atomic, tenant·workspace-scoped file store for an agent's persistent _ground-truth_ artifacts (identity / directives / memory-index), loaded as a read snapshot at run start, complementing the transactional MemoryAdapter (RFC 0004) with a durable file layer |
| Status | Accepted |
| Author(s) | David Tufts (@davidscotttufts) |
| Created | 2026-05-25 |
| Updated | 2026-05-25 (Active → Accepted — Milestone 2: the in-memory reference host implements capabilities.workspace end-to-end (§C CRUD + If-Match, §D run-start snapshot, §E WCT-1 isolation + WSR-1 SR-1 redaction); the workspace-cross-tenant-isolation invariant landed in SECURITY/invariants.yaml with its public conformance test, and the workspace-behavior + workspace-cross-tenant-isolation scenarios are live + green.) |
| Affects | schemas/capabilities.schema.json (host.workspace block) · new schemas/workspace-file.schema.json · api/openapi.yaml (workspace file endpoints) · api/asyncapi.yaml (workspace.updated event) · new spec/v1/agent-workspace.md · spec/v1/profiles.md (predicate) · RFCS/0048 (identity triple this scopes to) · new conformance scenarios · proposed SECURITY invariant workspace-cross-tenant-isolation (lands at implementation) |
| Compatibility | additive |
| Supersedes | — |
| Superseded by | — |
Summary
Autonomous agents in the wider industry (Claude Code, OpenClaw) keep persistent _ground-truth_ files — identity, standing directives, a memory index — that are loaded at the start of every session and treated as authoritative. openwop has no such surface: MemoryAdapter (RFC 0004) is a transactional entry store, not a named, versioned, editable file. This RFC adds an additive host.workspace capability: a tenant·workspace-scoped (RFC 0048) file store with atomic, optimistically-concurrent writes (If-Match ETag), a workspace.updated attribution event, and a read snapshot exposed to every run as ctx.workspace. It expands openwop's architecture by adding a _durable file layer_ alongside the _transactional memory layer_, without coupling the two.
Motivation
The proposed autonomous-agent feature set names SOUL.md / IDENTITY.md / AGENTS.md style "workspace files… mounted/loaded at session start; the agent uses them as ground truth." Today an openwop agent must smuggle this into either memory entries (wrong shape — entries are append-mostly, untyped, not addressable by path) or configurable (run-scoped, not persistent). Neither is versioned, neither is atomically editable by a human operator between runs, and neither is discoverable by sub-agents (RFC 0007 dispatch) the way a shared ground-truth file must be.
The spec is the right place because "what file does the agent treat as authoritative, and what version of it did this run see" is a cross-host portability and replay-determinism guarantee — a run replayed on another host must observe the same workspace snapshot — not an implementation detail.
Proposal
§A — capabilities.schema.json: workspace block (additive)
Per the corpus convention every host capability is a flat capabilities.<name> sibling (feedback, deadLetter, oauth, …) — there is no nested capabilities.host object — so the block is capabilities.workspace (the prose name "host.workspace" is the conceptual label, mirroring "host.feedback"/etc.):
"properties": {
+ "workspace": {
+ "type": "object",
+ "description": "RFC 0059. Versioned, tenant·workspace-scoped ground-truth file store. Scopes to the RFC 0048 owner triple.",
+ "required": ["supported"],
+ "additionalProperties": false,
+ "properties": {
+ "supported": { "type": "boolean" },
+ "versioned": { "type": "boolean", "description": "Each write bumps a monotonic version; prior versions are retrievable." },
+ "maxFileBytes": { "type": "integer", "minimum": 1, "description": "Per-file byte ceiling; writes beyond it return `workspace_too_large`." },
+ "maxFiles": { "type": "integer", "minimum": 1, "description": "Per-workspace file-count ceiling." },
+ "maxVersions": { "type": "integer", "minimum": 1, "description": "When `versioned`, retained historical versions (latest always retained)." }
+ }
+ }
}
§B — workspace-file.schema.json (new)
{
"$id": "https://openwop.dev/spec/v1/workspace-file.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["path", "content", "version", "updatedAt"],
"properties": {
"path": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$",
"description": "Workspace-relative path. No `..`, no leading `/`." },
"content": { "type": "string", "description": "UTF-8 file body." },
"contentType": { "type": "string", "default": "text/markdown" },
"version": { "type": "integer", "minimum": 1 },
"etag": { "type": "string", "description": "Opaque version token for If-Match concurrency." },
"updatedAt": { "type": "string", "format": "date-time" }
}
}
§C — endpoints (openapi.yaml, gated on host.workspace.supported)
| Method · path | Behavior |
|---|---|
GET /v1/host/workspace/files | List file metadata (no bodies) for the caller's {tenant, workspace}. |
GET /v1/host/workspace/files/{path} | Return one WorkspaceFile. 404 not_found if absent. |
PUT /v1/host/workspace/files/{path} | Atomic create/replace. MUST honor If-Match: <etag>; on mismatch return 409 workspace_conflict. On success bump version, emit workspace.updated. |
DELETE /v1/host/workspace/files/{path} | Remove the file (and, if versioned, tombstone). |
A write MUST be atomic — a concurrent reader observes either the prior or the new content, never a partial. When versioned: true, GET …/files/{path}?version=N MUST return the historical snapshot.
§D — run-time exposure (normative)
When host.workspace.supported, the host MUST expose the workspace as a read snapshot taken at run.started under ctx.workspace.get(path) / ctx.workspace.list(). The snapshot is immutable for the run's duration (mirrors RFC 0004 memory's read-as-of-run-start rule) so replay is deterministic. Writes from within a run go through the same PUT contract and are visible to _subsequent_ runs / loop iterations, not the current snapshot. Sub-agents dispatched via RFC 0007 inherit the parent's {tenant, workspace} and therefore the same workspace — this is how ground truth is shared.
§E — invariants
- WCT-1 (cross-tenant isolation). A workspace file MUST be scoped to a single
{tenant, workspace}. NoGET/listagainst a workspace owned by{T, W}MAY return a file owned by{T′, W′} ≠ {T, W}, regardless of the calling principal's permissions elsewhere. Mirrors CTI-1 (agent-memory.md). Proposed protocol-tier SECURITY invariantworkspace-cross-tenant-isolation, landing with its conformance test at implementation. - WSR-1 (secret redaction). A workspace write whose content contains a value the run's BYOK vault resolved MUST persist
[REDACTED:<secretId>]in place of the plaintext. Reuses the SR-1 mechanism (agent-memory.md).
Positive example. PUT /v1/host/workspace/files/DIRECTIVES.md with If-Match: "v3" when current is v3 → 200, version → 4, workspace.updated { path: 'DIRECTIVES.md', version: 4 }. Negative example. Same write with If-Match: "v3" when current is v5 → 409 workspace_conflict { details: { currentVersion: 5 } }.
Compatibility
Additive. New optional capability, new endpoints gated on advertisement, new schema, new event consumers MAY ignore. Hosts without host.workspace.supported are unaffected; a workflow that calls ctx.workspace.* declares host.workspace: 'supported' as a peer requirement and refuses to register on a host that lacks it (same pattern as RFC 0052). No existing surface changes.
Conformance
workspace-capability-shape.test.ts— block validates;maxFileBytes/maxFilespositive. (Always runs.)workspace-crud-roundtrip.test.ts— PUT→GET→list→DELETE round-trips;workspace.updatedemitted on write. (Gated onhost.workspace.supported.)workspace-etag-conflict.test.ts— staleIf-Match→409 workspace_conflict. (Gated.)workspace-cross-tenant-isolation.test.ts— a principal on{T′}cannot read{T}'s file (fail-closed). (Gated; backs WCT-1.)workspace-run-snapshot.test.ts— a write mid-run is invisible to the current run'sctx.workspacesnapshot but visible to the next run. (Gated.)
Alternatives considered
1. Model ground-truth files as memory entries (RFC 0004). Rejected — memory entries are unaddressable by path, untyped, and have no atomic-replace/ETag concurrency; operators need to _edit_ DIRECTIVES.md between runs the way they edit a file. 2. Put files in node packs (RFC 0003). Rejected — packs are immutable, signed, registry-distributed artifacts; workspace files are mutable per-tenant runtime state. 3. Standardize specific filenames (SOUL.md, IDENTITY.md). Rejected for v1 — the _store_ is the protocol surface; which files an agent keeps is application convention. Standardizing names couples the wire to one product's ontology.
Unresolved questions
1. Directory semantics. Is path a flat namespace with /-in-names, or true nested directories with list prefix filters? Proposed flat-with-slashes for v1. Resolve before Active. 2. Version retention. When versioned: true, how many historical versions must a host retain? Proposed: advertise maxVersions, default "latest only is mandatory, history best-effort." Decide before Active. 3. Workspace ↔ memory-index coupling. RFC 0062's distillation writes a memory-index manifest — should that live as a workspace file (MEMORY-INDEX.md) or a distinct surface? Proposed: a workspace file, to keep one durable layer. Confirm with 0062.
Phase-0 resolution (architect ruling, 2026-05-25)
Per docs/autonomous-agent-runtime-plan.md §8 — Unresolved questions resolved (wire shape pinned; Active-ready pending this RFC's schema + prose landing):
1. Directory semantics → flat namespace with /-in-names (matches the path pattern in workspace-file.schema.json); list takes an optional ?prefix=. Not nested directories. 2. Version retention → latest version is the MUST; versioned: true hosts retain ≥ an advertised maxVersions (history best-effort). 3. Memory-index coupling (with RFC 0062) → the index is a workspace file MEMORY-INDEX.json (machine-loaded, normative); an optional human-editable .md sibling MAY accompany it.
Status history
- 2026-05-25 — Draft → Active. The wire surface landed atomically per the repo's Active bar:
capabilities.workspaceblock +workspace-file.schema.json+workspace-file-create.schema.json, theworkspace.updatedRunEvent (run-event-payloads.schema.json+run-event.schema.jsonenum), the four/v1/host/workspace/files[/{path}]OpenAPI endpoints, the AsyncAPIworkspace.updatedmessage, thespec/v1/agent-workspace.mdprose doc (§C/§D/§E),workspace_conflict+workspace_too_largeerror codes, all three reference SDKs, and the always-onworkspace-capability-shape.test.tsconformance scenario.Active → Acceptedawaits reference-host _enforcement_ (CRUD/ETag/snapshot behavior + theworkspace-cross-tenant-isolationSECURITY invariant and its conformance test, which land at the implementation milestone with theWorkspaceAdapterwiring).
Implementation notes (non-normative)
apps/workflow-engine: today memory is tenant-scoped only (host/inMemorySurfaces.ts) andRunRecordcarries noowner.workspace(RFC 0048 optional, unpopulated). This RFC's reference wiring is the first surface to require workspace-scoped storage; sequence it after the app populates the RFC 0048 owner triple.- Storage: add a
WorkspaceAdapteralongsideMemoryAdapterinstorage-adapters.md(adapter authors notified via that doc).
Acceptance criteria
- [x]
spec/v1/agent-workspace.mdmerged with the endpoint + snapshot + invariant contract. - [x]
host.workspaceblock +workspace-file.schema.json+ OpenAPI endpoints +workspace.updated(AsyncAPI). - [x]
workspace_conflict+workspace_too_largeregistered inrest-endpoints.md. - [x] Conformance: shape always-on, plus the capability-gated behavior scenarios —
workspace-behavior.test.ts(CRUD/ETag/workspace_too_large/§D snapshot via the §C endpoints) andworkspace-cross-tenant-isolation.test.ts(WCT-1) — landed live + green at the implementation milestone (M2). - [x]
workspace-cross-tenant-isolationinvariant + public test landed inSECURITY/invariants.yaml(protocol-tier) at implementation, withworkspace-cross-tenant-isolation.test.tsas its public test. - [x] CHANGELOG entry under
[1.1.4 — unreleased]. - [x] Milestone 2 — reference-host enforcement (
Active → Accepted). The in-memory reference host (examples/hosts/in-memory) advertisescapabilities.workspace.supportedand implements §C (4 endpoints, versioned store,If-Match→409 workspace_conflict,maxFileBytes→workspace_too_large), §D (immutable run-start read snapshot on the run snapshot), and §E (WCT-1 owner-scoped isolation + WSR-1 SR-1 redaction on write). The cross-owner WCT-1 driver is the documentedPOST /v1/host/sample/workspace/opseam (host-sample-test-seams.md§9). WSR-1 reuses the host's existingscrubSecretShaped()(SR-1) — note this host has no BYOK vault, so redaction is structural; a BYOK-resolving host exercises the full plaintext path.
References
RFCS/0004-memory-layer.md— the transactional layer this complements.RFCS/0048-tenant-workspace-principal-identity-model.md— the owner triple this scopes to.RFCS/0057-memory-write-attribution-event.md— content-free attribution-event pattern reused byworkspace.updated.spec/v1/agent-memory.md— CTI-1 / SR-1 mechanisms reused as WCT-1 / WSR-1.