Skip to main content
Pre-release

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, pyyaml and langchain-corelanggraph alone brings its own dependency tree with it (langsmith, httpx, orjson, ormsgpack, zstandard and more). Measured in a clean install: 36 packages, not five. None of that is a defect — every one of them is something langgraph genuinely 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 pytest from inside this directory. The package's own asyncio_mode = "auto" setting lives in its pyproject.toml, and pytest only picks it up when invoked from here. Run it from the parent directory and every async def test 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_service works 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_service yet; 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= and tools= are stored on the Agent and 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 — see examples/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 both src/ and examples/. 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, pyyaml and langchain-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: langgraph alone 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

swisper_agent_sdk-0.1.0a1.tar.gz (84.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

swisper_agent_sdk-0.1.0a1-py3-none-any.whl (94.6 kB view details)

Uploaded Python 3

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

Hashes for swisper_agent_sdk-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 fe6ed9b80539ef4bae06a9db6ddbbda08326d8ad4e3b54a40f0bf64821bcc99f
MD5 5dc81a1e2e982ee7652a3d6529eb7579
BLAKE2b-256 5c8d932f8ad821b00c71113fd268e107d317fa31ae49c06b22d9b025835fe25f

See more details on using hashes here.

Provenance

The following attestation bundles were made for swisper_agent_sdk-0.1.0a1.tar.gz:

Publisher: publish-agent-sdk.yml on Fintama/helvetiq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file swisper_agent_sdk-0.1.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for swisper_agent_sdk-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 9b69353111450458e2068443359a060e02afe4c6fdadc6600facd62f21acc4c5
MD5 8cf5ab3ea87dacae821449aa9c3cedc4
BLAKE2b-256 2ce3aa31fa0a409cd961cd6cf247b19a1de5076551b926ed4ef2abeed5980097

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page