Skip to content

Workspace Access Contract

This page is the shared language for employee DevPod workspaces, Daytona agent sandboxes, and future workspace providers. Provider adapters may differ, but they must project the same principal, isolation, tool, credential, authorization, and evidence concepts.

  • DevPod owns employee developer workspaces. A human principal launches a project and repository workspace, authenticates through OIDC, and receives a per-employee CLIProxy meter.
  • Daytona owns agent execution sandboxes. An AgentSquad agent uses a stable nickname, assigned role, owned worktree, and Armory-selected tools.
  • Armory and ContextForge own MCP and skill loadout resolution. Workspace providers consume the resolved loadout; they do not synthesize tool contracts from prompt text.
  • SpiceDB is the first authorization graph for “may this principal receive this tool or secret binding?”
  • OpenBao, ESO, and SPIRE own secret and workload identity lifecycle. They issue or sync material only after authorization says the binding is allowed.
  • Nexus/OCI is the delivery path for encrypted internal packages. Workspace providers must not copy credential blobs into images or rebuild images for agent, model, or tool assignment changes.
C4Context
title Workspace access contract

Person(employee, "Employee", "Human launching a project workspace")
Person(agent, "AgentSquad agent", "Stable nickname, role, and owned worktree")

System_Boundary(fractalops, "FractalOps") {
  System(portal, "Portal", "Workspace launch and evidence surface")
  System(studio, "Studio", "Agent roster, lane state, handoff, and run evidence")
  System(workspaceContract, "Workspace Access Contract", "Provider-neutral identity, loadout, credential, authz, and evidence language")
  System(devpod, "DevPod Adapter", "Employee developer workspace provider")
  System(daytona, "Daytona Adapter", "Agent sandbox provider")
  System(armory, "Armory and ContextForge", "MCP and skill loadout resolution")
}

System_Ext(spicedb, "SpiceDB", "Authorization graph")
System_Ext(openbao, "OpenBao/ESO/SPIRE", "Secret and workload identity lifecycle")
System_Ext(cliproxy, "CLIProxy", "Model gateway, account routing, quota, and per-principal meter")
System_Ext(nexus, "Nexus/OCI", "Encrypted internal package distribution")
System_Ext(project, "Project Repositories", "Owned worktrees and source refs")

Rel(employee, portal, "Launches")
Rel(agent, studio, "Executes through")
Rel(portal, workspaceContract, "Projects launch request into")
Rel(studio, workspaceContract, "Projects agent assignment into")
Rel(workspaceContract, devpod, "Maps to employee workspace")
Rel(workspaceContract, daytona, "Maps to agent sandbox")
Rel(workspaceContract, armory, "Requests loadout")
Rel(workspaceContract, spicedb, "Checks permission")
Rel(workspaceContract, openbao, "Requests issued material")
Rel(workspaceContract, cliproxy, "Binds model endpoint and meter")
Rel(workspaceContract, nexus, "Fetches encrypted package")
Rel(devpod, project, "Mounts or clones repository")
Rel(daytona, project, "Owns worktree")

Use these names in code, API payloads, docs, PRs, and evidence. Avoid replacing them with provider-specific aliases unless the adapter is translating to a vendor API field.

NameMeaning
WorkspacePrincipalEmployee or agent identity that requests a workspace. Includes stable subject, display name, and principal kind.
WorkspaceProviderProvider adapter kind, currently devpod or daytona. It is not an agent role or repository name.
WorkspaceIsolationProject, repository, branch, namespace, worktree, and central-stack/project-scoped boundaries.
ToolLoadoutArmory-resolved MCP servers, skills, hooks, and client capabilities. It is data, not prose.
ToolCredentialBindingBinding between principal, tool, credential issuer, quota meter, and delivery channel.
AuthorizationGraphSpiceDB namespace, object, relation, and permission check required before a binding is issued.
SecretIssuerOpenBao, ESO, or SPIRE source that issues short-lived credential or workload identity material.
InternalPackageEnvelopeEncrypted Nexus/OCI package containing loadout and environment material plus manifest metadata.
WorkspaceEvidenceLaunch, readiness, usage, cleanup, and ownership facts used for proof and debugging.

Provider adapters keep their native request models, then project into this contract for policy, proof, and UI.

ProviderNative modelContract projection
DevPodDevPodWorkspaceIdentity, DevPodRepositoryBinding, provider.yaml, kubeconfigWorkspacePrincipal(kind=employee), WorkspaceProvider(devpod), employee namespace, repository binding, CLIProxy meter, MCP loadout
DaytonaDaytonaSandboxCreateIn, snapshot, organization, labelsWorkspacePrincipal(kind=agent), WorkspaceProvider(daytona), agent nickname, owned worktree, source refs, Armory loadout, tool telemetry
Future providerProvider-native create/observe modelSame contract before it can receive credentials or tools

The contract does not replace provider APIs. It prevents each provider from inventing its own naming, authorization, credential, and evidence dialect.

flowchart LR
  principal["WorkspacePrincipal"] --> isolation["WorkspaceIsolation"]
  isolation --> graph["AuthorizationGraph\nSpiceDB CheckPermission"]
  graph -->|allowed| issuer["SecretIssuer\nOpenBao / ESO / SPIRE"]
  graph -->|denied| reject["Reject binding"]
  issuer --> package["InternalPackageEnvelope\nNexus/OCI encrypted"]
  package --> loadout["ToolLoadout\nArmory / ContextForge"]
  loadout --> binding["ToolCredentialBinding\nCLIProxy + meter + MCP auth"]
  binding --> workspace["WorkspaceProvider\nDevPod or Daytona"]
  workspace --> evidence["WorkspaceEvidence"]

