This release is a pre-release and may not be stable for production use.
swisper-agent-sdk
Build a Swisper domain agent.
This package is self-contained. You do not need the Swisper backend, a database, or network access to build an agent and test it.
Install
Requires Python 3.11 or newer.
python3 -m venv .venv
./.venv/bin/pip install swisper-agent-sdk
Use the virtual environment's interpreter for everything below. Typing python3
rather than ./.venv/bin/python is the most common way to get a
ModuleNotFoundError that looks like a broken package and is not.
Five declared dependencies pull in more than five packages. This package declares
pydantic,langgraph,jsonpath-ng,pyyamlandlangchain-core—langgraphalone brings its own dependency tree with it (langsmith,httpx,orjson,ormsgpack,zstandardand more). Measured in a clean install: 36 packages, not five. None of that is a defect — every one of them is somethinglanggraphgenuinely needs — but a partner watching 36 packages scroll by after being told about five deserves to have been told first.
From a directory, instead
Handed the package as a tarball or a source checkout rather than installing from
PyPI? Point pip at the directory instead of the package name — everything else
on this page works exactly the same either way:
python3 -m venv .venv
./.venv/bin/pip install -e ./swisper-agent-sdk
Your first agent
This is swisper_agent_sdk/examples/twelve_line_agent.py, complete and
unabridged. It ships inside the package itself, so installing it is the only step
— there is nothing separate to fetch:
from swisper_agent_sdk import Agent, Delegation, Reply
agent = Agent(
name="echo_agent",
description="Repeats the task back. The smallest thing that is still an agent.",
)
@agent.handler
async def handle(d: Delegation) -> Reply:
if not d.task:
return Reply.ask("What would you like me to do?")
return Reply(text=f"You asked me to: {d.task}")
Run it — the same command whether you installed from PyPI or from a directory:
$ ./.venv/bin/python -m swisper_agent_sdk.examples.twelve_line_agent
You asked me to: book a train to Bern
d.task is what the user asked for. Returning a Reply answers them;
returning Reply.ask(...) puts a question to them and pauses the turn.
Running a turn yourself
harness runs a real turn against your agent with no core, no database and
no network:
import asyncio
from swisper_agent_sdk import harness
from my_agent import agent
result = asyncio.run(harness.turn(agent, "book a train to Bern"))
print(result.text)
Check your install
Needs the directory install above — tests/ ships in a source checkout, not in
the plain PyPI wheel:
$ cd swisper-agent-sdk
$ ../.venv/bin/pip install -e ".[dev]"
$ ../.venv/bin/python -m pytest
That runs a handful of smoke tests: the package imports, importing it leaves your logging alone, an agent answers a turn offline, and every bundled example runs and prints something. If those pass, the SDK works on your machine.
Run
pytestfrom inside this directory. The package's ownasyncio_mode = "auto"setting lives in itspyproject.toml, and pytest only picks it up when invoked from here. Run it from the parent directory and everyasync deftest fails — including correct ones — which looks like a broken SDK and is not.
[dev] is what installs pytest and pytest-asyncio. The base install
deliberately does not: it is the runtime, not the toolchain.
Writing your own tests
turn() returns a TurnResult with .text, .status, .resume_with, .cards,
.said, and two helpers for assertions — .said_something_about(...) and
.asked_for(...):
async def test_it_answers():
r = await harness.turn(agent, "weather in Bern tomorrow")
assert r.said_something_about("Bern")
async def test_it_can_ask_a_question():
r = await harness.turn(agent, "book me a train")
assert r.asked_for("departure time")
r = await harness.answer(r, "09:00") # resumes the paused turn
harness.answer(...) resumes a turn your agent paused with Reply.ask(...).
Inside the handler, d.answer is what the user replied and d.resumed is
True.
What a green harness run does and does not prove. The harness fakes core, the capability endpoint and the token. A green harness run proves your agent's logic, not that the capability works against a live core. Those are different claims and only the first one is tested here.
Saying things, and sending cards
Two ways to put something in front of the user besides your final answer.
await d.say("…") narrates a deterministic moment — typically just before a slow
call, where waiting for the model to decide to mention it is the wrong behaviour:
await d.say("Checking which trains actually run at that hour…")
await d.card("swi-transport", payload) emits a card. The types your agent may
emit are declared on the agent, and an undeclared type raises
UndeclaredCardTypeError — never a silent drop:
agent = Agent(name="…", description="…", cards=["swi-transport"])
await d.card("swi-transport", {"from": "Zurich", "to": "Bern"}) # fine
await d.card("swi-invented", {...}) # UndeclaredCardTypeError
The declared list is also what the prompt tells the model about, so it cannot invent a
card type that has no renderer. Both are visible in tests as result.said and
result.cards.
A fuller example
swisper_agent_sdk/examples/meal_planning_agent.py is the one to read once the
twelve-line agent makes sense. It is a working agent that recalls what it knows
about you, asks a question when it does not know enough, resumes on your answer,
and emits a card:
$ ./.venv/bin/python -m swisper_agent_sdk.examples.meal_planning_agent
[turn 1] status=waiting_for_input text="I don't have any dietary restrictions or allergies on file for you — could you tell me what I should plan around?"
[turn 2] status=complete text="Here's a plan built around: I'm vegetarian and allergic to peanuts.."
cards emitted: ['meal_plan']
It also shows how to reach data you are entitled to. Declare what you need on the agent and read it from the delegation:
agent = Agent(..., inputs=("fact_lookup_service",))
# in the handler:
facts = await d.fact_lookup_service.get_by_type("ALLERGY")
You only receive what you declare. Anything you did not ask for is absent from your delegation, not present-and-empty.
🔴
fact_lookup_serviceworks in-process and is withheld over the wire. It is a live service object, so it cannot be serialised to a remotely deployed agent — core drops it from the envelope unconditionally. The example above runs green under the harness and returns nothing once your agent is deployed remotely, with no error.This is stated here because it is the one place where following our own documentation produces a silent failure. Facts are meant to reach a remote agent pre-loaded onto the envelope, declared in the agent's contract — that path is seamed but not yet wired, so a remote agent currently receives no pre-loaded facts. If you are building for remote deployment, do not design around
fact_lookup_serviceyet; talk to us.
Two ways to write an agent, one runtime
@agent.handler is not a simplified mode you outgrow. A handler is compiled
into a one-node graph, so it runs through exactly the same machinery as an
agent you author as a graph yourself:
agent = Agent(name="…", description="…", graph=my_compiled_graph)
Start with a handler. Move to graph= when you need more than one node. You are
not choosing a path you have to undo later.
Telling the model how to behave
🔴 No planner ships in this package.
instructions=,routing=,narration_style=,tool_guidance=,prompt=andtools=are stored on theAgentand are not read when your agent runs. Nothing here calls a model.To use a composed prompt today, call
agent.compose_prompt(...)yourself and drive your own model with it — seeexamples/bring_your_own_model_agent.py. A handler-mode agent decides everything in Python.Everything in this section describes what
compose_prompt()returns, not what the runtime does with it.
Most agents need one thing — instructions=:
agent = Agent(
name="weather_agent",
description="Answers weather and forecast questions.",
instructions="""
You answer questions about weather and forecasts.
Prefer official meteorological sources over aggregators.
Never speculate beyond seven days — say you don't know instead.
""",
)
Your instructions reach the model verbatim. We never rewrite them, and they
are not a template: braces inside them are literal text, so you cannot
accidentally interpolate one of our internal variables.
The SDK composes the rest of the planner prompt around them — the planning loop, how to ask a question and resume, how to narrate, and a context block carrying the user's language, locale, timezone, today's date and their temporal context.
That context block is why this wrapper exists. Omit the language instruction and you answer a German user in English. Omit the locale rules and dates come out American. Omit temporal context and "tomorrow" is the wrong day. None of these fail a test you would think to write, and all of them reach a user.
If you want to replace one composed section rather than all of it, pass
narration_style=, tool_guidance= or routing=. If you want full control,
pass prompt= — and swisper_agent_sdk.prompts.scaffolding() is importable, so
you can still assemble the parts you did not want to write yourself.
📖 PROMPTING.md has the full map of what gets composed, plus
two rules that are not general prompting advice — they are things we got wrong in
production, in ways that reached users. Read it before you write your second
agent.
What this package deliberately does not give you
Said up front, because discovering it by hitting a wall is worse:
- No
swisper.*imports. The SDK never reaches into the Swisper backend, and a build check enforces that on bothsrc/andexamples/. If something you need is missing here, that is our gap to close — tell us rather than working around it. - No database access. Your agent never opens a database connection. Data you are entitled to arrives through a capability call that core authorises for that turn.
- Five declared dependencies, deliberately:
pydantic,langgraph,jsonpath-ng,pyyamlandlangchain-core. A test asserts that every import is declared and every declaration is imported — not a count — so a new one cannot arrive as a side effect of a convenient import, undeclared. (See the install-time note above:langgraphalone brings a much larger dependency tree with it — the count above is what we declare, not what actually installs.) - The SDK does not configure logging. Importing it leaves your root logger
untouched. Call
swisper_agent_sdk.correlation.configure_logging()yourself if you want ours.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swisper_agent_sdk-0.1.0a1.tar.gz.
File metadata
- Download URL: swisper_agent_sdk-0.1.0a1.tar.gz
- Upload date:
- Size: 84.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe6ed9b80539ef4bae06a9db6ddbbda08326d8ad4e3b54a40f0bf64821bcc99f
|
|
| MD5 |
5dc81a1e2e982ee7652a3d6529eb7579
|
|
| BLAKE2b-256 |
5c8d932f8ad821b00c71113fd268e107d317fa31ae49c06b22d9b025835fe25f
|
Provenance
The following attestation bundles were made for swisper_agent_sdk-0.1.0a1.tar.gz:
Publisher:
publish-agent-sdk.yml on Fintama/helvetiq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swisper_agent_sdk-0.1.0a1.tar.gz -
Subject digest:
fe6ed9b80539ef4bae06a9db6ddbbda08326d8ad4e3b54a40f0bf64821bcc99f - Sigstore transparency entry: 2598902697
- Sigstore integration time:
-
Permalink:
Fintama/helvetiq@ad1475094f849a714943fd8aa9a2015297cc565b -
Branch / Tag:
refs/tags/agent-sdk-v0.1.0a1 - Owner: https://github.com/Fintama
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-agent-sdk.yml@ad1475094f849a714943fd8aa9a2015297cc565b -
Trigger Event:
push
-
Statement type:
File details
Details for the file swisper_agent_sdk-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: swisper_agent_sdk-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 94.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b69353111450458e2068443359a060e02afe4c6fdadc6600facd62f21acc4c5
|
|
| MD5 |
8cf5ab3ea87dacae821449aa9c3cedc4
|
|
| BLAKE2b-256 |
2ce3aa31fa0a409cd961cd6cf247b19a1de5076551b926ed4ef2abeed5980097
|
Provenance
The following attestation bundles were made for swisper_agent_sdk-0.1.0a1-py3-none-any.whl:
Publisher:
publish-agent-sdk.yml on Fintama/helvetiq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swisper_agent_sdk-0.1.0a1-py3-none-any.whl -
Subject digest:
9b69353111450458e2068443359a060e02afe4c6fdadc6600facd62f21acc4c5 - Sigstore transparency entry: 2598902758
- Sigstore integration time:
-
Permalink:
Fintama/helvetiq@ad1475094f849a714943fd8aa9a2015297cc565b -
Branch / Tag:
refs/tags/agent-sdk-v0.1.0a1 - Owner: https://github.com/Fintama
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-agent-sdk.yml@ad1475094f849a714943fd8aa9a2015297cc565b -
Trigger Event:
push
-
Statement type: