Skip to main content

Tool capabilities reference

The capability framework gates external access for every tool. A tool declares a ToolCapabilities object; the framework validates the declaration against the personality policy, resolves scoped context objects at call time, and injects them into ToolContext.

Source

Types in packages/types/src/tool-capabilities.ts. Re-exported from @ethosagent/types.

Enforcement in packages/core/src/capability-validator.ts (registration-time) and packages/core/src/capability-resolver.ts (call-time). Scoped implementations in packages/core/src/scoped/.

ToolCapabilities

Signature

import type { ToolCapabilities, SecretRef, StorageScope } from '@ethosagent/types';

export type SecretRef = string;

export type StorageScope = 'tool-private' | 'session' | 'personality';

export interface ToolCapabilities {
network?: {
allowedHosts: string[];
};
secrets?: SecretRef[];
storage?: {
scope: StorageScope;
kind: 'kv';
ttlSecondsDefault?: number;
};
fs_reach?: {
read?: string[] | 'from-personality';
write?: string[] | 'from-personality';
};
process?: {
allowedBinaries: string[];
};
}

Fields

FieldTypeDescription
network{ allowedHosts: string[] } | undefinedHosts the tool may fetch. Supports exact match (api.github.com), wildcard (*), and subdomain wildcard (*.github.com). Intersected with personality safety.network.allow at call time.
secretsSecretRef[] | undefinedSecret reference names the tool may read. Each ref is resolved by the configured secrets backend at call time.
storage{ scope, kind, ttlSecondsDefault? } | undefinedKey-value storage request. scope determines the namespace; kind is always 'kv'; ttlSecondsDefault sets a default TTL in seconds for entries (optional).
fs_reach{ read?, write? } | undefinedFilesystem paths the tool may access. Each direction is either a string[] of absolute paths or 'from-personality' to defer to the personality's fs_reach config.
process{ allowedBinaries: string[] } | undefinedBinary names the tool may spawn. Supports exact match (git, npm) and wildcard (*).

Notes

  • A tool that touches no external surface declares capabilities: {}. The capabilities field is required on Tool<TArgs>.
  • network.allowedHosts: ['*'] means "whatever the personality allows." The wildcard is not "all hosts" -- it defers to the personality's safety.network.allow list. If the personality has no allow list, the resolved host set is empty.
  • fs_reach.read: 'from-personality' and fs_reach.write: 'from-personality' are the conventional choice for generic file tools (read_file, write_file). Tool-specific paths are for tools that know their exact filesystem footprint (e.g. a config reader that only touches ~/.ethos/config.yaml).

StorageScope

Signature

export type StorageScope = 'tool-private' | 'session' | 'personality';

Values

ValueResolved scope idLifecycleTypical use
'tool-private'tool:<toolName>Persists across sessions. Private to this tool.Caches, tool-internal state, rate-limit counters.
'session'session:<sessionId>Lives for the duration of the session. Shared across tools in the same session.Scratch state, session-local accumulators.
'personality'personality:<personalityId>Persists across sessions. Scoped to the active personality. Falls back to session:<sessionId> if no personality is active.Personality-specific tool state, cross-session memory.

Notes

  • The scope id is computed by resolveCapabilities in packages/core/src/capability-resolver.ts. The tool never constructs its own scope id.
  • The KeyValueStore instance the tool receives is already namespaced. Calling kvStore.set('foo', 'bar') stores under the resolved scope id; the tool does not prefix keys.
  • ttlSecondsDefault on the storage declaration sets a default TTL for all entries. Individual set calls can override with { ttlSeconds } in the options.

KeyValueStore

Signature

import type { KeyValueStore } from '@ethosagent/types';

export interface KeyValueStore {
get(key: string): Promise<string | null>;
set(key: string, value: string, opts?: { ttlSeconds?: number }): Promise<void>;
delete(key: string): Promise<void>;
list(prefix: string): Promise<string[]>;
}

Methods

MethodReturnsDescription
get(key)string | nullRead a value by key. Returns null if the key does not exist or has expired.
set(key, value, opts?)voidWrite a value. opts.ttlSeconds overrides the default TTL from the capability declaration.
delete(key)voidRemove a key. No-op if the key does not exist.
list(prefix)string[]List keys matching a prefix. Returns key names, not values.

