Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ Thumbs.db
# Scratch files
scratch_*

# Python bytecode
__pycache__/
*.pyc
*.pyo

# Logs and temp
*.log
npm-debug.log*
Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,43 @@ Run AI agents inside sandboxes and communicate with them over WebSocket.
### 1. Start the runtime (inside a sandbox)

```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
```

This starts a WebSocket server on port 8080 using the OpenAI agent handler by default. Use `--agent claude` for Claude. The Claude handler also requires the `claude` CLI to be installed in the sandbox, for example with `npm install -g @anthropic-ai/claude-code`.
This starts a WebSocket server on port 8080 using the default OpenAI handler. For fuller Claude-based sandbox examples, see [`examples/`](./examples).

### 2. Connect from Python

```python
import asyncio
from runtimeuse_client import RuntimeUseClient, QueryOptions
from runtimeuse_client import (
QueryOptions,
RuntimeEnvironmentDownloadableInterface,
RuntimeUseClient,
TextResult,
)

WORKDIR = "/runtimeuse"

async def main():
client = RuntimeUseClient(ws_url="ws://localhost:8080")

result = await client.query(
prompt="What is 2 + 2?",
prompt="Summarize the contents of the codex repository.",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
model="gpt-5.4",
pre_agent_downloadables=[
RuntimeEnvironmentDownloadableInterface(
download_url="https://github.com/openai/codex/archive/refs/heads/main.zip",
working_dir=WORKDIR,
)
],
),
)

assert isinstance(result.data, TextResult)
print(result.data.text)

asyncio.run(main())
Expand Down
123 changes: 123 additions & 0 deletions docs/content/docs/agent-runtime.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: Agent Runtime
description: CLI flags, built-in agent handlers, and custom handler authoring for the runtimeuse server.
---

The [agent runtime](https://www.npmjs.com/package/runtimeuse) is the process that runs inside the sandbox. It exposes a WebSocket server, receives invocations from the Python client, and delegates work to an agent handler.

## CLI

```bash
npx -y runtimeuse # OpenAI handler on port 8080
npx -y runtimeuse --agent claude # Claude handler
npx -y runtimeuse --port 3000 # custom port
npx -y runtimeuse --handler ./my-handler.js # custom handler entrypoint
```

## Built-in Handlers

- `openai`: the default handler, uses the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/) with shell and web search tools.
- `claude`: uses [Claude Agents SDK](https://platform.claude.com/docs/en/agent-sdk/overview) with Claude Code preset.

### OpenAI Handler

Requires `OPENAI_API_KEY` to be set in the environment. The handler runs the agent with shell access and web search enabled.

```bash
export OPENAI_API_KEY=your_openai_api_key
npx -y runtimeuse
```

### Claude Handler

Requires the `@anthropic-ai/claude-code` CLI and `ANTHROPIC_API_KEY`. Always set `IS_SANDBOX=1` and `CLAUDE_SKIP_ROOT_CHECK=1` in the sandbox environment.

```bash
npm install -g @anthropic-ai/claude-code
export ANTHROPIC_API_KEY=your_anthropic_api_key
export IS_SANDBOX=1
export CLAUDE_SKIP_ROOT_CHECK=1
npx -y runtimeuse --agent claude
```

## Programmatic Startup

If you want to embed RuntimeUse directly in your own Node process, start it programmatically:

```typescript
import { RuntimeUseServer, openaiHandler } from "runtimeuse";

const server = new RuntimeUseServer({
handler: openaiHandler,
port: 8080,
});

await server.startListening();
```

## Custom Handlers

When the built-in handlers are not enough, you can pass your own handler to `RuntimeUseServer`:

```typescript
import { RuntimeUseServer } from "runtimeuse";
import type {
AgentHandler,
AgentInvocation,
AgentResult,
MessageSender,
} from "runtimeuse";

const handler: AgentHandler = {
async run(
invocation: AgentInvocation,
sender: MessageSender,
): Promise<AgentResult> {
sender.sendAssistantMessage(["Running agent..."]);

const output = await myAgent(
invocation.systemPrompt,
invocation.userPrompt,
);

return {
type: "structured_output",
structuredOutput: output,
metadata: { duration_ms: 1500 },
};
},
};

