Metadata-Version: 2.4
Name: nonoka
Version: 0.1.0
Summary: Event-driven autonomous agent framework
Project-URL: Homepage, https://github.com/fyerfyer/nonoka
Project-URL: Repository, https://github.com/fyerfyer/nonoka
Project-URL: Documentation, https://github.com/fyerfyer/nonoka#readme
Author-email: Fu Yu <fyerfyer@126.com>
License: MIT
License-File: LICENSE
Keywords: agent,ai,autonomous,event-driven,llm
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: anyio>=4.13.0
Requires-Dist: fastapi>=0.136.1
Requires-Dist: openai>=2.38.0
Requires-Dist: pydantic-settings>=2.9.0
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: rich>=15.0.0
Requires-Dist: structlog>=25.5.0
Requires-Dist: typer>=0.25.1
Requires-Dist: uvicorn[standard]>=0.47.0
Description-Content-Type: text/markdown

# Nonoka Agent

An event-driven autonomous agent framework built with FastAPI, Pydantic, and modern Python async patterns.

## Features

- **Event-driven core** — all state changes are events, fully observable
- **Pluggable strategies** — meta, ReAct, task-loop with easy custom strategy support
- **First-class tools** — Pydantic schema generation via decorators
- **Config-driven** — run agents from YAML or environment variables
- **CLI + Server** — interactive chat, config-based runs, or FastAPI HTTP API
- **Structured tracing** — console and JSON file tracers built in

## Installation

```bash
pip install nonoka
```

Or with uv:

```bash
uv add nonoka
```

## Quick Start

### SDK — Write Python

```python
import asyncio
import nonoka

async def main():
    # From config file
    agent = await nonoka.Agent.from_config("agent.yaml")
    state = await agent.run("What is the capital of France?")
    print(state.tool_results[0].output)

    # Or programmatically with AgentBuilder
    agent = await (
        nonoka.AgentBuilder()
        .with_llm("openai", api_key="sk-...", model="deepseek-chat", base_url="https://api.deepseek.com")
        .with_strategy("react", nonoka.ReActStrategy())
        .with_tool(my_search_tool)
        .build()
    )
    state = await agent.run("Research the latest AI news")

asyncio.run(main())
```

### CLI — Run without code

```bash
# Interactive chat
nonoka chat --strategy react --llm-model deepseek-chat

# Run from config
nonoka run agent.yaml --task "What is 2 + 2?"

# Start server
nonoka server --host 0.0.0.0 --port 8000
```

### Docker

```bash
docker build -t nonoka .
docker run -e OPENAI_API_KEY=sk-... -p 8000:8000 nonoka
```

Or with Docker Compose:

```bash
docker compose up
```

## Configuration

Create an `agent.yaml`:

```yaml
llm:
  provider: openai
  api_key: ${OPENAI_API_KEY}
  model: deepseek-chat
  base_url: https://api.deepseek.com

default_strategy: react

store:
  type: sqlite
  db_path: ":memory:"

tracers:
  - type: console

policies:
  - type: retry
    max_retries: 3
  - type: timeout
    timeout_seconds: 60
```

Environment variables (NONOKA_ prefix):

| Variable | Description |
|----------|-------------|
| `NONOKA_LLM__API_KEY` | LLM API key |
| `NONOKA_LLM__MODEL` | Model name (e.g. gpt-4, deepseek-chat) |
| `NONOKA_LLM__BASE_URL` | Custom base URL for OpenAI-compatible APIs |
| `NONOKA_DEFAULT_STRATEGY` | Default strategy (meta, react, task_loop) |

## Writing Custom Tools

```python
import nonoka

@nonoka.tool
async def search(query: str, limit: int = 10) -> nonoka.TaskResult[list[str]]:
    """Search the web for information."""
    results = ["result1", "result2"]
    return nonoka.TaskResult.ok(results)

agent = await nonoka.AgentBuilder().with_tool(search).build()
```

## HTTP API

When running `nonoka server`:

| Endpoint | Description |
|----------|-------------|
| `POST /agents` | Create and start an agent |
| `GET /agents/{id}` | Get agent state |
| `GET /agents/{id}/events` | SSE stream of events |
| `GET /agents/{id}/trace` | Full event log |
| `POST /agents/{id}/cancel` | Cancel agent |

## Architecture

```
User Input -> EventBus.publish(TaskCreated)
    -> MetaStrategy.handle() -> StrategySelected
    -> SelectedStrategy.handle() -> Actions
    -> Agent.execute(actions)
        -> call_tool -> ToolExecutor -> ToolCompleted/ToolFailed
        -> emit_event -> EventBus.publish()
        -> complete -> AgentCompleted
    -> StateProjector.project(events) -> AgentState
    -> Tracer.on_event(event)
```

## License

MIT
