Features

Custom JavaScript tools

Build a tool by writing a JavaScript file, validate it, and enable its ID in agent.json. No API server edit or restart is needed when authoring tools. Tools run when the model calls them; discovering a file does not enable or run its execute function.

Write and enable

Put agent tools in data/tools/<name>.tool.js, beside the agent's other data. Put reusable organization tools in shared/tools/<name>.tool.js. Both appear in the agent configuration's Tools > Custom tools list, including errors. Shared tools use each calling agent's own data and database files. If names collide, the agent file overrides the shared file, and file tools override built-in tools. The winning source and overridden sources appear in the catalog. A broken override reports an error; it never falls back to a different implementation.

The filename supplies the ID: search-catalog.tool.js becomes search_catalog, with no added prefix. Use a lowercase kebab filename; tool names use lowercase letters, digits and underscores, up to 64 characters. Enable it with the checkbox or add that ID to the existing tools array in agent.json. Start a new session after changing source, schemas, permissions or enabled tools. Existing sessions reject changed source rather than gaining new permissions mid-session.

Config writes through Builder and MCP reject missing or invalid enabled tools. New sessions check the files again, including changes made outside the app. Broken tools remain visible in the catalog for repair. Sessions inspect only enabled files, with at most 16 file tools per agent. Catalog inspection validates 16 winning files per page, with four workers sharing a three-second budget. Budget failures are reported per file. Follow pagination.nextOffset by passing offset to tool_catalog; the UI has a Load more button. Enabled tools appear first. Neither startup nor catalog inspection evaluates shadowed files.

Copy docs/examples/tools/search-catalog.tool.js from the repository for a SQLite search example, or docs/examples/tools/match-garment.tool.js for a camera, database and LLM workflow.

For a minimal tool, write data/tools/search-catalog.tool.js:

  export const schemaVersion = "worldagents.tool.v1";
export const metadata = { title: "Search catalog", description: "Find products by name." };
export const inputSchema = {
  type: "object", properties: { query: { type: "string", minLength: 1 } },
  required: ["query"], additionalProperties: false,
};
export const capabilities = { databases: { catalog: { path: "databases/catalog.sqlite", mode: "read" } } };
export async function execute(ctx, input) {
  return ctx.db.query("catalog", "SELECT name FROM products WHERE name LIKE ? LIMIT 5", ["%" + input.query + "%"]);
}

The module exports schemaVersion = "worldagents.tool.v1", metadata: {title, description}, an object inputSchema with additionalProperties: false, and execute(ctx, input). Optional exports are capabilities and timeoutMs (default 30000, maximum 120000). Use direct named exports. Imports and require are unavailable.

execute returns a JSON value or a Promise. ctx.wait(ms) waits within the same deadline. Arguments are checked against the input schema before execution.

Context capabilities

Exported grantContext API
databases: {catalog: {path: "databases/catalog.sqlite", mode: "read"}}await ctx.db.query("catalog", sql, parameters) returns {rows, truncated}. Read-only connection, one SELECT/WITH, at most 100 rows or 128 KiB, two-second deadline.
Database mode: "write"Also permits ctx.db.execute(name, sql, parameters) for a single INSERT, UPDATE, DELETE, REPLACE, CREATE TABLE or CREATE INDEX. Returns {changes,lastInsertRowid}.
files: ["catalog"]ctx.files.read("catalog/file.json") returns {path,text}. Also grants those files to LLM calls. Paths stay inside agent data.
llm: {maxCalls: 3, models: ["default"]}ctx.llm.generate({system,prompt,messages,data,files,schema,thinking,model,includeCurrentFrame}) returns {text,json}, using the trigger LLM helper. Up to six calls and eight files per call.
camera: truectx.camera.available and capturedAt; permits includeCurrentFrame in LLM calls. Raw camera bytes are not handed to the script.

ctx.now, ctx.agent and ctx.session expose time and nonsecret identity/mode metadata. ctx.conversation.recentTurns contains up to twelve recent messages. Sequential and parallel LLM calls share the tool's deadline and budget. Model calls have no tools themselves and a 2048 output-token ceiling per call. All calls and their outcomes use the normal session tool log; nested LLM calls also emit start/finish timing events. The camera image is a snapshot at tool start; frames received over 15 seconds ago are unavailable, so a tool cannot silently reuse an old camera session.

default uses the platform's Google text model. Operators may configure other Google model aliases with WORLDAGENTS_TOOL_MODELS, a JSON object mapping alias to model ID. Scripts receive aliases, never API keys. Other providers need a host adapter; arbitrary outbound API access is unavailable.

Database files live only in the calling agent's data/databases folder. Always bind SQL parameters. No attachments, arbitrary pragmas or extension loading. Writable databases are shared across this agent's sessions and users: use them for an intentionally shared or single-wearer store, not private per-user data. Text files and tool inputs/results have a 256 KiB limit; each run permits at most 32 capability calls, eight concurrently. Database result truncation is explicit. Each SQL operation runs in a separate process so a deadline can stop a query even while SQLite is executing native code.

Builder and MCP

The per-draft Agent Builder's /agent mount points at data. It writes files with apply_patch, calls tool_catalog to check schemas and file errors, then edits the enabled tools array and calls validate_agent.

The web Builder uses the same tools as MCP, with its organization supplied automatically. Through MCP, use help {topic: "tools"} for the full contract, tool_catalog {organizationId,agentId,offset?} to inspect tools, file errors and enabled IDs before enabling. Write source and configuration through revision-checked write_file or edit_file: agent paths are agents/<agentId>/data/tools/<name>.tool.js, and shared paths are tools/<name>.tool.js. Read the config before editing its tools array. Upload a prepared SQLite database through upload_file or file_transfer.

Runtime isolation

Custom tools reuse the trigger process sandbox and host capability broker, with separate execution processes per tool revision and session. Different tools do not share a process with different permissions or another session's context. Execution requires Bubblewrap by default, with no network, organization files, credentials or writable system mounts in the child. JavaScript VM contexts are not the security boundary. Source discovery also runs in that sandbox without capabilities. Custom tools are unavailable in public sessions.

WORLDAGENTS_TOOL_SANDBOX=process is an explicit trusted-development override, including for local authoring on macOS. It is not a security boundary for untrusted code. Child V8 heaps are limited to 128 MiB. Deadlines and session cancellation abort outstanding host work. This does not provide per-organization CPU or total native-memory quotas.

StyleEyes catalog

Build a new index from the downloaded snapshot:

  node scripts/build-styleeyes-catalog.mjs /path/to/agent/data /tmp/catalog.sqlite

Upload the database to data/databases/catalog.sqlite, copy the two example tools into data/tools, and enable search_catalog and match_garment. The builder script refuses to overwrite an existing database, publishes it only after a successful build, and removes temporary files on failure so a corrected snapshot can be retried. It excludes photo references whose retailer filename identifies a different style. The database contains products, verified image references and an FTS5 text index. Snapshot dates come from the source product notes; missing dates stay unknown. It contains no live inventory or image embeddings.

The agent describes the visible garment, searches with hasPhotos: true, then passes up to four returned styleIds to match_garment. The comparison tool makes one LLM call with the current camera frame and those catalog photos; catalog retrieval stays in the search tool. Ask to see the tag when an exact style cannot be established visually.