Skip to main content
Tascarrel is alpha software and may break. We’re looking for your feedback — share it on GitHub.
Documentation

Configuration Reference

Tascarrel reads host-wide settings from $TASCARREL_HOME/config/server.toml and workspace settings from each workspace’s config.toml and settings.json. Unknown fields are rejected.

Server Configuration

The optional server.toml file uses kebab-case fields and is limited to 64 KiB. Hostd reads it at startup.

[remote-access]
public-origin = "https://tascarrel.example.com"

[authentication]
secret-file = "/run/secrets/tascarrel-auth"
FieldType and DefaultPurpose
remote-access.public-originHTTPS origin; unsetPublic UI origin and source of the HTTP-route DNS suffix
authentication.secret-filePath; generated key when absentExternal private 32-byte browser-authentication key

Relative authentication key paths resolve beside server.toml. Restart hostd after changing this file.

Workspace config.toml files use kebab-case fields and are limited to 4 MiB.

Virtual Machine and Features

FieldType and DefaultPurpose
vm.coresPositive integer; all available host coresVirtual CPUs
vm.memoryBinary size; one third of host memoryVM memory, such as "16G"
vm.diskBinary size; "1T"Sparse state disk; minimum 256 MiB
features.dockerBoolean; falseConfined Docker daemon in every pod
features.podmanBoolean; falseRootless Podman in every pod
features.virtualizationBoolean; false/dev/kvm in every pod
features.usbBoolean; falseDynamic Linux-host USB forwarding
nix.daemonBoolean; falsePersistent workspace-wide Nix daemon

Host Shares

FieldType and DefaultPurpose
shares.<name>.pathAbsolute or ~/-relative path; requiredHost directory exposed at /mnt/<name> in pods
shares.<name>.modeReadOnly, ReadWrite, or Overlay; requiredPod access policy
ModeBehavior
ReadOnlyEvery pod receives an ownership-normalized, read-only view
ReadWriteEvery pod reads and writes the host directory directly
OverlayEvery pod receives an isolated copy-on-write view whose changes require explicit inspection and apply

Share names contain up to 64 ASCII letters, digits, _, or - and start with a letter or digit. Tascarrel resolves and pins at most 32 shares when the VM starts. Every mode exposes the share at /mnt/<name> in each pod. ReadOnly and ReadWrite shares also appear at /mnt/shares/<name> in the VM. Overlay changes are private to one pod and persist until they are applied or the pod is deleted. Duplicate directories and paths overlapping Tascarrel’s configuration, state, or runtime trees are rejected. Share changes require a workspace restart.

Configure Tools and Processes

FieldTypePurpose
editors.code.extensionsArray of stringsMarketplace extensions installed before code-server starts
chat.commands.<name>.textStringText inserted by a slash command
env.<name>StringDefault process environment value; secret references use ${secrets.<provider>.<name>}

Image ENV values have the lowest precedence, followed by [env], .env, and per-process values. Tascarrel-owned identity and service variables take final precedence.

Secrets

FieldType and DefaultPurpose
secrets.providers.<name>.kind"sops"Select the current provider implementation
secrets.providers.<name>.fileRelative path; "secrets.json"SOPS-encrypted, string-valued JSON document

Initial secrets supplied by the workspace creation page are encrypted before the workspace is published. Hostd uses the user’s default id_ed25519 or id_rsa SSH key pair and requires its private-key file to work non-interactively.

SSH Authentication Agent

FieldType and DefaultPurpose
ssh-agent.known-hostsNon-empty array of OpenSSH linesPinned server keys for destination-constraint enforcement
ssh-agent.identities.<name>.secretProvider-qualified reference; requiredHost-owned, passphrase-free OpenSSH private-key document
ssh-agent.identities.<name>.destinationsNon-empty array of destinationsAllowed [user@]host targets passed to OpenSSH ssh-add -h

An enabled agent supports at most 16 identities, 128 known-host entries, and 32 destinations per identity. An identity name contains at most 64 bytes and no control characters. Each known-host entry contains at most 4,096 bytes and each destination at most 512 bytes. A destination cannot begin with -.

Hostd creates a private, ephemeral OpenSSH agent for each connection from a pod. It permits only identity listing, OpenSSH session binding, and signing across the VM boundary. The pod receives SSH_AUTH_SOCK; private-key documents and the agent’s management socket remain on the host. OpenSSH destination constraints require a compatible client and server. The separate workspace network policy must admit the destination port.

Setup and Initialization

