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
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ All notable changes to the OpenIntent SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.15.0] - 2026-03-02

### Added

- **RFC-0010 Retry Policy MCP Tools** — 4 new MCP tools: `set_retry_policy` (admin), `get_retry_policy` (read), `record_failure` (write), `get_failures` (read). MCP tool surface expanded from 66 to 70 tools; RBAC counts: reader=25, operator=43, admin=70.
- **`build_retry_failure_tools()`** — New helper in the Python MCP bridge (`openintent.mcp`) that constructs retry policy and failure-tracking tool definitions for MCP integration, enabling agents to manage retry policies and record/query failures through MCP.

### Changed

- **Native FastAPI SSE** — Replaced `sse-starlette` third-party dependency with FastAPI's built-in `EventSourceResponse` and `ServerSentEvent` (available since FastAPI 0.135.0). All four SSE subscription endpoints (`/api/v1/subscribe/intents/{id}`, `/api/v1/subscribe/portfolios/{id}`, `/api/v1/subscribe/agents/{id}`, `/api/v1/subscribe/channels/{id}`) now use native async generator pattern with `ServerSentEvent` objects instead of yielding raw dicts through `sse-starlette`'s `EventSourceResponse`. Keep-alive pings now use SSE comment format (`ServerSentEvent(comment="ping")`) per the SSE spec recommendation.

### Removed

- **`sse-starlette` dependency** — No longer required. The `server` optional dependency group now requires `fastapi>=0.135.0` (previously `>=0.104.0`) which includes native SSE support.

### Updated

- All version references updated to 0.15.0 across Python SDK, MCP server package, and changelog.
- FastAPI minimum version bumped from `>=0.104.0` to `>=0.135.0` in server extras.

---

## [0.14.1] - 2026-02-27

### Fixed
Expand Down
27 changes: 27 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ All notable changes to the OpenIntent SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.15.0] - 2026-03-02

### Added

- **RFC-0010 Retry Policy MCP Tools** — 4 new MCP tools for retry policy management and failure tracking:
- `set_retry_policy` — Set or update retry policy on an intent (admin tier).
- `get_retry_policy` — Retrieve the current retry policy for an intent (reader tier).
- `record_failure` — Record a failure event against an intent for retry tracking (operator tier).
- `get_failures` — List recorded failures for an intent (reader tier).
- **`build_retry_failure_tools()`** — New helper in the Python MCP bridge (`openintent.mcp`) that builds the 4 retry/failure tool definitions for use with `MCPToolProvider` and `MCPToolExporter`.
- **MCP Tool Surface Expansion** — MCP tool surface expanded from 66 to 70 tools; RBAC counts: reader=25, operator=43, admin=70.

### Changed

- **Native FastAPI SSE** — Replaced `sse-starlette` third-party dependency with FastAPI's built-in `EventSourceResponse` and `ServerSentEvent` (available since FastAPI 0.135.0). All four SSE subscription endpoints (`/api/v1/subscribe/intents/{id}`, `/api/v1/subscribe/portfolios/{id}`, `/api/v1/subscribe/agents/{id}`, `/api/v1/subscribe/channels/{id}`) now use native async generator pattern with `ServerSentEvent` objects instead of yielding raw dicts through `sse-starlette`'s `EventSourceResponse`. Keep-alive pings now use SSE comment format (`ServerSentEvent(comment="ping")`) per the SSE spec recommendation.

### Removed

- **`sse-starlette` dependency** — No longer required. The `server` optional dependency group now requires `fastapi>=0.135.0` (previously `>=0.104.0`) which includes native SSE support.

### Updated

- All version references updated to 0.15.0 across Python SDK, MCP server package, and changelog.
- FastAPI minimum version bumped from `>=0.104.0` to `>=0.135.0` in server extras.

---

## [0.14.1] - 2026-02-27

### Fixed
Expand Down
38 changes: 38 additions & 0 deletions docs/guide/cost-retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,46 @@ workflow:
!!! warning "Idempotency"
When using retry policies, ensure your agent handlers are idempotent — safe to call multiple times with the same input without side effects.

