Contributing to Nous
View SourceThanks for your interest in contributing. This document covers everything you need to develop, test, and submit changes to Nous.
Prerequisites
- Elixir 1.18+ (uses the built-in
JSONmodule) - OTP 27+
Setup
git clone https://github.com/nyo16/nous.git
cd nous
mix deps.get
mix compile
Running Tests
# Run all tests
mix test
# Run a specific test file
mix test test/nous/decisions_test.exs
# Run tests with verbose output
mix test --trace
Code Quality
# Check formatting
mix format --check-formatted
# Run credo linter
mix credo --strict
# Run dialyzer (first run builds PLT, takes a few minutes)
mix dialyzer
# All checks at once
mix compile --warnings-as-errors && mix format --check-formatted && mix credo --strict && mix test
Configuration
API keys are configured via environment variables:
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export GROQ_API_KEY="gsk_..."
# See config/config.exs for all supported providers
For local models (no API key needed):
# LM Studio — start the server, then:
agent = Nous.new("lmstudio:qwen3")
# Ollama — start the server, then:
agent = Nous.new("ollama:llama2")
# LlamaCpp — load a GGUF model directly (requires llama_cpp_ex dep):
:ok = LlamaCppEx.init()
{:ok, llm} = LlamaCppEx.load_model("model.gguf", n_gpu_layers: -1)
agent = Nous.new("llamacpp:local", llamacpp_model: llm)
# For thinking models (Qwen3, DeepSeek, etc.), disable <think> tags:
agent = Nous.new("llamacpp:local",
llamacpp_model: llm,
model_settings: %{enable_thinking: false}
)
Running Examples
# Run any example script
mix run examples/01_hello_world.exs
# Run with a specific provider
OPENAI_API_KEY=sk-... mix run examples/02_with_tools.exs
Generating Docs
mix docs
open doc/index.html
Project Structure
lib/nous/
├── agent.ex # Agent struct and builder
├── agent_runner.ex # Core execution loop
├── agent_server.ex # GenServer wrapper for supervised agents
├── fallback.ex # Fallback model chain support
├── decisions/ # Decision graph (goals, decisions, outcomes)
│ ├── store/ # Store backends (ETS, DuckDB)
│ ├── node.ex # Node struct
│ ├── edge.ex # Edge struct
│ ├── tools.ex # LLM-callable decision tools
│ └── context_builder.ex
├── knowledge_base/ # LLM-compiled wiki knowledge base
│ ├── store/ # Store backends (ETS)
│ ├── tools.ex # 9 KB agent tools
│ ├── workflows.ex # DAG pipelines (ingest, update, health, generate)
│ └── prompts.ex # LLM prompt templates
├── memory/ # Persistent memory with hybrid search
│ ├── store/ # Store backends (ETS, SQLite, DuckDB, etc.)
│ ├── embedding/ # Embedding providers
│ └── tools.ex # LLM-callable memory tools
├── plugins/ # Agent plugins
│ ├── decisions.ex # Decision graph plugin
│ ├── memory.ex # Memory plugin
│ ├── team_tools.ex # Team communication plugin
│ ├── sub_agent.ex # Sub-agent delegation
│ └── human_in_the_loop.ex
├── providers/ # LLM provider adapters
├── teams/ # Multi-agent team orchestration
│ ├── coordinator.ex # Team lifecycle management
│ ├── shared_state.ex # Per-team shared state (ETS)
│ ├── rate_limiter.ex # Budget and rate limiting
│ ├── role.ex # Role-based tool scoping
│ └── comms.ex # PubSub topic helpers
├── tool/ # Tool system
│ ├── behaviour.ex # Tool behaviour
│ ├── schema.ex # Declarative tool DSL
│ └── registry.ex # Tool collection and filtering
├── research/ # Deep research system
└── eval/ # Evaluation frameworkSubmitting changes
# Fork, clone, then:
mix deps.get
mix test # Make sure tests pass
mix format # Format your code
mix credo --strict # Check for issues
# Open a PR against master
See CHANGELOG.md for recent changes.
Security
Nous has project-wide security rules that are non-negotiable. Code that breaks these will be rejected on review:
- Never
String.to_atom/1on untrusted input. UseString.to_existing_atom/1with rescue, or pattern-match on a whitelist of literal strings. - Tools requiring approval are rejected without an
:approval_handler.Bash,FileWrite,FileEditneed one wired inRunContextor they refuse to run. - File tools enforce a workspace root via
PathGuard. Don't bypass it. - HTTP from agents goes through
UrlGuard. Don't make rawReq.get/1calls from a tool to a user-controlled URL. PromptTemplaterejects<% ... %>blocks — only<%= @var %>substitution is allowed (RCE prevention).- Sub-agent deps don't auto-forward. Declare which deps a sub-agent
sees with
:sub_agent_shared_deps, [:key1, :key2].
The full text and rationale for each rule lives in AGENTS.md.