Build your first agents with wxctl by creating the calculator-weather-agents example from scratch: two Python tools, a knowledge base, and two agents (one delegates to the other). You write every file by hand (each one is copy-paste ready below), then deploy and test it. It needs only a watsonx Orchestrate profile, and both tools are self-contained, so there is nothing external to set up. This assumes wxctl is already installed; if not, see Installation.
Prefer not to type it out? The finished example ships in the repo. Clone it with git clone https://github.com/randyphoa/wxctl.git, cd wxctl/examples/primitives/calculator-weather-agents, and skip to Create your profile. Every file below is identical to what you would clone.
1

Create the project

Make the directory layout the config expects. Tools load their source from a source_path directory, and the knowledge base loads its document by path, all resolved relative to config.yaml.
mkdir -p calculator-weather-agents/resources/knowledge_base \
         calculator-weather-agents/resources/tool/calculator \
         calculator-weather-agents/resources/tool/weather
cd calculator-weather-agents
You will fill in this tree over the next steps. Every command from here runs from the calculator-weather-agents/ directory.
calculator-weather-agents/
├── config.yaml                        # every resource: 1 KB, 2 tools, 2 agents, 4 tests
└── resources/
    ├── knowledge_base/
    │   └── ibm_history.txt            # grounding document for the knowledge base
    └── tool/
        ├── calculator/                # calculator_tool implementation
        │   ├── calculator.py          #   add / multiply / divide
        │   ├── schema.yaml            #   input + output JSON schema
        │   └── requirements.txt       #   Python dependencies
        └── weather/                   # weather_tool implementation
            ├── weather.py             #   bundled mock city forecasts
            ├── schema.yaml
            └── requirements.txt
2

Add the knowledge base document

The knowledge base is grounded in one plain-text document. Two of the tests assert facts taken straight from it (IBM founded in 1911 as the Computing-Tabulating-Recording Company). Create resources/knowledge_base/ibm_history.txt:
resources/knowledge_base/ibm_history.txt
IBM: COMPANY HISTORY REFERENCE
==============================

Section 1: Founding
-------------------
IBM was founded on June 16, 1911, in Endicott, New York, as the
Computing-Tabulating-Recording Company (CTR). CTR was formed by financier
Charles Ranlett Flint as a merger of three existing firms: the Tabulating
Machine Company (founded by Herman Hollerith, inventor of the punched-card
tabulating machine), the International Time Recording Company, and the
Computing Scale Company of America.

Section 2: The IBM Name
-----------------------
Thomas J. Watson Sr. joined CTR in 1914 as general manager and became
president the following year. Under his leadership the company adopted the
name International Business Machines Corporation (IBM) on February 14, 1924.
The name had already been in use by the company's Canadian subsidiary.

Section 3: Early Products
-------------------------
CTR's early product lines came directly from its three founding firms:
punched-card tabulating machines and sorters, employee time clocks and time
recorders, and commercial weighing scales. Punched-card data processing
became the company's core business and remained so into the era of
electronic computing.

Section 4: Major Milestones
---------------------------
1944: The Automatic Sequence Controlled Calculator (Harvard Mark I),
built by IBM, is presented to Harvard University.
1956: Thomas J. Watson Jr. succeeds his father as chief executive.
1964: IBM announces System/360, the first family of compatible mainframe
computers, one of the largest privately financed commercial projects of
its time.
1981: IBM releases the IBM Personal Computer (model 5150), which sets the
standard for the PC industry.
1997: IBM's Deep Blue chess computer defeats world champion Garry Kasparov
in a six-game match.
2011: IBM's Watson system wins the Jeopardy! quiz show against the two
best human champions, in the company's centennial year.
2019: IBM completes its acquisition of Red Hat, its largest acquisition,
anchoring its hybrid-cloud strategy.
2023: IBM launches watsonx, its enterprise AI and data platform.

