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.
When to use composites
Section titled “When to use composites”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
Basic structure
Section titled “Basic structure”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: 30sCalling tools
Section titled “Calling tools”Inside composite code, all tools from the same backend are available via tools.<tool_name>(params):
// Call a tool and get the responseconst repos = await tools.list_repos({ owner: params.owner });
// Use the result in another callfor (const repo of repos) { const issues = await tools.list_issues({ owner: params.owner, repo: repo.name, state: "open" });}Parameters
Section titled “Parameters”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.
Error handling
Section titled “Error handling”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 };}Dependencies
Section titled “Dependencies”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.
Real-world example
Section titled “Real-world example”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: 60sTimeouts
Section titled “Timeouts”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 30sNext steps
Section titled “Next steps”- Browse the Registry to see composites in real DADL files
- Read the DADL Specification for the full composites schema
- Write a DADL file that includes composites