# Public API

This document describes the main embedding surface of `sandlet-sdk` in its current `0.x` phase.

## Main entry points

Most users should work through the crate root and build an agent with:

```rust
Agent::builder()
```

The current root-level types and functions are:

- `Agent`
- `AgentBuilder`
- `Error`
- `ErrorKind`
- `Result`
- `CapabilityMetadata`
- `FunctionSpec`
- `FunctionModule`
- `FunctionModuleBuilder`
- `Module`
- `ModuleSpec`
- `ModuleParameter`
- `ModuleMetadata`
- `FunctionMetadata`
- `qualified_name`
- `register_fn`
- `RunLimits`
- `RunRequest`
- `ScriptSource`
- `AgentMetadata`
- `RunResult`
- `ExecutionContext`
- `RunCancellation`
- `builtin`

## How to read this surface

Not every public item has the same weight.

### Primary embedding API

These are the parts most embedders are expected to use directly:

- `Agent`
- `AgentBuilder`
- `FunctionModule`
- `FunctionModuleBuilder`
- `Module`
- `ModuleSpec`
- `FunctionSpec`
- `CapabilityMetadata`
- `qualified_name`
- `register_fn`
- `RunRequest`
- `ScriptSource`
- `RunResult`
- `RunCancellation`

### Module definition model

The canonical path for defining modules uses `ModuleSpec` and `FunctionSpec`:

- `ModuleSpec` is the source of truth for a module's name, documentation, capabilities, required parameters, and function declarations.
- `FunctionSpec` declares a function with its module-qualified name, documentation, argument names, and return type.
- `FunctionSpec::new(module_name, function_name, ...)` automatically qualifies the exported function name as `<module>_<function>`.
- `ModuleParameter` defines a runtime parameter (e.g., credentials, connection strings) that the module expects when a script is executed.
- `CapabilityMetadata` attaches light-weight capability labels (e.g. `network`, `filesystem`) to a module.
- `ModuleMetadata` and `FunctionMetadata` are still available but are now derived from the spec types.

### Naming contract

Exported host function names must follow the pattern `<module>_<function>`, where:

- `<module>` is the short group name (e.g. `strings`, `ip`, `weather`).
- `<function>` is the function-specific suffix (e.g. `uppercase`, `public_ip`, `for_city`).
- Module names must not contain underscores.

This naming is enforced during agent construction. A function whose exported name does not start with its module prefix will be rejected. The `qualified_name` helper constructs correct names and `FunctionSpec::new` applies it automatically.

### Builder-based module authoring

The preferred way to define a module uses `FunctionModule::builder`:

```rust
FunctionModule::builder("strings")
    .doc("Basic string helpers.")
    .function(FunctionSpec::new(
        "strings", "uppercase",
        "Uppercase the provided string.",
        vec!["value".into()],
        "String",
    ))
    .on_close(|| {
        // Optional cleanup hook run after execution completes
        Ok(())
    })
    .build(|runtime, _params| {
        register_fn(runtime, "strings_uppercase", "Uppercase the provided string.", &["value"], my_fn)
    })
```

For modules declaring capabilities and requiring parameters:

```rust
FunctionModule::builder("ip")
    .doc("IP discovery module.")
    .capability(CapabilityMetadata::network())
    .parameter(ModuleParameter {
        name: "API_KEY".into(),
        doc: "Key for the IP API".into(),
        secret: true,
    })
    .function(FunctionSpec::new("ip", "public_ip", "Fetch public IP.", vec![], "String"))
    .build(|runtime, params| {
        let api_key = params.get("API_KEY").cloned().unwrap_or_default();
        /* registration closure using api_key */
    })
```

### Validation

`AgentBuilder::build()` validates modules before constructing the agent. It rejects:

- empty module names
- module names containing underscores
- duplicate module names across all modules
- duplicate exported function names across all modules
- function names that do not follow the `<module>_<function>` naming contract

Validation failures return `Error::invalid_request` with a structured diagnostic and occur before any runtime registration.

### Cancellation

`RunLimits::timeout_s` enables best-effort timeout enforcement for a run. `0` means no timeout. Timeouts and explicit cancellation are reported as `ErrorKind::Cancelled` with structured diagnostic codes:

- `timeout-exceeded`
- `execution-cancelled`