## MCP Tool Access for Retry Policies

The [MCP server](mcp.md) exposes 4 dedicated tools for retry policy management via the Model Context Protocol, allowing MCP clients like Claude Desktop and Cursor to manage retry policies and query failure history directly.

| MCP Tool | Tier | Description |
|----------|------|-------------|
| `openintent_set_retry_policy` | `admin` | Configure retry policy for an intent (max attempts, backoff strategy, delays) |
| `openintent_get_retry_policy` | `read` | Retrieve the current retry policy for an intent |
| `openintent_record_failure` | `write` | Record a failure attempt against an intent for retry scheduling |
| `openintent_get_failures` | `read` | Query failure history for an intent |

These tools are available through the `@openintentai/mcp-server` package and follow the same RBAC model as all other MCP tools. The `build_retry_failure_tools()` function in the Python MCP bridge registers these tools automatically.

```python
from openintent import Agent, MCPTool, on_assignment

@Agent("retry-manager", model="gpt-4o", tools=[
MCPTool(
server="npx",
args=["-y", "@openintentai/mcp-server"],
role="admin",
allowed_tools=[
"openintent_set_retry_policy",
"openintent_get_retry_policy",
"openintent_record_failure",
"openintent_get_failures",
],
),
])
class RetryManager:
@on_assignment
async def work(self, intent):
return await self.think(intent.description)
```

With these tools, the total MCP tool surface is 70 tools across all categories, with RBAC counts of reader=25, operator=43, admin=70.

## Next Steps

