Metadata-Version: 2.4
Name: cadreen-sdk
Version: 0.7.2
Summary: Python SDK for Cadreen — intelligence-native automation infrastructure
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: httpx-sse>=0.4
Requires-Dist: httpx>=0.25
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# cadreen-sdk

Python SDK for [Cadreen](https://accomplishanything.today/infra/docs) — Intelligence as a Service.

> **⚠️ Compatibility Advisory:** Version 0.7.0 has known contract mismatches with the server. Version 0.7.0 is unsupported. **Upgrade to 0.7.2.** See [CHANGELOG](#v072) for details.

Cadreen is a cognitive operating system. Send messages describing what you want done, and Cadreen reasons, connects tools, recalls knowledge, governs actions, and escalates to humans when needed. The SDK handles authentication, retries, idempotency, streaming, and error classification.

## Install

```bash
pip install cadreen-sdk
```

## Quick Start

```python
import asyncio
from cadreen import Cadreen

cadreen = Cadreen(api_key="sk_cadreen_...")

async def main():
    # Intent — the main entry point
    result = await cadreen.ask(
        "Handle a refund request for invoice inv_123",
    )
    print(result.explain())

    # Discriminated union with match/case
    match result.type:
        case "direct":
            print(result.message.content)
        case "clarify":
            for q in result.questions:
                print(q)
        case "execution":
            async for event in cadreen.executions.stream(result.execution["id"]):
                print(event.type, event.data)
        case "blocked":
            print(result.reason_code)
        case "connect_required":
            print(result.endpoint)

asyncio.run(main())
```

## Chat Completions

OpenAI-compatible chat completions with built-in governance. Every tool call goes through governance before execution — auto-approved calls execute silently, blocked calls become conversation.

```python
from cadreen.resources.chat import (
    ChatCompletionRequest, ChatMessage,
    ChatToolDefinition, ChatFunctionDefinition,
)

# Basic completion
response = await cadreen.chat.completions(ChatCompletionRequest(
    messages=[ChatMessage(role="user", content="Hello!")],
))
print(response.choices[0].message.content)

# With tool calling
response = await cadreen.chat.completions(ChatCompletionRequest(
    messages=[ChatMessage(role="user", content="Refund order 456")],
    tools=[ChatToolDefinition(function=ChatFunctionDefinition(
        name="process_refund",
        description="Process a refund for an order",
        parameters={
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    ))],
))

# If governance needs approval, the response contains a text message
# asking the user to confirm — no tool_calls field
msg = response.choices[0].message
if msg.tool_calls:
    for tc in msg.tool_calls:
        print(f"{tc.function.name}({tc.function.arguments})")

# Resume a conversation
follow_up = await cadreen.chat.completions(ChatCompletionRequest(
    messages=[ChatMessage(role="user", content="What about order 789?")],
    conversation_id=response.conversation_id,
))
```

### Streaming

```python
async for chunk in await cadreen.chat.completions_stream(ChatCompletionRequest(
    messages=[ChatMessage(role="user", content="Hello!")],
)):
    if chunk.type == "reasoning_delta":
        # Model is thinking — render in a collapsible accordion
        print(f"[thinking] {chunk.reasoning}", end="", flush=True)
    elif chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
```

### Tool Discovery

```python
tools = await cadreen.chat.list_tools()
for tool in tools.data:
    print(f"{tool.function.name}: {tool.function.description}")
```

### Tool Chaining

When the model proposes tool calls, send results back for follow-up:

```python
response = await cadreen.chat.completions(ChatCompletionRequest(
    messages=[
        ChatMessage(role="user", content="What's the weather in NYC?"),
        ChatMessage(role="assistant", tool_calls=[ChatToolCall(
            id="tc_1",
            function=ChatFunctionCall(name="get_weather", arguments='{"city":"NYC"}'),
        )]),
        ChatMessage(role="tool", tool_call_id="tc_1", content='{"temp": 72, "condition": "sunny"}'),
    ],
))
# Model may propose more tools or return a final text response
```

## Configuration

```python
cadreen = Cadreen(
    api_key="sk_cadreen_...",
    base_url="https://accomplishanything.today",  # default
    max_retries=3,      # default 3
    timeout=30,          # default 30s
    profile="lean",     # optional: "lean" | "audit" | "full" (default "full")
)
```

### Response Profiles

| Profile | What you get | Use when |
|---------|-------------|----------|
| `"full"` (default) | Full response metadata | You want full transparency |
| `"audit"` | Only governance decision + confidence + blocking gaps | You need to react to gates |
| `"lean"` | No envelope. Just `trace_id` | Hot-looping, minimal payload |

## Marketplace

Browse and install integrations without knowing which provider powers them:

```python
# Browse available integrations
catalog = await cadreen.connections.catalog()
for category in catalog.categories:
    print(f"{category.name}: {len(category.integrations)} integrations")

# One-click install (returns OAuth URL)
install = await cadreen.connections.install("slack")
if install.status == "pending_auth":
    print(f"Authenticate at: {install.auth_url}")

# Check what's installed
print(catalog.installed)  # ["stripe", "github"]
```

## Memory

```python
# Store knowledge
await cadreen.memory.remember(
    type="reference",
    content={"text": "GDPR Article 17: Right to erasure", "title": "GDPR Art. 17"},
    authority=10,
)

# Search
results = await cadreen.memory.search("data deletion rules")

# Get by ID
item = await cadreen.memory.get("mem_abc123")
```

## Policies

```python
# Create a policy
await cadreen.policies.create(
    name="refund_threshold",
    rules=[{"condition": "refund_amount > 500", "action": "require_approval"}],
)

# Evaluate an action
evaluation = await cadreen.policies.evaluate(
    action="Process $750 refund for order 456"
)
```

## Connections

```python
# Register from OpenAPI spec
await cadreen.connections.register_openapi(
    name="internal-erp",
    spec_url="https://erp.example.com/openapi.json",
)

# Register MCP server
await cadreen.connections.register_mcp(
    name="my-mcp-server",
    url="https://mcp.example.com/sse",
    transport="sse",
)

# List installed
connections = await cadreen.connections.list()
```

## Traces

```python
trace = await cadreen.traces.get(result.trace_id)
print(trace.explain())

recent = await cadreen.traces.list(limit=10)
stats = await cadreen.traces.stats()
```

## Documents

```python
# List documents
docs = await cadreen.documents.list()
for doc in docs.documents:
    print(f"{doc.name} ({doc.content_type})")

# Upload a document from file path
result = await cadreen.documents.upload("/path/to/report.pdf")
print(f"Uploaded: {result.name} (ID: {result.id})")

# Upload from bytes
result = await cadreen.documents.upload_bytes(b"file contents", "data.txt")
print(f"Uploaded: {result.name} (ID: {result.id})")

# Get document details
doc = await cadreen.documents.get(result.id)
print(f"Status: {doc.status}, Size: {doc.size} bytes")
```

## Proposals

Cadreen watches your usage and suggests improvements — actions to automate, schedules to set, rules to relax. You decide what runs.

```python
# List proposals waiting for your decision
proposals = await cadreen.proposals.list()
for p in proposals.proposals:
    print(f"[{p.status}] {p.title} ({p.confidence:.0%} confidence)")

# Get a specific proposal
proposal = await cadreen.proposals.get("550e8400-...")

# Accept — executes via the intent engine
result = await cadreen.proposals.accept("550e8400-...")
print(f"Execution: {result.execution_id}, Action: {result.action}")

# Dismiss — teaches Cadreen what you don't want
await cadreen.proposals.dismiss("550e8400-...", reason="We handle this manually")

# See counts by status
stats = await cadreen.proposals.stats()
print(f"Waiting: {stats.proposed}, Accepted: {stats.accepted}")
```

## Agents

Agents are autonomous workers that handle tasks, follow rules, and learn from outcomes.

```python
# Create an agent
agent = await cadreen.agents.create(CreateAgentRequest(
    name="Support Agent",
    description="Handles customer support requests",
))
print(f"Agent created: {agent.id}")

# List agents
result = await cadreen.agents.list()
for a in result.agents:
    print(f"{a.name} ({a.status})")

# Get agent details
agent = await cadreen.agents.get("agent_123")

# Deploy an agent (creates immutable version)
await cadreen.agents.deploy("agent_123")

# Send a message to an agent
msg = await cadreen.agents.send_message("agent_123", SendMessageRequest(
    content="What's the refund policy?",
    from_agent_id="agent_456",
))

# Create an execution
exec = await cadreen.agents.create_execution("agent_123", CreateExecutionRequest(
    task="Process refund for order #1234",
))

# Knowledge — teach the agent
await cadreen.agents.create_knowledge("agent_123", CreateAgentKnowledgeRequest(
    type="reference",
    content="Refunds require manager approval for amounts over $100",
))

# Search knowledge
results = await cadreen.agents.search_knowledge("agent_123", SearchAgentKnowledgeRequest(
    query="refund policy",
))

# Governance — set rules
await cadreen.agents.create_governance("agent_123", CreateAgentGovernanceRequest(
    name="Refund Approval",
    rules=[{"action": "refund", "condition": "amount > 100", "decision": "handoff"}],
))

# Negotiations — agent-to-agent
negotiation = await cadreen.agents.start_negotiation("agent_123", StartNegotiationRequest(
    target_agent_id="agent_456",
    topic="Resource allocation",
    proposal={"resource": "GPU-1", "duration": "2h"},
))
```

## Federation

Federation lets workspaces share agents and knowledge across boundaries.

```python
# Create a federation link
link = await cadreen.federation.create(CreateFederationRequest(
    target_workspace_id="ws_456",
))
print(f"Link created: {link.id} (status: {link.status})")

# List federation links
result = await cadreen.federation.list()
for link in result.federations:
    print(f"{link.name} → {link.status}")

# Approve a pending link (target workspace only)
await cadreen.federation.approve("link_123")

# Link agents across workspaces
await cadreen.federation.link_agent("link_123", LinkFederationAgentRequest(
    local_agent_id="agent_123",
    remote_agent_id="agent_456",
))

# Update permissions
await cadreen.federation.update_permissions("link_123", FederationPermissions(
    share_knowledge=True,
    share_capabilities=True,
))
```

## External Agents (A2A)

Connect to agents from other systems (LangChain, CrewAI, etc.) using the A2A protocol.

```python
# Enable external agents (workspace setting)
await cadreen.external_agents.update_settings(enabled=True)

# Connect to an external agent
connection = await cadreen.external_agents.connect("agent_123", "https://example.com/.well-known/agent.json")
print(f"Connection: {connection.id} (status: {connection.status})")

# List connections
result = await cadreen.external_agents.list("agent_123")
for c in result.connections:
    print(f"{c.agent_name} → {c.status} ({c.health})")

# Approve a pending connection
await cadreen.external_agents.approve("agent_123", "conn_456")

# List interactions
interactions = await cadreen.external_agents.list_interactions("agent_123", "conn_456")
for i in interactions.interactions:
    print(f"{i.direction} {i.operation}: {i.status}")

# List all connections across workspace
all_conns = await cadreen.external_agents.list_all()
```

## Responses API

OpenAI-compatible responses API with built-in governance and memory.

```python
# Create a response
response = await cadreen.responses.create(ResponseRequest(
    model="cadreen",
    input="What tools do I have?",
))
print(response.output_text)

# Conversation state (server-managed)
response2 = await cadreen.responses.create(ResponseRequest(
    model="cadreen",
    input="What about refund tools?",
    previous_response_id=response.id,
))

# Streaming
async for event in cadreen.responses.stream(ResponseRequest(
    model="cadreen",
    input="Explain quantum computing",
)):
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
```

## Requirements

- Python 3.10+
- httpx >= 0.25
- httpx-sse >= 0.4

## Changelog

### v0.7.2
- Added `DevicesResource` — full device lifecycle (list, create, get, delete, status, state, map, tasks, collisions, avoidance, diagnose, ask, sync, blackboard)
- Fixed `DiagnoseRequest` type shadowing — renamed to `DeviceDiagnoseRequest` for device-specific diagnosis
- Fixed missing `AsyncIterator` import in `resources/intent.py`
- Fixed connection pooling — `HttpClient` now reuses a persistent `httpx.AsyncClient`; added async context manager support

### v0.7.1
- Fix: sandbox mode now raises `CadreenError(404)` for `post_stream` and `stream` instead of making network requests
- Fix: release workflow Homebrew formula update ordering
- Documented new server-side execution events: `execution_steps_complete`, `mission_completed_with_gaps`

### v0.7.0
- **BREAKING:** Removed `pathways` and `total_pathways` from connection responses. `ConnectionGroup` now returns only `capability` and `status`.
- **BREAKING:** Removed `Pathway` type. Internal routing details (connector, transport, tool_id) are no longer exposed.
- **BREAKING:** Changed `ConnectManualDetail` from `{pathways: [...]}` to `{capability, available, health}`.
- **BREAKING:** Removed `workspace_id` from response types: `SetupResult`, `SetupSession`, `WebhookSubscription`, `WebhookPayload`. (Still accepted on request types.)
- **BREAKING:** Removed `authScheme` from `ExternalAgentConnection` responses.
- **BREAKING:** Removed `atoms_consulted`, `episodes_matched`, `precedents_applied` from memory trace in intelligence metadata.
- **BREAKING:** `sources_consulted` renamed to `knowledge_queried` in MemoryTrace.
- **BREAKING:** All entity responses (Agent, Knowledge, Governance, Federation, Negotiation, ExternalAgentConnection) no longer include `workspace_id` or `workspaceId`.
- New MCP SSE endpoint: `GET /api/v1/cadreen/mcp/sse` + `POST /api/v1/cadreen/mcp/message`. Connect Cadreen as an MCP server without installing the npm package.
- Added `cadreen.agents` — full agent lifecycle (create, list, get, update, delete, deploy, get_config, get_capabilities)
- Added agent messaging (send_message, list_messages)
- Added agent executions (create_execution, list_executions)
- Added agent knowledge (create_knowledge, list_knowledge, search_knowledge, delete_knowledge)
- Added agent governance (create_governance, list_governance, update_governance, delete_governance)
- Added agent audit trail (list_audit)
- Added agent negotiations (start_negotiation, list_negotiations, get_negotiation, respond_to_negotiation)
- Added `cadreen.federation` — cross-workspace federation (create, list, get, approve, suspend, revoke)
- Added federation permissions (get_permissions, update_permissions)
- Added federation agent linking (link_agent, list_agents, unlink_agent)
- Added `cadreen.external_agents` — A2A external agent connections (connect, list, get, approve, suspend, revoke, delete)
- Added external agent interactions (list_interactions)
- Added external agent settings (get_settings, update_settings, list_all)
- Added `cadreen.responses` — OpenAI-compatible responses API (create, retrieve, stream)
- Added 37 new types: `Agent`, `AgentKnowledge`, `AgentGovernancePolicy`, `AgentNegotiation`, `FederationLink`, `FederationAgent`, `ExternalAgentConnection`, `ExternalAgentInteraction`, `ExternalAgentSettings`, `ExternalAgentSkill`, `ExternalAgentCapabilities`, etc.

### v0.6.3
- Fix: `invoke_stream()` crash — was using nonexistent `_client._session`, now uses `httpx-sse`
- Fix: `chat.completions()` and `completions_stream()` silently dropping `user_id` from request body

### v0.6.2
- Added `intelligence` field to `ChatCompletionResponse` — full intelligence metadata
- Added `conversation_id` field to `ChatCompletionResponse` — conversation continuity
- Added `reasoning` field to `ChatDelta` — streaming reasoning from thinking models
- Added `reasoning_tokens`, `cache_write_tokens`, `prompt_tokens_details` to `ChatUsage`
- Added `BlueprintsResource`: list, get, create, update, delete, run, get_runs
- Added `SchedulesResource`: list, get, create, update, delete, pause, resume, get_runs
- Added 16 new types: `Blueprint`, `BlueprintRun`, `Schedule`, `ScheduleRun`, etc.
- Document `reasoning_delta` streaming event in README
- IntelligenceMeta shape aligned with server

### v0.6.1
- Added `user_id` optional field to `IntentRequest` and `ChatCompletionRequest` — pass end-user identity for per-user context and memory filtering
- Added `WorkspaceUsersResource`: list, invite, update_role, remove — manage workspace team members
- Added `WorkspaceUser`, `WorkspaceRole`, `InviteUserRequest`, `UpdateRoleRequest` types
- Added `HttpClient.patch()` method for PATCH requests

### v0.6.0 (BREAKING)
- **BREAKING:** `ResolveEscalationRequest.resolution` renamed to `decision` — aligns SDK with API contract
- **BREAKING:** `DiagnoseRequest.error` renamed to `error_message` — aligns SDK with API contract
- Retry default increased from 2 to 3

### v0.5.5
- Added `cadreen.setup_sessions` — stateful setup sessions (create, list, get, add_resources, apply)
- Added `SetupSession`, `SetupSessionCreateRequest`, `SetupSessionAddRequest`, `SetupSessionApplyRequest`, `SetupSessionApplyResult` types

### v0.5.4
- Added `cadreen.proposals` — task proposals (list, get, accept, dismiss, stats)
- Added `TaskProposal`, `ProposalEvidence`, `ListProposalsResponse`, `AcceptProposalResponse`, `DismissProposalResponse`, `ProposalStatsResponse` types

### v0.5.3
- Added `dry_run` field to `IntentRequest` — preview intent classification, governance, and capability assessment without creating a mission or persisting conversation
- Fixed Go SDK `Version` constant (was stuck at 0.5.0)

### v0.5.2
- Added `dry_run` mode to `setup()` — preview what would be created without persisting
- Added `notice` and `dry_run` fields to `SetupResult`
- Added `cadreen.list_blueprints()`, `cadreen.get_blueprint()`, `cadreen.create_blueprint()`, `cadreen.delete_blueprint()`, `cadreen.run_blueprint()`, `cadreen.list_blueprint_runs()`
- Added `cadreen.list_schedules()`, `cadreen.get_schedule()`, `cadreen.create_schedule()`, `cadreen.pause_schedule()`, `cadreen.resume_schedule()`

### v0.5.1
- Added `cadreen.documents.upload(file_path)` and `cadreen.documents.upload_bytes(content, filename)` — upload documents
- Added `cadreen.documents` — document management (list, get)
- Added `cadreen.escalations` — escalation management (list, get, resolve)
- Added `cadreen.healing` — self-healing (stats, precedents, diagnose)
- Added `cadreen.webhooks` — webhook CRUD (create, list, delete)
- Added `cadreen.learning` — learning insights (patterns, episodes, suggestions)
- Added `cadreen.credentials` — credential management (list, create, delete)
- Added `cadreen.list_capabilities()` — list available capabilities
- Added `cadreen.assess(task, domain=)` — assess task readiness

### v0.5.0 (BREAKING)
- **BREAKING:** API endpoints moved to Cadreen surface (`/api/v1/cadreen/`):
  - `/api/v1/chat/completions` → `/api/v1/cadreen/chat/completions`
  - `/api/v1/tools` → `/api/v1/cadreen/tools`
- All external API calls now route through the Cadreen surface
- Renamed response profile levels for clarity

### v0.4.0
- Added `cadreen.chat.completions()` — OpenAI-compatible chat completions with governance
- Added `cadreen.chat.completions_stream()` — streaming chat completions via SSE
- Added `cadreen.chat.list_tools()` — discover available tools as OpenAI function definitions
- Added tool calling support: `tools` param, `tool_calls` in responses, tool chaining
- Added `conversation_id` for persistent conversations across requests
- Added `ChatCompletionRequest`, `ChatCompletionResponse`, `ChatToolDefinition` and related types

### v0.3.0
- Added `catalog()` — browse the unified integration marketplace
- Added `install(integration_id)` — one-click install with OAuth flow
- Added `CatalogResponse`, `InstallResponse` types

### v0.2.1
- Initial public release

## License

MIT
