Metadata-Version: 2.5
Name: strands-sprites
Version: 0.1.0
Summary: Persistent Sprites sandboxes for Strands agents
Project-URL: Repository, https://github.com/superfly/strands-sprites
Project-URL: Documentation, https://github.com/superfly/strands-sprites#readme
Project-URL: Issues, https://github.com/superfly/strands-sprites/issues
Author-email: Alex Ezell <alex@fly.io>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,sandbox,sprites,strands
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: sprites-py<1,>=0.7.2
Requires-Dist: strands-agents<2,>=1.59.0
Provides-Extra: dev
Requires-Dist: build<2,>=1; extra == 'dev'
Requires-Dist: mypy<2.4,>=1.15; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=1; extra == 'dev'
Requires-Dist: pytest-cov<8,>=6; extra == 'dev'
Requires-Dist: pytest-timeout<3,>=2; extra == 'dev'
Requires-Dist: pytest<10,>=8; extra == 'dev'
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
Requires-Dist: twine<8,>=7; extra == 'dev'
Description-Content-Type: text/markdown

# strands-sprites

A standalone [Strands](https://strandsagents.com/) sandbox extension for
[Sprites](https://sprites.dev/). Run an agent's shell commands and file operations
inside an existing Sprite, with files that persist between commands and agent runs.

## Install

<!-- Installation instructions updated by Codex. -->

Requires Python 3.10 or later, `strands-agents>=1.59.0,<2`, and
`sprites-py>=0.7.2,<1`.

```sh
python -m pip install strands-sprites
```

To install from source instead:

```sh
git clone https://github.com/superfly/strands-sprites.git
cd strands-sprites
python -m venv .venv
. .venv/bin/activate
python -m pip install .
```

The Sprite must already exist. Shell and code execution require `python3` and
`sh` on the Sprite's default PATH, plus its standard `/dev/fd` filesystem.
Choose an existing absolute working directory, such as `/home/sprite`.
The supervisor starts in the Sprite's default directory before selecting the
command's directory and environment. Its Python must work there; a project's
pyenv settings can still affect interpreters launched by the command itself.
File operations use the filesystem API and do not require Python or a shell.

Use an `AsyncSpritesClient` with `control_mode=False` (the default). The extension
rejects control-mode clients because supported SDK releases can drop streamed
output in that mode.

## Use with an agent

Set `SPRITES_TOKEN` and `SPRITE_NAME` in your environment, and configure the
credentials for your Strands model provider. This example uses Strands' default
model provider; you can pass your configured model as `Agent(model=..., sandbox=sandbox)`.

```python
import asyncio
import os

from sprites import AsyncSpritesClient
from strands import Agent
from strands_sprites import SpritesSandbox


async def main():
    async with AsyncSpritesClient(token=os.environ["SPRITES_TOKEN"]) as client:
        sandbox = SpritesSandbox(
            client.sprite(os.environ["SPRITE_NAME"]),
            working_dir="/home/sprite",
        )
        agent = Agent(sandbox=sandbox)
        await agent.invoke_async("List the files in the Sprite's working directory.")


asyncio.run(main())
```

The sandbox registers Strands' standard `sandbox_shell` and `sandbox_file_editor`
tools automatically, using the SDK descriptions with the Sprite's name appended.
File operations use the Sprites filesystem API, including directory metadata.
Code execution uses Strands' `PosixShellSandbox` interpreter command generation.

If you used the initial prototype, update any tool-name references from
`sprite_shell`/`sprite_file_editor` to `sandbox_shell`/`sandbox_file_editor`.

A runnable version is in [examples/agent.py](examples/agent.py).

## Check the workspace without a model

With the same Sprite environment variables set:

```sh
python examples/workspace_check.py
# Or choose the Sprite and directory explicitly:
python examples/workspace_check.py --sprite my-sprite --working-dir /home/sprite
```

This check writes a uniquely named temporary file, reads it in a separate command,
round-trips 1 MiB through the filesystem API, checks that a command-specific
variable preserves PATH, and removes the file. It needs only Sprites credentials;
it does not call a model.

## Execution and lifecycle

- You own the `AsyncSpritesClient` and `AsyncSprite`. The adapter never creates or
  deletes a Sprite or closes the client. Keep the client open for the agent's lifetime.
- Files persist in the Sprite. Each command starts a fresh shell, so shell variables
  and `cd` do not persist. Use `working_dir`, or the per-command `cwd` and `env` options.
  The supervisor explicitly merges `env` over its inherited environment.
- Commands, code, working directories and environment values travel as a JSON
  payload over WebSocket stdin. They are not placed in the exec URL. The supervisor
  runs commands from an unnamed temporary script, avoiding OS argument-size limits.
  File writes send raw bytes in an HTTP body, avoiding base64 command-size limits.
- `working_dir` selects a directory; it does not restrict filesystem access.
  Commands run with the Sprite user's permissions.
- Shell output streams asynchronously. The final result preserves whitespace and
  includes the complete stdout and stderr. Output is buffered without a size limit;
  redirect large output to files when needed.
- A positive, finite `timeout` sets a remote command deadline. The supervisor kills
  the command process group at that deadline. A confirmed deadline raises Strands'
  `SandboxTimeoutError` with partial output. An ordinary `exit 124` remains an exit code.
  `timeout=None` means there is no command deadline.
- The SDK transport gets an additional 30 seconds beyond a finite command deadline.
  Transport failures raise a sanitized `RuntimeError` reporting unknown execution
  status. HTTP 401, 403, 404 and 429 responses instead identify authentication,
  authorization, missing resources and rate limits. SDK failures are logged locally
  with their traceback. There are no automatic retries.
- Cancellation closes the transport. Remote cleanup follows the platform's disconnect
  grace period, and the supervisor forwards termination to the command process group.
  Cancellation is not an immediate acknowledgement that remote execution has stopped.
  Processes that deliberately detach from that group can outlive it.

The extension supports persistent shell and file access. It does not currently
expose checkpoint or Sprite lifecycle management as agent tools.

## Development

```sh
python -m pip install -e '.[dev]'
python -m pytest --cov=strands_sprites
python -m ruff check .
python -m ruff format --check .
python -m mypy src
python -m build
python -m twine check dist/*
```

If you use Hatch, `hatch run prepare` runs formatting checks, lint, type checking,
and tests. Tests use mocks and local POSIX subprocesses; they do not require cloud
credentials. CI tests Python 3.10–3.14 and resolves minimum direct dependencies
from scratch with `uv pip install --resolution lowest-direct`. Pull requests run
CI once; pushes run it only on `main`. A weekly job checks the latest published
Strands SDK for compatibility.

## Releases and catalog submission

Versions come from Git tags through `hatch-vcs`: `v0.1.0` builds version `0.1.0`.
Use a full Git checkout for development builds. Source distributions retain their
version metadata and can be built without Git. Untagged commits get development
versions; the publishing workflow rejects them.

Before the first release, register a pending trusted publisher in PyPI:

| Setting | Value |
| --- | --- |
| Project name | `strands-sprites` |
| GitHub owner | `superfly` |
| Repository | `strands-sprites` |
| Workflow filename | `publish.yml` |
| Environment | `pypi` |

This is configured in the PyPI account that will own the new project, under
[Publishing](https://pypi.org/manage/account/publishing/). No API token is needed.
See the [PyPI trusted publishing guide](https://docs.pypi.org/trusted-publishers/).

Once publishing is approved and the trusted publisher is configured, create a
`vX.Y.Z` tag on a tested commit from `main` and publish its GitHub release. The
`publish.yml` workflow verifies main ancestry, runs checks, builds the wheel and
source archive, verifies that both versions match the tag, and uploads them through
PyPI trusted publishing. Pushing a tag alone does not publish a package.

The Strands catalog entry uses `integrationType: integration` and
`maintainedBy: partner`. Submit it after the package is available on PyPI and this
repository is public, and set `addedDate` to the submission date. The repository
should carry the `strands-agents` topic.

## License

Apache-2.0. See [LICENSE](LICENSE).
