swebot-client
Python client for the swebot autonomous coding agent.
Pure Python — zero dependencies beyond the standard library.
Install
# pip
pip install swebot-client
# uv
uv add swebot-client
Quick start
import swebot
print(swebot.ask("explain goroutines"))
That is the whole ceremony for a one-shot question against a backend on localhost. Stream it instead, when you want to watch it arrive:
for token in swebot.stream("explain goroutines"):
print(token, end="", flush=True)
Take a session when the second question depends on the first — a session IS the context that makes a follow-up a follow-up:
with swebot.session(agent="developer") as s:
s.send("read parser.go")
print(s.send("what would you change about it?"))
Elsewhere, or behind a token:
swebot.connect("http://10.0.0.5:8080", token="…") # or set SWEBOT_TOKEN
And the full client is one call away — everything below is reachable from it:
client = swebot.Client(timeout=30)
client.set_mode("autonomous")
session = client.create_session(agent="developer")
The same library under two names
import swebot is the front door; swebot_client is the same objects under
the name existing scripts already import. They can be mixed in one program —
swebot.TurnFailed is swebot_client.TurnFailed, not a copy of it — so
nothing has to be rewritten to use the shorter form, and an except clause
keeps catching either way.
from swebot_client import SwebotClient # unchanged, and staying
Features
- Streaming — real-time token-by-token output via SSE
- Tool callbacks — observe tool invocations and results
- Session management — create, resume, list, delete sessions
- Mode / model control — switch modes, models, thinking, reasoning
- Todos & checkpoints — read, create, update, delete todos; list/restore checkpoints
- MCP management — configure MCP servers programmatically
- Plugin management — install, remove, enable, disable plugins
- AgentSpeak — multi-agent collaboration (join, leave, send messages)
- Context control — compact or clear session context
- Skills — list built-in skill agents
Examples
from swebot_client import SwebotClient, ToolCall
client = SwebotClient("http://localhost:8080")
client.wait_until_ready(timeout=10)
# Set mode to autonomous so the agent executes (not plans)
client.set_mode("autonomous")
# Enable extended thinking
client.set_thinking("high")
# Switch model
client.set_model("claude-opus-4-5")
# Create a session and send a message
session = client.create_session(agent="developer")
def on_tool(tc: ToolCall):
print(f" ⚙ {tc.name}")
reply = session.send_with_events(
"create a hello.py file",
on_tool_start=on_tool,
)
# Read todos
for todo in session.get_todos():
print(f"[{todo.status}] {todo.title}")
# Token usage
usage = session.token_usage()
print(f"Tokens: {usage['tokens_in']:,} in / {usage['tokens_out']:,} out ({usage['usage_pct']}%)")
Pipeline example
from swebot_client import SwebotClient
client = SwebotClient()
client.wait_until_ready()
client.set_mode("autonomous")
# Stage 1: generate
s1 = client.create_session()
code = s1.send("Write a Python prime sieve for 0-1000000")
# Stage 2: review
s2 = client.create_session("code_auditor")
review = s2.send(f"Review this code:\n{code}")
print(review)
API reference
Module-level calls
| Call | Description |
|---|---|
swebot.ask(message, agent="default", keep=False) |
One question in a session of its own → str |
swebot.stream(message, agent="default", keep=False) |
The same, token by token → Iterator[str] |
swebot.session(agent="default", keep=True) |
A session, as a context manager. Kept by default — pass keep=False for a scratch one |
swebot.connect(addr, token, …) |
Point those three at a different backend → the Client |
swebot.client() |
The client they are using |
A backend that is not running gives a ConnectionError naming the address and
telling you to start one, rather than a bare URLError from urllib.
The session ask and stream create is scratch: you never see its id,
nothing can resume it, and a script asking a thousand questions would leave a
thousand sessions behind. It is discarded when the answer is in — unless the
session is still doing something, in which case it is left alone:
- a wake-up is scheduled — the turn asked to be resumed, and the session is how it comes back. (The same rule swebot's own one-shot CLI applies.)
- the turn is still running — an autonomous or goal continuation outlives the stream that started it, so a one-shot that set one going leaves it working rather than deleting it mid-turn.
- anything unknown — an older backend that does not report wake-ups, a request that fails. Deleting is the direction that cannot be undone.
The backend checks again as it deletes, so a wake-up scheduled by another
backend between the check and the delete cannot be lost. keep=True holds on
to the session regardless; swebot.session() is for when the conversation
itself matters.
SwebotClient(addr="http://localhost:8080", token="", timeout=60.0, stream_timeout=None)
No token needed for a backend on your own machine. If it was started with
--token (which a backend bound off loopback requires), set SWEBOT_TOKEN in
your environment — the SDK reads it automatically.
timeout bounds ordinary requests, so a backend that accepts the connection
and then stops answering cannot hang your script; wait_until_ready(timeout=…)
passes its remaining time down to each probe. stream_timeout is separate and
defaults to no deadline, because a turn streams for as long as it runs and a
silent tool call of several minutes is normal.
Errors
A turn that fails — the provider erroring, a stall, a cancellation — raises
TurnFailed from send(), stream() and send_with_events(). Whatever
text arrived before the failure is on the exception as .partial:
from swebot_client import TurnFailed
try:
answer = session.send("refactor the parser")
except TurnFailed as e:
print("the turn failed:", e)
print("partial output was:", e.partial)
These used to return the empty string, or the partial text, with nothing to distinguish a failed turn from a short answer.
ToolCall
What the agent did, as on_tool_start / on_tool_result see it: id,
name, input, output, and two flags that are NOT the same question.
| Field | Means |
|---|---|
is_error |
the call did not succeed |
not_run |
there was no call — declined, blocked in plan mode, refused by a plugin, cancelled before it started |
A refused tool is not a failed tool, and anything auditing what the agent
actually did needs to tell them apart. examples/17_failures.py shows all
three outcomes.
Sessions
| Method | Description |
|---|---|
create_session(agent) |
Create a new session → Session |
get_session(id) |
Resume an existing session → Session |
list_sessions() |
List all sessions |
delete_session(id) |
Delete a session |
Agents / Providers / Models
| Method | Description |
|---|---|
list_agents() |
List available agents |
get_agent(name) |
Get agent details (name, system_prompt, tools) |
list_providers() |
List LLM providers |
list_models(provider) |
List models for a provider → {provider, active, models} |
set_model(model, provider) |
Switch model (and optionally provider) |
Mode / Thinking / Reasoning
| Method | Description |
|---|---|
get_mode() |
Current mode (plan, agent, autonomous) |
set_mode(mode) |
Set mode: "plan", "agent", "autonomous" |
get_thinking() |
Current thinking level |
set_thinking(level) |
Set thinking: "", "low", "medium", "high" |
get_reasoning() |
Current reasoning depth |
set_reasoning(level) |
Set reasoning: "low", "medium", "high" |
MCP Servers
| Method | Description |
|---|---|
list_mcp() |
List MCP servers with connection state |
add_mcp(name, ...) |
Add a new MCP server |
update_mcp(name, ...) |
Update an existing server |
delete_mcp(name) |
Remove a server |
enable_mcp(name) |
Enable a disabled server |
disable_mcp(name) |
Disable without removing |
reload_mcp() |
Re-read config.yaml and reconnect all enabled servers |
Scheduled work
Wake-ups (the agent's schedule tool) and cron jobs run in the backend,
with or without a client connected — nothing here blocks on them. List and
cancel them, and watch them fire with events(). See examples/19_scheduled.py.
| Method | Description |
|---|---|
list_wakeups(session_id="") |
Pending wake-ups, soonest first → list[dict] (id, session_id, message, fire_at, repeat_seconds) |
cancel_wakeup(id) |
Cancel a pending wake-up |
list_cron() |
Cron jobs → list[dict] (id, schedule, message, enabled, last_run, next_run) |
add_cron(schedule, message) |
Add a job: "every 5m", "daily at 10:00", "weekdays at 09:00", "once 2026-12-25 10:00" |
enable_cron(id) / disable_cron(id) |
Turn a job on or off, keeping it |
remove_cron(id) |
Delete a job |
events(kinds=None, session_id="", timeout=None) |
Watch the backend's live events → Iterator[Event]; schedule_fired / cron_fired when one starts, message_complete for its reply. Ends at timeout |
Plugins
| Method | Description |
|---|---|
list_plugins() |
List installed plugins |
install_plugin(url) |
Install from GitHub (owner/repo) |
remove_plugin(name) |
Uninstall a plugin |
enable_plugin(name) |
Re-enable a disabled plugin |
disable_plugin(name) |
Disable without removing |
AgentSpeak (Multi-Agent)
| Method | Description |
|---|---|
agentspeak_status() |
Connection status → {connected, project, role, url} |
agentspeak_projects() |
List visible projects |
agentspeak_who() |
List agents in current project |
agentspeak_messages(since) |
Fetch recent messages |
agentspeak_join(url, project, role) |
Connect and join a project |
agentspeak_leave() |
Disconnect from project |
agentspeak_send(message, to) |
Send message (broadcast or targeted) |
Backend Management
| Method | Description |
|---|---|
health() |
Check if backend is reachable |
wait_until_ready(timeout) |
Block until backend responds |
stats() |
Backend stats (model, tokens, uptime) |
version() |
Version info (version, commit, latest) |
skills() |
List built-in skill agents |
panic() |
Emergency stop — cancel all; the mode stays as it is |
shutdown() |
Gracefully stop the backend |
Session
Messaging
| Method | Description |
|---|---|
stream(message) |
Stream tokens → Iterator[str] |
send(message) |
Send and get full reply → str |
send_with_events(msg, on_token, on_tool_start, on_tool_result) |
Send with callbacks → str |
Todos
| Method | Description |
|---|---|
get_todos() |
List todo items → list[TodoItem] |
create_todo(title) |
Create a new todo |
update_todo(id, status) |
Update status: pending, in_progress, done |
delete_todo(id) |
Delete a todo |
Checkpoints
| Method | Description |
|---|---|
list_checkpoints() |
List file checkpoints → list[dict] |
wakeups() |
This session's pending wake-ups → list[dict] |
restore_checkpoint(id) |
Restore a file to a previous checkpoint |
Context
| Method | Description |
|---|---|
compact() |
Trigger context compaction |
clear() |
Clear all session context |
token_usage() |
THIS session's usage → {tokens_in, tokens_out, context_window, usage_pct} — falls when the session is compacted |
backend_token_totals() |
The backend's cumulative counters across every session |
Info
| Method | Description |
|---|---|
refresh() |
Reload session data from backend |
message_count |
Number of messages (property) |
Publishing to PyPI
cd sdk/python
python3 -m build
UV_PUBLISH_TOKEN=pypi-YOUR-TOKEN uv publish dist/*
Requirements
- Python 3.9+
- A running swebot backend (
swebot --serve)
License
MIT
Code review
Review what a session changed, then choose what to fix. Findings are verified
by an adversarial second pass — only confirmed ones can be applied.
from swebot_client import SwebotClient
session = SwebotClient().create_session()
session.send("Add a ParseInt helper to util/parse.go")
rev = session.review(mode="informative") # or "autonomous"
if rev.running:
rev = session.wait_for_review(rev.id)
for f in rev.findings:
print(f.id, f.severity, f.file, f.claim, "confirmed" if f.confirmed else "refuted")
keep = [f.id for f in rev.confirmed if f.blocking]
drop = [f.id for f in rev.confirmed if not f.blocking]
if keep:
session.apply_review(rev.id, keep)
if drop:
session.dismiss_findings(rev.id, drop, reason="not now")
A session that changed no files returns scope="out_of_scope" (or
"nothing_to_review") without calling a model at all.
Metadata
Release files for swebot-client 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| swebot_client-0.14.0.tar.gz | 46.8 kB | Without provenance | Details for swebot_client-0.14.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| swebot_client-0.14.0-py3-none-any.whl | Python 3 | none | any | Details for swebot_client-0.14.0-py3-none-any.whl |
Total release size: 70.2 kB
Release files / swebot_client-0.14.0.tar.gz
| Download URL | swebot_client-0.14.0.tar.gz |
|---|---|
| Size | 46.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d1c1d436e36dd1a5545e728eb51274ee82fb386338afc2d5dcb8d83cfa824f37
|
|
BLAKE2b-256 checksum How to use checksums |
fad15fbe2a191dc38d6565f9a70de34609b2396dd247f8e55295cd0844774c53
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / swebot_client-0.14.0-py3-none-any.whl
| Download URL | swebot_client-0.14.0-py3-none-any.whl |
|---|---|
| Size | 23.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c37d3fc31fb4ced44c8c535dc3593335181a504b04bc98436b8ce89e43716f03
|
|
BLAKE2b-256 checksum How to use checksums |
199f532b47ad6ca403463d6d258156ea55a0a899a6b3ba3c115972b1d43aa75d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for swebot-client 0.14.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.