Metadata-Version: 2.4
Name: arcana-agent
Version: 0.7.0
Summary: Agent Runtime for production — LLM-native execution with tools, budget, and trace built in.
Project-URL: Homepage, https://github.com/tyxben/arcana
Project-URL: Documentation, https://tyxben.github.io/arcana/
Project-URL: Repository, https://github.com/tyxben/arcana
Author-email: tysama <wty229037377@gmail.com>
License: MIT
License-File: LICENSE
Keywords: agent,ai,arcana,framework,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: openai>=1.0
Requires-Dist: pydantic>=2.6
Requires-Dist: python-dotenv>=1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.9
Provides-Extra: all-providers
Requires-Dist: anthropic>=0.42; extra == 'all-providers'
Requires-Dist: google-genai>=1.0; extra == 'all-providers'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.42; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: anthropic>=0.42; extra == 'dev'
Requires-Dist: google-genai>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
Provides-Extra: gemini
Requires-Dist: google-genai>=1.0; extra == 'gemini'
Provides-Extra: ui
Requires-Dist: fastapi>=0.110; extra == 'ui'
Requires-Dist: jinja2>=3.1; extra == 'ui'
Requires-Dist: uvicorn>=0.29; extra == 'ui'
Description-Content-Type: text/markdown

# Arcana

**Agent runtime that lets LLMs think, not just execute.**

