# Inline Tool Executor

Safe execution of inline (anonymous) tool code within skills.

## Overview

The `InlineToolExecutor` runs Python code strings defined directly in
skill YAML.  Execution is bounded by configurable timeout and output-size
limits, with write-guard enforcement delegated to the `SkillContext`.

## InlineToolResult

Frozen Pydantic model returned from every execution.

| Field | Type | Description |
|-------|------|-------------|
| `success` | `bool` | Whether the execution succeeded |
| `output` | `Any` | Captured stdout/stderr output |
| `error_message` | `str \| None` | Error description on failure |
| `duration_ms` | `float` | Wall-clock execution time in milliseconds |
| `truncated` | `bool` | Whether output was truncated due to size limits |

## InlineToolExecutor

### Constructor

```python
InlineToolExecutor(
    max_runtime_seconds: float = 30.0,
    max_output_bytes: int = 1_048_576,
)
```

| Parameter | Default | Description |
|-----------|---------|-------------|
| `max_runtime_seconds` | `30.0` | Maximum wall-clock seconds for execution |
| `max_output_bytes` | `1_048_576` | Maximum captured output before truncation (1 MiB) |

### `execute(tool, context, input_data) -> InlineToolResult`

Run an inline tool's code with safety guards.

1. **Validation** -- Checks the tool has code and uses a supported source.
2. **Write guard** -- If `tool.capability.writes` is `True`, calls
   `context.enforce_write_guard()`.  In a read-only context this
   returns an error result without executing.
3. **Path restriction** -- File-path keys in `input_data` (keys ending
   with `_path`, `_file`, or equal to `path`) are validated against
   `context.get_sandbox_path()`.
4. **Threaded execution** -- Code runs in a daemon thread with
   `max_runtime_seconds` timeout.
5. **Output capture** -- stdout and stderr are captured.  If the
   combined output exceeds `max_output_bytes`, it is truncated and
   `truncated=True` is set on the result.
6. **Change tracking** -- The invocation is recorded in the context's
   change tracker regardless of success/failure.

### `validate_tool(tool) -> list[str]`

Return a list of validation errors (empty means valid).

Checks:

- Tool has non-empty `code`.
- Tool's `source` is `custom` (Python).  Other source types are not
  supported for MVP.

## Safety Constraints

### Timeout

Execution uses `threading.Thread` with a join timeout.  If the thread
does not complete within `max_runtime_seconds`, the result reports:

```
Execution timed out after {max_runtime_seconds}s
```

The daemon thread is abandoned (not forcibly killed).  For MVP this is
acceptable; future versions may use process isolation.

### Output Size

Combined stdout + stderr is capped at `max_output_bytes`.  When
exceeded the output is byte-truncated and `InlineToolResult.truncated`
is set to `True`.

### Write Guard

Tools declaring `capability.writes = True` trigger a write-guard check
via `SkillContext.enforce_write_guard()`.  In a read-only context the
executor returns an error result **without executing any code**.

### Path Restriction

Input data keys matching `*_path`, `*_file`, or `path` are resolved
and checked against the sandbox root.  Paths that escape the sandbox
are rejected before execution.

### Language / Source Support

Only `source = custom` (Python) is supported for MVP.  Other source
types (`mcp`, `agent_skill`, `builtin`, `wrapped`) are rejected by
`validate_tool`.

### Network Access

For MVP, inline tools run in the host process and **no network
sandboxing** is enforced.  Tools should be treated as local-only.
Future milestones will introduce process-level or container-level
isolation for network restrictions.
