agentproto

AIP-34: WORKSPACE.md — agentworkspace/v1 (workspace identity manifest)

A markdown + frontmatter format for declaring a workspace's identity — globally addressable id, owner, storage choice, defaults, publish posture. The root manifest of every AIP-organized workspace; pairs with STORAGE.md (AIP-35) for the storage policy block.

FieldValue
AIP34
TitleWORKSPACE.md — agentworkspace/v1 (workspace identity manifest)
StatusDraft
TypeSchema
Domainworkspace.sh
RequiresAIP-1, AIP-2, AIP-19 (SECRETS), AIP-35 (STORAGE), AIP-36 (SANDBOX)

Abstract

WORKSPACE.md is a markdown-with-frontmatter file format that packages a single workspace's identity — its globally addressable id, owner, name, description, storage choice, defaults, and publish posture. It is the root manifest of every AIP-organized workspace and the entry point hosts read first when materializing a workspace.

The format pairs with two sibling blocks that mirror Mastra's Workspace = filesystem + sandbox shape:

Both fields accept inline or ref form.

The file is human-authored, version-controlled, machine-parseable, and grep-able — same posture as SKILL.md, TOOL.md, WORKFLOW.md, CODE.md.

Motivation

Workspaces today exist implicitly: a row in some app's database points at a folder somewhere, and conventions emerge — .tools/, .workflows/, .skills/, .runs/. The folder is real, the conventions are real, but the workspace itself has no manifest. That implicit shape blocks four real workflows:

  1. No portable identity. "What is this workspace? Whose is it? Where do its bytes live?" answers require querying the host's database. There's no manifest a reviewer can read.

  2. Storage choice is hidden. Whether a workspace is on Supabase, S3, GitHub, or local disk is a host config detail. The user can't override it without app-side changes.

  3. No publishability. Workspaces can't be templates, exports, or registry citizens because they have no addressable id and no manifest to publish.

  4. Hosts can't enumerate workspaces uniformly. A multi-tenant host with N workspaces per owner has to special-case its app schema to list, switch, or migrate them. With a root manifest, find . -name "WORKSPACE.md" -maxdepth 2 enumerates the substrate.

WORKSPACE.md makes the workspace a first-class declared entity.

Design principles

  1. One file at the root. A workspace is a folder containing exactly one WORKSPACE.md at its root. No subfolder counts.

  2. Identity is globally addressable. id follows the form @<owner-slug>/<workspace-slug>. Same posture as designkit / canvakit / agentproto manifests. The address is resolvable across hosts that share an owner namespace.

  3. Storage policy is its own block. WORKSPACE.md does NOT redefine storage configuration. It either inlines a STORAGE.md block (AIP-35) for one-off cases, or refs a STORAGE.md file / registry slug for reusable policies.

  4. Optional for backward compatibility. A workspace without a WORKSPACE.md is still a workspace — hosts fall back to defaults inferred from their database row. WORKSPACE.md is the upgrade path to declared identity, not a hard gate.

  5. Manifest wins on conflict. When both a WORKSPACE.md and a host-side row exist, the manifest body is the source of truth. The row caches hot fields (id, current provider) for routing efficiency only.

  6. Owner is descriptive, not load-bearing for FKs. The owner.{type, id, slug} block describes who owns this workspace for human and registry consumption. The DB shape on any host that persists workspaces uses typed FK tables per owner kind (e.g. guild_workspaces, user_workspaces, org_workspaces) — not polymorphic owner_type columns.

  7. Publish posture is opt-in. A workspace declares publish.template: true if it intends to be cloneable. The declaration is the input to a downstream registry; this AIP does not specify the registry itself.

Specification

File location

<workspace-root>/
  WORKSPACE.md          ← this AIP, exactly one
  .tools/<id>/TOOL.md   ← AIP-14
  .drivers/<id>/DRIVER.md ← AIP-30
  .workflows/<id>/WORKFLOW.md ← AIP-15
  .code/<id>/CODE.md    ← AIP-26
  .skills/<id>/SKILL.md ← AIP-3
  storage/<slug>.STORAGE.md  ← AIP-35 (if separated)
  ... other AIP folders ...

Frontmatter

YAML frontmatter, delimited by --- lines. All fields are case-sensitive.

Required fields

FieldTypeDescription
schemastringAlways workspace/v1.
idstringGlobally addressable id: @<owner-slug>/<workspace-slug>. Lowercase, digits, dashes.
versionsemver stringSpec version of THIS file. Bump on breaking shape change.
namestringHuman-readable display name. 1–80 chars.
ownerobject{ type: "guild" | "user" | "org", id: string, slug: string }. The slug participates in the addressable id above.
storageobjectFilesystem policy: either { inline: <STORAGE.md frontmatter body> } or { ref: "<path or registry slug>" }. See AIP-35.

