Skip to content

Workspace Configuration

Each workspace can optionally include a configuration file that customizes the environment, mount, and skills behavior for that specific workspace. The configuration is stored in a workspace.json file within the workspace's configuration directory (typically .kaiden in the sources directory).

Configuration File Location

By default, workspace configuration is stored at:

<sources-directory>/.kaiden/workspace.json

The configuration directory (containing workspace.json) can be customized using the --workspace-configuration flag when registering a workspace with init. The flag accepts a directory path, not the file path itself.

Configuration Structure

The workspace.json file uses a nested JSON structure:

{
  "environment": [
    {
      "name": "DEBUG",
      "value": "true"
    },
    {
      "name": "API_KEY",
      "secret": "github-token"
    }
  ],
  "mounts": [
    {"host": "$SOURCES/../main", "target": "$SOURCES/../main"},
    {"host": "$HOME/.ssh", "target": "$HOME/.ssh"},
    {"host": "/absolute/path/to/data", "target": "/workspace/data"}
  ],
  "skills": [
    "/absolute/path/to/commit-skill",
    "$HOME/review-skill"
  ],
  "mcp": {
    "commands": [
      {
        "name": "my-local-tool",
        "command": "python3",
        "args": ["/workspace/sources/scripts/mcp_server.py"],
        "env": {"DEBUG": "true"}
      }
    ],
    "servers": [
      {
        "name": "remote-api",
        "url": "https://api.example.com/mcp",
        "headers": {"Authorization": "Bearer token123"}
      }
    ]
  },
  "network": {
    "mode": "deny",
    "hosts": ["api.github.com"]
  },
  "secrets": ["my-github-token", "my-api-key"],
  "features": {
    "ghcr.io/devcontainers/features/go:1": {"version": "1.23"},
    "./tools/my-feature": {}
  }
}

Environment Variables

Define environment variables that will be set in the workspace runtime environment.

Structure:

{
  "environment": [
    {
      "name": "VAR_NAME",
      "value": "hardcoded-value"
    },
    {
      "name": "SECRET_VAR",
      "secret": "secret-reference"
    }
  ]
}

Fields: - name (required) - Environment variable name - Must be a valid Unix environment variable name - Must start with a letter or underscore - Can contain letters, digits, and underscores - value (optional) - Hardcoded value for the variable - Mutually exclusive with secret - Empty strings are allowed - secret (optional) - Reference to a runtime secret (e.g., a Podman secret) containing the value; the runtime injects it as an environment variable inside the workspace - Mutually exclusive with value - Cannot be empty - Use this when a local tool inside the workspace needs the credential via an environment variable - For credentials used in outbound network requests, use the secrets list field and kdn secret create instead — those are injected as HTTP headers by OneCLI

Validation Rules: - Variable name cannot be empty - Exactly one of value or secret must be defined - Variable names must follow Unix conventions (e.g., DEBUG, API_KEY, MY_VAR_123) - Invalid names include those starting with digits (1INVALID) or containing special characters (INVALID-NAME, INVALID@NAME)

Mount Paths

Configure additional directories to mount in the workspace runtime.

Structure:

{
  "mounts": [
    {"host": "$SOURCES/../main", "target": "$SOURCES/../main"},
    {"host": "$HOME/.gitconfig", "target": "$HOME/.gitconfig"},
    {"host": "/absolute/path/to/data", "target": "/workspace/data", "ro": true}
  ]
}

Fields: - host (required) - Path on the host filesystem to mount - target (required) - Path inside the container where the host path is mounted - ro (optional) - Mount as read-only (default: false)

Path Variables:

Both host and target support the following variables: - $SOURCES - Expands to the workspace sources directory on the host, or /workspace/sources in the container - $HOME - Expands to the user's home directory on the host, or /home/agent in the container

Paths can also be absolute (e.g., /absolute/path).

Validation Rules: - host and target cannot be empty - Each path must be absolute or start with $SOURCES or $HOME - $SOURCES-based container targets must not escape above /workspace - $HOME-based container targets must not escape above /home/agent

Skills

Configure skill directories to make available to the agent inside the workspace.

Each entry is a path to a directory on the host that contains a single skill — a SKILL.md file and any related files. The directory is mounted read-only inside the agent's skills directory using the directory's basename as the skill name, allowing the agent to discover and use it.

Structure:

{
  "skills": [
    "/absolute/path/to/commit-skill",
    "$HOME/review-skill"
  ]
}

Fields: - Each entry is a path to a host directory containing a single skill (SKILL.md and related files)

Path Variables:

Skills paths support the following variables: - $HOME - Expands to the user's home directory on the host

Paths can also be absolute (e.g., /absolute/path/to/commit-skill).

Mount targets per agent:

Each skill directory is mounted read-only under the agent's skills directory inside the container. The subdirectory name matches the basename of the host path:

