Skip to main content

Chapter 24: Pack-to-Pack Communication

TL;DR

  1. Declare the dependency in your aip.json file:
    "dependencies": ["worka/browser"]
  2. To call from your frontend: Use the useTool hook and provide the target object.
    const browserTarget = useMemo(() => ({ tenant: 'worka', name: 'browser' }), []);
    const { call: goToPage } = useTool('go_to_page', browserTarget);
    goToPage({ url: 'https://worka.ai' });
  3. To call from your backend: Use brokered elicitation. Send an elicit request with a worka_action in the _meta field, specifying the target pack, tool, and params.

No pack is an island. The true power of the Worka ecosystem comes from building packs that can use the capabilities of others. For example, your pack could use the powerful browser automation tools from the worka/browser pack, or the file system tools from the worka/fs pack, without having to re-implement that logic yourself.

Step 1: Declare Your Dependencies

Before your pack can communicate with another, you must declare its dependency in your aip.json file. This is a strict security requirement enforced by the Worka Host. If you attempt to call a tool on a pack that is not listed as a dependency, the Host will block the request.

You declare dependencies in a simple array:

{
...
"dependencies": [
"worka/browser",
"another-author/some-other-pack"
]
}

Method 1: Calling from the Frontend

Calling another pack's tool from your UI is the most common scenario. The process is identical to calling a tool on a virtual pack. You use the useTool hook and provide a target object that specifies the tenant and name of the pack you want to call.

Example: Using the worka/browser pack to go to a URL.

import { useTool } from '@worka-ai/sdk';
import { useMemo } from 'react';
import { Button } from '@chakra-ui/react';

function BrowserCaller() {
// Memoize the target object for the browser pack
const browserTarget = useMemo(() => ({ tenant: 'worka', name: 'browser' }), []);

// Get the 'go_to_page' tool from that pack
const { call: goToPage, isLoading } = useTool('go_to_page', browserTarget);

return (
<Button
isLoading={isLoading}
onClick={() => goToPage({ url: 'https://worka.ai' })}
>
Open Worka Website
</Button>
);
}

Method 2: Calling from the Backend

In more complex workflows, a tool in your backend may need to call a tool in another pack's backend. For example, your tool might generate some text and then need to pass it to a tool in a file-system pack to save it.

As always, direct communication between pack containers is forbidden. You must use the brokered elicitation pattern, just as you did for database access.

Your backend tool constructs an elicit request containing a worka_action. This action specifies the target pack you want to call.

Conceptual Example (Rust):

// In your tool function...

async fn my_tool(req: ToolRequest) -> ToolResponse {
let content_to_save = "This is the content for the file.";

// 1. Construct the worka_action targeting the browser pack
let save_action = json!({
"target": { "tenant": "worka", "name": "fs" },
"tool": "save_file",
"params": {
"path": "~/Documents/output.txt",
"content": content_to_save
}
});

// 2. Elicit the action from the host
let elicit_request = CreateElicitationRequestParam {
message: "Saving file...".to_string(),
meta: Some(Meta(serde_json::Map::from_iter(vec![(
"worka_action".to_string(),
save_action,
)]))),
..
};

// 3. The Host validates the dependency, calls the fs pack's tool,
// and returns the result here.
match context.elicit(elicit_request).await {
Ok(response) => ToolResponse::success(response.content.unwrap_or_default()),
Err(e) => ToolResponse::internal_error(e.to_string()),
}
}

This secure, brokered approach is fundamental to Worka, enabling a rich and composable ecosystem where packs can safely build upon each other's functionality.