Rules:

  • Authorization and secret lifecycle are separate. SpiceDB decides eligibility; OpenBao/ESO/SPIRE issue or sync material.
  • MCP loadout comes from Armory/ContextForge. Workspace code must not parse final agent replies to discover tools or handoff state.
  • CLIProxy owns model availability, account routing, quota, and management state. Workspace images must not pin user/model policy.
  • Add, change, or remove an agent without rebuilding provider images. Change roster, assignment, loadout, and credential bindings through data and packages.
  • Central stacks such as Serena, Windmill, GlitchTip, DataHub, PlaywrightGrid, and SearXNGGrid are loaded through ToolLoadout. Project-scoped systems stay inside WorkspaceIsolation.

ToolLoadout Resolution (Armory / ContextForge)

Section titled “ToolLoadout Resolution (Armory / ContextForge)”

Daytona-side loadout resolution as it runs today. The contract references Armory identifiers; it must not duplicate the Armory schema — the loadout SSOT stays in Armory.

  • Gateway: ContextForge at mcp-context-forge.armory.svc.cluster.local:4444. Tool calls authenticate with a per-principal JWT (iss=mcpgateway, aud=mcpgateway-api, jti, user.email = principal UPN), so MCP usage is attributable per principal — this is the ToolCredentialBinding identity leg.
  • Server registry: the armory-contextforge-registrar CronJob (armory namespace) reconciles the managed gateway servers (playwright, searxnggrid, agent-memory-archive, datahub, glitchtip). Registration is reconciled data, never image content.
  • fractalops-hud is portal-served and in-process; it is intentionally not a gateway server. Loadout tooling must not report it as a missing registration.
  • Per-session resolution: StudioArmoryService.resolve_loadout(template_id, agent_id, session_id, entry, project_slug) produces the ToolLoadout value; tool_loadout_ref in envelopes and evidence points at that resolution.

Agent Process Binding (Daytona ToolCredentialBinding)

Section titled “Agent Process Binding (Daytona ToolCredentialBinding)”

The coding-agent runtime (claude/codex/antigravity) is a data binding, never an image property. Two layers, both metadata-only:

  • Run-level default: StudioRunIn.agent_process_provider/model/effort is the whole-squad default applied to every roster entry at create_run. Required because roster agent ids are generated nicknames that do not exist before the run; role-keyed bindings cannot match at create time.
  • Per-agent mix: assignments target:provider:model:effort:speed where target is a live-session nickname or role key. Run-level selectors (default/all/squad) are create-time only and are skipped by live-session assignment.
  • Canonical provider tokens: claude-code | codex | antigravity (normalized aliases accepted). Model values are CLIProxy-owned aliases (for example fractalops-claude-code, fractalops-codex, fractalops-antigravity).
  • Meter: DevPod binds a per-employee CLIProxy meter; the Daytona twin is run/session-scoped cost facts (meter_ref = run or session scope). Both are ToolCredentialBinding.meter_ref projections.

The package distributed through Nexus/OCI contains encrypted, auditable material.

Required metadata:

schema_version: workspace-access.v1
workspace_provider: devpod | daytona
principal_kind: employee | agent
principal_subject: <stable subject>
project_slug: <project>
repository_slug: <owner/repo>
workspace_scope: <stable scope>
authorization_decision_ref: <SpiceDB check evidence>
secret_issuer_refs:
- <OpenBao/ESO/SPIRE ref>
tool_loadout_ref: <Armory loadout id>
meter_ref: <CLIProxy meter or quota binding>
expires_at: <RFC3339 timestamp>

The envelope may carry encrypted config files, MCP client config, CLIProxy endpoint material, and bootstrap hints. It must not carry raw long-lived OAuth or upstream provider credentials.

Every workspace proof should record these facts when applicable:

  • workspace_provider

  • workspace_scope

  • principal_subject

  • project_slug

  • repository_slug

  • workspace_status

  • workspace_kubernetes_ready or provider-native ready state

  • tool_loadout_ref

  • meter_ref

  • authorization_decision_ref

  • pod_count / pvc_count for Kubernetes providers

  • cleanup_count / cleanup_error_count

  • agent_provider, launch_contract_status, first_tool_name, current_tool_name, and blocker_code for agent (Daytona) principals

Tool-telemetry trust levels per runtime: codex parses a JSON event stream and claude parses stream-json tool_use blocks (both structured); antigravity is parsed from rendered TUI lines by regex with no JSON output mode. Evidence consumers must treat a missing antigravity tool name as a possible parse gap, not as an idle agent.

This is evidence shape, not a permanent incident log. Proof runs should point to durable evidence stores or GitHub workflow run URLs.

  • DevPod changes stay in the DevPod consumer lane: launch material, controller, provider.yaml, kubeconfig, employee workspace proof, DevPod docs.
  • Daytona changes stay in the Daytona/AgentSquad lane: sandbox lifecycle, snapshots, repository upload, agent tool telemetry, first-tool proof.
  • Armory, ContextForge, CLIProxy, SpiceDB, OpenBao, ESO, SPIRE, and Nexus are shared platform surfaces. Announce intent before changing shared behavior.
  • Shared contract evolution starts in this Starlight page, then moves into code only when at least two providers consume the same term.
  • No provider-specific Sanmopia or project-only settings in this contract.
  • No build step for agent nickname, role, model, MCP, or credential assignment.
  • No final-answer parsing as a control plane. Use MCP tools, hooks, structured values, and observed provider state.
  • No secret storage in Portal, Studio metadata, GitHub issue bodies, or browser DOM.