Notes

  • The store is injected into ToolContext.kvStore when the tool declares storage in its capabilities. Absent if the tool does not declare storage or no kvStoreFactory backend is configured.
  • Values are strings. Tools that need structured data should serialise to JSON.

ScopedFetch

Signature

import type { ScopedFetch } from '@ethosagent/types';

export interface ScopedFetch {
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
}

Behaviour

The implementation (ScopedFetchImpl in packages/core/src/scoped/scoped-fetch.ts) parses the URL, extracts the hostname, and checks it against the resolved host set (intersection of the tool's allowedHosts and the personality's safety.network.allow). If the host is not in the set, the call throws with HOST_NOT_ALLOWED. Otherwise it delegates to globalThis.fetch.

Host matching supports:

  • Exact match: 'api.github.com' matches api.github.com.
  • Wildcard: '*' matches any host.
  • Subdomain wildcard: '*.github.com' matches api.github.com and raw.github.com but not github.com itself.

Notes

  • Injected into ToolContext.scopedFetch when the tool declares network. Absent otherwise.
  • The resolved host set may be smaller than the tool's declared set. A tool declaring ['api.github.com', 'api.stripe.com'] under a personality that allows ['*.github.com'] gets a ScopedFetch that permits only api.github.com.

ScopedFs

Signature

import type { ScopedFs } from '@ethosagent/types';

export interface ScopedFs {
read(path: string): Promise<string>;
write(path: string, content: string | Buffer): Promise<void>;
exists(path: string): Promise<boolean>;
list(path: string): Promise<string[]>;
}

Methods

MethodReturnsDescription
read(path)stringRead utf-8 text. Throws PATH_NOT_REACHABLE if the path is outside the declared read set. Throws if the file does not exist.
write(path, content)voidWrite utf-8 text or a Buffer. Throws PATH_NOT_REACHABLE if the path is outside the declared write set.
exists(path)booleanCheck existence. Throws PATH_NOT_REACHABLE if the path is outside the declared read set.
list(path)string[]List directory children. Throws PATH_NOT_REACHABLE if the path is outside the declared read set.

Notes

  • Injected into ToolContext.scopedFs when the tool declares fs_reach. Absent otherwise.
  • The implementation (ScopedFsImpl in packages/core/src/scoped/scoped-fs.ts) resolves paths via node:path.resolve and normalize before checking. Relative paths are resolved against process.cwd().
  • Delegates to the Storage interface, not to raw node:fs. The same Storage abstraction powers ScopedStorage from the pre-existing filesystem boundary.
  • ScopedFs is distinct from ScopedStorage. ScopedStorage (from @ethosagent/storage-fs) is the personality-level decorator on the full Storage interface. ScopedFs is the capability-gated subset exposed to individual tools.

ScopedSecretsResolver

Signature

import type { ScopedSecretsResolver, SecretRef } from '@ethosagent/types';

export interface ScopedSecretsResolver {
get(ref: SecretRef): Promise<string>;
}

Behaviour

The implementation (ScopedSecretsImpl in packages/core/src/scoped/scoped-secrets.ts) checks the requested ref against the tool's declared secrets set. If the ref is not declared, the call throws with SECRET_NOT_DECLARED. Otherwise it delegates to the configured secrets backend.

Notes

  • Injected into ToolContext.secretsResolver when the tool declares secrets and a secretsBackend is configured. Absent otherwise.
  • The secrets backend is a function (ref: SecretRef) => Promise<string> provided via CapabilityBackends.secretsBackend. The framework does not mandate where secrets are stored -- the backend is a wiring decision.

ScopedProcess

Signature

import type { ScopedProcess, SpawnOpts, ProcessResult } from '@ethosagent/types';

export interface SpawnOpts {
cwd?: string;
env?: Record<string, string>;
timeout?: number;
}

export interface ProcessResult {
exitCode: number;
stdout: string;
stderr: string;
}

export interface ScopedProcess {
spawn(binary: string, args: string[], opts?: SpawnOpts): Promise<ProcessResult>;
}

Behaviour