Section 5: IBM Today
--------------------
IBM is headquartered in Armonk, New York, and focuses on hybrid cloud and
artificial intelligence, spanning software, consulting, and infrastructure.
Research has been a constant thread: IBM employees have earned Nobel Prizes
and IBM has been among the top recipients of U.S. patents for decades.
3

Add the calculator tool

A Python tool is a directory holding the implementation, its schema, and its dependencies. main() is the entry point the config names in binding.python.function, and schema.yaml is the source of truth for the tool’s input and output schema at apply time.Create resources/tool/calculator/calculator.py:
resources/tool/calculator/calculator.py
"""
Calculator tool for watsonx Orchestrate
Supports add, multiply, and divide operations
"""


def add(a: float, b: float) -> float:
    """
    Add two numbers together

    Args:
        a: First number
        b: Second number

    Returns:
        Sum of a and b
    """
    return a + b


def multiply(a: float, b: float) -> float:
    """
    Multiply two numbers

    Args:
        a: First number
        b: Second number

    Returns:
        Product of a and b
    """
    return a * b


def divide(a: float, b: float) -> float:
    """
    Divide two numbers

    Args:
        a: First number (dividend)
        b: Second number (divisor)

    Returns:
        Quotient of a divided by b
    """
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b


def main(operation: str, a: float, b: float) -> dict:
    """
    Main entry point for the tool

    Args:
        operation: The mathematical operation to perform ('add', 'multiply', or 'divide')
        a: The first numeric operand to use in the calculation
        b: The second numeric operand to use in the calculation

    Returns:
        Result dictionary with operation details and calculated result
    """
    if operation == "add":
        result = add(a, b)
    elif operation == "multiply":
        result = multiply(a, b)
    elif operation == "divide":
        try:
            result = divide(a, b)
        except ValueError as e:
            return {"error": str(e)}
    else:
        return {"error": f"Unknown operation: {operation}"}

    return {
        "operation": operation,
        "a": a,
        "b": b,
        "result": result
    }
Create resources/tool/calculator/schema.yaml:
resources/tool/calculator/schema.yaml
input_schema:
    type: object
    properties:
        operation:
            type: string
            description: The mathematical operation to perform. Supported operations are 'add' (addition), 'multiply' (multiplication), and 'divide' (division).
            enum:
                - add
                - multiply
                - divide
        a:
            type: number
            description: The first numeric operand to use in the calculation. Can be any integer or decimal number.
        b:
            type: number
            description: The second numeric operand to use in the calculation. Can be any integer or decimal number.
    required:
        - operation
        - a
        - b
output_schema:
    type: object
    description: Result of the calculation
    properties:
        operation:
            type: string
        a:
            type: number
        b:
            type: number
        result:
            type: number
            description: The calculated result
Create resources/tool/calculator/requirements.txt:
resources/tool/calculator/requirements.txt
ibm-watsonx-orchestrate==1.6.1
4

Add the weather tool

The second tool returns bundled mock forecasts for a handful of cities, with a fair-weather fallback for anything else, so it runs with no external weather API.Create resources/tool/weather/weather.py:
resources/tool/weather/weather.py
"""
Weather forecast tool for watsonx Orchestrate
Returns mocked weather forecast data for a given city
"""


def main(city: str) -> dict:
    """
    Main entry point for the tool

    Args:
        city: The name of the city to get the weather forecast for

    Returns:
        Result dictionary with weather forecast details
    """
    forecasts = {
        "new york": {
            "city": "New York",
            "temperature_f": 35,
            "condition": "Partly Cloudy",
            "humidity": 55,
            "wind_mph": 12,
            "forecast": "Cold with partly cloudy skies. Expect temperatures around 35°F with moderate winds.",
        },
        "london": {
            "city": "London",
            "temperature_f": 45,
            "condition": "Rainy",
            "humidity": 80,
            "wind_mph": 8,
            "forecast": "Overcast with light rain throughout the day. Temperatures around 45°F.",
        },
        "tokyo": {
            "city": "Tokyo",
            "temperature_f": 50,
            "condition": "Sunny",
            "humidity": 40,
            "wind_mph": 5,
            "forecast": "Clear skies and sunny. Pleasant temperatures around 50°F with light winds.",
        },
        "sydney": {
            "city": "Sydney",
            "temperature_f": 78,
            "condition": "Sunny",
            "humidity": 60,
            "wind_mph": 10,
            "forecast": "Warm and sunny. Temperatures around 78°F with a gentle breeze.",
        },
    }

    lookup = city.strip().lower()
    if lookup in forecasts:
        return forecasts[lookup]

    return {
        "city": city,
        "temperature_f": 65,
        "condition": "Clear",
        "humidity": 50,
        "wind_mph": 7,
        "forecast": f"Fair weather expected in {city}. Temperatures around 65°F with calm winds.",
    }
