Chapter 24: Pack-to-Pack Communication
TL;DR
- Declare the dependency in your
aip.json
file:"dependencies": ["worka/browser"]
- To call from your frontend: Use the
useTool
hook and provide thetarget
object.const browserTarget = useMemo(() => ({ tenant: 'worka', name: 'browser' }), []);
const { call: goToPage } = useTool('go_to_page', browserTarget);
goToPage({ url: 'https://worka.ai' }); - To call from your backend: Use brokered elicitation. Send an
elicit
request with aworka_action
in the_meta
field, specifying thetarget
pack,tool
, andparams
.
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.