How-Tos

Build a tool

A tool package is an AWORG Capability, a folder of small Python files. Here's the whole contract.

What a tool package is

In AWORG, a Tool is one thing the Resident can call, and a Capability is a folder of related tools that the owner switches on and off as a unit. A tool package in the store is one Capability.

weather/
  __init__.py        the capability: its label and description
  get_weather.py     one tool
  _helpers.py        anything starting with _ is not a tool

The folder name is the capability's name, and the package's name.

__init__.py

LABEL = "Weather"
DESCRIPTION = (
    "Looking up the weather anywhere by name: what it is doing there now, "
    "and the days ahead."
)
VERSION = "1.0.0"      # optional; the store reads it
LICENSE = "MIT"        # optional
HOMEPAGE = "https://example.com/weather"   # optional

The owner sees LABEL and DESCRIPTION in the Capabilities pane.

A tool file

Each .py beside __init__.py that doesn't start with _ is a tool. It declares three constants and one async function:

from __future__ import annotations

import httpx

from aworg.tools.base import ToolContext, ToolError, ToolResult

NAME = "get_weather"

DESCRIPTION = (
    "Look up the current weather and the forecast for a place, by name. "
    "Give a town, city, postcode or landmark."
)

INPUT_SCHEMA = {
    "type": "object",
    "properties": {
        "place": {"type": "string", "description": "Where to look up."},
        "days": {"type": "integer", "description": "Days of forecast, 0 to 7."},
    },
    "required": ["place"],
}


async def run(context: ToolContext, place: str, days: int = 3) -> ToolResult:
    if not place.strip():
        raise ToolError("get_weather needs a place to look up.")
    async with httpx.AsyncClient(timeout=20) as client:
        ...
    return ToolResult(
        text="Kyoto: 18°C, light rain ...",   # what the model reads
        summary="18°C, light rain",           # the one-line version shown in the interface
        payload={"raw": "..."},               # the full data, for the owner
    )
  • NAME is what the model calls. Keep it unique; service_verb works well.
  • DESCRIPTION is what the model reads to decide whether to call it. Say what it does, what to give it and what comes back.
  • INPUT_SCHEMA is JSON Schema for the arguments. They arrive as keyword arguments to run.
  • run gets a ToolContext first and returns a ToolResult.
  • Raise ToolError with a sentence the model can act on ("No place called 'Daytn' was found. Check the spelling.") rather than letting a traceback escape.

The store reads NAME, DESCRIPTION and LABEL without running your code, the same way AWORG does, so they must be plain string literals, not built from function calls or f-strings.

Cleaning up on reload

AWORG reloads a capability when its files change. Before dropping your modules, it calls unload() in any of them that defines one, sync or async. If your tool keeps something alive between calls, such as a browser, a connection or a subprocess, close it there, or it's orphaned when the new version loads:

async def unload() -> None:
    await _client.aclose()

Files your capability creates in its own folder (a downloaded binary, a cache) are kept when aworg get installs a new version. Only what the store shipped is replaced.

The rules of an installed capability

  1. Import absolutely. from aworg.tools.base import ..., not from ..base import .... Your folder lives in the owner's home, not inside the package.
  2. It runs as the Aworg. Your code loads into AWORG's process, with its interpreter and its permissions. Nothing sandboxes it. That's what makes tools powerful, and it's why owners are told to read tools before installing them. Keep yours short enough to read.
  3. Use what AWORG has, or bring your own. httpx is always there. Anything else, you have to vendor into your folder or install at run time. The Chromium capability, for example, downloads its browser with an install_engine tool rather than shipping it.
  4. Don't try to hide. INTERNAL and REQUIRED are reserved for AWORG's own capabilities. The store refuses packages that declare them, and AWORG ignores them.
  5. You can't replace a built-in. filesystem, shell, http, application, delegation, journal, knowledge and planning are taken.

What the context gives you

  • context.paths.workspace is the Living Workspace, the application's files.
  • context.paths.home is the Aworg's home, ~/.aworg. Keep large downloads outside your capability folder so a reinstall doesn't throw them away.
  • context.host has facts about the machine: OS, Python, CPU and so on.
  • context.progress(fraction, detail) reports how far a slow tool has got.
  • context.result_limit is the most text the model can take back. Trim to fit.

Images

A tool can show the model a picture by returning images=[{"media_type": "image/png", "data": "<base64>"}] in its ToolResult, alongside the text.

Try it locally

cp -r weather ~/.aworg/capabilities/

A running Aworg notices within seconds; no restart. Every time you save a change, the next message runs the new code. If yours doesn't appear in the Capabilities pane, AWORG shows the reason it refused it.

Publish it

Zip the folder and upload it. See Publish a package.