Skip to main content

Overview

The Workspace interface defines how each agent session gets an isolated copy of the codebase. Workspace plugins manage repository isolation, branch setup, and post-creation hooks. Plugin Slot: workspace
Default Plugin: worktree

Interface Definition

export interface Workspace {
  readonly name: string;
  
  create(config: WorkspaceCreateConfig): Promise<WorkspaceInfo>;
  destroy(workspacePath: string): Promise<void>;
  list(projectId: string): Promise<WorkspaceInfo[]>;
  postCreate?(info: WorkspaceInfo, project: ProjectConfig): Promise<void>;
  exists?(workspacePath: string): Promise<boolean>;
  restore?(config: WorkspaceCreateConfig, workspacePath: string): Promise<WorkspaceInfo>;
}

Methods

name
string
required
Plugin name identifier (e.g. "worktree", "clone").
create
(config: WorkspaceCreateConfig) => Promise<WorkspaceInfo>
required
Create an isolated workspace for a session.Parameters:
  • config.projectId - Project identifier
  • config.project - Full project configuration
  • config.sessionId - Session identifier
  • config.branch - Git branch name
Returns: WorkspaceInfo with path and metadata
destroy
(workspacePath: string) => Promise<void>
required
Destroy a workspace and clean up resources.Parameters:
  • workspacePath - Absolute path to workspace directory
list
(projectId: string) => Promise<WorkspaceInfo[]>
required
List existing workspaces for a project.Parameters:
  • projectId - Project identifier
Returns: Array of WorkspaceInfo
postCreate
(info: WorkspaceInfo, project: ProjectConfig) => Promise<void>
Optional: Run hooks after workspace creation (symlinks, installs, etc.).Parameters:
  • info - Created workspace info
  • project - Project configuration
exists
(workspacePath: string) => Promise<boolean>
Optional: Check if a workspace exists and is a valid git repo.Parameters:
  • workspacePath - Path to check
Returns: true if workspace exists and is valid
restore
(config: WorkspaceCreateConfig, workspacePath: string) => Promise<WorkspaceInfo>
Optional: Restore a workspace (e.g. recreate a worktree for an existing branch).Parameters:
  • config - Workspace creation config
  • workspacePath - Path where workspace should be restored
Returns: WorkspaceInfo for restored workspace

WorkspaceCreateConfig

export interface WorkspaceCreateConfig {
  projectId: string;
  project: ProjectConfig;
  sessionId: SessionId;
  branch: string;
}
projectId
string
required
Project identifier from orchestrator config
project
ProjectConfig
required
Full project configuration with repo, path, symlinks, postCreate commands
sessionId
SessionId
required
Session identifier
branch
string
required
Git branch name for this workspace

WorkspaceInfo

export interface WorkspaceInfo {
  path: string;
  branch: string;
  sessionId: SessionId;
  projectId: string;
}
path
string
required
Absolute path to workspace directory
branch
string
required
Git branch name
sessionId
SessionId
required
Session identifier
projectId
string
required
Project identifier

Usage Examples

Implementing a Workspace Plugin

import type { Workspace, WorkspaceCreateConfig, WorkspaceInfo } from "@composio/ao-core";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";

const execFileAsync = promisify(execFile);