The implementation (ScopedProcessImpl in packages/core/src/scoped/scoped-process.ts) checks the requested binary against the tool's declared allowedBinaries set. Wildcard ('*') permits any binary. If the binary is not in the set, the call throws with BINARY_NOT_ALLOWED. Otherwise it spawns the process via node:child_process.spawn.

Notes

  • Injected into ToolContext.scopedProcess when the tool declares process. Absent otherwise.
  • opts.env is merged with process.env -- the tool's declared env vars are additive, not replacing.
  • opts.timeout is passed directly to child_process.spawn's timeout option. The child is killed if it exceeds the timeout.

Error catalog

All errors thrown by scoped context implementations use a stable prefix in the error message. Catch by message.startsWith(code) or by checking the Error.message directly.

CodeThrown byWhen
HOST_NOT_ALLOWEDScopedFetchImplThe requested hostname is not in the resolved allowedHosts set.
SECRET_NOT_DECLAREDScopedSecretsImplThe requested secret ref is not in the tool's declared secrets set.
PATH_NOT_REACHABLEScopedFsImplThe requested path is not covered by the tool's declared fs_reach (read or write).
BINARY_NOT_ALLOWEDScopedProcessImplThe requested binary is not in the tool's declared allowedBinaries set.

Error message format

Each error follows the pattern <CODE>: <detail>:

HOST_NOT_ALLOWED: api.stripe.com is not in the declared allowedHosts
SECRET_NOT_DECLARED: STRIPE_KEY is not in the tool's declared secrets
PATH_NOT_REACHABLE: read not permitted for /etc/passwd
BINARY_NOT_ALLOWED: rm is not in the declared allowedBinaries

Notes

  • These errors propagate to ToolResult as { ok: false, code: 'execution_failed', error: err.message } via the executeParallel catch handler. The LLM sees the full error message.
  • Registration-time validation errors (CapabilityValidationError) are a separate type with { tool, capability, message } fields. They are not thrown -- they are returned as an array from validateRegistration and validateToolsForPersonality.

CapabilityBackends

Signature

import type { CapabilityBackends } from '@ethosagent/core';

export interface CapabilityBackends {
kvStoreFactory?: (tool: string, scopeId: string) => KeyValueStore;
secretsBackend?: (ref: SecretRef) => Promise<string>;
storage?: Storage;
personalityFsReach?: { read: string[]; write: string[] };
personalityNetworkAllow?: string[];
}

Fields

FieldTypeDescription
kvStoreFactory(tool, scopeId) => KeyValueStoreFactory for scoped key-value stores. Called once per tool per turn.
secretsBackend(ref) => Promise<string>Resolves a secret ref to its value. Called by ScopedSecretsImpl.
storageStorageFilesystem abstraction passed to ScopedFsImpl.
personalityFsReach{ read: string[]; write: string[] }The active personality's fs_reach config. Used to resolve 'from-personality' declarations and to intersect with tool-declared paths.
personalityNetworkAllowstring[]The active personality's network allow list. Used to intersect with tool-declared hosts.

Notes

  • Passed to DefaultToolRegistry at construction. Shared across all tool executions.
  • If absent, tools that declare real capabilities (needsBackends returns true) fail closed with not_available.
  • personalityFsReach and personalityNetworkAllow are set per personality, typically updated when the personality changes.

Used by

ConsumerRole
packages/types/src/tool-capabilities.tsType definitions for all capability interfaces.
packages/types/src/tool.tsTool.capabilities field; ToolContext scoped fields (kvStore, secretsResolver, scopedFetch, scopedFs, scopedProcess).
packages/core/src/capability-validator.tsvalidateRegistration -- registration-time policy check.
packages/core/src/capability-resolver.tsresolveCapabilities -- call-time context building.
packages/core/src/tool-registry.tsneedsBackends guard; executeParallel capability resolution.
packages/core/src/scoped/scoped-fetch.tsScopedFetchImpl -- host-gated fetch.
packages/core/src/scoped/scoped-fs.tsScopedFsImpl -- path-gated filesystem.
packages/core/src/scoped/scoped-secrets.tsScopedSecretsImpl -- ref-gated secrets.
packages/core/src/scoped/scoped-process.tsScopedProcessImpl -- binary-gated process spawning.

See also