FieldType and DefaultPurpose
setup.steps[].scriptString; requiredSynchronous shell script run while preparing the reusable seed
init.steps[].scriptString; requiredShell script started for each new pod
init.steps[].waitBoolean; falseWait for this init step before continuing

Caches and Repositories

FieldTypePurpose
caches[].nameStringStable backing-subvolume name
caches[].pathAbsolute or ~-relative pathRead-write mount destination in every pod
repos."<path>".sourceStringUpstream URL for the host-owned checkout below /workspace/<path>
repos."<path>".branchOptional short branch nameBranch checked out instead of the upstream default
repos."<path>".gitGit policy tableComplete repository-specific replacement for the workspace policy

Runtime-owned paths and overlapping cache destinations are rejected.

Git Policy

FieldType and DefaultPurpose
git.default-policyallow, deny, or require-approval; require-approvalAction for unmatched refs
git.branches[].patternStringCase-sensitive glob matched against a short branch name
git.branches[].policyPolicy stringAction for the first matching branch rule
git.tags[].patternStringCase-sensitive glob matched against a short tag name
git.tags[].policyPolicy stringAction for the first matching tag rule

A single * stays within one slash-delimited component; ** crosses components.

Host Commands

Host commands let authenticated workspace pods request narrowly configured processes on the physical host. Every request captures an immutable execution plan and requires host-side approval. The process runs as the Tascarrel host user without a separate container or virtual machine.

[host-commands.deploy]
description = "Deploy one server from the captured infrastructure worktree"
program = "nix"
arguments = [
  "develop",
  "--command",
  "bash",
  "scripts/deploy.sh",
  "${parameters.host}",
]
working-directory = "${inputs.infrastructure}"
approval = "always"
timeout-seconds = 7200

[host-commands.deploy.parameters.host]
required = true
allowed-values = ["staging", "production"]

[host-commands.deploy.inputs.infrastructure]
repository = "infrastructure"
capture = "working-tree"

[host-commands.deploy.environment]
inherit = ["HOME", "SSH_AUTH_SOCK"]
FieldType and DefaultPurpose
host-commands.<name>.descriptionString; unsetPurpose shown to callers and approvers
host-commands.<name>.programString; requiredAbsolute executable or bare name resolved from hostd’s PATH
host-commands.<name>.argumentsArray of strings; emptyTrusted arguments and complete parameter or input placeholders
host-commands.<name>.working-directoryString; private operation directoryAbsolute path, private relative path, or one complete input placeholder
host-commands.<name>.approval"always"Require approval for every request
host-commands.<name>.timeout-secondsPositive integer; unsetStop execution after the configured duration
host-commands.<name>.parameters.<name>.requiredBoolean; true without a defaultRequire the caller to supply the parameter
host-commands.<name>.parameters.<name>.defaultString; unsetValue selected when the caller omits the parameter
host-commands.<name>.parameters.<name>.allowed-valuesNon-empty array; unsetComplete finite set of accepted values
host-commands.<name>.parameters.<name>.patternRust regular expression; unsetPattern matched against the complete parameter value
host-commands.<name>.inputs.<name>.repositoryConfigured repository path; requiredRepository captured from the requesting pod
host-commands.<name>.inputs.<name>.captureCapture policy; "working-tree"Select working-tree, clean-head, commit, or published-ref
host-commands.<name>.environment.inheritArray of environment names; emptyResolve hostd environment values immediately before execution
host-commands.<name>.environment.valuesString map; emptyAdd literal non-secret environment values

Placeholders must occupy an entire argument or the entire working-directory value. They use ${parameters.<name>} or ${inputs.<name>}. A working-tree input includes staged, unstaged, and non-ignored untracked files. A published-ref input accepts HEAD only when a remote-tracking ref can reach it.

Hostd watches config.toml. Valid edits change the registered command catalog and apply to new requests after a short debounce without restarting hostd or the workspace VM. Existing operations retain the exact definition captured when they were requested. Invalid edits leave the preceding valid catalog active and expose a configuration error through command discovery.

Network Policy

FieldType and DefaultPurpose
network.host-portsIntegers or "<host>:<pod>" strings; emptyHost-loopback services made available at host.tascarrel.internal
network.defaultallow or deny; allowDefault egress action
network.allow-localBoolean; falsePermit local, private, link-local, and host-interface addresses
network.allow-addressesArray of IP strings; emptyAddresses admitted when the default is deny
network.deny-addressesArray of IP strings; emptyAddresses always rejected
network.allow-hostsArray of host patterns; emptyHTTP hosts admitted when the default is deny
network.deny-hostsArray of host patterns; emptyHTTP hosts always rejected
network.allow-portsArray of ports; [80, 443]Destination TCP ports available to pods
network.http-portsArray of ports; [80]Ports interpreted as HTTP
network.https-portsArray of ports; [443]Ports interpreted as HTTPS

