Skip to content

Use Composites

Composites are JavaScript functions that orchestrate multiple API calls in a single tool invocation. They run in ToolMesh’s sandboxed goja runtime and can call any tool defined in the same backend.

Use composites when:

  • A workflow requires multiple API calls in sequence (e.g. create a resource, then configure it)
  • You want to transform or aggregate data from several endpoints
  • An operation needs conditional logic the AI agent shouldn’t have to figure out
  • You want to reduce token usage by collapsing multi-step tasks into one tool call

Composites are defined alongside tools in the DADL file:

backend:
name: my-api
# ... auth, defaults, tools ...
composites:
create_and_configure:
description: "Create a new project and set its default settings"
params:
name:
type: string
required: true
description: "Project name"
template:
type: string
description: "Template to apply (default: blank)"
code: |
const project = await tools.create_project({ name: params.name });
if (params.template) {
await tools.apply_template({
project_id: project.id,
template: params.template
});
}
await tools.update_settings({
project_id: project.id,
notifications: true,
visibility: "team"
});
return project;
timeout: 30s

Inside composite code, all tools from the same backend are available via tools.<tool_name>(params):

// Call a tool and get the response
const repos = await tools.list_repos({ owner: params.owner });
// Use the result in another call
for (const repo of repos) {
const issues = await tools.list_issues({
owner: params.owner,
repo: repo.name,
state: "open"
});
}

Composite parameters work the same way as tool parameters — they define what the AI agent passes in:

params:
owner:
type: string
required: true
description: "Repository owner"
include_forks:
type: boolean
description: "Include forked repos (default: false)"

Inside the code, access them via params.owner, params.include_forks, etc.

Use standard try/catch. Errors propagate to the AI agent as tool errors:

try {
const result = await tools.delete_item({ id: params.id });
return { deleted: true, id: params.id };
} catch (e) {
return { deleted: false, error: e.message };
}

If a composite requires specific tools to exist, declare them:

composites:
full_report:
description: "Generate a full project report"
depends_on:
- list_projects
- get_project_stats
- list_members
code: |
// ...

This helps with validation and documentation.

From the GitHub DADL — a composite that forks a repo and creates a branch in one call:

composites:
fork_and_branch:
description: "Fork a repo and create a working branch"
params:
owner:
type: string
required: true
repo:
type: string
required: true
branch_name:
type: string
required: true
depends_on:
- fork_repo
- create_branch
- get_branch
code: |
const fork = await tools.fork_repo({
owner: params.owner,
repo: params.repo
});
// Wait for fork to be ready
const base = await tools.get_branch({
owner: fork.owner.login,
repo: fork.name,
branch: fork.default_branch
});
const branch = await tools.create_branch({
owner: fork.owner.login,
repo: fork.name,
ref: "refs/heads/" + params.branch_name,
sha: base.commit.sha
});
return { fork, branch: params.branch_name };
timeout: 60s

Set a timeout to limit execution time. Composites that call many endpoints or loop over large datasets should use generous timeouts:

timeout: 60s # default is typically 30s