Optional fields

FieldTypeDefaultDescription
descriptionstring""One-paragraph purpose. ≤2000 chars.
sandboxobject(none)Sandbox policy: either { inline: <SANDBOX.md frontmatter body> } or { ref: "<path or registry slug>" } or { file: "<path>" }. See AIP-36. Optional — when absent, command-execution tools MUST be unavailable.
codeobject(none)Code policy: either { inline: <CODE.md frontmatter body> } or { ref: "<path or registry slug>" } or { file: "<path>" }. See AIP-26. Optional — when present, the workspace IS a code-workspace / repo (see "Workspace as repo" below).
identityobject | array(none)AIP-23 identity-ref block — default identity for all sub-blocks of this workspace. Sub-blocks (storage, sandbox, code) override. See AIP-23 identity-ref.
policyobject | array(none)AIP-38 POLICY block — access grants, defaults, limits, requirements scoped to this workspace. Composable: inline / ref / file, OR an array that composes (most-restrictive wins). Grants on AIP-39 ACTION ids. See AIP-38 POLICY.md.
defaultsobject{}{ read_only: bool } and other workspace-wide defaults.
publishobject{ template: false, visibility: "private" }Publish posture: { template: bool, registry?: string, visibility: "private" | "unlisted" | "public" }.
tagsstring[][]Free-form discovery tags.
created_atstring (ISO 8601)noneCreation timestamp.
metadataobject{}Free-form. Authors MAY stash adapter-specific hints under namespaced keys.

Body

Markdown body following the frontmatter. Recommended sections:

  • ## Description — long-form purpose, what's in this workspace, who maintains it.
  • ## Layout — non-default folder conventions (e.g. "this workspace also uses .briefs/ for content drafts").
  • ## Maintainers — humans + agents responsible.

The body is informational. Hosts MUST function reading only the frontmatter.

The defineWorkspace standard signature

defineWorkspace(definition: WorkspaceDefinition): WorkspaceHandle

interface WorkspaceDefinition {
  schema:      "workspace/v1"
  id:          string                     // "@<owner>/<slug>"
  version:     string
  name:        string
  description?: string
  owner: {
    type: "guild" | "user" | "org"
    id:   string
    slug: string
  }
  storage:
    | { inline: StorageDefinition }       // AIP-35 (required)
    | { ref:    string }                  // path or registry slug
    | { file:   string }                  // workspace-relative path
  sandbox?:
    | { inline: SandboxDefinition }       // AIP-36 (optional)
    | { ref:    string }
    | { file:   string }
  code?:
    | { inline: CodeDefinition }          // AIP-26 (optional — workspace as repo)
    | { ref:    string }
    | { file:   string }
  identity?:                              // AIP-23 identity-ref
    | IdentityRef                          // single
    | IdentityRef[]                        // multi-attribution
  defaults?: { readOnly?: boolean }
  publish?: {
    template?:   boolean
    registry?:   string
    visibility?: "private" | "unlisted" | "public"
  }
  tags?:       string[]
  createdAt?:  string
  metadata?:   Record<string, unknown>
}

Conformance rules

  1. Exactly one WORKSPACE.md per workspace root. Multiple files at root is a spec violation; nested ones are ignored.

  2. id is immutable. Once set, never changes. Renaming the workspace mutates name, never id. Hosts that need to change id MUST treat the result as a new workspace.

  3. owner.slug and the slug suffix of id MUST match. A workspace at @acme-corp/marketing-ops MUST have owner.slug = "acme-corp". Validators MUST reject mismatches.

  4. storage.inline and storage.ref are mutually exclusive. Exactly one form per WORKSPACE.md.

  5. No I/O at parse time. Reading WORKSPACE.md MUST NOT trigger credential resolution, network calls, or storage instantiation. Hosts materialize storage lazily.

Stable identity

id + version together form the workspace's stable identity. A breaking change to the manifest shape (a storage provider swap, an owner reassignment, a schema upgrade that drops fields) SHOULD bump version.

Address resolution

Hosts that support cross-host addressable workspaces MUST implement an address resolver that takes @<owner-slug>/<slug> and returns either a workspace handle or not-found:

resolveWorkspaceAddress(addr: string): Promise<WorkspaceHandle | null>

Single-app hosts resolve trivially: look up <owner-slug> in the appropriate owner table (guilds / users / orgs), then JOIN the matching workspace table by <slug>. Multi-app hosts iterate owner kinds.

