<p align="center">
<img src="assets/command.svg" alt="Command" width="500">
</p>
<h1 align="center">Command</h1>
<p align="center">
<strong>Core Library for AI Agent Orchestration</strong>
</p>
<p align="center">
<a href="https://hex.pm/packages/command"><img src="https://img.shields.io/hexpm/v/command.svg?style=flat-square" alt="Hex Version"></a>
<a href="https://hexdocs.pm/command"><img src="https://img.shields.io/badge/hex-docs-blue.svg?style=flat-square" alt="Hex Docs"></a>
<a href="https://github.com/nshkrdotcom/command/actions"><img src="https://img.shields.io/github/actions/workflow/status/nshkrdotcom/command/ci.yml?branch=main&style=flat-square" alt="CI Status"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License"></a>
</p>
<p align="center">
<a href="#features">Features</a> |
<a href="#installation">Installation</a> |
<a href="#usage">Usage</a> |
<a href="#architecture">Architecture</a> |
<a href="#contributing">Contributing</a>
</p>
---
Command is an Elixir library for AI agent orchestration. It provides persistent session management, multi-provider LLM integration, tool approval workflows, DAG-based workflow execution, and RAG context management with pgvector-backed vector search.
## Features
- **Sessions**: Persistent, resumable agent work contexts with branching/forking support
- **Agent Calls**: Multi-provider LLM integration (Anthropic, OpenAI, Google, Cohere) with full lifecycle tracking
- **Tool Uses**: Track and approve tool invocations with human-in-the-loop workflows
- **Workflows**: DAG-based orchestration with step dependencies and approval gates
- **Indexes**: RAG context management with pgvector-backed vector search via Portfolio ecosystem
- **Approvals**: Configurable auto-approval rules and manual review queues
- **Artifacts**: Versioned file/output storage with diff tracking
- **Cost Tracking**: Per-call cost tracking with daily summaries and provider/model breakdowns
- **Scheduling**: Cron, interval, and one-time job scheduling
- **Presence**: Multi-user awareness and activity audit logging
- **Phoenix Integration**: LiveView components and PubSub helpers for real-time UIs
## Installation
Add `command` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:command, "~> 0.1.0"}
]
end
```
## Configuration
### Database
Command requires PostgreSQL with the following extensions:
- `citext` - Case-insensitive text for emails
- `pg_trgm` - Trigram matching for full-text search
- `btree_gin` - GIN index support
- `vector` - pgvector for embeddings
Configure your database in `config/config.exs`:
```elixir
config :command, Command.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "command_dev"
```
### Encryption
Command encrypts sensitive data (API credentials) using Cloak. Configure the vault:
```elixir
config :command, Command.Vault,
ciphers: [
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1",
key: Base.decode64!("your-32-byte-key-base64-encoded"),
iv_length: 12
}
]
```
Generate a production key:
```elixir
:crypto.strong_rand_bytes(32) |> Base.encode64()
```
### Portfolio Adapters
Command uses the Portfolio ecosystem for RAG/LLM operations. Register adapters in your application startup:
```elixir
# In your application.ex or config
PortfolioCore.register(:embedder, MyApp.Embedder, %{model: "text-embedding-3-small"})
PortfolioCore.register(:llm, MyApp.LLM, %{model: "claude-sonnet-4-20250514"})
PortfolioCore.register(:vector_store, MyApp.VectorStore, %{})
PortfolioCore.register(:retriever, MyApp.Retriever, %{})
```
## Setup
```bash
# Install dependencies
mix deps.get
# Create and migrate database
mix ecto.setup
# Run tests
mix test
# Check code quality
mix lint
```
## Usage
### Users and API Credentials
```elixir
# Create a user
{:ok, user} = Command.Accounts.create_user(%{
email: "dev@example.com",
name: "Developer"
})
# Store an encrypted API credential
{:ok, credential} = Command.Accounts.create_api_credential(user, %{
name: "My Anthropic Key",
provider: "anthropic",
api_key: "sk-ant-..."
})
# Retrieve and decrypt
{:ok, key} = Command.Accounts.get_decrypted_key(credential)
```
### Sessions and Messages
```elixir
# Create a session
{:ok, session} = Command.Sessions.create_session(user, %{
name: "Code Review",
purpose: "Review PR #123",
default_agent: "claude",
default_model: "claude-sonnet-4-20250514"
})
# Add messages
{:ok, _msg} = Command.Sessions.create_message(session, %{
role: "user",
content: "Please review the authentication module"
})
# Get conversation history
messages = Command.Sessions.list_messages(session)
# Fork a session at a specific message
{:ok, forked} = Command.Sessions.fork_session(session, message, %{
name: "Alternative approach"
})
# Archive a session
{:ok, _} = Command.Sessions.archive_session(session)
```
### Agent Calls
```elixir
# Create an agent call
{:ok, call} = Command.Agents.create_agent_call(session, %{
provider: "anthropic",
model: "claude-sonnet-4-20250514",
prompt_messages: [
%{"role" => "user", "content" => "Review this code..."}
],
system_prompt: "You are a code reviewer.",
temperature: 0.7,
max_tokens: 4096,
tools_provided: ["bash", "read_file", "write_file"]
})
# Mark as streaming
{:ok, _} = Command.Agents.start_streaming(call)
# Complete the call
{:ok, completed} = Command.Agents.complete_agent_call(call, %{
response_content: "The code looks good...",
stop_reason: "end_turn",
tokens_in: 150,
tokens_out: 200,
cost_cents: 5
})
# Handle failure
{:ok, failed} = Command.Agents.fail_agent_call(call, %{
error_type: "rate_limit",
error_message: "Too many requests"
})
```
### Tool Uses and Approvals
```elixir
# Create a tool use
{:ok, tool_use} = Command.Agents.create_tool_use(call, %{
tool_name: "bash",
tool_use_id: "toolu_123",
input: %{"command" => "git diff HEAD~1"},
requires_approval: true
})
# Approve the tool use
{:ok, approved} = Command.Agents.approve_tool_use(tool_use, %{
approved_by_id: user.id
})
# Or deny it
{:ok, denied} = Command.Agents.deny_tool_use(tool_use, %{
denial_reason: "Command too dangerous"
})
# Execute and complete
{:ok, _} = Command.Agents.start_tool_execution(approved)
{:ok, _} = Command.Agents.complete_tool_use(approved, %{
output: "diff output...",
exit_code: 0
})
```
### Approval Rules
```elixir
# Create an auto-approval rule
{:ok, rule} = Command.Approvals.create_approval_rule(user, %{
name: "Auto-approve temp files",
approval_type: "file_write",
conditions: %{path_pattern: "/tmp/**", action: "create"},
action: "approve",
max_auto_approvals_per_hour: 100
})
# Match rules against an approval item
matching_rules = Command.Approvals.find_matching_rules(user, approval_item)
```
### Workflows
```elixir
# Create a workflow
{:ok, workflow} = Command.Workflows.create_workflow(user, %{
name: "Code Review Pipeline",
slug: "code-review",
steps: [
%{
"id" => "analyze",
"name" => "Analyze Code",
"type" => "agent_call",
"config" => %{"agent" => "claude", "prompt_template" => "Analyze: {{code}}"}
},
%{
"id" => "review",
"name" => "Generate Review",
"type" => "agent_call",
"depends_on" => ["analyze"]
}
],
input_schema: %{
"pr_url" => %{"type" => "string", "required" => true}
}
})
# Start a workflow run
{:ok, run} = Command.Workflows.start_workflow_run(workflow, user, %{
input: %{"pr_url" => "https://github.com/..."},
trigger_type: "manual"
})
# Progress through steps
{:ok, step} = Command.Workflows.start_step(run, "analyze")
{:ok, _} = Command.Workflows.complete_step(step, %{
output: %{"analysis" => "..."}
})
```
### RAG Indexes
```elixir
# Create an index
{:ok, index} = Command.Indexes.create_index(user, %{
name: "My Codebase",
slug: "my-codebase",
source_type: "local_repo",
source_config: %{
path: "/home/user/project",
include: ["**/*.ex", "**/*.exs"],
exclude: ["deps/**", "_build/**"]
},
embedding_provider: "openai",
embedding_model: "text-embedding-3-small"
})
# Add documents
{:ok, doc} = Command.Indexes.create_context_document(index, %{
uri: "lib/my_app/core.ex",
title: "Core Module",
source_type: "file",
content_hash: :crypto.hash(:sha256, content) |> Base.encode16()
})
# Add chunks with embeddings
{:ok, chunk} = Command.Indexes.create_context_chunk(doc, %{
content: "defmodule MyApp.Core do...",
chunk_index: 0,
token_count: 150,
embedding: embedding_vector, # [float, ...]
language: "elixir"
})
# Search for similar chunks
chunks = Command.Indexes.search_chunks(index, query_embedding, k: 10)
```
### Portfolio Integration
Command integrates with the Portfolio ecosystem for RAG operations:
```elixir
# Generate embeddings
{:ok, embedding} = Command.Portfolio.embed("text to embed")
# LLM completion
{:ok, response} = Command.Portfolio.complete([
%{role: "user", content: "Hello"}
])
# RAG retrieval
{:ok, chunks} = Command.Portfolio.retrieve("search query", %{}, k: 10)
# Store in vector index
:ok = Command.Portfolio.store_embedding("my-index", "doc-1", embedding, %{source: "file.ex"})
# Search vectors
{:ok, results} = Command.Portfolio.search_vectors("my-index", query_embedding, 10)
```
### Cost Tracking
```elixir
# Record a cost
{:ok, _} = Command.Costs.record_cost(user, %{
source_type: "agent_call",
source_id: call.id,
provider: "anthropic",
service: "chat",
model: "claude-sonnet-4-20250514",
tokens_in: 150,
tokens_out: 200,
cost_cents: 5
})
# Get cost summaries
daily_cost = Command.Costs.get_daily_cost(user)
weekly_cost = Command.Costs.get_weekly_cost(user)
monthly_cost = Command.Costs.get_monthly_cost(user)
# Breakdown by provider/model
by_provider = Command.Costs.get_cost_by_provider(user, days: 7)
by_model = Command.Costs.get_cost_by_model(user, days: 30)
# Daily summaries
summaries = Command.Costs.list_daily_summaries(user, days: 30)
```
### Artifacts
```elixir
# Create an artifact
{:ok, artifact} = Command.Artifacts.create_artifact(user, %{
name: "generated_code.ex",
artifact_type: "code",
storage_backend: "local",
storage_path: "/tmp/artifacts/abc123.ex",
language: "elixir",
size_bytes: 1024
})
# Create a new version
{:ok, v2} = Command.Artifacts.create_artifact_version(artifact, %{
name: "generated_code.ex",
storage_backend: "local",
storage_path: "/tmp/artifacts/def456.ex"
})
# Create a diff
{:ok, diff} = Command.Artifacts.create_diff(user, artifact, %{
name: "Changes v1 to v2",
diff_content: "@@ -1,3 +1,5 @@...",
additions: 10,
deletions: 2
})
# Get version history
history = Command.Artifacts.get_version_history(artifact)
```
### Scheduled Jobs
```elixir
# Create a scheduled job
{:ok, job} = Command.Scheduling.create_scheduled_job(user, %{
job_type: "workflow",
job_config: %{workflow_id: workflow.id, input: %{}},
schedule_type: "cron",
cron_expression: "0 9 * * *",
timezone: "America/New_York"
})
# Or interval-based
{:ok, job} = Command.Scheduling.create_scheduled_job(user, %{
job_type: "reindex",
job_config: %{index_id: index.id},
schedule_type: "interval",
interval_seconds: 3600
})
# Manage job lifecycle
{:ok, _} = Command.Scheduling.pause_job(job)
{:ok, _} = Command.Scheduling.resume_job(job)
{:ok, _} = Command.Scheduling.cancel_job(job)
```
### Presence and Activity
```elixir
# Join a resource
{:ok, presence} = Command.Presence.join_resource(user, "session", session.id, %{
client_id: "browser-tab-123",
device_type: "desktop"
})
# Update status
{:ok, _} = Command.Presence.update_presence(presence, %{status: "editing"})
# Heartbeat
{:ok, _} = Command.Presence.heartbeat(presence)
# List who's present
present_users = Command.Presence.list_resource_presence("session", session.id)
# Log activity
{:ok, _} = Command.Presence.log_user_activity(
user,
"session.update",
"session",
session.id,
%{changes: ["name"]}
)
# Query activity
activities = Command.Presence.list_user_activity(user, limit: 50)
```
### Phoenix LiveView Integration
```elixir
defmodule MyAppWeb.SessionLive do
use MyAppWeb, :live_view
import Command.Phoenix.LiveHelpers
def mount(%{"id" => id}, _session, socket) do
session = Command.Sessions.get_session!(id)
socket =
socket
|> assign(:session, session)
|> subscribe_to_session(id)
|> subscribe_to_user(socket.assigns.current_user.id)
|> stream(:messages, Command.Sessions.list_messages(session))
{:ok, socket}
end
def handle_info(msg, socket) do
case handle_command_event(msg, socket) do
{:handled, socket} -> {:noreply, socket}
{:unhandled, _msg} -> {:noreply, socket}
end
end
end
```
Use the included components:
```heex
<Command.Phoenix.Components.session_status status={@session.status} />
<Command.Phoenix.Components.cost_display cents={@session.total_cost_cents} />
<Command.Phoenix.Components.token_usage
tokens_in={@call.tokens_in}
tokens_out={@call.tokens_out}
/>
<Command.Phoenix.Components.approval_status status={@approval.status} />
```
### PubSub Events
Subscribe to real-time updates:
```elixir
# Session events
Command.PubSub.subscribe("session:#{session_id}")
Command.PubSub.subscribe("session:#{session_id}:messages")
Command.PubSub.subscribe("session:#{session_id}:agent_calls")
# User events
Command.PubSub.subscribe("user:#{user_id}:sessions")
Command.PubSub.subscribe("user:#{user_id}:approvals")
Command.PubSub.subscribe("user:#{user_id}:costs")
# Handle events
def handle_info({Command.PubSub, :message_created, message}, socket) do
{:noreply, stream_insert(socket, :messages, message)}
end
```
## Architecture
Command follows an Elixir contexts pattern with 10 bounded contexts:
```
lib/
├── command.ex # Main module, delegates to contexts
├── command/
│ ├── application.ex # OTP application
│ ├── repo.ex # Ecto repository
│ ├── repo/
│ │ └── paginator.ex # Cursor-based pagination
│ ├── vault.ex # Cloak vault for encryption
│ ├── encrypted_binary.ex # Ecto type for encrypted fields
│ ├── pubsub.ex # PubSub wrapper
│ │
│ ├── accounts.ex # User management context
│ ├── accounts/
│ │ ├── user.ex # User schema
│ │ └── api_credential.ex # Encrypted API key schema
│ │
│ ├── sessions.ex # Session management context
│ ├── sessions/
│ │ ├── session.ex # Session schema (branchable)
│ │ └── message.ex # Conversation message schema
│ │
│ ├── agents.ex # Agent execution context
│ ├── agents/
│ │ ├── agent_call.ex # LLM API call schema
│ │ └── tool_use.ex # Tool invocation schema
│ │
│ ├── workflows.ex # Workflow orchestration context
│ ├── workflows/
│ │ ├── workflow.ex # Workflow definition (DAG)
│ │ ├── workflow_run.ex # Workflow execution instance
│ │ └── workflow_step.ex # Step execution record
│ │
│ ├── indexes.ex # RAG index context
│ ├── indexes/
│ │ ├── index.ex # Index configuration
│ │ ├── context_document.ex # Source document
│ │ └── context_chunk.ex # Embedded chunk (pgvector)
│ │
│ ├── approvals.ex # Approval workflow context
│ ├── approvals/
│ │ ├── approval_item.ex # Approval queue item
│ │ └── approval_rule.ex # Auto-approval rule
│ │
│ ├── artifacts.ex # File/output management context
│ ├── artifacts/
│ │ └── artifact.ex # Versioned artifact schema
│ │
│ ├── costs.ex # Cost tracking context
│ ├── costs/
│ │ ├── cost_record.ex # Individual cost entry
│ │ └── cost_daily_summary.ex # Aggregated daily summary
│ │
│ ├── scheduling.ex # Job scheduling context
│ ├── scheduling/
│ │ └── scheduled_job.ex # Scheduled job schema
│ │
│ ├── presence.ex # Presence/activity context
│ ├── presence/
│ │ ├── presence_record.ex # User presence tracking
│ │ └── activity_log.ex # Audit log schema
│ │
│ ├── portfolio.ex # Portfolio ecosystem integration
│ │
│ └── phoenix/ # Phoenix integration
│ ├── components.ex # HEEx components
│ └── live_helpers.ex # LiveView helpers
```
## Database Schema
Command uses 21 tables across 10 domains. See [SCHEMA.md](SCHEMA.md) for complete documentation.
| Domain | Tables |
|--------|--------|
| Accounts | `users`, `api_credentials` |
| Sessions | `sessions`, `messages` |
| Agents | `agent_calls`, `tool_uses` |
| Workflows | `workflows`, `workflow_runs`, `workflow_steps` |
| Indexes | `indexes`, `context_documents`, `context_chunks` |
| Approvals | `approval_items`, `approval_rules` |
| Artifacts | `artifacts` |
| Costs | `cost_records`, `cost_daily_summaries` |
| Scheduling | `scheduled_jobs` |
| Presence | `presence_records`, `activity_logs` |
All tables use UUIDs for distributed-friendly operation.
## Dependencies
Command builds on:
- **Ecto SQL** - Database layer with PostgreSQL
- **Phoenix** - Web framework integration
- **Phoenix LiveView** - Real-time UI components
- **Phoenix PubSub** - Event broadcasting
- **Cloak Ecto** - Field-level encryption
- **Portfolio Core/Index** - Hexagonal RAG/LLM integration
- **Oban** (optional) - Background job processing
## License
MIT License - see LICENSE file for details.
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests and linting (`mix test && mix lint`)
4. Commit your changes (`git commit -m 'Add amazing feature'`)
5. Push to the branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request