Quickstart
Cut your agent's token bill by 97% in under 5 minutes.
Start with natural language — no schemas, no structured queries required.
- Docker Desktop 24+ with Compose V2 (
docker compose, notdocker-compose) - For Steps 2–3 (natural language): set one of
ANTHROPIC_API_KEY,OPENAI_API_KEY, orGOOGLE_API_KEY - Steps 4–5 (direct API and MCP) work without any LLM key
Works with your agent stack
pip install nocturnusai[langchain] tools = get_nocturnusai_tools(client) CrewAI pip install nocturnusai[crewai] tools = get_nocturnusai_tools(client) AutoGen pip install nocturnusai[autogen] tools = get_nocturnusai_tools(client) OpenAI Agents pip install nocturnusai[openai-agents] tools = get_nocturnusai_tools(client) Anthropic pip install nocturnusai[anthropic] tools = get_nocturnusai_tool_definitions() LangGraph pip install nocturnusai[langgraph] saver = NocturnusAICheckpointSaver(client) Also available via MCP protocol (Cursor, Claude, any MCP client) · TypeScript SDK · REST API
Copy a prompt into your AI coding assistant and it will install NocturnusAI for you.
Start the Server
One command. Installs Docker Compose stack, waits for healthy, and installs the native CLI binary:
$ curl -fsSL https://raw.githubusercontent.com/Auctalis/nocturnusai/main/install.sh | bash
# With an API key for LLM features (enables /extract and /synthesize in Steps 2–3):
$ curl -fsSL https://raw.githubusercontent.com/Auctalis/nocturnusai/main/install.sh | bash -s -- --key sk-ant-...
# With local Ollama (free, no API key needed — install Ollama first):
$ curl -fsSL https://raw.githubusercontent.com/Auctalis/nocturnusai/main/install.sh | bash -s -- --host-ollama Verify it's running:
$ curl http://localhost:9300/health
{ "status": "healthy" } ./data directory for WAL logs and snapshots. Delete that folder to wipe the knowledge base and start fresh.
nocturnusai native binary on your PATH. Run nocturnusai for an interactive REPL, or use nocturnusai -e "tell ..." in scripts.
Feed It Text, Ask It Questions
The fastest way to get started. Set an LLM API key — ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY — and NocturnusAI handles fact extraction and Q&A automatically.
Extract facts from plain text:
$ curl -X POST http://localhost:9300/extract \
-H "Content-Type: application/json" \
-d '{
"text": "Acme Corp is on the enterprise plan and is based in Austin. They get 24/7 SLA support.",
"assert": true
}' {
"facts": [
{ "predicate": "customer_tier", "args": ["acme_corp", "enterprise"] },
{ "predicate": "location", "args": ["acme_corp", "austin"] },
{ "predicate": "sla_tier", "args": ["acme_corp", "24_7"] }
],
"asserted": true
} Ask a natural language question — get a sourced answer:
$ curl -X POST http://localhost:9300/synthesize \
-H "Content-Type: application/json" \
-d '{"question": "What support level does Acme Corp get?"}' {
"answer": "Acme Corp gets 24/7 SLA support on their enterprise plan.",
"derivation": [
{ "fact": "sla_tier(acme_corp, 24_7)" },
{ "fact": "customer_tier(acme_corp, enterprise)" }
],
"confidence": 0.97
} derivation field shows the exact facts that produced this answer. This is verification — if the fact isn't in the KB, the answer says so rather than making something up.
Skip extraction and synthesis — just send your conversation turns and get ranked facts back:
POST /context
{ "turns": ["Acme is on enterprise plan", "They have 24/7 SLA"] }
→ { "facts": [
{"predicate":"customer_tier", "args":["acme","enterprise"], "salience":0.95},
{"predicate":"sla_tier", "args":["acme","24_7"], "salience":0.90}
],
"factsReturned": 2, "contradictions": 0
} Feed these facts to GPT-4o or Claude — 200 tokens instead of 150K. Full guide →
Connect Any Agent in 30 Seconds
NocturnusAI is an MCP server. Any MCP-compatible agent, IDE, or framework connects with a two-line config — no SDK, no API wrapper, no code changes required.
Cursor / Windsurf (.cursor/mcp.json):
{
"mcpServers": {
"nocturnus": {
"url": "http://localhost:9300/mcp/sse"
}
}
} Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"nocturnus": {
"url": "http://localhost:9300/mcp/sse",
"transport": "sse"
}
}
} Your agent immediately gets 9 tools: tell, ask, teach, forget, recall, context, compress, cleanup, predicates.
POST /context/optimize with goals to get only the facts your agent needs — 97% fewer tokens billed per request. Learn about context optimization → Structured Facts & Queries
Use the structured API directly — no LLM key required. Facts are stored as typed predicates, giving you deterministic pipelines without any LLM in the extraction path. These are the same endpoints the MCP tools call internally.
Store a structured fact:
$ curl -X POST http://localhost:9300/tell \
-H "Content-Type: application/json" \
-d '{"predicate": "customer_tier", "args": ["acme_corp", "enterprise"]}'
{ "status": "ok", "atom": "customer_tier(acme_corp, enterprise)" } Query with a variable (the ? prefix marks unknowns):
$ curl -X POST http://localhost:9300/ask \
-H "Content-Type: application/json" \
-d '{"predicate": "customer_tier", "args": ["acme_corp", "?tier"]}'
{ "results": ["customer_tier(acme_corp, enterprise)"] } LangChain Agent with Verified Knowledge
Install the Python SDK with LangChain integration:
$ pip install nocturnusai[langchain] langchain-anthropic A complete working agent in 20 lines:
from nocturnusai import SyncNocturnusAIClient
from nocturnusai.langchain import get_nocturnusai_tools
from langchain_anthropic import ChatAnthropic
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
# Connect to Nocturnus — get 4 LangChain tools backed by the reasoning engine
client = SyncNocturnusAIClient("http://localhost:9300")
tools = get_nocturnusai_tools(client)
# → assert, query, infer, context
llm = ChatAnthropic(model="claude-sonnet-4-6")
prompt = ChatPromptTemplate.from_messages([
("system", "Use your tools to store and retrieve verified facts. Never guess."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
result = executor.invoke({
"input": "Remember: Acme Corp is on the enterprise plan. Now — what plan are they on?"
})
print(result["output"]) # Agent gets optimized facts from /context — 200 tokens, not 150K.
# Correct. Sourced. 750x cheaper than context stuffing.
Acme Corp is on the enterprise plan. (verified from knowledge base) Running From Source
Skip Docker if you're contributing to the codebase or prefer to run the JVM directly. Requires JDK 17+.
$ git clone https://github.com/Auctalis/nocturnusai.git
$ cd nocturnusai
# Run the HTTP API server on :9300
$ ./gradlew :nocturnusai-server:run
# Run the interactive REPL (connects to a running server)
$ ./gradlew :nocturnusai-cli:run
# Run all tests
$ ./gradlew test LLM keys work the same way — set them as environment variables before running Gradle, or add them to a .env file loaded by your shell.
Environment Variables
Quick reference for all configuration variables. Copy into a .env file or pass directly to Docker Compose / Gradle.
| Variable | Default | Purpose |
|---|---|---|
PORT | 9300 | Server port |
HOST | 0.0.0.0 | Bind address |
API_KEY | none | Legacy single-key auth. Set this or use AUTH_ENABLED=true for full RBAC. |
CORS_ALLOWED_ORIGINS | localhost:3000, 5173, 8080 | Comma-separated allowed origins. Set to * only for fully public read-only APIs. |
MAX_REQUEST_BODY_BYTES | 10485760 (10 MB) | Maximum allowed request body size. Requests exceeding this return 413. |
STORAGE_DIR | ./data | WAL and snapshot directory |
ANTHROPIC_API_KEY | none | Enables /extract and /synthesize using Claude |
OPENAI_API_KEY | none | Enables LLM features using GPT-4o |
GOOGLE_API_KEY | none | Enables LLM features using Gemini |
OLLAMA_BASE_URL | none | Enables LLM features using a local Ollama instance |
EXTRACTION_ENABLED | false (true in Docker) | Toggle /extract and /synthesize endpoints. The official Docker image sets this to true automatically. |
LLM_MODEL | provider default | Override the LLM model (e.g. claude-sonnet-4-6) |
LLM_TEMPERATURE | 0.1 | Lower = more deterministic fact extraction |
AUTH_ENABLED | false | Enable role-based auth. When true, requires admin bootstrap on first run. |
NOCTURNUSAI_ADMIN_USER | admin | Bootstrap admin username — change before exposing to any network |
NOCTURNUSAI_ADMIN_PASS | nocturnusai | Bootstrap admin password — change before exposing to any network |
API_KEY_DEFAULT_EXPIRY_DAYS | none (no expiry) | Default expiry for new API keys in days. Recommended: 365 for production. |
ENCRYPTION_KEY | none | 64 hex-char AES-256 key for at-rest encryption. Generate with openssl rand -hex 32 |
TLS_ENABLED | false | Enable HTTPS on TLS_PORT |
TLS_PORT | 9443 | HTTPS port when TLS is enabled |
TLS_KEYSTORE_PATH | none | Path to JKS or PKCS12 keystore file |
TLS_KEYSTORE_PASSWORD | none | Keystore password |
REPLICATION_MODE | LEADER | LEADER or FOLLOWER |
LEADER_URL | none | Leader URL when running as a follower |
Troubleshooting
Port 9300 is already in use
Another process is bound to that port. Either stop it, or start Nocturnus on a different port:
$ PORT=9301 docker compose up -d
$ curl http://localhost:9301/health docker-compose command not found
You're running Compose V1. The docs use Compose V2 syntax (docker compose with a space). Update Docker Desktop to 24+ or install the Compose plugin.
/extract returns "LLM provider not configured"
The server started without an LLM API key. Stop the container, then restart with the key:
$ docker compose down
$ ANTHROPIC_API_KEY=sk-ant-... docker compose up -d /ask returns empty results
Two common causes:
- The fact hasn't been stored yet — call
/tellfirst, then/ask - Predicate names are case-sensitive —
customer_tierandCustomer_Tierare different predicates
Check what's actually stored:
$ curl http://localhost:9300/predicates Facts are gone after restart
The ./data directory holds all WAL logs and snapshots. If it was deleted, facts won't survive a restart. Make sure Docker Compose mounts it as a volume — the default docker-compose.yml already does this. If you're running from source, ensure STORAGE_DIR points to a persistent path.