Your first agent
In this tutorial you build a small package: a product coach that answers
questions about Apollia from a glossary. The agent is a few lines of
apollia.toml; the glossary is one Python tool of about thirty lines. You
install the package into a running Apollia runtime and talk to the agent from
the command line. Plan for around fifteen minutes.
By the end you will have seen the whole loop that every package goes through: declare the agent, write a tool, describe, build, install, run.
Before you start
-
Apollia installed, with the
apollia-oscommand on yourPATH. -
The SDK importable by the Python you work with (
pip install -e ./sdkfrom a clone of the repository, in a virtual environment). It is needed to describe and to test the package, not to build or install it. -
The runtime running, in a second terminal:
apollia-os start --port 7771package install,runandllm statusall talk to it; without it they exit withruntime not started (connection refused). Leave it running for the whole tutorial and stop it withapollia-os stopat the end. -
An LLM backend configured. Check it with
apollia-os llm status. If nothing is set up yet, register a local model withapollia-os llm setup --local --model /path/to/model.gguf, or an OpenAI-compatible server you designate withapollia-os llm backends create <name> --provider openai --base-url <url> --model <m>(add--api-key <key>when the server requires one), then runapollia-os llm reload. See Install and run the runtime and the CLI reference for everyllmsubcommand.
You do not need to know the SDK yet. This tutorial introduces one decorator; the rest is ordinary Python and a TOML file.
Step 1: declare the agent
A package is a folder with an apollia.toml at its root. Create a folder
called coach, and in it the file apollia.toml:
[package]
name = "coach"
version = "0.1.0"
publisher = "You"
description = "A product coach that answers questions about Apollia from a small glossary."
sdk = ">=0.3.0"
[[agents]]
id = "coach"
name = "Coach"
mission = """
You are a product coach for Apollia. Answer in the language of the person, in
two to four sentences.
When the person asks what a term means, call the tool `define` with that term
and build your answer on the definition it returns. When the tool knows no
such term, say so plainly, list the terms it knows, and point the person to
the documentation. Never invent a definition.
"""
examples = [
"What is a package?",
"What is the difference between a tool and a workflow?",
]
[agents.defaults]
access = ["pkg:coach/define"]
autonomy = "assisted"
That is the whole agent:
[[agents]]declares it.missionis its instructions, written as you would brief a colleague.examplesare the requests its card shows.[agents.defaults]says what the package recommends.accesslists the tools the agent may call, one by one:pkg:coach/defineis the tooldefineof the packagecoach, which the next step writes. The person who installs the package can grant or revoke each access afterwards; a default is never a constraint.
An agent is data. No Python class or object stands for it, and the runtime loads no agent from a Python module: the engine built into the runtime runs the agent from this entry, with its mission, the tools it was granted and the model you configured.
Step 2: write the tool
The Python of a package brings tools. A tool is an async function under
@tool. Create coach/tools/glossary.py:
"""The glossary tool of the package coach."""
from typing import Annotated
from apollia import tool
from apollia.types import Ctx
GLOSSARY = {
"agent": "A mission, examples and granted tools, written as data in apollia.toml.",
"package": "A folder with an apollia.toml: any mix of agents, tools and workflows.",
"tool": "An async Python function under @tool, which an agent may be granted.",
"workflow": "An async Python function under @workflow, whose steps are declared ahead.",
}
@tool(
"define",
"Look a term up in the Apollia glossary.",
action="read",
examples=[{"term": "package"}],
)
async def define(
ctx: Ctx,
term: Annotated[str, "The term to define, one or two words."],
) -> dict[str, object]:
key = term.strip().lower()
ctx.logger.info("define called for %s", key)
if key not in GLOSSARY:
return {"found": False, "known_terms": sorted(GLOSSARY)}
return {"found": True, "term": key, "definition": GLOSSARY[key]}
Four things to read here:
- The name and the description are what the model sees. The name is the
last part of
pkg:coach/define. action="read"is the family of the tool: what calling it does to the world. There are five (read,read_personal,write_or_delete,send_external,execute_code), and every family exceptreadis sensitive: the runtime asks the person before such a tool runs.- The signature is the schema.
ctxcomes first and is left out; every other parameter is an input, andAnnotatedcarries the description the model reads.examplesare sample inputs shown beside the schema. ctxis the context the runtime hands the function. This tool only usesctx.logger, a standardlogging.Logger. The full contract is the SDK / ctx reference.
Use absolute imports (from apollia import ...), never relative ones.
Step 3: test the tool
A tool is a plain async def function: a test awaits it as written, with a
MockContext in place of the runtime. Create coach/tests/test_glossary.py:
import asyncio
from apollia.testing import MockContext
from tools.glossary import define
def test_define_returns_the_definition_of_a_known_term():
# GIVEN a context and a term of the glossary, in another case
ctx = MockContext()
# WHEN the tool is awaited as written
result = asyncio.run(define(ctx, " Package "))
# THEN the definition comes back under the normalised term
assert result["found"] is True
assert result["term"] == "package"
Run it from the package folder:
cd coach
python -m pytest -p no:cacheprovider tests
cd ..
-p no:cacheprovider keeps pytest from leaving a .pytest_cache folder
behind: the build of the next step takes every file of the package folder.
Step 4: describe and build
apollia.toml must say which tools the Python defines, so that reading the
manifest never runs the code. You do not write those entries by hand:
python -m apollia describe coach
The command imports the modules of the folder and writes a [[tools]] entry
for define at the end of apollia.toml: its module, its description, its
family and its input schema. Run it again after every change to a decorated
function.
Then build the archive and read it back:
apollia-os package build coach
apollia-os package inspect coach-0.1.0.apo
build validates the manifest and writes coach-0.1.0.apo in the current
folder. No Python runs. If the manifest names a tool the package does not
define, or the Python defines one the manifest does not describe, this is
where you find out. inspect prints the manifest, the entries of the archive
and its signature state, unsigned here.
Step 5: install
preview shows what the package contains and asks for, before anything is
written. install copies it into the package store of the runtime:
apollia-os package preview coach-0.1.0.apo
apollia-os package install coach-0.1.0.apo
An unsigned package installs with a notice. Confirm the agent is there:
apollia-os agent list --definitions
Step 6: talk to it
run sends one message to the agent and prints the reply:
apollia-os run coach "What is a package?"
You should get a two to four sentence answer built on the glossary. Ask it about a term the glossary does not hold and it tells you so, because the mission instructed it to.
apollia-os run is a single call. For an ongoing back and forth, use
apollia-os chat or the desktop app, where the agent appears on the Agents
screen with its mission, its examples and its accesses.
What you built
A package with one agent and one tool. The agent is data in apollia.toml: a
mission, examples, and the list of what it may reach. The tool is a Python
function the agent was granted. That is the smallest complete package that
carries code; a package with only [[agents]] and no Python is valid too.
Next steps
- Change the mission, build and install again: an update keeps the changes the person made to the agent on their side.
- Start the next package from the folder
apollia-os package new <name>writes, which builds as written. - Sign the archive with
apollia-os package build coach --sign, so that the person who installs it sees who published it. - Chain several agents and a question to the user in a workflow: Build a multi-agent assistant.