Self-hosted autonomous AI agent runtime — provider-agnostic, channel-independent.
Built with Mastra, Bun + TypeScript.
Lines of code
─────────────────────────────────────────────────────────────────────
Language Files Lines Blanks Comments Code
─────────────────────────────────────────────────────────────────────
TypeScript 16 1 706 270 32 1 404
Markdown 9 854 247 0 607
JSON 6 123 0 0 123
YAML 3 133 19 15 99
Dockerfile 1 33 13 0 20
License 1 91 27 0 64
─────────────────────────────────────────────────────────────────────
Total 36 2 940 576 47 2 317
─────────────────────────────────────────────────────────────────────
cp .env.example .env
# Add your API key (ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, or OPENROUTER_API_KEY)
# Set AGENT_MODEL (e.g. claude-haiku-4-5, gemini-2.0-flash, openrouter:meta-llama/llama-3.3-70b-instruct)
# Edit `docker-compose.yml` and uncomment a channel adapter to connect the agent to an external platform.
docker compose up -d
curl http://localhost:4111/healthDevelopment
bun install
bun run dev # HTTP server with hot reload
bun run lint
bun run formatCustomize the agent by editing files in workspace/:
| File | Description |
|---|---|
SOUL.md |
Agent identity — name, worldview, opinions, interests |
STYLE.md |
Communication style guide |
MEMORY.md |
Long-term memory (auto-consolidated after conversations) |
HEARTBEAT.md |
Periodic tasks checklist (runs every 30 min by default) |
AGENTS.md |
Other agents in the system (for multi-agent setups) |
cron.json |
Scheduled jobs (not yet implemented) |
skills/ |
Custom skills — each in its own SKILL.md file |
Skills are Markdown files with YAML frontmatter, placed in workspace/skills/<name>/SKILL.md.
SKILL.md format
---
id: my-skill
name: My Skill
description: Does something useful
requirements:
binaries: ["jq"]
env_vars: ["API_KEY"]
---
# Skill instructions hereThe agent detects available skills at startup and injects their metadata into the system prompt.
The agent comes with built-in tools:
| Tool | Enabled by default | Description |
|---|---|---|
| Web search | Yes | Provider-native: Anthropic (webSearch_20250305), Google (googleSearch), OpenRouter (:online suffix) |
| Web fetch | Yes | Fetches a URL and returns readable text |
| File read/write | Yes | Read and write files in the workspace |
| Shell | No | Execute shell commands — enable with SHELL_TOOL_ENABLED=true |
Additional tools can be added via MCP servers.
Add MCP servers in workspace/.mcp.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "..." }
}
}
}Tools from MCP servers are automatically discovered and registered at startup.
Health & Info
curl http://localhost:4111/health
# {"status":"ok"}curl http://localhost:4111/identity
# {"hasSoul":true,"hasStyle":true,"hasMemory":false,"hasAgents":false}curl http://localhost:4111/system-promptcurl http://localhost:4111/skills
# {"skills":[{"id":"...","name":"...","description":"...","available":true}]}Generate & Stream
curl -X POST http://localhost:4111/api/agents/main/generate \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Hello"}]}'Response:
{
"text": "Hello! How can I help you?",
"threadId": "thread-123",
"usage": { "inputTokens": 100, "outputTokens": 20 },
"cost": 0.0002
}Pass threadId in subsequent requests to continue the same conversation. Without it, the last thread is reused automatically.
SSE streaming response.
curl -X POST http://localhost:4111/api/agents/main/stream \
-H "Content-Type: application/json" -N \
-d '{"messages": [{"role": "user", "content": "Hello"}]}'Notifications
Subscribe to proactive notifications (SSE). Used by channel adapters to receive messages pushed by the agent (heartbeat results, plugin-triggered alerts, etc.).
curl -N http://localhost:4111/api/agents/main/notifications/streamEach event is a JSON object:
{ "text": "...", "timestamp": "2026-03-03T07:00:00.000Z", "source": "heartbeat" }Push a notification directly to all connected channels, without invoking the LLM. Intended for plugins that need to deliver content (e.g. a PDF digest) via the channel already connected to the agent.
curl -X POST http://localhost:4111/api/agents/main/notify \
-H "Content-Type: application/json" \
-d '{"text": "Hello from a plugin", "source": "my-plugin"}'To send a document through the channel (e.g. nanofleet-news), pass a JSON payload as text:
{
"text": "{\"type\":\"document\",\"url\":\"http://nanofleet-plugin-nanofleet-news:8830/digests/2026-03-03.pdf\",\"filename\":\"journal-2026-03-03.pdf\"}",
"source": "nanofleet-news"
}Memory
curl http://localhost:4111/api/agents/main/memory/threadsCreate a new thread explicitly (e.g. to start a fresh conversation).
curl -X POST http://localhost:4111/api/agents/main/memory/threads \
-H "Content-Type: application/json" \
-d '{"resourceId": "user-123"}'curl http://localhost:4111/memory/configUsage
curl http://localhost:4111/api/agents/main/usage{
"totalInputTokens": 10000,
"totalOutputTokens": 5000,
"totalCacheReadTokens": 8000,
"totalCacheWriteTokens": 2000,
"totalCost": 0.15,
"cacheHitRate": 80,
"requests": 50
}curl http://localhost:4111/api/agents/main/usage/threads/thread-123Channels connect the agent to external platforms (Telegram, Discord, ...). Each channel is a standalone adapter that forwards messages to the agent via HTTP/SSE.
Available channels: nanofleet-agent-channels
The docker-compose.yml includes a commented Telegram example — uncomment and configure it to add a channel alongside the agent.
If you use NanoFleet, channels are deployed and managed from the web dashboard — no manual docker-compose configuration needed.