Skip to content

Layer 3: Capabilities

The Capabilities Layer (L3) defines what actions an agent can perform. It provides a registry of tools, functions, and APIs that the agent can invoke to interact with the world.

interface Action {
id: string
name: string
description: string
parameters: ParameterSchema
returns: ReturnSchema
permissions: string[]
rateLimit?: RateLimit
}
interface ParameterSchema {
type: 'object'
properties: Record<string, JSONSchema>
required: string[]
}
// Example: Weather action
const getWeatherAction: Action = {
id: 'get-weather',
name: 'Get Weather',
description: 'Fetch current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name or coordinates'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
default: 'celsius'
}
},
required: ['location']
},
returns: {
type: 'object',
properties: {
temperature: { type: 'number' },
condition: { type: 'string' },
humidity: { type: 'number' }
}
},
permissions: ['weather:read'],
rateLimit: {
requests: 100,
window: '1h'
}
}
class CapabilityRegistry {
private actions = new Map<string, Action>()
register(action: Action): void {
// Validate action schema
this.validateActionSchema(action)
// Register action
this.actions.set(action.id, action)
}
getAction(id: string): Action | undefined {
return this.actions.get(id)
}
listActions(filter?: ActionFilter): Action[] {
let actions = Array.from(this.actions.values())
if (filter?.permissions) {
actions = actions.filter(a =>
filter.permissions.some(p => a.permissions.includes(p))
)
}
return actions
}
async execute(
actionId: string,
params: Record<string, any>,
context: ExecutionContext
): Promise<ActionResult> {
const action = this.getAction(actionId)
if (!action) throw new Error(`Action ${actionId} not found`)
// Check permissions
this.checkPermissions(action, context.user)
// Validate parameters
this.validateParameters(action, params)
// Check rate limit
await this.checkRateLimit(action, context.user)
// Execute action
try {
const result = await this.handlers.get(actionId)(params, context)
return { success: true, data: result }
} catch (error) {
return { success: false, error: error.message }
}
}
}
class CapabilityRegistry:
def __init__(self):
self.actions: Dict[str, Action] = {}
self.handlers: Dict[str, Callable] = {}
def register(self, action: Action, handler: Callable) -> None:
"""Register an action and its handler"""
# Validate action schema
self.validate_action_schema(action)
# Register action and handler
self.actions[action.id] = action
self.handlers[action.id] = handler
def list_actions(self, filter: Optional[ActionFilter] = None) -> List[Action]:
"""List available actions"""
actions = list(self.actions.values())
if filter and filter.permissions:
actions = [
a for a in actions
if any(p in a.permissions for p in filter.permissions)
]
return actions
async def execute(
self,
action_id: str,
params: Dict[str, Any],
context: ExecutionContext
) -> ActionResult:
"""Execute an action"""
action = self.actions.get(action_id)
if not action:
raise ValueError(f"Action {action_id} not found")
# Check permissions
self.check_permissions(action, context.user)
# Validate parameters
self.validate_parameters(action, params)
# Check rate limit
await self.check_rate_limit(action, context.user)
# Execute action
try:
handler = self.handlers[action_id]
result = await handler(params, context)
return ActionResult(success=True, data=result)
except Exception as error:
return ActionResult(success=False, error=str(error))
globe HTTP Requests
const httpGetAction: Action = {
id: 'http-get',
name: 'HTTP GET',
description: 'Perform HTTP GET request',
parameters: {
type: 'object',
properties: {
url: { type: 'string', format: 'uri' },
headers: { type: 'object' },
timeout: { type: 'number', default: 30000 }
},
required: ['url']
},
permissions: ['http:read']
}
async function handleHttpGet(
params: { url: string; headers?: Record<string, string> },
context: ExecutionContext
): Promise<any> {
const response = await fetch(params.url, {
method: 'GET',
headers: params.headers,
signal: AbortSignal.timeout(params.timeout || 30000)
})
return {
status: response.status,
headers: Object.fromEntries(response.headers),
body: await response.json()
}
}
database Database Queries
const dbQueryAction: Action = {
id: 'db-query',
name: 'Database Query',
description: 'Execute database query',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
params: { type: 'array' }
},
required: ['query']
},
permissions: ['database:read']
}
async function handleDbQuery(
params: { query: string; params?: any[] },
context: ExecutionContext
): Promise<any[]> {
// Validate query (prevent SQL injection)
validateSafeQuery(params.query)
// Execute with parameter binding
const results = await context.db.query(
params.query,
params.params || []
)
return results.rows
}
file File Operations
const readFileAction: Action = {
id: 'file-read',
name: 'Read File',
description: 'Read file contents',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
encoding: { type: 'string', default: 'utf-8' }
},
required: ['path']
},
permissions: ['file:read']
}
async function handleReadFile(
params: { path: string; encoding: string },
context: ExecutionContext
): Promise<string> {
// Validate path (prevent directory traversal)
const safePath = validateSafePath(params.path, context.workspace)
// Read file
const content = await fs.promises.readFile(
safePath,
{ encoding: params.encoding }
)
return content
}
envelope Email
const sendEmailAction: Action = {
id: 'email-send',
name: 'Send Email',
description: 'Send email message',
parameters: {
type: 'object',
properties: {
to: { type: 'string', format: 'email' },
subject: { type: 'string' },
body: { type: 'string' },
html: { type: 'boolean', default: false }
},
required: ['to', 'subject', 'body']
},
permissions: ['email:send'],
rateLimit: { requests: 10, window: '1h' }
}
{
"layers": {
"capabilities": {
"actions": [
{
"id": "get-weather",
"name": "Get Weather",
"description": "Fetch weather data",
"handler": "./handlers/weather.js",
"parameters": {
"location": { "type": "string", "required": true }
},
"permissions": ["weather:read"],
"rateLimit": {
"requests": 100,
"window": "1h"
}
},
{
"id": "send-notification",
"name": "Send Notification",
"handler": "./handlers/notify.js",
"parameters": {
"message": { "type": "string", "required": true },
"channel": { "type": "string", "enum": ["email", "sms", "push"] }
},
"permissions": ["notify:send"]
}
],
"security": {
"defaultPermissions": [],
"requireAuth": true,
"auditLog": true
}
}
}
}
pen Action Design