const server = new RuntimeUseServer({ handler, port: 8080 });
await server.startListening();
```

### Handler Contracts

Your handler receives an `AgentInvocation` with:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `systemPrompt` | `string` | System prompt for the agent. |
| `userPrompt` | `string` | User prompt sent from the Python client. |
| `model` | `string` | Model name passed by the client. |
| `outputFormat` | `{ type: "json_schema"; schema: ... } \| undefined` | Present when the client requests structured output. Pass to your agent to enforce the schema. |
| `signal` | `AbortSignal` | Fires when the client sends a cancel message. Pass to any async operations that support cancellation. |
| `logger` | `Logger` | Use `invocation.logger.log(msg)` to emit log lines visible in sandbox logs. |

Use `MessageSender` to stream intermediate output before returning the final result:

- `sendAssistantMessage(textBlocks: string[])`: emit text blocks the Python client receives via `on_assistant_message`.
- `sendErrorMessage(error: string, metadata?: Record<string, unknown>)`: signal a non-fatal error before aborting.

Return an `AgentResult` from your handler:

```typescript
// Text result
return { type: "text", text: "...", metadata: { duration_ms: 100 } };

// Structured output result
return { type: "structured_output", structuredOutput: { file_count: 42 }, metadata: {} };
```

`metadata` is optional and is passed through to `result.metadata` on the Python side.
47 changes: 19 additions & 28 deletions docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -1,43 +1,34 @@
---
title: Introduction
description: Run AI agents in sandboxes and communicate with them over WebSocket.
description: Run AI agents (Claude Code, OpenAI Agents, and more) in any sandbox, controlled from Python over WebSocket.
---

## What is RuntimeUse?
[RuntimeUse](https://github.com/getlark/runtimeuse) is an open-source runtime and client library for running AI agents inside isolated sandboxes and controlling them from Python over WebSocket.

RuntimeUse lets you run an AI agent inside any sandbox and communicate with it over WebSocket. It handles the runtime lifecycle for you: file downloads, pre-commands, artifact uploads, cancellation, and structured results.

It is made up of two parts:
<img src="/terminal.svg" alt="RuntimeUse terminal" style={{ maxWidth: '560px', width: '100%' }} />

1. **`runtimeuse`**: the TypeScript runtime that runs inside the sandbox and exposes a WebSocket server.
2. **`runtimeuse-client`**: the Python client that connects from outside the sandbox and sends invocations.

Today, the recommended path is to run the runtime in the sandbox and use the Python client from your application code.
<Card
title="Quickstart"
description="See how to integrate with popular sandbox providers."
href="/quickstart"
/>

## Built-in Agent Handlers

The runtime ships with two built-in handlers:
## When to use

- **`openai`** (default) — uses `@openai/agents` SDK
- **`claude`** — uses `@anthropic-ai/claude-agent-sdk` with Claude Code tools and `bypassPermissions` mode
- Your agent needs filesystem, CLI, or network access inside an isolated runtime.
- Your application should stay outside the sandbox while still controlling the run.
- You don't want to build infrastructure for interacting with your agent in sandbox.

Switch between them with `--agent openai` or `--agent claude`.
## What it handles

The Claude handler also requires the `claude` CLI to be installed in the sandbox, for example:
- **Task invocations**: send a prompt to any agent runtime and receive a result over WebSocket as text or typed JSON.
- **pre_agent_downloadables**: fetch code, repos, or data into the sandbox before the run starts.
- **Pre-commands**: run bash commands before the agent starts executing.
- **Artifact uploads**: move generated files out of the sandbox with a presigned URL handshake.
- **Streaming and cancellation**: receive progress updates and stop runs cleanly.
- **Secret-aware execution**: redact sensitive values before they leave the sandbox.

```bash
npm install -g @anthropic-ai/claude-code
```

## Key Features

- **Sandbox-agnostic** — works with any provider that can run `npx` and expose a port
- **Artifact management** — files written to the artifacts directory are automatically detected and uploaded through a presigned URL handshake with the client
- **Secret redaction** — secret values are recursively replaced before they leave the sandbox
- **Pre-commands** — run shell commands before agent invocation with automatic secret redaction and abort support

<Cards>
<Card title="Quickstart" href="/docs/quickstart" />
<Card title="Runtime Package" href="https://github.com/getlark/runtimeuse/tree/main/packages/runtimeuse" />
<Card title="Python Client" href="https://github.com/getlark/runtimeuse/tree/main/packages/runtimeuse-client-python" />
</Cards>
8 changes: 8 additions & 0 deletions docs/content/docs/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"pages": [
"index",
"quickstart",
"python-client",
"agent-runtime"
]
}
Loading