Agent Mount target
Claude Code ~/.claude/skills/<basename>/
Goose ~/.agents/skills/<basename>/
Cursor ~/.cursor/skills/<basename>/
OpenCode ~/.opencode/skills/<basename>/
OpenClaw ~/.openclaw/skills/<basename>/

For example, a skills path of /home/user/commit-skill is mounted at ~/.claude/skills/commit-skill/ for Claude Code, making the skill discoverable by the agent.

Validation Rules: - Each path cannot be empty - Each path must be an absolute path or start with $HOME - $SOURCES-based paths are not supported for skills

MCP Servers

Configure MCP (Model Context Protocol) servers to give the agent access to external tools and data sources. Two types are supported:

  • Commands — local MCP servers launched by the agent inside the workspace using stdio transport
  • Servers — remote MCP servers accessed over SSE (Server-Sent Events)

Structure:

{
  "mcp": {
    "commands": [
      {
        "name": "my-tool",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace/sources"],
        "env": {"NODE_ENV": "production"}
      }
    ],
    "servers": [
      {
        "name": "remote-api",
        "url": "https://api.example.com/mcp",
        "headers": {"Authorization": "Bearer token123"}
      }
    ]
  }
}

Command fields (commands[*]): - name (required) - Unique name for this MCP server - command (required) - Executable to run (e.g., npx, python3, node) - args (optional) - Arguments to pass to the command - env (optional) - Environment variables to set for the process

Server fields (servers[*]): - name (required) - Unique name for this MCP server - url (required) - SSE endpoint URL of the remote MCP server - headers (optional) - HTTP headers to include in requests (e.g., Authorization)

Agent support:

MCP server configuration is applied to agents that support it at workspace registration time. For Claude Code, both command-based and URL-based MCP servers are written to ~/.claude.json under the top-level mcpServers key (user scope), so they are available across all projects inside the workspace.

Validation Rules: - name cannot be empty and must be unique across both commands and servers combined — a command and a server cannot share the same name, since all entries map to the same flat mcpServers key in the agent settings - command cannot be empty for command entries - url cannot be empty for server entries

Network Access

Control network access for the workspace. By default, network access is denied (deny mode). You can allow all network access or restrict it to specific hosts.

Structure:

{
  "network": {
    "mode": "deny",
    "hosts": ["example.com", "api.github.com"]
  }
}

Fields: - mode (optional) - Network access mode - "allow" - Permits all network access (no restrictions) - "deny" - Blocks all outbound network access from the workspace agent, except for the hosts listed in hosts and the hosts associated with configured secrets - hosts (optional) - List of hostnames to allow when in deny mode - Only meaningful when mode is "deny" - Each entry must be a non-empty string - Omitting hosts (or leaving it empty) is valid: the workspace is fully isolated, with no outbound access permitted unless secrets contribute hosts

Automatic host injection: When mode is "deny", kdn automatically adds the required hosts to the allowed list from two sources — no explicit hosts entry is needed for either:

  • Secrets: The hosts associated with each configured secret are added automatically. For example, a github secret automatically allows api.github.com.
  • Credentials: The hosts required by each intercepted credential mount are added automatically. For example, mounting $HOME/.config/gcloud automatically allows oauth2.googleapis.com and aiplatform.googleapis.com.

Validation Rules: - If mode is set, it must be either "allow" or "deny" - If mode is "allow", hosts must not be set (they are meaningless in allow mode) - Host entries cannot be empty strings

Secrets

Configure secrets to inject into the workspace. Each entry is the name of a secret previously created with kdn secret create. At workspace creation time, kdn looks up the secret value from the system keychain and provisions it into the workspace via OneCLI, which injects it as an HTTP header into matching outbound requests. This is distinct from the secret field in environment variables, which references runtime secrets by name for environment variable injection.

When network.mode is "deny", the hosts associated with each secret are automatically added to the allowed list — you do not need to duplicate them under network.hosts.

Structure:

{
  "secrets": ["my-github-token", "my-api-key"]
}

Fields: - Each entry is a secret name (string) referencing a secret stored with kdn secret create

Validation Rules: - Secret names cannot be empty - Duplicate names within the list are rejected

Dev Container Features

Install Dev Container Features into the workspace image at build time. Features are reusable environment components that add languages, runtimes, and tools to your workspace.

Structure:

{
  "features": {
    "<feature-id>": {},
    "<feature-id>": {"<option>": "<value>"}
  }
}

Each key is a feature ID — either an OCI reference (ghcr.io/org/repo/feature:tag) or a relative path to a local directory (./my-feature). Each value is a map of options that override the feature's defaults; use an empty object {} to accept all defaults.

Fields: - Feature ID (required) — OCI reference or relative path to a local directory - Options (required, can be empty) — key/value pairs that customise the feature

Validation Rules: - Feature IDs must be OCI references or relative paths (./…); https:// tarball URIs are not supported - Local paths are resolved relative to the workspace configuration directory (e.g. .kaiden/)

