A minimalistic Python library for building AI agents using functional, immutable DAG operations.
Features
Functional & Immutable - The DAG is immutable. Every operation returns a new instance. No hidden state, no mutations, easy to reason about.
dag = DAG() dag = dag.system("You are helpful.") # New DAG dag = dag.user("Hello") # New DAG dag = dag.assistant(response.content) # New DAG
Conversation Graph - Everything is a node in a directed acyclic graph: system prompts, messages, tool calls, results. Branch and merge for parallel tool execution.
Built-in Tools - BashTool, ReadTool, WriteTool, EditTool, GlobTool, GrepTool, StatTool, PythonTool, TodoWriteTool, WebFetchTool.
Sub-Agents - Tools can spawn their own agents using SubAgentTool. Supports recursive nesting and parallel execution.
Visualization - Print any DAG to see the conversation flow, or export to HTML:
SYSTEM: You are helpful.
│
▼
USER: What files are here?
│
▼
TOOL_USE: Bash
│
▼
TOOL_RESULT: file1.py, file2.py
│
▼
ASSISTANT: I found 2 Python files...
dag.save("conversation.json") # Save the graph
uv run nano-agent-viewer conversation.json # Creates conversation.htmlMulti-Provider - Works with Claude API, Claude Code OAuth, Gemini API, OpenAI API, or ChatGPT/Codex OAuth.
Quick Start
import asyncio from nano_agent import ClaudeAPI, DAG, BashTool, run async def main(): api = ClaudeAPI() # Uses ANTHROPIC_API_KEY dag = ( DAG() .system("You are a helpful assistant.") .tools(BashTool()) .user("What is the current date?") ) dag = await run(api, dag) print(dag) asyncio.run(main())
Installation
git clone https://github.com/NTT123/nano-agent.git
cd nano-agent
uv syncDevelopment
# Install pre-commit hooks (required for contributing) uv run pre-commit install # Run tests uv run pytest # Type checking uv run mypy . # Format code uv run pre-commit run --all-files
CLI
nano-cli is a lightweight, terminal-based AI coding assistant similar to Claude Code or Cursor. It provides an agentic loop that can read files, execute commands, edit code, and browse the web—all from your terminal.
Features
- Agentic execution: Automatically handles tool calls in a loop until the task is complete
- Session persistence: Auto-saves conversations and can resume from where you left off
- Multi-provider: Works with Claude (via Claude Code OAuth) or Gemini APIs
- Rich TUI: Syntax-highlighted output, streaming responses, and interactive confirmations
- Project context: Automatically loads
CLAUDE.mdfrom your current directory as context - Built-in tools: Bash, Read, Write, Edit, Glob, Grep, Stat, TodoWrite, WebFetch, Python
Installation
Install the CLI globally using uv:
uv tool install git+https://github.com/NTT123/nano-agent.git
Authentication
Capture your Claude Code auth credentials first:
Usage
Once installed, you can use nano-cli from any project directory:
Additional options:
# Run with Gemini instead of Claude nano-cli --gemini nano-cli --gemini gemini-2.5-flash # specific model # Continue from saved session nano-cli --continue nano-cli --continue my-session.json # Debug mode (show raw response blocks) nano-cli --debug
Commands
| Command | Description |
|---|---|
/quit, /exit, /q |
Exit the application |
/clear |
Reset conversation and clear screen |
/continue, /c |
Continue agent execution without user message |
/save [filename] |
Save session to file (default: session.json) |
/load [filename] |
Load session from file |
/renew |
Refresh OAuth token (for 401 errors) |
/render |
Re-render history (after terminal resize) |
/debug |
Show DAG as JSON |
/help |
Show help message |
Input Controls
| Key | Action |
|---|---|
| Enter | Send message |
| \ + Enter | Insert new line (for multiline input) |
| Esc | Cancel current operation (during execution) |
| Ctrl+D | Exit |
Note: Ctrl+J and Shift+Enter are not supported.
Chat Bots (Discord + Slack)
Two chat frontends share the same agent core (queue/worker/session/tools):
- nano-discord-bot — runs in Discord channels and threads.
- nano-slack-bot — runs in Slack channels and threads via Socket Mode.
Both use the same bot/bot_state.py, bot/bot_agent.py, session persistence under logs/, and agent loop. Platform-specific code lives in bot/discord_bot.py + bot/bot_tools.py and bot/slack_bot.py + bot/slack_tools.py.
Discord Bot
nano-discord-bot is an AI assistant that runs 24/7 as a Discord bot. It uses the same nano-agent core and tools to provide an agentic coding assistant directly in Discord channels and threads.
Features
- Queue-based messaging: User messages are queued and processed asynchronously, so the bot handles concurrent messages gracefully
- Channel workers: Each channel gets a dedicated worker that processes messages in order
- Discord tools: Send messages, send files, create threads, explore guild/channel structure, and call the Discord REST API
- Session persistence: Message queues are persisted to disk and recovered on restart
- All built-in tools: Bash, Read, Write, Edit, Glob, Grep, Stat, Python, WebFetch, and more
Setup
# Install with bot extras (discord.py, python-dotenv) uv sync --extra bot # Set your Discord bot token export DISCORD_BOT_TOKEN=your-token-here # Run the bot (default provider: Claude Code) uv run nano-discord-bot
Provider selection
The bot supports two LLM providers, selected via BOT_PROVIDER:
-
claude(default) — Claude Code OAuth. Runnano-agent-capture-authonce to populate~/.nano-agent.json. -
codex— ChatGPT/Codex OAuth. Reads~/.codex/auth.jsonand auto-refreshes the access token (every 8 days). Populate it either with the officialcodex loginCLI, or with the built-in Python PKCE flow:# One-time login (opens a browser, binds 127.0.0.1:1455) uv run python -m nano_agent.providers.codex_login # Run the bot against Codex BOT_PROVIDER=codex uv run nano-discord-bot # Optional: override the model (default: gpt-5.5) CODEX_MODEL=gpt-5.5 BOT_PROVIDER=codex uv run nano-discord-bot
Remote servers: the OAuth flow needs a local browser and port 1455. If the bot runs on a headless host, log in locally first, then copy
~/.codex/auth.jsonto the server. Auto-refresh keeps it alive from there;/renewonly works on a host where the browser + port 1455 are reachable.
Slash Commands
| Command | Description |
|---|---|
/clear |
Clear conversation history in the current channel/thread |
/queue |
Show queued user messages |
/cd <path> |
Change working directory |
/cwd |
Show current working directory |
/thread <topic> |
Start a new conversation in a Discord thread |
/renew |
Re-run OAuth login for the active provider |
/explore |
Explore visible Discord context (guild/channels/threads) |
Slack Bot
nano-slack-bot is the Slack counterpart. Same queue-based architecture, same BOT_PROVIDER switch (Claude Code or Codex), same agent loop — but driven by Slack events via slack-bolt Socket Mode.
Slack app setup
A ready-made manifest is at bot/slack_manifest.json — it declares every scope, event, and slash command the bot needs. Fastest setup:
- Go to https://api.slack.com/apps → Create New App → From an app manifest → pick a workspace.
- Paste the contents of
bot/slack_manifest.jsoninto the JSON tab and submit. Review and create. - Basic Information → App-Level Tokens → Generate with the
connections:writescope. This gives you thexapp-…token. - Install to Workspace. Copy the Bot User OAuth Token (
xoxb-…) from OAuth & Permissions. - Put both tokens in
.env:SLACK_BOT_TOKEN=xoxb-…SLACK_APP_TOKEN=xapp-…
If you want to set things up manually instead, the manifest encodes:
- Bot scopes:
app_mentions:read,chat:write,channels:history,channels:read,groups:history,groups:read,im:history,im:read,im:write,mpim:history,mpim:read,files:write,users:read,commands. - Event subscriptions:
app_mention,message.channels,message.groups,message.im,message.mpim. - Slash commands:
/clear,/queue,/cd,/cwd,/thread,/renew. - Socket Mode: on (no public HTTP endpoint needed).
Setup
# Install with bot extras (also installs discord.py, slack-bolt, slack-sdk) uv sync --extra bot # Add tokens to .env echo "SLACK_BOT_TOKEN=xoxb-…" >> .env echo "SLACK_APP_TOKEN=xapp-…" >> .env # Run the bot (default provider: Claude Code) uv run nano-slack-bot # Or with Codex OAuth BOT_PROVIDER=codex uv run nano-slack-bot
Behavior
- DMs to the bot: always answered in the DM by default. If the user asks
for a new thread, the bot can explicitly create one with
CreateThread. - Channel messages: the bot responds when mentioned (
@nano-bot), or when a user replies in a thread the bot is already in. SetSLACK_RESPOND_TO_ALL_MESSAGES=trueto respond to every message in channels the bot is in (not recommended for busy workspaces). - Threads: each thread is its own conversation (keyed by
channel_id:thread_ts). Channel-level messages reply in-thread by default so the main channel stays tidy.
Slash Commands
Same surface as Discord — names differ only in platform flavor:
| Command | Description |
|---|---|
/clear |
Clear conversation history in the current channel |
/queue |
Show queued user messages |
/cd <path> |
Change working directory |
/cwd |
Show current working directory |
/thread <topic> |
Open a new thread in the current channel/DM and start a conversation |
/renew |
Re-run OAuth login for the active provider |
Slack-specific tools
The agent gets Slack analogues of the Discord tools: SendUserMessage, SendFile (files_upload_v2), CreateThread, ExploreSlack, SlackAPI, plus the shared queue tools. SlackAPI can call any Web API method — use action=discover to inspect context, then action=request with method and body_json.
Sub-Agents
Create tools that spawn their own agents using SubAgentTool:
from dataclasses import dataclass from typing import Annotated from nano_agent import SubAgentTool, TextContent, ReadTool from nano_agent.tools.base import Desc @dataclass class CodeReviewInput: file_path: Annotated[str, Desc("Path to the file to review")] @dataclass class CodeReviewTool(SubAgentTool): name: str = "CodeReview" description: str = "Spawn a sub-agent to review code" async def __call__(self, input: CodeReviewInput) -> TextContent: summary = await self.spawn( system_prompt="You are an expert code reviewer...", user_message=f"Review: {input.file_path}", tools=[ReadTool()], ) return TextContent(text=summary)
Features:
- Recursive nesting: Sub-agents can spawn their own sub-agents (with depth limits)
- Parallel execution: Multiple sub-agent tools can run concurrently
- Graph visualization: Sub-agent graphs are captured and viewable in HTML export
See examples/parallel_sub_agents.py and examples/recursive_sub_agents.py for complete examples.
License
MIT