# API Reference

Complete reference for the MFP public API.

## Imports

```python
from mfp import (
    # Core
    Runtime,
    RuntimeConfig,
    # Agent lifecycle
    AgentHandle,
    bind,
    unbind,
    # Protocol tools (sync)
    mfp_send,
    mfp_channels,
    mfp_status,
    # Protocol tools (async)
    mfp_send_async,
    mfp_channels_async,
    mfp_status_async,
    # Types
    AgentCallable,
    AsyncAgentCallable,
    AgentId,
    AgentStatus,
    AgentError,
    AgentErrorCode,
    ChannelId,
    ChannelInfo,
    DeliveredMessage,
    Receipt,
    # Constants
    PROTOCOL_VERSION,
    __version__,
)
```

Everything in `__all__` is the stable public API. Everything else is internal and may change without notice.

---

## Core

### `Runtime`

The central coordinator for all MFP operations.

**Constructor:**
```python
Runtime(config: RuntimeConfig)
```

**Methods:**

- **`shutdown() -> None`**
  Gracefully shutdown the runtime, flushing all pending operations and closing storage.

**Example:**
```python
config = RuntimeConfig()
runtime = Runtime(config)
# ... use runtime ...
runtime.shutdown()
```

---

### `RuntimeConfig`

Configuration for runtime behavior. All fields have defaults, so `RuntimeConfig()` works out of the box.

**Constructor:**
```python
RuntimeConfig(
    # Frame
    default_frame_depth: int = 4,
    # Quarantine
    validation_failure_threshold: int = 5,
    max_message_rate: int = 1000,
    max_payload_size: int = 1_048_576,
    # Encoding
    encoding_algorithm: bytes = b"aes-256-gcm",
    supported_algorithms: tuple[bytes, ...] = (b"aes-256-gcm",),
    # Resource limits
    max_channels_per_agent: int = 100,
    max_agents: int = 10_000,
    # Timeouts
    agent_timeout_seconds: float = 30.0,
    pipeline_timeout_seconds: float = 5.0,
    storage_timeout_seconds: float = 10.0,
    # Persistence (None = in-memory only)
    storage: StorageConfig | None = None,
)
```

**Key Parameters:**

- `max_message_rate` — Messages per second per agent before rate limiting (default: 1000)
- `max_payload_size` — Maximum payload size in bytes (default: 1 MB)
- `agent_timeout_seconds` — Agent callable timeout; exceeding triggers quarantine (default: 30.0)
- `storage` — Persistence configuration; `None` for in-memory only (default: None)
- `supported_algorithms` — Encoding algorithms supported for negotiation (default: AES-256-GCM)

**Example:**
```python
from mfp.storage.engine import StorageConfig

config = RuntimeConfig(
    max_message_rate=500,
    agent_timeout_seconds=10.0,
    storage=StorageConfig(path="state.db"),
)
```

---

## Agent Lifecycle

### `bind()`

Register an agent callable with a runtime.

**Signature:**
```python
bind(
    runtime: Runtime,
    agent_callable: AgentCallable,
) -> AgentHandle
```

**Parameters:**

- `runtime` — The runtime to bind to
- `agent_callable` — A callable with signature `(msg: DeliveredMessage) -> None`. Async callables (`async (msg: DeliveredMessage) -> None`) are auto-detected.

**Returns:** `AgentHandle` for interacting with the agent

**Raises:** `AgentError` if binding fails

**Example:**
```python
def my_agent(msg: DeliveredMessage) -> None:
    print(msg.payload.decode())

handle = bind(runtime, my_agent)

# Async agents also work:
async def my_async_agent(msg: DeliveredMessage) -> None:
    await process(msg.payload)

async_handle = bind(runtime, my_async_agent)
```

---

### `unbind()`

Deregister an agent and close all its channels.

**Signature:**
```python
unbind(handle: AgentHandle) -> None
```

**Parameters:**

- `handle` — The agent handle to unbind

**Side effects:**
- Closes all channels
- Flushes pending messages
- Transitions agent to `UNBOUND` state