The host daemon reloads this table after a short debounce period. New TCP flows receive the latest valid snapshot, while active flows retain their original snapshot. An invalid edit leaves the last valid snapshot in effect. An initially invalid configuration uses deny-all until it becomes valid.

Each [[network.secret-injection]] entry supports:

FieldType and DefaultPurpose
hostHost pattern; requiredExact host or subdomain pattern to match
pathsNon-empty array of globs; every pathAbsolute request path patterns to match
methodsNon-empty array of strings; requiredCase-sensitive methods admitted for matching host and path
headerString; all eligible headersLimit placeholder lookup to one header
placeholderString; inferred from the secret nameValue to replace
secretProvider-qualified reference; requiredHost-owned value to insert

When a host matches one or more injection rules, the host proxy rejects a request unless at least one matching rule lists its method and matches its path. A rule without paths matches every path; an explicit array must contain between one and 64 patterns, each at most 2,048 bytes. Every pattern starts with /. Glob syntax supports *, **, ?, character classes, and brace alternatives. A single * does not cross /, while ** does. Query strings are ignored. A literal /mcp therefore admits only /mcp, while /api/** admits its descendants. Only rules admitting the complete request participate in secret injection.

A host-port mapping enters the HTTP mediator when its pod-visible port appears in network.http-ports or network.https-ports. Secret-injection rules for these requests use host.tascarrel.internal as the logical request host. The proxy injects matching secrets, then forwards Host and a same-origin Origin as localhost to the loopback service. Mappings on other ports remain raw TCP connections and cannot use HTTP secret injection.

Portable Interface Settings

The host-owned settings.json file contains portable interface preferences, including harness model preferences, a workspace Model Context Protocol (MCP) server catalog, and Tasci’s model catalog. It uses camelCase names and can be edited through Workspace → Settings.

Usage Cost Centers

The optional usage object declares workspace-local cost centers for chat usage attribution:

{
  "usage": {
    "defaultCostCenter": "client_alpha",
    "costCenters": {
      "client_alpha": {
        "name": "Client Alpha"
      },
      "internal_research": {
        "name": "Internal Research",
        "archived": true
      }
    }
  }
}

Cost-center IDs are stable keys containing 1–64 ASCII letters, numbers, hyphens, or underscores. Their display names can be changed without changing chat assignments. An archived cost center remains available for historical attribution but cannot be the default for new chats.

Manage declarations and the default under Workspace → Settings → Usage. The same page reports monthly token usage and locally calculated cost across all projects in the workspace. A chat’s current assignment applies to its whole recorded history, including usage from archived chats; chats without an assignment appear under Unassigned.

Harness Model Preferences

The optional chat.harnesses object has codex and claudeCode entries. Each entry supports:

FieldPurpose
defaultModelModel and non-default options selected for new chats
modelOrderModel identifiers placed first in the specified order
hiddenModelsModels omitted from ordinary selection controls
favoriteModelsModels shown before other visible models

MCP Servers

The optional chat.mcpServers object declares Streamable HTTP servers once for the workspace. Each server applies to every coding harness unless its harnesses array selects a subset:

{
  "chat": {
    "mcpServers": {
      "exa": {
        "displayName": "Exa",
        "endpoint": "https://mcp.exa.ai/mcp"
      },
      "private-tools": {
        "endpoint": "https://mcp.example.com/mcp",
        "headers": {
          "Authorization": "Bearer tascarrel-secret:mcp-api-token",
          "X-Workspace": "development"
        },
        "harnesses": ["Tasci", "ClaudeCode"]
      }
    }
  }
}

An MCP server supports:

FieldPurpose
displayNameOptional interface label
endpointAbsolute Streamable HTTP URL without credentials, a query, or a fragment
headersOptional map of HTTP header names to non-secret, placeholder-bearing templates
harnessesOptional nonempty subset of Tasci, Codex, and ClaudeCode

Omitting harnesses selects all three harnesses. Tascarrel resolves the selection when it starts or attaches a harness session, so an existing session keeps its original MCP catalog.

The map key becomes the native MCP server name. Tasci exposes model-visible tool names as mcp__<server>__<tool>; Codex and Claude Code retain their native tool naming. Configuring a server trusts all tools and descriptions it advertises. Header values may use any valid HTTP header text, including placeholders handled by network.secret-injection.

This portable catalog deliberately covers the shared provider intersection: remote Streamable HTTP endpoints and static header templates. Configure local standard-I/O servers and provider-specific MCP features through the harness’s native configuration when required.

Tasci Model Preferences and Endpoints

Tasci is bundled with Tascarrel. Its chat.tasci settings map workspace-local model aliases to OpenAI-compatible Chat Completions endpoints:

{
  "chat": {
    "tasci": {
      "defaultModel": "development",
      "modelOrder": ["development"],
      "favoriteModels": ["development"],
      "endpoints": {
        "local": {
          "protocol": "OpenAiChatCompletions",
          "baseUrl": "http://host.tascarrel.internal:18080/v1"
        },
        "host-injected": {
          "protocol": "OpenAiChatCompletions",
          "baseUrl": "https://api.example.com/v1",
          "authorization": {
            "header": "Authorization",
            "value": "Bearer tascarrel-secret:model-api-token"
          }
        }
      },
      "models": {
        "development": {
          "endpoint": "local",
          "model": "provider-model-id",
          "displayName": "Development Model",
          "toolCalls": true
        }
      }
    }
  }
}

The Tasci object supports:

FieldPurpose
defaultModelModel alias selected for new chats
modelOrderModel aliases placed first in the specified order
hiddenModelsModels omitted from ordinary selection controls
favoriteModelsModels shown before other visible models
endpointsInference endpoints keyed by workspace-local aliases
modelsSelectable models keyed by workspace-local aliases

An endpoint supports:

FieldPurpose
displayNameOptional interface label
protocolOpenAiChatCompletions; the only currently supported protocol
baseUrlAbsolute HTTP or HTTPS API URL without credentials, a query, or a fragment
authorizationOptional non-secret authorization header template

An authorization template supplies the header and a placeholder-bearing value:

{
  "header": "Authorization",
  "value": "Bearer tascarrel-secret:model-api-token"
}

The template is not a credential. Configure a matching network.secret-injection rule in config.toml; the host proxy replaces the placeholder after the request leaves the workspace. Tasci never receives the secret. Older prefix and credential settings remain readable for migration, but Tascarrel converts them into the default tascarrel-secret:<name> placeholder instead of resolving the referenced secret.

A model supports:

FieldPurpose
endpointAlias of the endpoint that serves the model
modelProvider-native model identifier
displayNameOptional interface label
contextWindowPositive context limit used for automatic compaction
maxOutputTokensPositive request output limit and compaction reserve
toolCallsWhether the model supports structured tool calls
parallelToolCallsWhether it supports parallel structured calls
pricingOptional versioned token prices for cost calculation

Pricing associates a rate set with one model. Each monetary amount uses the currency’s minor unit and applies to tokenCount tokens. For example, an input amount of 250 with currency USD and a token count of 1000000 means USD 2.50 per million input tokens:

{
  "catalogVersion": "provider:2026-08-01",
  "tokenCount": 1000000,
  "input": { "currency": "USD", "amount": 250 },
  "cacheReadInput": { "currency": "USD", "amount": 25 },
  "cacheWriteInput": { "currency": "USD", "amount": 300 },
  "output": { "currency": "USD", "amount": 1000 }
}

catalogVersion, tokenCount, input, and output are required. cacheReadInput and cacheWriteInput are optional; Tascarrel uses the ordinary input rate when either cache-specific rate is absent. Change the catalog version whenever any rate changes. Configure these values in the model editor under Settings → Tasci or in settings.json.

Tasci automatically projects streamed reasoning_content from compatible endpoints and retains it in assistant history. It requests streamed usage metadata from compatible endpoints. When contextWindow is configured, Tasci uses reported usage when available and a conservative text estimate otherwise to trigger context compaction before later requests overflow. The same current observation appears in the chat status bar; estimated values have a leading ~, while a missing observation appears as N/A.

Model selectors qualify each model name with its endpoint display name, such as GLM 5.2 (Melious). The endpoint alias is used when no display name is configured.

pricing contains catalogVersion, a positive tokenCount, required input and output monetary amounts, and optional cacheReadInput and cacheWriteInput amounts. Each amount has an ISO 4217 currency and an integer amount in that currency’s minor unit.