Capabilities System
Overview
The SheetBot capabilities system allows agents to report their capabilities to the server through a flexible three-tier configuration. This enables intelligent task routing where tasks are matched to agents with the appropriate tools, resources, and characteristics. The system consists of static, dynamic, and override capabilities that are merged in a specific order.
Architecture
The capabilities system works through a three-tier configuration that allows for flexible capability definition:
1. Static Capabilities (.capabilities.json)
Manually defined capabilities that are explicitly set by the user. These include:
- Custom tool configurations
- Environment-specific settings
- User-defined packages or features
- Static system properties
2. Dynamic Capabilities (.capabilities.dynamic.{extension})
Runtime-detected capabilities that are automatically discovered by executing detection scripts. These include:
- Installed software versions (git, clang, cmake, node, etc.)
- Operating system details and versions
- System resources (memory, load averages)
- Hardware information
- Available tools and utilities
3. Override Capabilities (.capabilities.override.json)
Final overrides that can modify or add to any capabilities from the previous tiers. These are useful for:
- Forcing specific capability values
- Adding computed or derived capabilities
- Environment-specific overrides
- Testing and debugging
File Structure
.agent-directory/
├── .capabilities.json # Static capabilities
├── .capabilities.dynamic.ts/.py/.sh # Dynamic detection script
└── .capabilities.override.json # Override capabilities
Capability Loading and Merging
Capabilities are loaded and merged in the following order:
- Base capabilities - OS and architecture from the runtime environment
- Static capabilities - Loaded from
.capabilities.json(if exists) - Dynamic capabilities - Generated by
.capabilities.dynamic.{extension}script (if exists) - Override capabilities - Applied from
.capabilities.override.json(if exists)
Each layer can add new capabilities or override existing ones. The dynamic capabilities receive the static capabilities as input, allowing them to build upon or modify existing definitions.
Static Capabilities Example
{
"packages": ["typescript", "node"],
"custom_tools": ["my-custom-linter"],
"environment": "production",
"max_concurrent_tasks": 4
}
Override Capabilities Example
{
"hostname": "custom-agent-name",
"memory": {
"total": 16384,
"unit": "MB"
},
"debug_mode": true
}
Dynamic Capabilities Detection
The dynamic capabilities library (scripts/lib/capabilities.ts) automatically detects:
Software Versions
- Git - Version detection via
git --version - Clang - Version detection via
clang --version - CMake - Version detection via
cmake --version - Node.js - Version detection via
node --version - UV - Version detection via
uv --version - SCons - Version detection via
scons --version - VirtualBox - Version detection via
vboxmanage --version - Deno - Version from
Deno.version.deno
System Information
- OS Details - OS type, release version with major/minor/patch parsing
- Memory - Total, free, and available memory in MB
- Load Average - 1min, 5min, and 15min load averages
- Hostname - System hostname
- Build Info - Deno build target, arch, etc.
Linux-Specific Information
- Distribution Details - Name, version, codename from
/etc/os-release - Pretty Name - Human-readable OS description
Usage by Agent Runners
All agent runners follow the same capability loading pattern: static → dynamic → override.
TypeScript Agent Runner (scripts/agent.template.ts)
// Load static capabilities
let localCapabilities = {};
const capabilitiesJSONPath = path.resolve("./.capabilities.json");
if (fs.existsSync(capabilitiesJSONPath)) {
const capabilitiesText = Deno.readTextFileSync(capabilitiesJSONPath);
localCapabilities = Object.assign({}, localCapabilities, JSON.parse(capabilitiesText));
}
// Load dynamic capabilities
const capabilitiesPath = path.resolve("./.capabilities.dynamic.ts");
if (fs.existsSync(capabilitiesPath)) {
const { getCapabilities } = await import(path.toFileUrl(capabilitiesPath).toString());
const dynamicCapabilities = await getCapabilities(localCapabilities);
localCapabilities = Object.assign({}, localCapabilities, dynamicCapabilities);
}
// Apply overrides
const capabilitiesOverrideJSONPath = path.resolve("./.capabilities.override.json");
if (fs.existsSync(capabilitiesOverrideJSONPath)) {
const capabilitiesOverrideText = Deno.readTextFileSync(capabilitiesOverrideJSONPath);
localCapabilities = Object.assign({}, localCapabilities, JSON.parse(capabilitiesOverrideText));
}
Python Agent Runner (scripts/agent.template.py)
# Load static capabilities
local_capabilities = {}
if os.path.exists("./.capabilities.json"):
with open("./.capabilities.json") as f:
local_capabilities.update(json.load(f))
# Load dynamic capabilities
if os.path.exists("./.capabilities.dynamic.py"):
spec = importlib.util.spec_from_file_location(
"capabilities", "./.capabilities.dynamic.py"
)
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
dynamic_capabilities = module.getCapabilities(local_capabilities)
local_capabilities.update(dynamic_capabilities)
# Apply overrides
if os.path.exists("./.capabilities.override.json"):
with open("./.capabilities.override.json") as f:
local_capabilities.update(json.load(f))
Node.js Agent Runner (scripts/agent.template.js)
const localCapabilities = {};
if (fs.existsSync("./.capabilities.json")) {
Object.assign(localCapabilities, JSON.parse(fs.readFileSync("./.capabilities.json", "utf8")));
}
if (fs.existsSync("./.capabilities.dynamic.mjs")) {
const mod = await import(pathToFileURL("./.capabilities.dynamic.mjs").href);
if (mod.getCapabilities) Object.assign(localCapabilities, await mod.getCapabilities(localCapabilities));
}
if (fs.existsSync("./.capabilities.override.json")) {
Object.assign(localCapabilities, JSON.parse(fs.readFileSync("./.capabilities.override.json", "utf8")));
}
const capabilities = { os: os.platform(), arch: os.arch(), node: process.version, ...localCapabilities };
Shell Agent Runner (scripts/agent.template.sh)
# Load static capabilities
local_capabilities="{}"
if [ -f "./.capabilities.json" ]; then
local_capabilities=$(jq -s '.[0] * .[1]' <(echo "$local_capabilities") ./.capabilities.json)
fi
# Load dynamic capabilities
if [ -f "./.capabilities.dynamic.sh" ]; then
source "./.capabilities.dynamic.sh"
if command -v getCapabilities >/dev/null 2>&1; then
dynamic_caps=$(getCapabilities "$local_capabilities")
local_capabilities=$(jq -s '.[0] * .[1]' <(echo "$local_capabilities") <(echo "$dynamic_caps"))
fi
fi
# Apply overrides
if [ -f "./.capabilities.override.json" ]; then
local_capabilities=$(jq -s '.[0] * .[1]' <(echo "$local_capabilities") ./.capabilities.override.json)
fi
Static Capabilities Setup
Static capabilities are defined in a JSON file that contains manually configured capabilities:
Creating Static Capabilities
Create .capabilities.json in your agent directory:
{
"packages": ["typescript", "node", "python"],
"gpu": {
"available": true,
"type": "nvidia",
"memory_gb": 8
},
"storage": {
"type": "ssd",
"size_gb": 500,
"available_gb": 200
},
"network": {
"bandwidth_mbps": 100,
"latency_ms": 5
},
"custom_properties": {
"team": "backend",
"region": "us-west-2",
"cost_center": "engineering"
}
}
Override Capabilities
Create .capabilities.override.json to override any capabilities:
{
"hostname": "production-agent-01",
"memory": {
"total": 32768,
"unit": "MB"
},
"packages": ["typescript", "node", "python", "production-tools"],
"environment": "production",
"debug_mode": false
}
Setup Instructions
For TypeScript Agents
-
Download the capabilities library:
curl -o .capabilities.dynamic.ts <sheetbot_baseurl>/scripts/lib/capabilities.ts -
Or create a dynamic import file
.capabilities.dynamic.ts:async function getCapabilities(staticCapabilities) { const lib = await import(Deno.env.get("SHEETBOT_BASEURL") + "/scripts/lib/capabilities.ts"); return await lib.getCapabilities(staticCapabilities); } export { getCapabilities }; -
Test the capabilities:
deno run --allow-all .capabilities.dynamic.ts
For Python Agents
Create .capabilities.dynamic.py:
import subprocess
import json
import platform
import psutil # pip install psutil
def getCapabilities(staticCapabilities):
capabilities = {}
# Detect software versions
try:
result = subprocess.run(['git', '--version'], capture_output=True, text=True)
if result.returncode == 0:
version = result.stdout.split()[-1]
capabilities['git'] = {'version': version}
except:
pass
# Add system info
capabilities.update({
'os': platform.system().lower(),
'arch': platform.machine(),
'memory': {
'total': psutil.virtual_memory().total // (1024*1024),
'available': psutil.virtual_memory().available // (1024*1024),
'unit': 'MB'
}
})
return capabilities
For Shell Agents
Create .capabilities.dynamic.sh:
#!/bin/bash
getCapabilities() {
local static_caps="$1"
local caps="{}"
# Detect git
if command -v git >/dev/null 2>&1; then
local git_version=$(git --version | awk '{print $3}')
caps=$(jq -n --arg version "$git_version" '{"git": {"version": $version}}')
fi
# Detect node
if command -v node >/dev/null 2>&1; then
local node_version=$(node --version | sed 's/v//')
local node_caps=$(jq -n --arg version "$node_version" '{"node": {"version": $version}}')
caps=$(jq -s '.[0] * .[1]' <(echo "$caps") <(echo "$node_caps"))
fi
# Add system info
local mem_info=$(free -m | awk 'NR==2{printf "{\"total\":%s,\"free\":%s,\"available\":%s,\"unit\":\"MB\"}", $2, $4, $7}')
local mem_caps=$(jq -n --argjson mem "$mem_info" '{"memory": $mem}')
caps=$(jq -s '.[0] * .[1]' <(echo "$caps") <(echo "$mem_caps"))
echo "$caps"
}
Capabilities Data Structure
The final capabilities object sent to the server has this structure:
{
"os": "linux",
"arch": "x86_64",
"hostname": "agent-host",
"software": {
"git": {
"version": "2.34.1",
"major_version": 2,
"minor_version": 34,
"patch_version": 1
},
"deno": {
"version": "1.28.0",
"major_version": 1,
"minor_version": 28,
"patch_version": 0
}
},
"packages": ["git", "deno"],
"build": {
"target": "x86_64-unknown-linux-gnu",
"arch": "x86_64",
"os": "linux"
},
"os": {
"os": "linux",
"release": {
"version": "5.15.0",
"major_version": 5,
"minor_version": 15,
"patch_version": 0
}
},
"memory": {
"total": 8192,
"free": 2048,
"available": 4096,
"unit": "MB"
},
"loadavg": {
"1min": 0.5,
"5min": 0.3,
"15min": 0.2
},
"linux": {
"release": {
"version": "20.04",
"major_version": 20,
"minor_version": 4,
"patch_version": 0
},
"pretty_name": "Ubuntu 20.04.4 LTS",
"name": "Ubuntu",
"versionCodename": "focal",
"versionId": "20.04"
}
}
Task Matching
The server uses reported capabilities to match tasks to appropriate agents. Tasks can specify required capabilities, and only agents with matching capabilities will be assigned those tasks.
Testing and Validation
Testing Dynamic Capabilities
For TypeScript dynamic capabilities:
# Test the capabilities detection
deno run --allow-all scripts/lib/capabilities.ts
For Python dynamic capabilities:
# Test by importing and running your dynamic script
python3 -c "
import sys
sys.path.append('.')
import importlib.util
spec = importlib.util.spec_from_file_location('capabilities', './.capabilities.dynamic.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
caps = module.getCapabilities({})
import json
print(json.dumps(caps, indent=2))
"
For Shell dynamic capabilities:
# Source and test the function
source ./.capabilities.dynamic.sh
getCapabilities "{}"
Validating Final Capabilities
To see what capabilities your agent will report:
# For TypeScript agents - temporarily modify agent.template.ts to print capabilities
# Look for the line: console.log("capabilities:", localCapabilities);
# For Python agents - temporarily add: print("capabilities:", local_capabilities)
# For Shell agents - temporarily add: echo "capabilities: $local_capabilities"
Error Handling
The capabilities loading process is designed to be fault-tolerant:
- Missing files are ignored
- Detection failures for individual tools don’t prevent other detections
- The agent continues with whatever capabilities were successfully detected
- Errors are logged but don’t stop agent execution