Skip to main content

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

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

agentway-0.1.0.tar.gz (37.0 kB view details)

Uploaded Source

Built Distribution

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

agentway-0.1.0-py3-none-any.whl (34.8 kB view details)

Uploaded Python 3

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

Hashes for agentway-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3f4ebcf2f644776b183af953169371f70a9456e5514b43c2918b3d3520ab84b1
MD5 bf19619ea49059279b0d2c9effc457ca
BLAKE2b-256 5c5fcb545bbe7a5a4f33b9d3ce7d2f661bd6754b042310845ca6b0c853ae18e3

See more details on using hashes here.

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

Hashes for agentway-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 706fb4747df3e203dfd7da488b3e7eb858f4939c837e832e5b4d4e49e4da143c
MD5 b60902b52e03588dcc318c3a5a4cfd1f
BLAKE2b-256 3b0f150cdf2fa786b25408cfdb4a8258a075be8a18cacb267253d747c7ac2375

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page