Metadata-Version: 2.4
Name: agentway
Version: 0.1.0
Summary: Python SDK for AgentWay - runtime control plane for AI agents
Project-URL: Homepage, https://agentwayai.com
Project-URL: Documentation, https://agentwayai.com/docs/sdk-python
License: Proprietary
License-File: LICENSE
Keywords: agents,ai,control-plane,llm,observability
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# agentway (Python SDK)

Register a long-lived autonomous agent with [AgentWay](https://agentwayai.com)
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](https://agentwayai.com) has a free tier. The SDK is what your
agent imports; the dashboard, the control plane and the storage are the service.

```bash
pip install agentway
```

- **Sign up / dashboard** — [agentwayai.com](https://agentwayai.com)
- **Documentation** — [agentwayai.com/docs](https://agentwayai.com/docs)
- **Full working agent** — [agentwayai.com/docs/full-agent](https://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:

```python
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.

```python
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

```python
@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:

```python
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

```python
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:

```python
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

```python
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

```python
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.

```python
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

```bash
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](https://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.