Create resources/tool/weather/schema.yaml:
resources/tool/weather/schema.yaml
input_schema:
    type: object
    properties:
        city:
            type: string
            description: The name of the city to get the weather forecast for (e.g. "New York", "London", "Tokyo").
    required:
        - city
output_schema:
    type: object
    description: Weather forecast result for the requested city
    properties:
        city:
            type: string
        temperature_f:
            type: number
            description: Temperature in Fahrenheit
        condition:
            type: string
            description: Current weather condition
        humidity:
            type: number
            description: Humidity percentage
        wind_mph:
            type: number
            description: Wind speed in miles per hour
        forecast:
            type: string
            description: Human-readable weather forecast summary
Create resources/tool/weather/requirements.txt:
resources/tool/weather/requirements.txt
ibm-watsonx-orchestrate==1.6.1
5

Write the config

config.yaml is the whole deployment: nine YAML documents separated by ---, the knowledge base, the two tools, the two agents, then the four tests. The ${tool.…}, ${knowledge_base.…}, and ${agent.…} references wire the resources together; wxctl turns them into a dependency graph and creates each resource in order. The tool blocks declare no input_schema: each tool loads its schema from the schema.yaml you created beside it, which is the source of truth at apply time. Create config.yaml:
config.yaml
kind: knowledge_base
ref_name: ibm_history_kb
name: ibm_history_kb
display_name: IBM History Knowledge Base
description: |
  General information about IBM: its founding, the origin of the name,
  early products, and major milestones.
documents:
  - path: ./resources/knowledge_base/ibm_history.txt
---
kind: tool
ref_name: calculator_tool
name: calculator_tool
display_name: Calculator Tool
description: |
  A calculator that adds, multiplies, or divides two numbers.
  Use for any arithmetic the user requests.
permission: read_only
is_async: false
source_path: ./resources/tool/calculator
binding:
  python:
    function: calculator:main
---
kind: tool
ref_name: weather_tool
name: weather_tool
display_name: Weather Tool
description: |
  Returns the current weather and a short forecast for a given city.
  Use whenever the user asks about weather conditions.
permission: read_only
is_async: false
source_path: ./resources/tool/weather
binding:
  python:
    function: weather:main
---
kind: agent
ref_name: calculator_agent
name: calculator_agent
display_name: Calculator Agent
description: |
  Performs arithmetic with the calculator tool and answers questions
  about IBM's history from the knowledge base.
instructions: |
  Use the calculator tool for any arithmetic the user requests; do not
  compute results yourself.
  Use the IBM history knowledge base for questions about IBM and cite it.
  Keep answers concise.
llm: groq/openai/gpt-oss-120b
style: default
tools:
  - ${tool.calculator_tool}
knowledge_base:
  - ${knowledge_base.ibm_history_kb}
chat_with_docs:
  enabled: true
