agentway (Python SDK)
Register a long-lived autonomous agent with AgentWay so a human can watch, direct, pause, and answer it.
This is the client library for AgentWay, a hosted service. It talks to an AgentWay account, so you need one for it to do anything — agentwayai.com has a free tier. The SDK is what your agent imports; the dashboard, the control plane and the storage are the service.
pip install agentway
- Sign up / dashboard — agentwayai.com
- Documentation — agentwayai.com/docs
- Full working agent — agentwayai.com/docs/full-agent
Works with any framework, or none: it reports outward from wherever your agent already runs, so there is no runtime to adopt and nothing to redeploy.
Only dependency is httpx. The SDK installs into somebody else's agent, so extra
dependencies are extra chances to conflict.
Setup
Once, in the dashboard: create a project, create a scope, generate a key for that
scope. The project page shows a public id like aw_x7k2m9p4qa.
Then in your agent:
from agentway import Agent
agent = Agent(
project_id="aw_x7k2m9p4qa", # from the project page
scope="devteam", # must already exist
slug="invoice-processor", # this agent's identity
name="Invoice Processor",
)
while agent.running():
agent.working("processing the queue")
process_one()
agent.done()
The agent creates itself on first run — no dashboard step per agent. Deploy twenty agents with one key and they populate the canvas themselves.
running() is one HTTP call that does everything: sends the heartbeat, checks
whether an operator paused you, and collects any directives or answers. An agent
that only ever calls running() still gets liveness detection, cooperative
pause, and directive delivery.
Why project_id is in code
The key already resolves to a project, so this looks redundant. It isn't. Without
it, a staging key pasted into a production agent's .env registers happily into
staging and looks perfectly healthy — nobody notices until someone wonders why
production is empty. Declaring the project in code turns that into an immediate
startup error.
A scope that doesn't exist is also an error, not an invitation to create one. A typo would otherwise put a phantom department on the canvas, and the canvas has to be trustworthy at a glance.
Reporting
Free by default — activity rides along on the next check-in rather than issuing its own request. That's deliberate: telemetry nobody bothers to write is an empty dashboard.
agent.working("indexing repo") # shows as busy
agent.progress("42% complete")
agent.done("indexed 1,204 files") # shows as idle
agent.failed("rate limited") # idle, but flagged as an error
agent.note("skipping vendor/") # commentary, status unchanged
agent.error("token expired") # error without implying it stopped
agent.note("streaming", flush=True) # send immediately
Only the newest queued activity survives to the next check-in — the dashboard
shows current state, and a backlog of stale progress lines is noise. Use
flush=True or a task_ref for anything that must be kept.
Directives
@agent.on_directive
def handle(directive):
if directive.is_redelivery:
return # already handled it
agent.acknowledge(directive, "on it") # received
do_the_thing(directive.body)
agent.complete(directive, "done") # actually did it
# or: agent.decline(directive, "no access to that repo")
Or poll, if you prefer:
while agent.running():
for directive in agent.directives():
handle(directive)
acknowledge and complete are different states on purpose. Acknowledged means
received; completed means acted on. An operator needs to tell those apart, so
don't send complete until it's true.
Delivery is at-least-once. directive.is_redelivery is how you find out you've
seen one before — check it before doing anything irreversible.
Asking a human
answer = agent.ask(
"Which environment?",
"Deploy v2.1 to staging or production?",
options=["staging", "production"],
blocking=True, # tells the dashboard the agent is stopped
wait=True, # block here until answered
timeout=600,
)
if answer and answer.selected_option == "production":
deploy_to_production()
blocking=True puts the item at the top of the operator's inbox and drives the
"agents waiting on you" badge. Set it only when it's true. wait=True requires
it — an agent that stops to wait must say so, or the dashboard shows it as
working while it sits idle.
While waiting, the SDK keeps heartbeating, so a blocked agent stays visibly alive instead of being swept offline.
Pause
Pause is cooperative. When an operator pauses your agent, running() blocks at
its next call and returns True when they resume. That call site is the
checkpoint: you finished a unit of work and came back to the top of the loop, so
nothing is interrupted mid-action.
The SDK confirms the pause once, then keeps heartbeating so the agent doesn't look dead while it waits.
Nothing can pause an agent mid-action. If you're 40 seconds into an API call,
the pause lands when that returns and you next call running().
Pause latency equals the gap between calls that talk to AgentWay. If one loop iteration takes ten minutes, pause takes up to ten minutes — and an operator experiences that as a broken button rather than as your loop shape. For long work, check inside it:
for batch in huge_dataset:
if not agent.should_continue():
save_progress()
break
process(batch)
should_continue() reports the pause without blocking on a resume, so the caller
decides how to wind down. It's throttled to the heartbeat interval, so calling it
a million times still costs one request per interval — safe in a tight inner loop.
The dashboard shows each agent's measured check-in cadence ("checks in every ~8m"), so an operator knows what to expect before pressing anything.
Scope awareness
Two views of your department. Peers are opt-in (track_peers=True); the tree is
on by default, since news rides along with the check-in and costs no request.
peers() — who's here, right now
agent = Agent(..., track_peers=True)
while agent.running():
upstream = agent.peer("api-scaffolder")
if upstream and not upstream.is_healthy:
agent.note("upstream is failing; backing off")
time.sleep(60)
continue
do_work()
Each Peer carries status, current activity, errors_last_hour,
blocked_on_human, and is_healthy. A snapshot — the state of the room. This is
what you check to decide something.
catch_up() — what happened, in order
while agent.running():
do_work()
for entry in agent.catch_up():
print(entry.author_agent_slug, entry.title, entry.body)
Entries and broadcasts this agent has not seen, oldest first. It advances the read cursor, so a polling agent gets what's new rather than the same rows every tick, and it's empty when nothing changed. This is what you read to understand something.
The difference is real. A snapshot can tell you frontend-builder has three
errors this hour. Only the sequence shows all three landed seconds after
api-scaffolder shipped a schema change. Causation lives in ordering.
tree_news carries the counts from the last check-in, so you can tell whether
anything is waiting without fetching it. context(branch=...) reads the current
state of a subtree, and full(node) fetches a body that came back truncated.
Reading is never conditional on writing: an agent that declares no branches
and contributes nothing still sees everything its colleagues recorded. Your own
entries never come back to you — you already know what you wrote.
Both are awareness, not messaging. An agent can observe that a peer it depends on keeps failing and back off. It cannot send that peer an instruction. Directives stay human-only, so every instruction in the system has a person behind it in the audit trail.
Terminate
Returns False from running(), so the loop exits and normal shutdown runs. Pass
exit_on_terminate=True to raise Terminated instead, if the loop is nested
somewhere that needs to unwind.
When AgentWay is down
running() returns True and your agent keeps working.
This is deliberate and it's the SDK's most important promise: a control plane that takes the fleet down when it has a bad deploy is worse than no control plane. Queued activity is preserved for the next successful check-in, and a paused agent stays paused rather than assuming it may resume.
The one exception is a rejected key (401), which raises immediately — a revoked key will never start working, and retrying forever would hide the misconfiguration.
Configuration
| Env var | Meaning |
|---|---|
AGENTWAY_API_KEY |
Scope key from the dashboard. Required. |
AGENTWAY_URL |
API root. Defaults to the hosted service, https://api.agentwayai.com. Set it only to point at a local backend. |
AGENTWAY_PROJECT_ID |
Alternative to passing project_id=. |
AGENTWAY_SCOPE |
Alternative to passing scope=. |
AGENTWAY_AGENT_SLUG |
Alternative to passing slug=. |
Identity can come from either code or environment. Code is usually better — it's versioned with the agent, and it's what makes the wrong-environment check work.
agent = Agent(
project_id="aw_x7k2m9p4qa",
scope="devteam",
slug="invoice-processor",
name="Invoice Processor",
api_key="ak_...",
version="1.2.0",
poll_interval=60.0, # check in less often than the 15s default
track_peers=True, # peer snapshots
follow_tree=True, # tree news on each check-in (default)
exit_on_terminate=False,
max_retries=4,
)
Missing identity raises at construction, not on the first request — a config error belongs on line one, not thirty seconds into a deploy.
Renamed scopes
If an operator renames a scope, agents whose code names the old one keep working: the server resolves through an alias and the SDK logs a warning telling you to update the source. A rename is a warning, not an outage.
running() throttles itself to the heartbeat interval, so calling it in a tight
inner loop costs one request per interval, not one per iteration.
Tests
python -m pytest sdk/python/tests -q
88 tests, no network. They cover the promises above: outage tolerance, pause
confirmation firing exactly once, activity piggybacking, retry policy, identity
validation, peer health, feed draining and delta cursors, should_continue()
throttling, re-registration recovery, and that a raising handler doesn't kill the
loop.
Licence
Source-available, not open source. The full text ships in the package as
LICENSE, and is also at
agentwayai.com/docs/license.
You may install and use it, redistribute it unmodified as a dependency — including in commercial and closed-source products — and read, inspect and audit the source. You may not modify it, redistribute a modified version, sell it as a product, or use it to build a competing service.
In practice: if you are using AgentWay, this licence does not constrain what you build. It constrains taking the SDK and reselling it.
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 agentway-0.1.0.tar.gz.
File metadata
- Download URL: agentway-0.1.0.tar.gz
- Upload date:
- Size: 37.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f4ebcf2f644776b183af953169371f70a9456e5514b43c2918b3d3520ab84b1
|
|
| MD5 |
bf19619ea49059279b0d2c9effc457ca
|
|
| BLAKE2b-256 |
5c5fcb545bbe7a5a4f33b9d3ce7d2f661bd6754b042310845ca6b0c853ae18e3
|
File details
Details for the file agentway-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentway-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
706fb4747df3e203dfd7da488b3e7eb858f4939c837e832e5b4d4e49e4da143c
|
|
| MD5 |
b60902b52e03588dcc318c3a5a4cfd1f
|
|
| BLAKE2b-256 |
3b0f150cdf2fa786b25408cfdb4a8258a075be8a18cacb267253d747c7ac2375
|