export function create(): Workspace {
  return {
    name: "worktree",
    
    async create(config: WorkspaceCreateConfig): Promise<WorkspaceInfo> {
      const { project, sessionId, branch } = config;
      const workspacePath = join(project.path, "..", sessionId);
      
      // Create worktree
      await execFileAsync("git", [
        "worktree",
        "add",
        "-b", branch,
        workspacePath,
        project.defaultBranch
      ], {
        cwd: project.path,
        timeout: 30_000
      });
      
      return {
        path: workspacePath,
        branch,
        sessionId,
        projectId: config.projectId
      };
    },
    
    async destroy(workspacePath: string): Promise<void> {
      // Remove worktree
      await execFileAsync("git", [
        "worktree",
        "remove",
        "--force",
        workspacePath
      ], { timeout: 30_000 });
    },
    
    async list(projectId: string): Promise<WorkspaceInfo[]> {
      // Get project config to find repo path
      const project = config.projects[projectId];
      if (!project) return [];
      
      const { stdout } = await execFileAsync("git", [
        "worktree",
        "list",
        "--porcelain"
      ], {
        cwd: project.path,
        timeout: 10_000
      });
      
      // Parse worktree list output
      const worktrees: WorkspaceInfo[] = [];
      const lines = stdout.trim().split("\n");
      
      for (let i = 0; i < lines.length; i += 3) {
        const path = lines[i].replace("worktree ", "");
        const branch = lines[i + 2]?.replace("branch refs/heads/", "") || "";
        
        // Extract session ID from path
        const sessionId = path.split("/").pop() || "";
        
        worktrees.push({
          path,
          branch,
          sessionId,
          projectId
        });
      }
      
      return worktrees;
    },
    
    async postCreate(info: WorkspaceInfo, project: ProjectConfig): Promise<void> {
      // Create symlinks
      if (project.symlinks) {
        for (const link of project.symlinks) {
          const source = join(project.path, link);
          const target = join(info.path, link);
          await execFileAsync("ln", ["-sf", source, target], {
            timeout: 5_000
          });
        }
      }
      
      // Run postCreate commands
      if (project.postCreate) {
        for (const cmd of project.postCreate) {
          await execFileAsync("sh", ["-c", cmd], {
            cwd: info.path,
            timeout: 60_000
          });
        }
      }
    },
    
    async exists(workspacePath: string): Promise<boolean> {
      try {
        await execFileAsync("git", ["rev-parse", "--git-dir"], {
          cwd: workspacePath,
          timeout: 5_000
        });
        return true;
      } catch {
        return false;
      }
    },
    
    async restore(config: WorkspaceCreateConfig, workspacePath: string): Promise<WorkspaceInfo> {
      const { project, sessionId, branch } = config;
      
      // Check if worktree already exists
      if (await this.exists!(workspacePath)) {
        return {
          path: workspacePath,
          branch,
          sessionId,
          projectId: config.projectId
        };
      }
      
      // Recreate worktree from existing branch
      await execFileAsync("git", [
        "worktree",
        "add",
        workspacePath,
        branch
      ], {
        cwd: project.path,
        timeout: 30_000
      });
      
      return {
        path: workspacePath,
        branch,
        sessionId,
        projectId: config.projectId
      };
    }
  };
}

Using Workspace in Session Manager

import type { Workspace, WorkspaceCreateConfig } from "@composio/ao-core";

const workspace: Workspace = registry.get("workspace", "worktree");

const createConfig: WorkspaceCreateConfig = {
  projectId: "my-app",
  project: projectConfig,
  sessionId: "my-app-1",
  branch: "fix-auth-bug"
};

const info = await workspace.create(createConfig);
console.log(`Workspace created at ${info.path}`);

// Run post-creation hooks
if (workspace.postCreate) {
  await workspace.postCreate(info, projectConfig);
}

// Later: destroy workspace
await workspace.destroy(info.path);

Implementation Notes

Workspace Isolation

The workspace plugin must ensure complete isolation:
  • Each session has its own branch and working directory
  • No shared state between sessions (except .git in worktree mode)
  • Symlinks can share large files (node_modules, build artifacts)

Post-Creation Hooks

The postCreate() method should:
  1. Create symlinks specified in project config
  2. Run postCreate commands (npm install, copy configs, etc.)
  3. Handle errors gracefully (warn but don’t fail)

Restoration

The restore() method enables restoring sessions after:
  • Machine reboot (worktrees still exist but runtime is gone)
  • Manual cleanup (recreate worktree from existing branch)
  • Plugin crash recovery

Built-in Plugins

  • worktree - Git worktrees (default, lightweight)
  • clone - Full git clones (isolated but slower)
Future plugins could support Docker volumes, remote filesystems, cloud storage, etc.

See Also

  • Session - Session interface
  • Agent - Agent plugin interface
  • Runtime - Runtime execution environment interface

Build docs developers (and LLMs) love