**Example:**
```python
unbind(handle)
# handle is now invalid
```

---

### `AgentHandle`

Handle for an active agent, providing access to agent operations.

**Attributes:**

- `agent_id: bytes` — Unique 32-byte agent identifier (read-only)
- `runtime: Runtime` — Associated runtime (read-only)

**Methods:**

- **`send(channel_id: ChannelId, payload: bytes) -> Receipt`**
  Send a message on a channel. Requires ACTIVE state.

- **`channels() -> list[ChannelInfo]`**
  List channels visible to this agent. Requires ACTIVE state.

- **`status() -> AgentStatus`**
  Query own lifecycle status. Requires BOUND or ACTIVE state.

**Note:** Channel establishment is a Layer 2 (admin) operation on `Runtime`, not on `AgentHandle`.

**Example:**
```python
handle = bind(runtime, my_agent)
channel_id = runtime.establish_channel(handle.agent_id, peer_handle.agent_id)
```

---

## Protocol Tools

### `mfp_send()`

Send an encrypted message on a channel.

**Signature:**
```python
mfp_send(
    handle: AgentHandle,
    channel_id: ChannelId,
    payload: bytes,
) -> Receipt
```

**Parameters:**

- `handle` — Sending agent's handle
- `channel_id` — Target channel ID (`ChannelId`)
- `payload` — Message payload (bytes)

**Returns:** `Receipt` with `message_id`, `channel`, `step`, and `correlation_id`

**Raises:**
- `AgentError(UNBOUND)` — Agent not in ACTIVE state
- `AgentError(INVALID_CHANNEL)` — Channel doesn't exist or sender not bound to it

**Example:**
```python
receipt = mfp_send(
    handle,
    channel_id=channel.channel_id,
    payload=b"Hello, world!",
)
print(receipt.message_id.value.hex())
```

---

### `mfp_channels()`

List all channels for an agent.

**Signature:**
```python
mfp_channels(handle: AgentHandle) -> list[ChannelInfo]
```

**Parameters:**

- `handle` — Agent handle to query

**Returns:** List of `ChannelInfo` objects

**Example:**
```python
channels = mfp_channels(handle)
for ch in channels:
    print(f"Channel: {ch.channel_id.hex()[:16]}")
    print(f"Peer: {ch.peer_id.hex()[:16]}")
```

---

### `mfp_status()`

Get current status of an agent.

**Signature:**
```python
mfp_status(handle: AgentHandle) -> AgentStatusInfo
```

**Parameters:**

- `handle` — Agent handle to query

**Returns:** `AgentStatus` with fields:
- `agent_id: AgentId` — The agent's identity
- `state: AgentState` — Current state (BOUND, ACTIVE, QUARANTINED, TERMINATED)
- `channel_count: int` — Number of active channels

**Example:**
```python
from mfp.core.types import AgentState

status = mfp_status(handle)
if status.state == AgentState.ACTIVE:
    print(f"{status.channel_count} channels active")
```

---

## Async Protocol Tools

### `mfp_send_async()`

Async variant of `mfp_send()`. Uses `Runtime.send_async()` internally.

**Signature:**
```python
async mfp_send_async(
    handle: AgentHandle,
    channel_id: ChannelId,
    payload: bytes,
) -> Receipt
```

### `mfp_channels_async()`

Async variant of `mfp_channels()`.

**Signature:**
```python
async mfp_channels_async(handle: AgentHandle) -> list[ChannelInfo]
```

### `mfp_status_async()`

Async variant of `mfp_status()`.

**Signature:**
```python
async mfp_status_async(handle: AgentHandle) -> AgentStatus
```

---

## Types

### `AgentId`

Frozen dataclass wrapping agent identifiers.

```python
@dataclass(frozen=True)
class AgentId:
    value: bytes
```

---

### `ChannelId`

Frozen dataclass wrapping channel identifiers (16 bytes).

```python
@dataclass(frozen=True)
class ChannelId:
    value: bytes  # 16 bytes
```

---

### `AgentStatus`

Frozen dataclass returned by `mfp_status()`.

**Fields:**