additional_properties:
  icon: '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect fill="#0f62fe" width="64" height="64" rx="8"/><rect x="16" y="10" width="32" height="44" rx="4" fill="#fff"/><rect x="20" y="14" width="24" height="10" rx="2" fill="#0f62fe"/><circle cx="24" cy="32" r="3" fill="#0f62fe"/><circle cx="32" cy="32" r="3" fill="#0f62fe"/><circle cx="40" cy="32" r="3" fill="#0f62fe"/><circle cx="24" cy="40" r="3" fill="#0f62fe"/><circle cx="32" cy="40" r="3" fill="#0f62fe"/><circle cx="40" cy="40" r="3" fill="#0f62fe"/><circle cx="24" cy="48" r="3" fill="#0f62fe"/><circle cx="32" cy="48" r="3" fill="#0f62fe"/><circle cx="40" cy="48" r="3" fill="#0f62fe"/></svg>'
  welcome_content:
    welcome_message: Hello, I'm your Calculator Agent
    description: I perform calculations and answer questions about IBM's history from my knowledge base.
  starter_prompts:
    customize:
      - title: Calculate
        subtitle: Do some math
        prompt: What is 42 multiplied by 17?
        state: "active"
      - title: IBM History
        subtitle: Ask about IBM
        prompt: When was IBM founded and what were its early products?
        state: "active"
---
kind: agent
ref_name: weather_agent
name: weather_agent
display_name: Weather Agent
description: |
  Reports the current weather and a short forecast for a city, and
  delegates any arithmetic to the Calculator Agent.
instructions: |
  Use the weather tool to answer weather questions; never invent
  conditions yourself.
  Delegate any arithmetic to the Calculator Agent collaborator.
llm: groq/openai/gpt-oss-120b
style: default
tools:
  - ${tool.weather_tool}
collaborators:
  - ${agent.calculator_agent}
additional_properties:
  icon: '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect fill="#0f62fe" width="64" height="64" rx="8"/><circle cx="26" cy="28" r="10" fill="#ffdd57"/><path d="M44 36c0-5.5-4.5-10-10-10a10 10 0 0 0-9.8 8A8 8 0 0 0 16 42a8 8 0 0 0 8 8h20a7 7 0 0 0 0-14z" fill="#fff"/></svg>'
  welcome_content:
    welcome_message: Hello, I'm your Weather Agent
    description: I report city weather and hand any math off to the Calculator Agent.
  starter_prompts:
    customize:
      - title: Weather
        subtitle: Check a city
        prompt: What's the weather in Tokyo right now?
        state: "active"
---
kind: test
ref_name: test_01_calculator
agent: ${agent.calculator_agent}
turns:
  - message: "What is 42 multiplied by 17?"
    expect_tools:
      - calculator_tool
    expect_answer: "Should state the result is 714, computed with the calculator tool"
---
kind: test
ref_name: test_02_ibm_history
agent: ${agent.calculator_agent}
turns:
  - message: "When was IBM founded and under what name?"
    expect_answer: "Should state IBM was founded in 1911 as the Computing-Tabulating-Recording Company (CTR), citing the knowledge base"
---
kind: test
ref_name: test_03_weather
agent: ${agent.weather_agent}
turns:
  - message: "What's the weather in Tokyo right now?"
    expect_tools:
      - weather_tool
    expect_answer: "Should report Tokyo as sunny with temperatures around 50°F, from the weather tool"
---
kind: test
ref_name: test_04_delegation
agent: ${agent.weather_agent}
turns:
  - message: "What is 19 plus 23?"
    expect_answer: "Should answer 42, delegating the arithmetic to the Calculator Agent collaborator"
6

Create your profile

A profile tells wxctl where your services live and how to authenticate. Every resource in this example is a watsonx Orchestrate resource, so the profile needs a single watsonx_orchestrate block.Create ~/.wxctl/profiles.yaml with exactly this content, then replace the three values called out below:
~/.wxctl/profiles.yaml
profiles:
  default:
    deployment: saas
    watsonx_orchestrate:
      url: "https://api.us-south.watson-orchestrate.cloud.ibm.com/instances/<INSTANCE_ID>"
      auth_type: apikey
      apikey: "••••••••••••••••••••••••••••••••"