[![PyPI](https://img.shields.io/pypi/v/arcana-agent)](https://pypi.org/project/arcana-agent/)
[![Python](https://img.shields.io/pypi/pyversions/arcana-agent)](https://pypi.org/project/arcana-agent/)
[![License](https://img.shields.io/github/license/tyxben/arcana)](./LICENSE)
[![CI](https://img.shields.io/github/actions/workflow/status/tyxben/arcana/ci.yml?label=tests)](https://github.com/tyxben/arcana/actions)
[![Coverage](https://img.shields.io/codecov/c/github/tyxben/arcana)](https://codecov.io/gh/tyxben/arcana)

---

## The Problem

Most agent frameworks treat LLMs as unreliable workers that need a manager watching every step. They force rigid formats -- ReAct loops, JSON command schemas, mechanical retries -- and dump entire tool catalogs into every prompt. The result is high ceremony, low capability. The framework spends its complexity constraining the LLM instead of releasing it.

## The Arcana Approach

Arcana is an operating system for LLM agents, not a pipeline. The LLM decides strategy; the runtime provides services -- budget enforcement, tool dispatch, trace recording, context management. The framework never interprets LLM output: raw facts (`TurnFacts`) and runtime assessment (`TurnAssessment`) are kept visibly separate. Eight design principles and four prohibitions, codified in a [Constitution](./CONSTITUTION.md), govern every line of code.

---

## Quick Start

```bash
pip install arcana-agent
```

```python
import asyncio
import arcana

async def main():
    result = await arcana.run("Summarize this article", api_key="sk-xxx")
    print(result.output)
    print(f"Cost: ${result.cost_usd:.4f} | Tokens: {result.tokens_used}")

asyncio.run(main())
```

Use a different provider:

```python
import asyncio, arcana

# OpenAI
result = asyncio.run(arcana.run("Hello", provider="openai", api_key="sk-proj-xxx"))

# Anthropic (pip install arcana-agent[anthropic])
result = asyncio.run(arcana.run("Hello", provider="anthropic", model="claude-sonnet-4-20250514", api_key="sk-ant-xxx"))
```

---

## Core Features

### Tools with Affordances

Tools declare *when* and *why*, not just *how*. The LLM reasons about whether to call a tool, not just how.

```python
@arcana.tool(
    when_to_use="When you need to do math calculations",
    what_to_expect="Returns the numeric result as a string",
    failure_meaning="The expression was malformed",
)
def calc(expression: str) -> str:
    return str(eval(expression))

result = await arcana.run("What is 15 * 37 + 89?", tools=[calc], api_key="sk-xxx")
```

### Runtime

Create once at startup, use across your entire application. Holds providers, tools, budget, and trace as long-lived resources.

```python
runtime = arcana.Runtime(
    providers={"deepseek": "sk-xxx", "openai": "sk-proj-xxx"},
    tools=[calc, web_search],
    budget=arcana.Budget(max_cost_usd=5.0),
    trace=True,
)

result = await runtime.run("Analyze recent trends in quantum computing")
```

### Interactive Chat

Multi-turn sessions with persistent history, shared budget, and context compression.

```python
async with runtime.chat() as c:
    r = await c.send("What are the main themes in this dataset?")
    r = await c.send("Expand on the second theme")
    print(c.total_cost_usd)
```

### Ask User

The LLM can ask clarifying questions mid-execution. If no handler is provided, it proceeds with best judgment -- interaction is a capability, not a dependency.

```python
result = await runtime.run(
    "Book a restaurant for dinner",
    input_handler=lambda q: input(f"Agent asks: {q}\n> "),
)
```

### Structured Output

Return validated Pydantic instances instead of raw text.

```python
from pydantic import BaseModel

class Summary(BaseModel):
    title: str
    key_points: list[str]
    sentiment: str

result = await arcana.run(
    "Summarize this article",
    response_format=Summary,
    api_key="sk-xxx",
)
print(result.parsed.title)       # str
print(result.parsed.key_points)  # list[str]
```

> `result.parsed` is always `BaseModel | None` -- never a raw dict. It is `None` when no `response_format` is set or when parsing fails. `result.output` contains the same parsed model when successful, or the raw text when parsing fails.

### Multimodal

Pass images alongside text. URLs, local file paths, and data URIs all work.

```python
result = await arcana.run(
    "Describe what you see in this image",
    images=["https://example.com/photo.jpg"],
    provider="openai",
    api_key="sk-proj-xxx",
)
```

### Pipeline

Sequential steps with optional parallel branches. Each step's output flows as context to the next.

```python
result = await runtime.chain([
    arcana.ChainStep(name="research", goal="Find key facts about quantum computing"),
    [  # parallel branch
        arcana.ChainStep(name="summary", goal="Write a concise summary"),
        arcana.ChainStep(name="critique", goal="Identify gaps and biases",
                         budget=arcana.Budget(max_cost_usd=0.50)),
    ],
    arcana.ChainStep(name="final", goal="Integrate summary and critique into a report"),
])
print(result.steps["final"])
```

### Batch Processing

Process many independent tasks concurrently with automatic throttling.

```python
results = await runtime.run_batch([
    {"goal": "Summarize article 1", "response_format": Summary},
    {"goal": "Summarize article 2", "response_format": Summary},
    {"goal": "Summarize article 3", "response_format": Summary},
], concurrency=10)

print(f"{results.succeeded}/{len(results.results)} succeeded")
print(f"Total cost: ${results.total_cost_usd:.4f}")
```

### Multi-Agent Teams

Runtime provides communication and budget. Agents decide strategy -- no forced hierarchy. Two modes: `"shared"` (all agents see everything) and `"session"` (independent contexts, messages arrive as user messages).

```python
result = await runtime.team(
    "Design a landing page for an AI product",
    agents=[
        arcana.AgentConfig(name="designer", prompt="You are a senior UX designer."),
        arcana.AgentConfig(name="copywriter", prompt="You are a conversion copywriter."),
        arcana.AgentConfig(name="critic", prompt="You find weaknesses and suggest improvements."),
    ],
    max_rounds=3,
    mode="shared",  # or "session" for independent contexts
)
```

### Budget Scoping

Isolate budget for a subset of runs without affecting the global runtime budget.

```python
async with runtime.budget_scope(max_cost_usd=0.50) as scoped:
    r1 = await scoped.run("Classify this document")
    r2 = await scoped.run("Extract key entities")
    print(f"Scoped cost: ${scoped.budget_used_usd:.4f}")
```

### Graph Orchestration

For workflows that need explicit state machines. Available when you need it, never forced.

```python
from arcana import StateGraph, START, END

graph = runtime.graph(state_schema=MyState)
graph.add_node("research", research_fn)
graph.add_node("write", write_fn)
graph.add_edge(START, "research")
graph.add_edge("research", "write")
graph.add_edge("write", END)

app = graph.compile()
result = await app.ainvoke(initial_state)
```

---

## What Makes Arcana Different

| | LangChain | CrewAI | AutoGPT | **Arcana** |
|---|---|---|---|---|
| **LLM autonomy** | Framework-driven chains | Role-locked agents | Fully autonomous, no guardrails | LLM decides strategy within runtime boundaries |
| **Token efficiency** | Full context every call | Full prompt per agent | Unbounded context growth | Working-set discipline -- only what this step needs |
| **Thinking signals** | Ignored | Ignored | Ignored | Runtime listens to thinking for confidence, never constrains |
| **Tool management** | All tools in every prompt | Per-agent tool sets | All tools always | Dynamic per-turn exposure with affordances |
| **User interaction** | Not built in | Not built in | Not built in | `ask_user` built-in, graceful fallback if no handler |
| **Pipelines** | Chain + glue code | Sequential tasks | No pipelines | `chain()` with auto context passing + parallel branches |
| **Default path** | Chain/graph required | Crew required | Agent loop always | Direct answer when possible, agent loop when needed |

---

## Providers

DeepSeek | OpenAI | Anthropic | Google Gemini | Kimi (Moonshot) | GLM (Zhipu) | MiniMax | Ollama

All providers use a single OpenAI-compatible adapter. Adding a new provider is one function call.

---

## Documentation

| Guide | Description |
|---|---|
| [Quick Start](./docs/guide/quickstart.md) | Installation through deployment |
| [Configuration](./docs/guide/configuration.md) | Full configuration reference |
| [Providers](./docs/guide/providers.md) | Provider setup and fallback chains |
| [API Reference](./docs/guide/api.md) | Public API documentation |
| [Architecture](./docs/architecture.md) | System design and internals |
| [Constitution](./CONSTITUTION.md) | Design principles and prohibitions |
| [Examples](./examples/) | Runnable code examples |
| [Changelog](./CHANGELOG.md) | Release history |

---

## Installation

```bash
pip install arcana-agent                   # Core (DeepSeek, OpenAI)
pip install arcana-agent[anthropic]        # + Claude support
pip install arcana-agent[gemini]           # + Gemini support
pip install arcana-agent[all-providers]    # All providers
pip install arcana-agent[ui]              # + Trace Web UI
```

Or with uv:

```bash
uv add arcana-agent
uv add arcana-agent --extra all-providers
```

Requires Python 3.11+.

---

## License

MIT