- `agent_id: AgentId` — The agent's identity
- `state: AgentState` — Current lifecycle state (BOUND, ACTIVE, QUARANTINED, TERMINATED)
- `channel_count: int` — Number of active channels

**Example:**
```python
from mfp import mfp_status
from mfp.core.types import AgentState

status = mfp_status(handle)
if status.state == AgentState.ACTIVE:
    print(f"Agent is active with {status.channel_count} channels")
```

---

### `ChannelInfo`

Agent-visible channel information. No protocol-internal state exposed.

**Fields:**

- `channel_id: ChannelId` — Channel identifier
- `peer: AgentId` — Peer agent identifier
- `status: ChannelStatus` — Channel status (ACTIVE, QUARANTINED, CLOSED)

**Example:**
```python
channels = mfp_channels(handle)
info = channels[0]
print(f"Channel with {info.peer.value.hex()[:8]}")
```

---

### `DeliveredMessage`

Decoded message delivered to the destination agent callable.

**Fields:**

- `payload: bytes` — Decoded message payload
- `sender: AgentId` — Sending agent's identity
- `channel: ChannelId` — Channel the message arrived on
- `message_id: MessageId` — Unique message identifier
- `correlation_id: str` — Trace ID for observability (default: `""`)

**Example:**
```python
def my_agent(msg: DeliveredMessage) -> None:
    print(f"From {msg.sender.value.hex()[:8]}: {msg.payload.decode()}")
```

---

### `AsyncAgentCallable`

Type alias for async agent handler functions.

```python
AsyncAgentCallable = Callable[[DeliveredMessage], Awaitable[None]]
```

Both `AgentCallable` and `AsyncAgentCallable` are accepted by `bind()`. Async callables are auto-detected via `inspect.iscoroutinefunction()`.

---

### `Receipt`

Proof of message send operation.

**Fields:**

- `message_id: MessageId` — Unique message identifier
- `channel: ChannelId` — The channel the message was sent on
- `step: int` — Ratchet step at send time
- `correlation_id: str` — Trace ID for observability (default: `""`)

**Example:**
```python
receipt = mfp_send(handle, channel_id, b"message")
print(f"Message ID: {receipt.message_id.value.hex()}")
print(f"Step: {receipt.step}")
```

---

### `AgentError`

Exception raised for agent operation failures.

**Constructor:**
```python
AgentError(code: AgentErrorCode, message: str)
```

**Attributes:**

- `code: AgentErrorCode` — Error category
- `message: str` — Human-readable description

**Example:**
```python
from mfp import AgentError, AgentErrorCode

try:
    mfp_send(handle, bad_channel_id, b"test")
except AgentError as e:
    if e.code == AgentErrorCode.CHANNEL_NOT_FOUND:
        print("Channel doesn't exist")
```

---

### `AgentErrorCode`

Enum of error categories.

**Values:**

- `UNBOUND` — Operation not available in current agent state
- `QUARANTINED` — Agent is quarantined
- `INVALID_CHANNEL` — Sender not bound to channel
- `CHANNEL_CLOSED` — Channel is closed
- `CHANNEL_QUARANTINED` — Channel is quarantined
- `PAYLOAD_TOO_LARGE` — Payload exceeds size limit
- `RESOURCE_LIMIT_EXCEEDED` — Resource limit hit (max agents, max channels)
- `TIMEOUT` — Agent callable exceeded timeout

**Example:**
```python
from mfp import AgentErrorCode

if error.code == AgentErrorCode.TIMEOUT:
    print("Agent callable timed out")
```

---

## Thread Safety

**Note:** The MFP API is **not thread-safe**. All operations on a `Runtime` or `AgentHandle` should be performed from a single thread, or externally synchronized.

For concurrent applications, use separate `Runtime` instances per thread/process.

---

## See Also

- [Quickstart Guide](quickstart.md) — hands-on tutorial
- [Architecture](architecture.md) — internal design
- [Production Guide](production-guide.md) — deployment and monitoring
- [Migration Guide](migration-guide.md) — upgrading from 0.1.0