ReplaceWithWhere to find it
us-southYour instance region, if different (for example eu-de, au-syd)The host in your Orchestrate instance URL
<INSTANCE_ID>Your Orchestrate instance ID (a GUID)watsonx Orchestrate → your instance → Settings → API details, service instance URL
••••…Your IBM Cloud API keycloud.ibm.comManage → Access (IAM) → API keys → Create
Keep this file private; it holds a credential. To avoid writing the key to disk, set it in your shell and point the profile at it instead: apikey: "${env:WXCTL_WATSONX_ORCHESTRATE_APIKEY}".
Prefer to scaffold instead of copy-paste? Run wxctl init -f config.yaml from this directory. It writes this same watsonx_orchestrate block with placeholders you then fill in. For the full profile format, Software (Cloud Pak for Data) auth, and every auth type, see Profiles & credentials.
7

Validate the profile

Confirm wxctl can reach Orchestrate and authenticate before you deploy anything.
wxctl profile validate
It prints a per-service line and exits non-zero on failure. A wrong host, instance ID, or key is caught here.
8

Preview the plan

A dry run that shows the dependency graph and every resource wxctl would create. Nothing is changed.
wxctl plan -f config.yaml
9

Apply

Validate, plan, then create the resources: the knowledge base, the two Python tools, and the two agents (five resources).
wxctl apply -f config.yaml
wxctl builds a DAG from the ${kind.ref_name} references in the config and creates resources in dependency order, resolving each reference into the ID the API expects.
10

Test

Run the four kind: test checks you wrote against the deployed agents.
wxctl test -f config.yaml
The checks confirm the Calculator Agent calls calculator_tool for 42 × 17 (714) and answers the “when was IBM founded” question (1911, as CTR) from the knowledge base, and the Weather Agent calls weather_tool for Tokyo and answers 19 + 23 (42) by delegating to the Calculator Agent.
11

Tear down

Remove everything the example created when you are done.
wxctl destroy -f config.yaml

What you just deployed

The single config.yaml holds nine YAML documents separated by ---: five resources you deploy plus four kind: test checks.
  • 1 knowledge base, ibm_history_kb, grounded in the short IBM company-history document.
  • 2 Python tools, calculator_tool (add / multiply / divide) and weather_tool (mock city forecasts).
  • 2 agents: the Calculator Agent (calculator tool + knowledge base, chat_with_docs enabled) and the Weather Agent (weather tool), which lists the Calculator Agent as a collaborators entry so it delegates arithmetic to it.
  • 4 tests: a calculation that must call calculator_tool, an IBM-history question answered from the knowledge base, a Tokyo forecast that must call weather_tool, and a math question to the Weather Agent that exercises delegation.
Resources reference each other with ${kind.ref_name} (the resource’s ID after creation) or ${kind.ref_name.field} (a specific field). That wiring is the DAG wxctl builds, then applies in dependency order.

Core commands

CommandDescription
wxctl init [-f <file>]Scaffold a profile with service URLs, auth, and credentials
wxctl profile validateCheck the profile against the live services
wxctl plan -f <file>Dry run that shows what would change
wxctl apply -f <file>Validate, plan, and execute
wxctl test -f <file>Run kind: test checks against deployed resources
wxctl destroy -f <file>Tear down the resources in a config
wxctl resourcesList the resource kinds wxctl supports
wxctl explain <kind>Show a kind’s fields, dependencies, and endpoints
-f accepts files, directories, or - for stdin, and is repeatable. Global flags: -p, --profile <name> selects a profile, --profile-path <path> uses a custom profile file, --full-trace captures full-fidelity run records.

Next steps

Build it with IBM Bob

The other way to build this: describe the scenario in one sentence and let IBM Bob compose it over MCP.

Worked example

Build a tool, agent, and test from scratch, one resource at a time.

Profiles & credentials

Every auth type, the Software (CP4D) shape, and ${env:VAR}.

Resource kinds

Every kind wxctl supports, with its deployment and endpoint.

Declarative model

Resources, ${kind.ref_name} references, and how the DAG is built.