This comprehensive guide covers everything you need to know for developing with Arbor, from initial setup through advanced development workflows.
Minimum Requirements:
- Elixir: 1.15.7 or higher
- Erlang/OTP: 26.1 or higher
- Git: 2.30+ for optimal experience
- Memory: 4GB RAM minimum, 8GB recommended
- Storage: 2GB free space
Recommended Tools:
- asdf: Version manager for Elixir/Erlang
- Docker: For containerized development and observability stack
- VS Code or IntelliJ with Elixir extensions
# Install asdf if not already installed
git clone https://github.com/asdf-vm/asdf.git ~/.asdf
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
source ~/.bashrc
# Add Elixir and Erlang plugins
asdf plugin add erlang
asdf plugin add elixir
# Install versions from .tool-versions file
asdf install
# Verify installation
elixir --version
erl -versionmacOS (Homebrew):
brew install elixirUbuntu/Debian:
wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i erlang-solutions_2.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang elixirArch Linux:
sudo pacman -S elixir-
Clone the repository:
git clone https://github.com/azmaveth/arbor.git cd arbor -
Run automated setup:
./scripts/setup.sh
This script will:
- Verify prerequisites
- Install Hex and Rebar3
- Install project dependencies
- Build Dialyzer PLT files
- Run initial tests
- Create necessary directories
-
Verify installation:
./scripts/test.sh --fast
Arbor uses an Elixir umbrella project with four main applications:
apps/
├── arbor_contracts/ # 🔗 Foundation layer (zero dependencies)
│ ├── schemas/ # Data structure definitions
│ ├── types/ # Type specifications
│ └── protocols/ # Behavior contracts
├── arbor_security/ # 🛡️ Security & capability management
│ ├── kernel/ # Capability granting engine
│ ├── audit/ # Security event logging
│ └── auth/ # Authentication systems
├── arbor_persistence/ # 💾 State management & storage
│ ├── event_store/ # Event sourcing implementation
│ ├── projections/ # CQRS read models
│ └── adapters/ # Storage backend adapters
└── arbor_core/ # 🧠 Core business logic
├── agents/ # Agent management & orchestration
├── sessions/ # Multi-agent coordination
├── tasks/ # Task distribution & execution
└── supervisors/ # OTP supervision trees
Strict dependency hierarchy ensures modularity and testability:
arbor_contracts (foundational, no deps)
↑
arbor_security ← arbor_persistence
↑ ↑
arbor_core ←--------┘
Rules:
arbor_contractshas zero external dependencies- Dependencies flow upward only (no circular dependencies)
- Each app has a single, clear responsibility
- Inter-app communication through well-defined contracts
# Start development server with distributed capabilities
./scripts/dev.sh
# In another terminal - run tests continuously
./scripts/test.sh --fast
# Generate comprehensive coverage report
./scripts/test.sh --coverage
# Connect to running development node for debugging
./scripts/console.sh
# Performance benchmarking
./scripts/benchmark.shThe ./scripts/dev.sh script starts:
- IEx session with full project loaded
- Distributed node (
arbor@localhost) - Hot code reloading enabled
- Unicode support for proper output
- Development cookie for remote connections
Unit Tests:
# Run all unit tests
mix test
# Run specific test file
mix test test/arbor/core/agent_supervisor_test.exs
# Run tests with pattern matching
mix test --only property
mix test --only integrationIntegration Tests:
# Full integration test suite
mix test --only integration
# Database integration tests
mix test test/arbor/persistence/integration/Property-Based Tests:
# Run property-based tests (using StreamData)
mix test --only property
# Generate test data samples
mix test --seed 42 --only propertyPerformance Tests:
# Benchmarking with Benchee
./scripts/benchmark.sh --suite all
# Specific benchmark suites
./scripts/benchmark.sh --suite messaging
./scripts/benchmark.sh --suite persistenceCoverage Configuration (coveralls.json):
{
"coverage_options": {
"minimum_coverage": 80,
"treat_no_relevant_lines_as_covered": true
},
"skip_files": [
"test/",
"deps/"
]
}Test Aliases (mix.exs):
defp aliases do
[
"test.all": ["test", "test --only integration"],
"test.ci": ["test --cover", "coveralls"],
"test.watch": ["test.watch"]
]
end# Run Credo checks
mix credo
# Strict mode (fails on any issues)
mix credo --strict
# Generate suggestions
mix credo suggest
# Check specific files
mix credo lib/arbor/core/agent_supervisor.exCredo Configuration (.credo.exs):
%{
configs: [
%{
name: "default",
files: %{
included: ["lib/", "src/", "test/", "web/", "apps/"],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
strict: true,
checks: [
# Enabled checks
{Credo.Check.Consistency.TabsOrSpaces},
{Credo.Check.Design.TagTODO, exit_status: 0},
# Disabled checks
{Credo.Check.Readability.LargeNumbers, false}
]
}
]
}# Build PLT (first time, slow)
mix dialyzer --plt
# Run type checking
mix dialyzer
# Check specific files
mix dialyzer lib/arbor/core/agent_supervisor.ex
# Explain warnings
mix dialyzer --explainDialyzer Configuration (mix.exs):
defp dialyzer do
[
plt_file: {:no_warn, "priv/plts/dialyzer.plt"},
plt_add_apps: [:mix, :ex_unit],
flags: [:error_handling, :race_conditions, :underspecs]
]
endArbor supports hot code reloading during development:
# In IEx session
iex> recompile()
Compiling 1 file (.ex)
:ok
# Reload specific modules
iex> r Arbor.Core.AgentSupervisor
{:reloaded, [Arbor.Core.AgentSupervisor]}
# Check running processes
iex> Process.list() |> length()
42# Start with debugging enabled
iex --dbg pry -S mix
# Set breakpoints in code
require IEx; IEx.pry()
# Inspect process state
iex> Process.info(pid)
iex> :sys.get_state(pid)
# Trace function calls
iex> :dbg.tracer()
iex> :dbg.p(pid, [:call])
iex> :dbg.tpl(Arbor.Core.Agent, :spawn, [])Observer (GUI):
# Start Observer GUI
iex> :observer.start()Observer CLI (Terminal):
# Start terminal-based observer
iex> :observer_cli.start()Process Inspection:
# Find processes by name
iex> Process.whereis(Arbor.Core.AgentSupervisor)
# Inspect supervision tree
iex> Supervisor.which_children(Arbor.Core.Supervisor)
# Check process mailbox
iex> Process.info(pid, :message_queue_len)# Connect to remote node
./scripts/console.sh --node arbor@remote.host
# Inspect cluster status
iex> Node.list()
iex> :net_adm.ping(:"arbor@other.node")
# Distributed process inspection
iex> :global.registered_names()
iex> :rpc.call(node, Module, function, args)Development (config/dev.exs):
import Config
config :arbor_core,
distributed: true,
node_name: "arbor@localhost",
cookie: :arbor_dev,
telemetry_enabled: true
config :logger,
level: :debug,
format: "$time $metadata[$level] $message\n"Test (config/test.exs):
import Config
config :arbor_core,
distributed: false,
async_testing: true
config :logger, level: :warn
# Fast test database
config :arbor_persistence,
adapter: Arbor.Persistence.Adapters.MemoryProduction (config/prod.exs):
import Config
config :arbor_core,
distributed: true,
telemetry_enabled: true
config :logger,
level: :info,
backends: [LoggerJSON]Environment Variables:
# Development
export MIX_ENV=dev
export ARBOR_NODE_NAME=arbor@dev.local
export ARBOR_COOKIE=secure_dev_cookie
# Observability
export PROMETHEUS_ENDPOINT=http://localhost:9090
export JAEGER_ENDPOINT=http://localhost:14250
# Security
export ARBOR_SECRET_KEY_BASE=$(mix phx.gen.secret)
export ARBOR_CAPABILITY_KEY=$(mix phx.gen.secret)Using Benchee:
defmodule Arbor.Core.AgentBench do
use Benchee
def run do
Benchee.run(%{
"spawn_agent" => fn -> Arbor.Core.spawn_agent(:worker, %{}) end,
"spawn_agent_supervised" => fn ->
Arbor.Core.spawn_agent_supervised(:worker, %{})
end
},
time: 10,
memory_time: 2,
formatters: [
Benchee.Formatters.HTML,
Benchee.Formatters.Console
])
end
endRunning Benchmarks:
# All benchmarks
./scripts/benchmark.sh
# Specific suites
./scripts/benchmark.sh --suite messaging
./scripts/benchmark.sh --suite persistence
# With output formats
./scripts/benchmark.sh --format html --saveUsing :fprof:
# Profile function execution
:fprof.apply(Arbor.Core, :heavy_function, [args])
:fprof.profile()
:fprof.analyse()Using :eprof:
# Profile process execution
:eprof.start_profiling([pid])
# ... run code ...
:eprof.stop_profiling()
:eprof.analyze()# Check memory usage
iex> :erlang.memory()
# Process memory inspection
iex> Process.info(pid, :memory)
# Garbage collection stats
iex> :erlang.garbage_collect(pid)# Start full development environment
docker-compose up -d
# View logs
docker-compose logs -f arbor
# Execute commands in container
docker-compose exec arbor iex
# Scale services
docker-compose up -d --scale arbor=3# Build development image
docker build --target debug -t arbor:dev .
# Run with hot reloading
docker run -it --rm \
-v $(pwd):/app \
-p 4000:4000 \
arbor:dev \
./scripts/dev.sh# Unit tests only
mix test --exclude integration
# Integration tests only
mix test --only integration
# Property-based tests
mix test --only property
# Slow tests (full suite)
mix test --include slow# Run CI test suite locally
./scripts/test.sh --ci
# Generate CI coverage report
mix coveralls.html --umbrella# Performance test suite
./scripts/benchmark.sh --suite load
# Stress testing
MIX_ENV=test mix run scripts/stress_test.exsDialyzer PLT Issues:
# Clear and rebuild PLT
rm -rf _build/*/dialyxir_*
./scripts/setup.shDependency Conflicts:
# Clean and reinstall dependencies
mix deps.clean --all
mix deps.get
mix deps.compilePort Conflicts:
# Find processes using ports
lsof -i :4000
lsof -i :9001
# Kill processes if needed
pkill -f "beam.smp"Memory Issues:
# Increase EVM memory
export ERL_MAX_PORTS=32768
export ERL_MAX_ETS_TABLES=32768Node Connection Problems:
# Check node is running
./scripts/console.sh --node arbor@localhost
# Verify cookie consistency
cat ~/.erlang.cookie
# Reset distributed environment
pkill -f "epmd"
epmd -daemonHot Reloading Not Working:
# Force recompilation
iex> mix.
iex> recompile()
# Check for syntax errors
iex> c("lib/path/to/file.ex")Connection Problems:
# Check database status
docker-compose ps postgres
# Reset database
mix ecto.drop
mix ecto.create
mix ecto.migrate# lib/mix/tasks/arbor.analyze.ex
defmodule Mix.Tasks.Arbor.Analyze do
use Mix.Task
@shortdoc "Analyze Arbor system performance"
def run(args) do
Mix.Task.run("app.start")
# Analysis logic
end
endProcess Monitoring:
defmodule Arbor.Dev.ProcessMonitor do
def monitor_agents do
agents = Arbor.Core.list_agents()
Enum.each(agents, fn agent ->
info = Process.info(agent.pid)
IO.puts("Agent #{agent.id}: #{inspect(info)}")
end)
end
endDevelopment Fixtures:
defmodule Arbor.Dev.Fixtures do
def create_test_agents(count \\ 10) do
1..count
|> Enum.map(fn i ->
Arbor.Core.spawn_agent(:test_agent, %{id: "test_#{i}"})
end)
end
end# Build development release
./scripts/release.sh --env dev
# Test release locally
_build/dev/rel/arbor/bin/arbor start# Build production release
./scripts/release.sh --env prod --version 1.0.0
# Create release package
./scripts/release.sh --package- Elixir Official Docs
- OTP Design Principles
- Phoenix Framework (for web interface components)
Happy coding! 🌳✨
This development guide should cover most scenarios you'll encounter while working with Arbor. If you have questions or run into issues not covered here, please check our Contributing Guidelines or reach out to the community.