Skip to main content

stackshift-agent

Write an agent that runs on StackShift. The package receives the events that wake your agent, checks that StackShift sent them, runs your handlers, and gives your code the actions, StackShift calls, state and model access an agent needs.

pip install stackshift-agent

Python 3.9 or newer. The Anthropic and OpenAI clients and the two adapters use packages you install yourself (anthropic, openai, claude-agent-sdk, openai-agents). None of them is needed for the rest.

A first agent

from stackshift_agent import agent


@agent.action("lookup_order")
async def lookup_order(args):
    return {"order_id": args["order_id"], "status": "shipped"}


@agent.on("inbox.message")
async def answer(event):
    order = await lookup_order({"order_id": event.data["message"]["subject"]})
    await agent.inbox.reply(event.data["message"], f"Your order is {order['status']}.")


agent.start()

agent.start() listens on PORT (8080 when it is not set) and answers POST /stackshift/events. Declare lookup_order in the agent: block of stackshift.yaml; StackShift only lets an action run if it is declared there.

Events

agent.on(event) runs for each event of that name: inbox.message, agreement.offer_received, agreement.agreed, agreement.declined, agreement.expired, agreement.waiting_for_you, a2a.message, schedule.tick, webhook.received, approval.decided and budget.warning. The handler gets an object with type, id, at, data, untrusted and run_id. Handlers can be plain or async functions.

Each event is checked against STACKSHIFT_EVENT_KEY (the Ed25519 signature on the delivery) before any handler runs. Events that arrive twice are dropped by their id. A handler that raises ends the run with the outcome failed; a run that saved an action for approval ends waiting; anything else ends done.

Actions and approvals

agent.action(key) wraps your function. Each call asks StackShift first, with the arguments, and StackShift answers allowed, waiting or refused under the agent's level.

  • Allowed: your function runs and its result is recorded.
  • Refused: the call raises ActionRefusedError with a reason in plain words.
  • Waiting: by default the call is saved in the agent's state, the run ends with the outcome waiting, and when the owner decides the call runs again with exactly the arguments they approved. Pass wait=True to hold the run for up to 55 seconds instead.

Mark text your agent fetched itself with await agent.untrusted(text, "web:example.com"). StackShift then asks before an action built from that text does anything that matters.

Calling StackShift

operation = await agent.platform.call("database.inspect", {"database_id": database_id}, reason="Check the size")
inbox = await agent.platform.inspect("mail.agent_inboxes.get", {"inbox_id": inbox_id})

call proposes the action through the agent's connection and waits up to 55 seconds for it to settle (wait=False returns at once). The agent can only use the actions its owner allowed. inspect runs a read.

agent.inbox has send, reply, info and id. agent.agreements has list, inspect, limits, verify, wait, propose, respond, cancel and count_reply; they work when agreements are on for the agent's inbox.

State and locks

await agent.state.set("last_report", {"at": 1}, ttl_seconds=86400)
last = await agent.state.get("last_report")
async with agent.state.locked("nightly"):
    ...

Models

agent.anthropic() and agent.openai() return the official clients, already pointed at StackShift and signed in with the agent's run token; agent.async_anthropic() and agent.async_openai() return the async ones. There is no provider key in the agent. Pass max_tokens on every call.

Internet access

await agent.fetch(url, method="GET", headers=None, body=None) returns a response with status, headers, text() and json(). For a host where the owner saved a key to send on the way out, the request goes through StackShift, which adds the key. The agent never holds it.

Claude Agent SDK and OpenAI Agents SDK

from stackshift_agent.adapters.claude import claude_tools
from stackshift_agent.adapters.openai_agents import openai_tools

claude = await claude_tools(agent)
tools = await openai_tools(agent)

claude_tools returns mcp_servers and allowed_tools for ClaudeAgentOptions. openai_tools returns FunctionTool objects for an Agent. Both turn your declared actions into tools with the approval step built in. A refusal or a wait comes back to the model as text, not as an error. Call them while handling an event.

What this does and does not enforce

The wrapper governs your agent's model, which is what goes wrong in practice. It is not a guard against the developer: you own the code, so you can skip it. What StackShift enforces whatever the code does: model calls through StackShift, actions on StackShift, email and agreements, and which internet addresses the agent can reach.

Metadata

Release files for stackshift-agent 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stackshift-agent 0.1.0
File Size Uploaded Provenance Actions
stackshift_agent-0.1.0.tar.gz 25.1 kB Without provenance Details for stackshift_agent-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for stackshift-agent 0.1.0
File Interpreter ABI Platform Actions
stackshift_agent-0.1.0-py3-none-any.whl Python 3 none any Details for stackshift_agent-0.1.0-py3-none-any.whl

Total release size: 46.4 kB

Release files / stackshift_agent-0.1.0.tar.gz

Download URL stackshift_agent-0.1.0.tar.gz
Size 25.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d8966611132c3e30f38cecf4cfa9b1500fe6820724d41460a0f36fd1ad74176a
BLAKE2b-256 checksum
How to use checksums
0e363383ead23050959f46fa1b38677370d629946bc91ca9df66b98e121ff20a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Release files / stackshift_agent-0.1.0-py3-none-any.whl

Download URL stackshift_agent-0.1.0-py3-none-any.whl
Size 21.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
186695176298f22b4522797ce63646e66322b169e41ca77857075f1f3e28a00c
BLAKE2b-256 checksum
How to use checksums
9dc620622ae6313d6bb8aaf21aadd96480f3e0d3ba5044a5e96a26cfca496c58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Security for stackshift-agent 0.1.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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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