The slug lives on the workspace row; the owner_slug lives on the owner row. No denormalization is required.

Workspace as repo (codespace pattern)

A workspace is just a folder of files plus a manifest. When that folder happens to be a git repo (storage.provider = github) AND has a CODE.md attached, the workspace plays the role of a codespace — a runnable code project the agent (or the user) can iterate on.

There is no separate "CODESPACE.md" primitive. The pattern is:

# WORKSPACE.md at the root of acme/marketing-bot repo
schema: workspace/v1
id: "@acme/marketing-bot"
version: 1.0.0
name: "Marketing Bot"
owner: { type: org, id: "01HQ8VR...", slug: acme }

storage:
  inline:
    provider: github
    config: { owner: acme, repo: marketing-bot, branch: main }
    sync:
      pull:   { on: turn-start, ttl_seconds: 30 }
      commit: { on: per-turn, message_template: "{{operator}}: {{summary}}" }
      push:   { on: per-conversation, branch_policy: per-conversation, pr_policy: auto }

sandbox:
  inline:
    provider: mastra-e2b
    config: { template: "code-interpreter" }
    mounts:
      - source: workspace
        at: /workspace
        mode: read-write

code:
  file: "./CODE.md"     # the workspace IS this code module

identity:
  - { ref: "operator://current" }
  - { ref: "user://current", role: "co-author" }

Mounting workspaces of this kind into other workspaces (via the host's mount/composition mechanism — typically the host's analog of Mastra's Workspace.mounts) gives the "codespace mounted at /marketing-bot/" experience: the parent workspace's agent can read/write the marketing-bot repo's files at a path prefix, with all the sync semantics declared in this WORKSPACE.md flowing through.

The "codespace" terminology is descriptive, not normative. Implementations MAY surface UI affordances ("This workspace is a code project") when code is present, but this AIP does not mandate any specific behaviour beyond "the workspace declares a CODE.md".

Backward compatibility

A workspace without a WORKSPACE.md is still a valid workspace. Hosts MUST fall back to defaults derived from their persistence layer. Adding a WORKSPACE.md is additive — it never changes the workspace's address (which the host derives from its row).

To migrate a host into the AIP-34 model:

  1. On workspace creation, write a default WORKSPACE.md derived from the host's row.
  2. On read, prefer the manifest body over the row fields it covers.
  3. On row mutation, re-emit the manifest to keep them in sync.

Example

---
schema: workspace/v1
id: "@acme-corp/marketing-ops"
version: 1.0.0
name: Acme Marketing Ops
description: |
  Marketing automation workspace for Acme Corp — content briefs,
  publishing routines, brand-guidelines kit.
owner:
  type: org
  id: 01HQ8VRZX5WNNGCV6YD2N7B6RX
  slug: acme-corp
storage:
  ref: "@acme-corp/shared-s3-policy"
defaults:
  read_only: false
publish:
  template: false
  visibility: private
tags: [marketing, content, brand]
created_at: 2026-05-02T10:00:00Z
---

## Description

The marketing team's primary workspace. Brand kits live under
`.designs/`, content briefs under `.briefs/`, automation routines
under `.workflows/`.

## Maintainers

- Operator `bob` (marketing director)
- Operator `eve` (content strategist)
- Routine `weekly-report` (every Monday 09:00 UTC)

Security considerations

WORKSPACE.md is declarative: a malicious manifest can lie about owner or id. Hosts MUST treat the manifest as untrusted input and verify ownership against their persistence layer at read time. The manifest's owner.id MUST match the host row's owner FK; mismatches MUST be flagged.

storage.ref to a registry slug crosses a trust boundary. Hosts SHOULD validate that the resolving registry policy is acceptable under workspace-owner policy before instantiating the backing storage.

publish.visibility: "public" does NOT auto-publish. The declaration is input to a downstream registry; the host decides whether and when to honour it.

Open questions

  1. Mounting workspaces into other workspaces. A future extension may allow a workspace to declare mounts: [{ at: "/external/lib", source: "@vendor/lib" }]. Defer until concrete need.

  2. Workspace versioning across hosts. When a workspace declared at host A is published and forked at host B, what's the lineage record? Likely a separate LINEAGE.md AIP.

  3. Per-workspace policy bundles. Workspaces may want to declare default approval policies, default agent identity bindings, default compute budgets. Whether these live in WORKSPACE.md or sibling manifests is an open call.

See also

Resources

Supporting artifacts for AIP-34. Links open the file on GitHub — markdown and JSON render natively in GitHub's viewer. Browse the full resource tree →