> ## Documentation Index
> Fetch the complete documentation index at: https://docs.raysurfer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Register Python Functions

> Expose your existing Python functions so agents can call them in a remote sandbox

Register your existing Python functions so agents can call them. Raysurfer wraps your functions with `@rs.tool`, auto-infers the schema from the signature and docstring, and routes tool calls back to your app during sandbox execution.

<Note>
  Source: [rayxc-org/raysurfer-python](https://github.com/rayxc-org/raysurfer-python)
</Note>

## Setup

Install the SDK and set `RAYSURFER_API_KEY` in your environment.

```bash theme={null}
uv add raysurfer
```

## Register Tools

`@rs.tool` can wrap any Python function. The function signature is converted to schema fields, and the function docstring is used as the tool description in the tool payload.

```python theme={null}
from raysurfer import AsyncRaySurfer

rs = AsyncRaySurfer(api_key="your_api_key")

@rs.tool
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b
```

## How Functions Get Passed Back To The Agent

`@rs.tool` registers two things locally in your app process:

* A tool schema (name, description, parameters) that Raysurfer sends to the sandbox run
* A callback function that stays local and is invoked when the sandbox requests that tool

Execution flow:

1. `execute()` opens a callback session and sends tool schemas plus your task to Raysurfer.
2. Sandbox code calls a tool (for example `add(5, 3)`).
3. Raysurfer sends a `tool_call` message back to your app.
4. Your local callback runs, and the SDK sends `tool_result` back to the sandbox.
5. The sandbox continues running with that result.

This is the mechanism that passes tool execution back into your running agent app.

## Execute Modes

Use exactly one mode per `execute()` call:

### Primary Mode: Pass `user_code`

```python theme={null}
result = await rs.execute(
    "Compute 5 + 3",
    user_code="print(add(5, 3))",
)
```

### End-To-End Sandbox Example

```python theme={null}
import asyncio
from raysurfer import AsyncRaySurfer


async def main() -> None:
    rs = AsyncRaySurfer(api_key="your_api_key")

    @rs.tool
    def add(a: int, b: int) -> int:
        """Add two numbers."""
        return a + b

    result = await rs.execute(
        "Compute 5 + 3",
        user_code="result = add(5, 3)\nprint(result)",
    )
    print(result.result)      # "8"
    print(result.tool_calls)  # tool call history


asyncio.run(main())
```

### Optional Mode: Sandbox Codegen

In this mode, code is generated inside Raysurfer's remote sandbox from your prompt.

```python theme={null}
result = await rs.execute(
    "Compute 5 + 3",
    codegen_api_key="YOUR_ANTHROPIC_API_KEY",
    codegen_prompt="Write Python code that calls add(a, b) for 5 and 3, then prints the result.",
    codegen_model="claude-opus-4-6",
)
```

## Full SDK Reference

See [Python SDK](/sdk/python#programmatic-tool-calling) for full method signatures and response fields.
