Workspace Access Contract
Workspace Access Contract
Section titled “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.
Boundary
Section titled “Boundary”- 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.
C4 Context
Section titled “C4 Context”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")
Contract Vocabulary
Section titled “Contract Vocabulary”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.
| Name | Meaning |
|---|---|
WorkspacePrincipal | Employee or agent identity that requests a workspace. Includes stable subject, display name, and principal kind. |
WorkspaceProvider | Provider adapter kind, currently devpod or daytona. It is not an agent role or repository name. |
WorkspaceIsolation | Project, repository, branch, namespace, worktree, and central-stack/project-scoped boundaries. |
ToolLoadout | Armory-resolved MCP servers, skills, hooks, and client capabilities. It is data, not prose. |
ToolCredentialBinding | Binding between principal, tool, credential issuer, quota meter, and delivery channel. |
AuthorizationGraph | SpiceDB namespace, object, relation, and permission check required before a binding is issued. |
SecretIssuer | OpenBao, ESO, or SPIRE source that issues short-lived credential or workload identity material. |
InternalPackageEnvelope | Encrypted Nexus/OCI package containing loadout and environment material plus manifest metadata. |
WorkspaceEvidence | Launch, readiness, usage, cleanup, and ownership facts used for proof and debugging. |
Provider Projection
Section titled “Provider Projection”Provider adapters keep their native request models, then project into this contract for policy, proof, and UI.
| Provider | Native model | Contract projection |
|---|---|---|
| DevPod | DevPodWorkspaceIdentity, DevPodRepositoryBinding, provider.yaml, kubeconfig | WorkspacePrincipal(kind=employee), WorkspaceProvider(devpod), employee namespace, repository binding, CLIProxy meter, MCP loadout |
| Daytona | DaytonaSandboxCreateIn, snapshot, organization, labels | WorkspacePrincipal(kind=agent), WorkspaceProvider(daytona), agent nickname, owned worktree, source refs, Armory loadout, tool telemetry |
| Future provider | Provider-native create/observe model | Same 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.
Authorization And Secret Flow
Section titled “Authorization And Secret Flow”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 insideWorkspaceIsolation.
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 theToolCredentialBindingidentity leg. - Server registry: the
armory-contextforge-registrarCronJob (armory namespace) reconciles the managed gateway servers (playwright,searxnggrid,agent-memory-archive,datahub,glitchtip). Registration is reconciled data, never image content. fractalops-hudis 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 theToolLoadoutvalue;tool_loadout_refin 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/effortis the whole-squad default applied to every roster entry atcreate_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:speedwhere 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 examplefractalops-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 areToolCredentialBinding.meter_refprojections.
Internal Package Envelope
Section titled “Internal Package Envelope”The package distributed through Nexus/OCI contains encrypted, auditable material.
Required metadata:
schema_version: workspace-access.v1workspace_provider: devpod | daytonaprincipal_kind: employee | agentprincipal_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.
Evidence Contract
Section titled “Evidence Contract”Every workspace proof should record these facts when applicable:
-
workspace_provider -
workspace_scope -
principal_subject -
project_slug -
repository_slug -
workspace_status -
workspace_kubernetes_readyor provider-native ready state -
tool_loadout_ref -
meter_ref -
authorization_decision_ref -
pod_count/pvc_countfor Kubernetes providers -
cleanup_count/cleanup_error_count -
agent_provider,launch_contract_status,first_tool_name,current_tool_name, andblocker_codefor 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.
Ownership
Section titled “Ownership”- 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.
Non-Goals
Section titled “Non-Goals”- 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.