DO:

  • Keep actions focused and atomic
  • Provide clear descriptions
  • Define comprehensive parameter schemas
  • Document return values

DON’T:

  • Create overly complex actions
  • Skip parameter validation
  • Return inconsistent data structures
shield Security

DO:

  • Implement permission checks
  • Validate all inputs
  • Use rate limiting
  • Audit all action executions
  • Sanitize outputs

DON’T:

  • Trust user inputs
  • Allow unlimited executions
  • Expose internal errors
  • Skip authorization
triangle-exclamation Error Handling

DO:

  • Return structured errors
  • Log failures with context
  • Implement retries for transient errors
  • Provide helpful error messages

DON’T:

  • Throw raw exceptions
  • Lose error context
  • Retry on permanent failures
describe('Capabilities Layer', () => {
it('should execute action with valid params', async () => {
const registry = new CapabilityRegistry()
registry.register(getWeatherAction, handleGetWeather)
const result = await registry.execute(
'get-weather',
{ location: 'Paris' },
mockContext
)
expect(result.success).toBe(true)
expect(result.data).toHaveProperty('temperature')
})
it('should reject action without permission', async () => {
const registry = new CapabilityRegistry()
registry.register(getWeatherAction, handleGetWeather)
const contextWithoutPermission = {
user: { permissions: [] }
}
await expect(
registry.execute('get-weather', { location: 'Paris' }, contextWithoutPermission)
).rejects.toThrow('Permission denied')
})
it('should enforce rate limits', async () => {
const registry = new CapabilityRegistry()
registry.register(getWeatherAction, handleGetWeather)
// Execute 100 times (the limit)
for (let i = 0; i < 100; i++) {
await registry.execute('get-weather', { location: 'Paris' }, mockContext)
}
// 101st should fail
await expect(
registry.execute('get-weather', { location: 'Paris' }, mockContext)
).rejects.toThrow('Rate limit exceeded')
})
})