Embedders that need explicit cancellation can call `Agent::run_once_with_cancellation(request, token)` with a `RunCancellation`. The default `Agent::run_once(request)` creates a timeout-backed token from `request.limits.timeout_s`.

The current `register_fn` host-function API does not pass `ExecutionContext` into registered functions, so host functions are not forcibly interrupted mid-call. Cancellation is observed at SDK-managed checkpoints before and after registration, compilation, and entrypoint calls.

Example:

```rust
let cancellation = sandlet_sdk::RunCancellation::none();
let result = agent.run_once_with_cancellation(request, cancellation.clone());

// Another thread or task can request cancellation.
cancellation.cancel();
```

Future host-function APIs that receive `ExecutionContext` should call `ctx.check_cancelled()?` during long-running work and return the resulting `Error` unchanged.

### Supporting public types

These are public because they are part of normal embedding today, but they may still change shape as the SDK matures:

- `RunLimits`
- `RunCancellation`
- `FunctionMetadata`
- `ModuleMetadata`
- `AgentMetadata`

`AgentMetadata` contains:

- `agent_name`
- `sdk_version` — SDK crate version
- `modules` — list of `ModuleMetadata` including capability labels
- `declared_limits` — default runtime limits as a `HashMap<String, String>`
- `metadata` — operator/runtime inspection metadata as a `HashMap<String, String>`

These fields are also exposed on the gRPC `DescribeResponse` wire message.

### Supporting diagnostic types

These are public so callers can inspect structured diagnostics produced during a run:

- `ErrorKind` — stable failure-domain enum (`InvalidRequest`, `Compile`, `RuntimeContract`, `RuntimeExecution`, `Transport`, `ResourceExhausted`, `Cancelled`, `Internal`, `Panic`)
- `Diagnostic`
- `LogEntry`
- `RunDiagnostics`
- `Category`
- `Severity`
- `SourceLocation`

### Builtin and Official Modules

The SDK provides several built-in modules that are necessary for building robust scripts:

- `builtin::fs::module()`: Provides the `fs_create_tempfile()` function, creating safe temporary file handles (`LocalTempFile`). Essential for working securely with the filesystem.

You can also use external capability crates that integrate cleanly with the SDK:

- `sandlet-s3`: Provides S3 operations (`s3_download`, `s3_upload`, `s3_list`) operating securely over `LocalTempFile` handles.

### Public convenience surface

These are public because they are useful, but they are not the center of the SDK design:

- `builtin`
- `Error`
- `ErrorKind`
- `Result`

## What this means in practice

Today the SDK is aimed at embedders who want to:

- create an agent binary in Rust
- register one or more host modules
- execute Roto scripts through the SDK CLI
- execute Roto scripts over the built-in gRPC transport

## Internal modules

Some modules exist to support the current implementation and should not be treated as stable embedding interfaces:

- `cli`
- `engine`
- `grpc`
- `loader`

The following details are also internal:

- `ModuleRef`
- `Agent::new_validated(...)` construction

## Stability in `0.x`

The SDK is still in an early phase. The purpose of this document is not to promise a final `1.0` design, but to make
the current supported surface clear.

Reasonable expectations today:

- the primary embedding API is the part of the crate most likely to remain recognizable
- builder-based agent construction is the preferred path
- module registration through `FunctionModule::builder`, `ModuleSpec`, and `FunctionSpec` is the intended extension model
- module metadata derives from the structured spec and is not duplicated separately
- validation of module definitions happens at agent build time, not at runtime
- supporting public types may still be reshaped to improve ergonomics or runtime clarity

Things still expected to evolve:

- hard-stop cancellation for pure CPU-bound Roto code or registered host functions that do not return to an SDK checkpoint
- per-run metadata shape and discovery

## gRPC transport

The crate exposes `Agent::serve` which starts a tonic gRPC server. The protobuf schema is located at `proto/sandlet/v1/agent.proto` and defines two RPCs:

- `Describe` — returns agent metadata
- `RunOnce` — executes the `main` entrypoint of an inline script and returns either `RunSuccess` or `RunFailure`

Application-level errors (compile failures, contract violations, resource limits, panics) are returned inside the protobuf `RunFailure` message with a machine-readable `kind` enum, a readable message prefixed by the error kind, and attached diagnostics. Only true transport-level failures produce tonic `Status` errors.

Only inline scripts are accepted over gRPC. The `entrypoint` field is not part of the gRPC request; the runtime always executes `main`.