Example — install Go and Node.js:

{
  "features": {
    "ghcr.io/devcontainers/features/go:1": {"version": "1.23"},
    "ghcr.io/devcontainers/features/node:1": {"version": "20"}
  }
}

Example — use a local feature:

{
  "features": {
    "./tools/my-feature": {}
  }
}

Port Forwarding

Forward ports from the workspace to the host so that services running inside the workspace are reachable from the host machine.

Structure:

{
  "ports": [8080, 3000]
}

Fields: - Each entry is an integer workspace port to forward

At workspace creation time, kdn allocates a free host port for each requested workspace port and binds it to 127.0.0.1. The assigned host ports are reported in the forwards field of the workspace JSON output (kdn list --output json / kdn workspace list --output json). Use kdn open / kdn workspace open to open a forwarded port directly in the browser:

{
  "forwards": [
    {"bind": "127.0.0.1", "port": 54321, "target": 8080},
    {"bind": "127.0.0.1", "port": 54322, "target": 3000}
  ]
}

Merging behaviour: When configuration is merged across levels, port lists are union-merged and deduplicated (base ports first, then override ports with duplicates removed).

Configuration Validation

When you register a workspace with kdn init, the configuration is automatically validated. If workspace.json exists and contains invalid data, the registration will fail with a descriptive error message.

Example - Invalid configuration (both value and secret set):

$ kdn init /path/to/project --runtime podman --agent claude
Error: workspace configuration validation failed: invalid workspace configuration:
environment variable "API_KEY" (index 0) has both value and secret set

Example - Invalid configuration (missing host in mount):

$ kdn init /path/to/project --runtime podman --agent claude
Error: workspace configuration validation failed: invalid workspace configuration:
mount at index 0 is missing host

Configuration Examples

Basic environment variables:

{
  "environment": [
    {
      "name": "NODE_ENV",
      "value": "development"
    },
    {
      "name": "DEBUG",
      "value": "true"
    }
  ]
}

Using secrets:

{
  "environment": [
    {
      "name": "API_TOKEN",
      "secret": "github-api-token"
    }
  ]
}

git worktree:

{
  "mounts": [
    {"host": "$SOURCES/../main", "target": "$SOURCES/../main"}
  ]
}

Sharing user configurations:

{
  "mounts": [
    {"host": "$HOME/.claude", "target": "$HOME/.claude"},
    {"host": "$HOME/.gitconfig", "target": "$HOME/.gitconfig"},
    {"host": "$HOME/.kube/config", "target": "$HOME/.kube/config", "ro": true}
  ]
}

MCP command server (local tool):

{
  "mcp": {
    "commands": [
      {
        "name": "filesystem",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace/sources"]
      }
    ]
  }
}

MCP remote server with authentication:

{
  "mcp": {
    "servers": [
      {
        "name": "company-api",
        "url": "https://mcp.company.com/sse",
        "headers": {"Authorization": "Bearer mytoken"}
      }
    ]
  }
}

Network access - allow all:

{
  "network": {
    "mode": "allow"
  }
}

Network access - deny with exceptions:

{
  "network": {
    "mode": "deny",
    "hosts": ["api.github.com", "registry.npmjs.org"]
  }
}

Network access - fully isolated (deny, no hosts):

{
  "network": {
    "mode": "deny"
  }
}

Network access - deny with secrets (hosts inferred automatically):

{
  "network": {
    "mode": "deny"
  },
  "secrets": ["my-github-token"]
}

The my-github-token secret (type github) automatically allows api.github.com without any hosts entry.

Secrets:

{
  "secrets": ["my-github-token", "my-internal-api"]
}

Complete configuration:

{
  "environment": [
    {
      "name": "NODE_ENV",
      "value": "development"
    },
    {
      "name": "DATABASE_URL",
      "secret": "local-db-url"
    }
  ],
  "mounts": [
    {"host": "$SOURCES/../main", "target": "$SOURCES/../main"},
    {"host": "$HOME/.claude", "target": "$HOME/.claude"},
    {"host": "$HOME/.gitconfig", "target": "$HOME/.gitconfig"}
  ],
  "mcp": {
    "commands": [
      {
        "name": "filesystem",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace/sources"]
      }
    ],
    "servers": [
      {
        "name": "remote-api",
        "url": "https://api.example.com/mcp"
      }
    ]
  },
  "network": {
    "mode": "deny"
  },
  "secrets": ["my-github-token"]
}

The my-github-token secret (type github) automatically allows api.github.com, so no explicit hosts entry is needed.

Notes

  • Configuration is optional - workspaces can be registered without a workspace.json file
  • The configuration file is validated only when it exists
  • Validation errors are caught early during workspace registration (init command)
  • All validation rules are enforced to prevent runtime errors
  • The configuration model is imported from the github.com/openkaiden/kdn-api/workspace-configuration/go package for consistency across tools