- [MCP Integration](mcp.md) — Full MCP server and bridge documentation
- [LLM Adapters](adapters.md) — Automatic cost tracking for LLM calls
- [Governance & Arbitration](governance.md) — Budget enforcement and escalation
- [YAML Workflows](workflows.md) — Declarative retry and cost configuration
18 changes: 13 additions & 5 deletions docs/guide/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Researcher:
```

!!! info "RBAC role determines tool visibility"
The `role` field on `MCPTool` maps directly to the RBAC system. When connecting to `@openintentai/mcp-server`, the server filters its tool listing based on the role: `reader` sees 4 read-only tools, `operator` sees 10 read+write tools, `admin` sees all 16 tools. The `allowed_tools` field further restricts within the role's permissions — both gates must pass.
The `role` field on `MCPTool` maps directly to the RBAC system. When connecting to `@openintentai/mcp-server`, the server filters its tool listing based on the role: `reader` sees 25 read-only tools, `operator` sees 43 read+write tools, `admin` sees all 70 tools. The `allowed_tools` field further restricts within the role's permissions — both gates must pass.

!!! warning "Least-privilege by design"
`MCPTool.role` defaults to `"reader"` — the most restrictive level. Each agent should declare exactly the minimum role it needs. In multi-agent topologies, each agent's MCP server runs as an isolated child process with its own explicit role, so one agent's privilege never leaks to another. Do **not** set `OPENINTENT_MCP_ROLE` as a global environment variable in multi-agent setups — declare the role per-agent on each `MCPTool` instead.
Expand Down Expand Up @@ -256,6 +256,10 @@ With the MCP server connected, Claude can interact with OpenIntent directly. Her
| `openintent_assign_agent` | `admin` | Assign an agent to an intent |
| `openintent_unassign_agent` | `admin` | Remove an agent assignment |
| `openintent_create_channel` | `admin` | Create a messaging channel |
| `openintent_set_retry_policy` | `admin` | Configure retry policy for an intent |
| `openintent_get_retry_policy` | `read` | Retrieve the retry policy for an intent |
| `openintent_record_failure` | `write` | Record a failure attempt for retry scheduling |
| `openintent_get_failures` | `read` | Query failure history for an intent |

### Available Resources

Expand Down Expand Up @@ -287,9 +291,9 @@ Every tool belongs to a **permission tier** — `read`, `write`, or `admin`. Rol

| Role | Tiers Granted | Tool Count | Use Case |
|------|---------------|------------|----------|
| **`reader`** | `read` | 4 | Dashboards, monitoring, auditing. The MCP client can observe but never modify protocol state. |
| **`operator`** | `read` + `write` | 10 | Worker agents that create intents, update state, and communicate. Cannot change lifecycle status, manage leases, or restructure agent assignments. |
| **`admin`** | `read` + `write` + `admin` | 16 | Trusted orchestrators with full control. Required for lifecycle management, lease coordination, and structural operations. |
| **`reader`** | `read` | 25 | Dashboards, monitoring, auditing. The MCP client can observe but never modify protocol state. |
| **`operator`** | `read` + `write` | 43 | Worker agents that create intents, update state, and communicate. Cannot change lifecycle status, manage leases, or restructure agent assignments. |
| **`admin`** | `read` + `write` + `admin` | 70 | Trusted orchestrators with full control. Required for lifecycle management, lease coordination, and structural operations. |

#### Tool Classification

Expand All @@ -301,6 +305,8 @@ Every tool belongs to a **permission tier** — `read`, `write`, or `admin`. Rol
| `openintent_list_intents` | Query intents with status filters |
| `openintent_get_events` | Read the immutable event log |
| `openintent_get_messages` | Read channel message history |
| `openintent_get_retry_policy` | Retrieve the retry policy for an intent |
| `openintent_get_failures` | Query failure history for an intent |

**Write tier** — bounded mutations with protocol safety:

Expand All @@ -312,6 +318,7 @@ Every tool belongs to a **permission tier** — `read`, `write`, or `admin`. Rol
| `openintent_send_message` | Send a channel message | Scoped to channel membership |
| `openintent_ask` | Request/reply on a channel | Scoped to channel membership |
| `openintent_broadcast` | Broadcast to channel | Scoped to channel membership |
| `openintent_record_failure` | Record a failure attempt | Append-only failure record for retry scheduling |

**Admin tier** — structural and lifecycle operations:

Expand All @@ -323,6 +330,7 @@ Every tool belongs to a **permission tier** — `read`, `write`, or `admin`. Rol
| `openintent_assign_agent` | Add agent to intent | Changes who can work on an intent. |
| `openintent_unassign_agent` | Remove agent from intent | Removing an active agent disrupts in-progress work. |
| `openintent_create_channel` | Create messaging channel | Establishes communication topology between agents. |
| `openintent_set_retry_policy` | Configure retry policy for an intent | Controls retry behavior including max attempts, backoff strategy, and delay parameters. Misconfiguration can cause excessive retries. |

#### Default Role

Expand Down Expand Up @@ -398,7 +406,7 @@ If the role is not set or is set to an unrecognized value, the server falls back
The server logs its active role and tool count at startup:

```
[openintent-mcp] Server started – role="operator", tools=10/16, connected to http://localhost:8000
[openintent-mcp] Server started – role="operator", tools=43/70, connected to http://localhost:8000
```

If the role is `admin`, a warning is emitted:
Expand Down
4 changes: 2 additions & 2 deletions docs/overrides/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<!-- Hero -->
<div class="oi-hero">
<div class="oi-hero__badge">v0.14.0 &mdash; Federation Protocol, Security &amp; Python SDK Implementation</div>
<div class="oi-hero__badge">v0.15.0 &mdash; Native FastAPI SSE &amp; RFC-0010 Retry MCP Tools</div>
<h1 class="oi-hero__title">Stop Duct-Taping Your Agents Together</h1>
<p class="oi-hero__subtitle">
OpenIntent is a durable, auditable protocol for multi-agent coordination. Structured intents replace fragile chat chains. Versioned state replaces guesswork. Ship agent systems that actually work in production.
Expand All @@ -50,7 +50,7 @@ <h1 class="oi-hero__title">Stop Duct-Taping Your Agents Together</h1>
<div class="oi-stat__label">Tests</div>
</div>
<div class="oi-stat">
<div class="oi-stat__number">v0.14.0</div>
<div class="oi-stat__number">v0.15.0</div>
<div class="oi-stat__label">Latest</div>
</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ The server enforces role-based access control with three tiers:

| Role | Tools | Description |
|---|---|---|
| `reader` | 21 | Read-only observation: list/get intents, events, agents, costs |
| `operator` | 38 | Bounded mutations: create intents, post events, manage leases |
| `admin` | 62 | Full lifecycle: governance, identity, vaults, triggers, plans |
| `reader` | 25 | Read-only observation: list/get intents, events, agents, costs, retry policies, failures |
| `operator` | 43 | Bounded mutations: create intents, post events, manage leases, record failures |
| `admin` | 70 | Full lifecycle: governance, identity, vaults, triggers, plans, retry policies |

Tools not permitted by the assigned role are hidden from the MCP tool listing entirely.

Expand All @@ -93,6 +93,7 @@ The role gate works alongside the `allowed_tools` allowlist — both must pass f
- **Verifiable Logs** (RFC-0019) — Hash chains, Merkle proofs
- **Tracing** (RFC-0020) — Distributed trace propagation
- **Messaging** (RFC-0021) — Agent-to-agent channels
- **Retry Policies** (RFC-0010) — Set/get retry policies, record and query failures

## Resources

Expand Down
2 changes: 1 addition & 1 deletion mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openintentai/mcp-server",
"version": "0.14.1",
"version": "0.15.0",
"description": "MCP server exposing the OpenIntent Coordination Protocol as MCP tools and resources",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
47 changes: 47 additions & 0 deletions mcp-server/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -926,4 +926,51 @@ export class OpenIntentClient {
trace_id: params.trace_id,
});
}

// ── Retry Policy & Failures (RFC-0010) ──────────────────────────────

async setRetryPolicy(params: {
intent_id: string;
policy: Record<string, unknown>;
}): Promise<unknown> {
return this.request("PUT", `/api/v1/intents/${params.intent_id}/retry-policy`, params.policy);
}

async getRetryPolicy(params: {
intent_id: string;
}): Promise<unknown> {
return this.request("GET", `/api/v1/intents/${params.intent_id}/retry-policy`);
}

async recordFailure(params: {
intent_id: string;
agent_id: string;
attempt_number: number;
error_code?: string;
error_message?: string;
retry_scheduled_at?: string;
metadata?: Record<string, unknown>;
}): Promise<unknown> {
return this.request("POST", `/api/v1/intents/${params.intent_id}/failures`, {
agent_id: params.agent_id,
attempt_number: params.attempt_number,
error_code: params.error_code,
error_message: params.error_message,
retry_scheduled_at: params.retry_scheduled_at,
metadata: params.metadata ?? {},
});
}

async getFailures(params: {
intent_id: string;
limit?: number;
}): Promise<unknown> {
const query = new URLSearchParams();
if (params.limit !== undefined) query.set("limit", String(params.limit));
const qs = query.toString();
return this.request(
"GET",
`/api/v1/intents/${params.intent_id}/failures${qs ? `?${qs}` : ""}`,
);
}
}
2 changes: 1 addition & 1 deletion mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function main() {
const server = new Server(
{
name: "openintent-mcp",
version: "0.14.1",
version: "0.15.0",
},
{
capabilities: {
Expand Down
6 changes: 6 additions & 0 deletions mcp-server/src/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export const TOOL_TIERS: Record<string, ToolTier> = {
openintent_start_trace: "write",
openintent_link_spans: "write",

// Retry Policy & Failures (RFC-0010)
openintent_set_retry_policy: "admin",
openintent_get_retry_policy: "read",
openintent_record_failure: "write",
openintent_get_failures: "read",

// Federation (RFC-0022)
openintent_federation_status: "read",
openintent_list_federated_agents: "read",
Expand Down
Loading