Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RUSVEL

The Solo Builder’s AI-Powered Virtual Agency One binary, one human, infinite leverage.

RUSVEL is an AI-powered virtual agency built with Rust + SvelteKit. It gives a single person the leverage of an entire agency through 14 autonomous departments, each powered by AI agents.

Quick Start

git clone https://github.com/mbaneshi/rusvel
cd rusvel
cargo run
# Open http://localhost:3000

What’s Inside

  • God Agent — Your AI companion that knows your identity, products, and mission
  • 14 Departments — Forge, Code, Content, Harvest, GTM, Finance, Product, Growth, Distribution, Legal, Support, Infra, Flow, Messaging
  • Knowledge/RAG — fastembed + lancedb for semantic search over your documents
  • Self-Improvement — The app can analyze and improve its own codebase
  • 55 workspace members — Hexagonal architecture; 22 port traits in rusvel-core/src/ports.rs (15 primary + 5 *Store + ChannelPort + BrowserPort + RusvelBasePort)
  • ~645 tests (workspace sum); full cargo test passes in a normal dev environment

See Repository status for canonical metrics and links to docs/status/current-state.md on GitHub.

Architecture

God Agent (Chat — full authority + visibility)
├── Forge      — Mission planning, goals, reviews
├── Code       — Full Claude Code capabilities
├── Content    — Draft, adapt, publish across platforms
├── Harvest    — Find opportunities, score, propose
├── GTM        — CRM, outreach, invoicing, deals
├── Finance    — Ledger, runway, tax estimation
├── Product    — Roadmap, pricing, feedback
├── Growth     — Funnel, cohorts, KPIs
├── Distribution — SEO, marketplace, affiliates
├── Legal      — Contracts, compliance, IP
├── Support    — Tickets, knowledge base, NPS
├── Infra      — Deploy, monitor, incidents
├── Flow       — DAG workflow engine
└── Messaging  — Notification channels

Each department is autonomous — own config, own chat, own agents, own events. God sees everything and can orchestrate any combination.

Stack

  • Rust edition 2024, SQLite WAL, Axum, Clap 4, tokio
  • SvelteKit 5, Tailwind CSS 4
  • LLM: Claude CLI (Max subscription), Ollama, OpenAI, Claude API
  • RAG: fastembed (local ONNX embeddings) + lancedb (vector search)

Getting Started

Get RUSVEL running in under 5 minutes.

  1. Installation — Build from source or use Docker
  2. First Run — Start the server and explore the dashboard
  3. First Mission — Create a session and generate your first daily plan

Installation

Prerequisites

RUSVEL is a Rust + SvelteKit application. You need the following installed:

DependencyVersionRequiredPurpose
RustEdition 2024 (nightly or stable 1.85+)YesBackend, all engines
Node.js20+YesFrontend build
pnpm9+YesFrontend package manager
SQLite3.35+BundledDatabase (WAL mode)
OllamaLatestOptionalLocal LLM inference

Install Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Install Node.js + pnpm

Use your preferred method (nvm, fnm, or direct install):

# With nvm
nvm install 22
nvm use 22

# Install pnpm
corepack enable
corepack prepare pnpm@9.15.4 --activate
# Or: npm install -g pnpm

Install Ollama (optional)

Ollama provides free local LLM inference. RUSVEL auto-detects it on first run.

# macOS
brew install ollama

# Linux
curl -fsSL https://ollama.com/install.sh | sh

# Start the service
ollama serve

If you skip Ollama, you can configure Claude API or OpenAI keys instead.

Clone and Build

git clone https://github.com/mbaneshi/rusvel.git
cd rusvel
cargo build

The workspace has 50 members (see Repository status). First build takes a few minutes; subsequent builds are incremental.

Build the Frontend

cd frontend
pnpm install
pnpm build

The built frontend is served by the Rust binary at runtime. During development you can run pnpm dev for hot-reload on port 5173.

Verify the Installation

Run the full test suite to confirm everything works:

cargo test

You should see on the order of ~399 tests passing (exact count changes over time). Use cargo test exit code 0 as the gate.

test result: ok. … passed; 0 failed

Test individual crates

cargo test -p rusvel-core       # Core domain types and port traits
cargo test -p rusvel-db         # SQLite stores (41 tests)
cargo test -p forge-engine      # Agent orchestration (15 tests)
cargo test -p content-engine    # Content creation (7 tests)
cargo test -p harvest-engine    # Opportunity discovery (12 tests)

Next Steps

Once installed, proceed to First Run to launch RUSVEL for the first time.

First Run

Start the Server

From the project root, run:

cargo run

RUSVEL starts the API server on port 3000. You will see:

RUSVEL API listening on 0.0.0.0:3000

Open your browser to http://localhost:3000.

Create Your First Session

Sessions are RUSVEL’s workspaces. Everything – goals, events, conversations, agent runs – lives inside a session.

Via the Web UI

The frontend guides you through creating a session on first load. The onboarding checklist tracks your progress:

  1. Create a session
  2. Add a goal
  3. Generate a daily plan
  4. Chat with a department
  5. Create an agent

Via the CLI

# Create a session
cargo run -- session create "My Startup"

# Output:
# Session created: My Startup
#   ID: a1b2c3d4-...  (set as active session)

The session ID is saved to ~/.rusvel/active_session and used by all subsequent CLI commands.

List and Switch Sessions

# List all sessions
cargo run -- session list

# Switch active session
cargo run -- session switch <session-id>

LLM Configuration

RUSVEL supports four LLM providers. On first run, it auto-detects Ollama if running locally.

ProviderSetupBest For
OllamaAuto-detected if runningFree local inference
Claude APISet ANTHROPIC_API_KEY env varHigh-quality reasoning
Claude CLIInstall claude CLI toolClaude Max subscription
OpenAISet OPENAI_API_KEY env varGPT models

You can configure the default model per department in the Settings page or via the API:

curl -X PUT http://localhost:3000/api/config \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4-20250514"}'

The MCP Server

RUSVEL can also run as an MCP (Model Context Protocol) server for integration with Claude Desktop or other MCP clients:

cargo run -- --mcp

This starts a stdio JSON-RPC server instead of the web server.

Directory Structure

After first run, RUSVEL creates:

~/.rusvel/
├── active_session    # UUID of the current session
├── config.toml       # Global configuration
└── rusvel.db         # SQLite database (WAL mode)

Next Steps

With your session created, head to First Mission to set goals and generate your first daily plan.

First Mission

What is a Mission?

A mission is RUSVEL’s daily planning system, powered by the Forge engine. It reads your active goals, checks the state of all departments, and generates a prioritized task list for the day.

The workflow is: set goals > generate daily plan > execute > review progress.

Set Goals

Goals are high-level objectives with a timeframe. They drive what the daily plan prioritizes.

Via the Web UI

  1. Navigate to the Forge department from the sidebar
  2. Click the “Set new goal” quick action
  3. The Forge agent will ask for context and help you define the goal

Via the CLI

# Add a goal with default month timeframe
cargo run -- forge mission goal add "Launch MVP"

# Add a goal with specific timeframe and description
cargo run -- forge mission goal add "Get 10 beta users" \
  --description "Recruit beta testers from HN and Reddit" \
  --timeframe week

# List all goals
cargo run -- forge mission goals

Output from goals:

ID                                      TITLE                      TIMEFRAME   STATUS      PROGRESS
----------------------------------------------------------------------------------------------------
a1b2c3d4-...                            Launch MVP                 Month       Active      0%
e5f6g7h8-...                            Get 10 beta users          Week        Active      0%

Goal Timeframes

TimeframePlanning Horizon
dayToday only
weekThis week
monthThis month
quarterThis quarter

Generate a Daily Plan

The daily plan is an AI-generated prioritized task list based on your goals and current state.

Via the Web UI

  1. Open the Forge department
  2. Click “Daily plan” quick action
  3. The agent generates tasks with priorities and focus areas

Via the CLI

cargo run -- forge mission today

Output:

Generating daily plan...

Daily Plan -- 2026-03-23
==================================================
  1. [High] Finalize API endpoint documentation
  2. [High] Write integration tests for auth flow
  3. [Medium] Draft landing page copy
  4. [Medium] Set up CI pipeline
  5. [Low] Review dependency updates

Focus areas:
  - Ship the authentication feature
  - Prepare for beta launch

Notes: Focus on high-priority items first. The auth flow is blocking beta signups.

Review Progress

Periodic reviews summarize what was accomplished and identify blockers.

Via the CLI

# Weekly review (default)
cargo run -- forge mission review

# Monthly review
cargo run -- forge mission review --period month

# Quarterly review
cargo run -- forge mission review --period quarter

Output:

Generating Week review...

Review (Week)
==================================================

Accomplishments:
  - Completed API authentication flow
  - Deployed staging environment
  - Drafted 3 blog posts

Blockers:
  - Waiting on SSL certificate for production
  - Need feedback on pricing page design

Insights:
  - Code velocity increased 40% after adding test automation
  - Content pipeline is 2 days behind schedule

Next actions:
  - Follow up on SSL certificate
  - Schedule design review for pricing page

Explore Other Departments

With your mission set, explore the 14 department apps. Each has its own AI agent specialized for that domain:

  • Code – implement features, analyze code, run tests
  • Content – write blog posts, adapt for social platforms
  • Harvest – find freelance opportunities, draft proposals
  • GTM – manage CRM, send outreach, track deals

See the full list in Departments.

Concepts

Core building blocks of the RUSVEL system.

Sessions

What Are Sessions?

Sessions are RUSVEL’s top-level organizational unit. Think of them as workspaces or projects. All data in RUSVEL – goals, events, conversations, agent runs, content items, opportunities – is scoped to a session.

You always have one active session. All CLI commands and API calls operate on the active session by default.

Session Kinds

Each session has a kind that hints at its purpose:

KindUse CaseExample
ProjectA codebase or product you are building“RUSVEL”, “My SaaS App”
LeadA potential client or deal“Acme Corp Engagement”
ContentCampaignA content series or marketing push“Q1 Blog Series”
GeneralCatch-all for anything else“Scratch Pad”

The session kind is informational. It does not restrict which departments or features you can use.

Creating Sessions

CLI

cargo run -- session create "My Startup"

This creates a new session and sets it as active. The session ID is stored in ~/.rusvel/active_session.

API

curl -X POST http://localhost:3000/api/sessions \
  -H "Content-Type: application/json" \
  -d '{"name": "My Startup", "kind": "Project"}'

Listing Sessions

CLI

cargo run -- session list

Output shows all sessions with the active one marked:

ID                                      NAME                  KIND        UPDATED
------------------------------------------------------------------------------------------
a1b2c3d4-e5f6-...                       My Startup            Project     2026-03-23 14:30 *
b2c3d4e5-f6g7-...                       Blog Series           Content..   2026-03-22 09:15

API

curl http://localhost:3000/api/sessions

Switching Sessions

CLI

cargo run -- session switch <session-id>

API

Load a specific session by ID:

curl http://localhost:3000/api/sessions/<session-id>

Data Stored Per Session

Each session contains:

  • Goals – strategic objectives with timeframes and progress tracking
  • Events – an immutable, append-only log of everything that happens
  • Conversations – chat history with each department agent
  • Agent runs – records of every agent execution (input, output, tokens used)
  • Content items – blog posts, tweets, proposals in various states
  • Opportunities – leads, gigs, and deals in the pipeline
  • Contacts – CRM entries tied to the session
  • Tasks – generated from daily plans, linked to goals

Session Configuration

Sessions can override global configuration. The config cascade is:

Global config  →  Department config  →  Session config

This lets you use different LLM models, approval policies, or tool permissions per session.

Departments

Overview

RUSVEL organizes work into 14 department apps (dept-* crates), each with its own specialized AI agent surface. Together they form a virtual agency that a solo founder can command from a single interface.

Departments follow the DepartmentApp pattern (ADR-014). Each department lives in its own dept-* crate that implements the DepartmentApp trait, declares its capabilities via a DepartmentManifest, and registers with the host at boot. Adding a new department means adding a new dept-* crate – zero changes to rusvel-core.

The DepartmentApp Trait

Every department crate implements this contract:

#![allow(unused)]
fn main() {
pub trait DepartmentApp: Send + Sync {
    fn manifest(&self) -> DepartmentManifest;
    fn register(&self, ctx: &mut RegistrationContext) -> Result<()>;
}
}

The DepartmentManifest declares the department’s ID, name, icon, color, system prompt, capabilities, tabs, quick actions, routes, tools, and CLI commands. The host collects all manifests to generate the DepartmentRegistry, API routes, CLI subcommands, and frontend navigation.

The 14 dept-* Crates

CrateDepartmentEngineFocus
dept-forgeForgeForgeAgent orchestration, goal planning, mission management
dept-codeCodeCodeCode intelligence, implementation, testing
dept-contentContentContentContent creation, publishing, calendar
dept-harvestHarvestHarvestOpportunity discovery, proposals, pipeline
dept-flowFlowFlowDAG workflow engine, visual workflow builder
dept-gtmGTMGoToMarketCRM, outreach, deals, invoicing
dept-financeFinanceFinanceRevenue, expenses, runway, tax
dept-productProductProductRoadmap, pricing, feedback
dept-growthGrowthGrowthFunnels, cohorts, KPIs, retention
dept-distroDistroDistributionMarketplace, SEO, affiliates
dept-legalLegalLegalContracts, compliance, IP
dept-supportSupportSupportTickets, knowledge base, NPS
dept-infraInfraInfraDeployments, monitoring, incidents
dept-messagingMessaging— (shell)Cross-channel messaging surface

Department UI Structure

Each department page has two panels:

Chat Panel (right side)

The AI agent for this department. Send messages, get responses, use quick actions. The agent has access to department-specific tools via the ScopedToolRegistry – each department only sees tools relevant to its domain.

Department Panel (left side)

A tabbed panel with up to 9 tabs depending on the department:

TabPurpose
ActionsQuick-action buttons for common tasks
AgentsCustom agent profiles for this department
WorkflowsMulti-step agent chains (sequential, parallel, loop, graph)
SkillsReusable prompt templates with variables
RulesConstraints injected into the system prompt
MCPMCP server connections (Code department)
HooksEvent-triggered automations
DirsWorking directories for code operations
EventsEvent log for this department

The God Agent

The main Chat page (not tied to any department) runs the God Agent. This agent has full authority and visibility over all departments. Use it for cross-cutting tasks:

  • “What is the status across all departments?”
  • “Move the proposal from Harvest to Content for editing”
  • “Generate a weekly summary of all activity”

Quick Actions

Each department has predefined quick-action buttons that send a prompt to the agent with one click. These are declared in the department’s DepartmentManifest and can be customized.

Department Configuration

Each department inherits global config and can override:

  • Model – which LLM to use for this department’s agent
  • Effort – low, medium, or high (controls response depth)
  • Permission mode – what tools the agent can use
  • Working directories – for code-operating departments

Configure via the Settings page or the API:

curl -X PUT http://localhost:3000/api/dept/code/config \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4-20250514", "effort": "high"}'

Agents

What Are Agents?

Agents are AI personas with specific roles, instructions, tools, and budgets. Each department has a default agent, and you can create custom agents for specialized tasks.

Agents are powered by the AgentRuntime (in rusvel-agent), which wraps LLM access, tool execution, streaming, and memory into a single orchestration layer.

Agent Profile Structure

Every agent has:

name            — Display name (e.g., "rust-engine")
role            — One-line role description
instructions    — System prompt guiding behavior
model           — Which LLM to use (provider + model name)
allowed_tools   — List of tools the agent can invoke
capabilities    — What the agent can do (code_analysis, content_creation, etc.)
budget_limit    — Optional spending cap per run
department      — Which department this agent belongs to

Seeded Agents

RUSVEL ships with 5 pre-configured agents:

AgentDepartmentRole
rust-engineCodeRust development – writes, tests, and refactors Rust code
svelte-uiCodeFrontend development – SvelteKit components and styling
test-writerCodeWrites unit and integration tests for existing code
content-writerContentDrafts blog posts, social media content, and documentation
proposal-writerHarvestWrites proposals for freelance opportunities

Creating Custom Agents

Via the Web UI

  1. Navigate to any department
  2. Open the Agents tab in the department panel
  3. Click “Add Agent”
  4. Fill in name, role, instructions, and model
  5. Save

Via the API

curl -X POST http://localhost:3000/api/agents \
  -H "Content-Type: application/json" \
  -d '{
    "name": "seo-analyst",
    "role": "SEO and keyword research specialist",
    "instructions": "You analyze websites for SEO performance...",
    "model": "claude-sonnet-4-20250514",
    "department": "distro",
    "capabilities": ["seo"]
  }'

Managing Agents

# List all agents
curl http://localhost:3000/api/agents

# Get a specific agent
curl http://localhost:3000/api/agents/<id>

# Update an agent
curl -X PUT http://localhost:3000/api/agents/<id> \
  -H "Content-Type: application/json" \
  -d '{"instructions": "Updated instructions..."}'

# Delete an agent
curl -X DELETE http://localhost:3000/api/agents/<id>

Model Selection and ModelTier Routing

Agents can use any configured LLM provider:

ProviderExample Models
Ollamallama3.1, mistral, codellama
Claude APIclaude-sonnet-4-20250514, claude-opus-4-20250514
Claude CLIReal streaming via Claude CLI binary
OpenAIgpt-4o, gpt-4o-mini

The model is set per agent and falls back to the department default, then the global default.

ModelTier routing automatically classifies models into three tiers – Haiku (fast/cheap), Sonnet (balanced), Opus (powerful/expensive). The CostTracker in MetricStore logs token usage and costs per tier, enabling budget-aware model selection.

Tools and ScopedToolRegistry

Agents have access to 22+ tools:

  • 10 built-in tools: read_file, write_file, edit_file, glob, grep, bash, git_status, git_diff, git_log, and tool_search (meta-tool)
  • 12 engine tools: harvest (5: scan, score, propose, list, pipeline), content (5: draft, adapt, publish, list, approve), code (2: analyze, search)

The ScopedToolRegistry filters tools per department – the Content department sees content tools, the Code department sees code tools. This prevents tool overload and keeps agents focused.

Deferred tool loading via the tool_search meta-tool saves ~85% of tool-description tokens. Instead of loading all tool schemas upfront, agents discover tools on-demand by searching for capabilities they need.

Agent Budgets

Set a budget_limit to cap how much an agent can spend per run. This is useful for expensive operations like long code generation sessions.

When the budget is exceeded, the agent pauses and requests approval to continue.

How Agents Execute

When you chat with a department, here is what happens:

  1. Your message goes to the department’s agent
  2. The AgentRuntime loads the system prompt (department prompt + active rules via load_rules_for_engine())
  3. The ScopedToolRegistry provides department-specific tools
  4. The LLM generates a response via run_streaming(), emitting AgentEvents (text chunks, tool calls, tool results)
  5. Tool calls are executed and results fed back to the LLM in a tool-use loop
  6. LlmStreamEvent provides character-by-character SSE streaming to the frontend
  7. An event is logged with tokens used and tools called

Engines never call the LLM directly (ADR-009). They always go through the AgentPort, which handles prompt construction, tool routing, retries, and memory injection.

Frontend Components

The chat interface includes:

  • ToolCallCard – renders tool invocations inline in the chat
  • ApprovalCard – shows approval requests for sensitive operations
  • ApprovalQueue – sidebar badge showing pending approvals across all departments

Skills & Rules

Skills

Skills are reusable prompt templates with variable placeholders. They let you save common prompts and re-invoke them with different inputs.

Skill Structure

name        — Display name (e.g., "Write Blog Post")
template    — Prompt text with {variable} placeholders
department  — Which department this skill belongs to
variables   — List of variable names used in the template

Example Skill

Name: Draft Technical Blog Post
Template: |
  Write a technical blog post about {topic}.
  Target audience: {audience}.
  Length: {length} words.
  Include code examples in {language}.
  Tone: educational but practical.

When invoked, you fill in {topic}, {audience}, {length}, and {language}.

Managing Skills

Via the Web UI

  1. Navigate to a department
  2. Open the Skills tab in the department panel
  3. Click “Add Skill”
  4. Write the template with {variable} placeholders
  5. Save

Via the API

# Create a skill
curl -X POST http://localhost:3000/api/skills \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Proposal Template",
    "template": "Write a proposal for {gig_title} on {platform}...",
    "department": "harvest"
  }'

# List all skills
curl http://localhost:3000/api/skills

# Update a skill
curl -X PUT http://localhost:3000/api/skills/<id> \
  -H "Content-Type: application/json" \
  -d '{"template": "Updated template..."}'

# Delete a skill
curl -X DELETE http://localhost:3000/api/skills/<id>

Rules

Rules are constraints injected into agent system prompts. They enforce policies, coding standards, or business logic across all interactions with a department.

Rule Structure

name        — Display name (e.g., "Hexagonal Architecture")
content     — The constraint text injected into system prompts
department  — Which department this rule applies to
enabled     — Whether this rule is currently active

Seeded Rules

RUSVEL ships with 3 pre-configured rules:

RuleDepartmentPurpose
Hexagonal ArchitectureCodeEngines never import adapters. Use port traits only.
Human Approval GateContent, GTMAll publishing and outreach requires human approval.
Crate Size LimitCodeEach crate must stay under 2000 lines.

How Rules Work

When a department’s agent processes a message:

  1. All enabled rules for that department are loaded
  2. Rule content is appended to the department system prompt
  3. The combined prompt guides the agent’s behavior

This means rules are “always on” constraints, not one-time instructions.

Managing Rules

Via the Web UI

  1. Navigate to a department
  2. Open the Rules tab
  3. Toggle rules on/off with the enable switch
  4. Add new rules with “Add Rule”

Via the API

# Create a rule
curl -X POST http://localhost:3000/api/rules \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Always Write Tests",
    "content": "Every code change must include corresponding unit tests.",
    "department": "code",
    "enabled": true
  }'

# List all rules
curl http://localhost:3000/api/rules

# Toggle a rule off
curl -X PUT http://localhost:3000/api/rules/<id> \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'

Skills vs Rules

SkillsRules
PurposeReusable promptsBehavioral constraints
When usedOn demand, when invokedAlways active (if enabled)
Contains variablesYes ({variable})No
Injected intoChat as a user messageSystem prompt

Workflows

What Are Workflows?

Workflows chain multiple agents together to accomplish multi-step tasks. Instead of manually prompting one agent at a time, you define a flow that routes work between agents automatically.

Workflow Patterns

RUSVEL supports four execution patterns:

Sequential

Agents run one after another. Each agent’s output feeds into the next as input.

Agent A → Agent B → Agent C → Result

Example: Research a topic (Agent A) > write a blog post (Agent B) > adapt for Twitter (Agent C).

Parallel

Multiple agents run simultaneously. Results are collected and merged.

Agent A ─┐
Agent B ─┤→ Merge → Result
Agent C ─┘

Example: Analyze code quality (Agent A), run security scan (Agent B), check dependencies (Agent C) – all at once.

Loop

An agent runs repeatedly until a condition is met or a maximum iteration count is reached.

Agent A → Check → (repeat or done)

Example: Iterate on a draft until it meets quality criteria.

Graph

A directed acyclic graph of agents with conditional branching. The most flexible pattern.

Agent A → [if pass] → Agent B → Agent D
       → [if fail] → Agent C → Agent D

Example: Analyze an opportunity, route to different proposal strategies based on the score.

Creating Workflows

Via the Web UI

  1. Navigate to a department that supports workflows (Forge, Code, GTM)
  2. Open the Workflows tab (labeled “Flows”)
  3. Click “Add Workflow”
  4. Define the steps, agents, and pattern
  5. Save

Via the API

# Create a workflow
curl -X POST http://localhost:3000/api/workflows \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Blog Pipeline",
    "department": "content",
    "pattern": "sequential",
    "steps": [
      {"agent": "content-writer", "prompt": "Research and outline: {topic}"},
      {"agent": "content-writer", "prompt": "Write full draft from outline"},
      {"agent": "content-writer", "prompt": "Adapt for Twitter thread"}
    ]
  }'

# List workflows
curl http://localhost:3000/api/workflows

# Get a specific workflow
curl http://localhost:3000/api/workflows/<id>

Running Workflows

Via the API

curl -X POST http://localhost:3000/api/workflows/<id>/run \
  -H "Content-Type: application/json" \
  -d '{"variables": {"topic": "Hexagonal Architecture in Rust"}}'

The workflow executes each step in order, passing context between agents. Results are streamed back as events.

Workflow Variables

Workflows accept variables at execution time. Use {variable_name} in step prompts:

{
  "steps": [
    {"prompt": "Analyze the {language} codebase in {directory}"},
    {"prompt": "Write tests for any uncovered functions"}
  ]
}

When running, provide the variables:

{"variables": {"language": "Rust", "directory": "crates/rusvel-core"}}

Managing Workflows

# Update a workflow
curl -X PUT http://localhost:3000/api/workflows/<id> \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Pipeline", "steps": [...]}'

# Delete a workflow
curl -X DELETE http://localhost:3000/api/workflows/<id>

RUSVEL Domain Concepts — How Everything Fits Together

The Mental Model

Everything in RUSVEL is scoped to a Department. Each department owns its agents, skills, rules, hooks, and MCP servers. When a user chats with a department, all these pieces compose into a single agent execution. Session is the work boundary that tracks cost, memory, goals, and events across departments. Events are the glue — hooks react to events, enabling cross-department automation without coupling.


The Hierarchy

Session                          ← your work context (project, lead, campaign)
 ├── has Goals                   ← what you're trying to achieve
 ├── has Budget                  ← spending limit across all departments
 ├── scopes Events              ← everything that happens is tagged to session
 ├── scopes Memory              ← what the system remembers, per session
 │
 └── talks to Departments ──────← the 14 organizational units
      │
      ├── owns Agents            ← personas (@security-reviewer)
      ├── owns Skills            ← prompt shortcuts (/research topic)
      ├── owns Rules             ← "always do X" injected into every chat
      ├── owns Hooks             ← "when X happens, do Y"
      ├── owns MCP Servers       ← external tool providers
      ├── owns Config            ← model, temperature, per-dept settings
      ├── owns Chat History      ← conversation per department
      │
      └── can trigger:
           ├── Workflow          ← simple: step1 → step2 → step3
           ├── Playbook          ← rich: Agent → Approval → Flow
           └── Flow              ← DAG: parallel branches, conditions

JSON Manifest Shape

{
  "session": {
    "id": "uuid",
    "kind": "project",
    "goals": ["Launch MVP by April"],
    "budget_limit_usd": 50.0,
    "departments": {
      "forge": {
        "agents": [
          { "name": "strategist", "role": "Plan daily missions", "model": "opus" }
        ],
        "skills": [
          { "name": "daily-brief", "template": "Generate executive brief for {{input}}" }
        ],
        "rules": [
          { "name": "concise", "content": "Keep responses under 200 words", "enabled": true }
        ],
        "hooks": [
          { "event": "forge.chat.completed", "type": "http", "action": "https://slack.com/webhook" }
        ],
        "mcp_servers": [
          { "name": "github", "type": "stdio", "command": "mcp-github" }
        ],
        "chat_history": "dept_msg_forge",
        "config": { "default_model": "sonnet", "temperature": 0.7 }
      },
      "content": { "...same shape..." },
      "harvest": { "...same shape..." }
    },
    "workflows": [
      { "name": "publish-pipeline", "steps": ["draft", "review", "publish"] }
    ],
    "playbooks": [
      {
        "name": "content-from-code",
        "steps": [
          { "action": "Agent", "persona": "code-analyst", "prompt": "Analyze {{input}}" },
          { "action": "Approval", "message": "Proceed with draft?" },
          { "action": "Agent", "persona": "writer", "prompt": "Write article from {{last_output}}" }
        ]
      }
    ],
    "flows": [
      {
        "name": "opportunity-pipeline",
        "nodes": ["scan", "score", "decide", "propose"],
        "connections": [
          { "scan": "score" },
          { "score": "decide" },
          { "decide[true]": "propose" },
          { "decide[false]": "archive" }
        ]
      }
    ]
  }
}

Visual Diagrams

System Overview

graph TB
    subgraph Session["SESSION (work context)"]
        Goals["Goals"]
        Budget["Budget"]
        Memory["Memory (FTS5)"]
        EventBus["Event Bus"]
    end

    subgraph Departments["DEPARTMENTS (14 units)"]
        subgraph Forge["Forge"]
            FA["Agents"]
            FS["Skills"]
            FR["Rules"]
            FH["Hooks"]
            FM["MCP Servers"]
            FC["Chat History"]
        end
        subgraph Content["Content"]
            CA["Agents"]
            CS["Skills"]
            CR["Rules"]
            CH["Hooks"]
        end
        subgraph Harvest["Harvest"]
            HA["Agents"]
            HS["Skills"]
            HR["Rules"]
            HH["Hooks"]
        end
        Other["+ 11 more departments"]
    end

    subgraph Orchestration["ORCHESTRATION (cross-department)"]
        Workflow["Workflow\n(sequential)"]
        Playbook["Playbook\n(Agent + Approval + Flow)"]
        Flow["Flow\n(DAG with branches)"]
    end

    Session --> Departments
    Departments --> Orchestration
    EventBus --> FH & CH & HH
    Forge & Content & Harvest --> EventBus
    Orchestration --> EventBus

Chat Request Flow

sequenceDiagram
    actor User
    participant API as Department API
    participant Config as Config Cascade
    participant Skill as Skill Resolver
    participant Agent as Agent Override
    participant Rules as Rule Loader
    participant RAG as Knowledge/RAG
    participant Runtime as AgentRuntime
    participant LLM as LLM Provider
    participant Hooks as Hook Dispatch
    participant Events as Event Bus

    User->>API: POST /api/dept/{id}/chat
    API->>Config: Load dept config (registry + stored + user)
    API->>API: Load conversation history

    alt message starts with /skill-name
        API->>Skill: resolve_skill(message)
        Skill-->>API: expanded template with {{input}}
    end

    alt message mentions @agent-name
        API->>Agent: lookup AgentProfile
        Agent-->>API: override system prompt + model
    end

    API->>Rules: load_rules_for_engine(dept_id)
    Rules-->>API: enabled rules appended to system prompt

    API->>RAG: embed query + vector search
    RAG-->>API: knowledge snippets injected

    API->>Runtime: create(AgentConfig) + run_streaming()

    loop Tool-use loop
        Runtime->>LLM: prompt + tools
        LLM-->>Runtime: text or tool_call
        Runtime-->>User: SSE: text_delta / tool_call
    end

    Runtime-->>API: Done (final text)
    API->>API: Store assistant message
    API->>Events: emit("{dept}.chat.completed")
    API->>Hooks: dispatch_hooks(event)

    par Hook execution (fire-and-forget)
        Hooks->>Hooks: command: sh -c "..."
        Hooks->>Hooks: http: POST webhook
        Hooks->>Hooks: prompt: claude -p "..."
    end

    API-->>User: SSE: run_completed

Entity Scoping

graph LR
    subgraph Global
        Sessions["Sessions"]
        Workflows["Workflows"]
        Playbooks["Playbooks"]
        Flows["Flows"]
    end

    subgraph "Scoped by metadata.engine"
        Agents["Agents"]
        Skills["Skills"]
        Rules["Rules"]
        Hooks["Hooks"]
        MCP["MCP Servers"]
        Chat["Chat History\n(dept_msg_{id})"]
        DeptConfig["Dept Config"]
    end

    subgraph "Scoped by session_id"
        Events["Events"]
        Memory["Memory"]
        Goals["Goals"]
        Runs["Runs"]
        Threads["Threads"]
    end

    Sessions --> Events & Memory & Goals & Runs
    Runs --> Threads

Three Orchestration Levels

graph TB
    subgraph Workflow["WORKFLOW (simple)"]
        direction LR
        W1["Step 1:\nagent + prompt"] --> W2["Step 2:\nagent + prompt"] --> W3["Step 3:\nagent + prompt"]
    end

    subgraph Playbook["PLAYBOOK (rich)"]
        direction LR
        P1["Agent Step:\ncode-analyst\nanalyzes repo"] --> P2["Approval Step:\nhuman reviews"] --> P3["Agent Step:\nwriter creates\narticle"] --> P4["Flow Step:\npublish pipeline"]
    end

    subgraph Flow["FLOW (DAG)"]
        direction TB
        F1["Scan\n(code node)"] --> F2["Score\n(agent node)"]
        F2 --> F3{"Score > 80?\n(condition)"}
        F3 -->|true| F4["Generate Proposal\n(agent node)"]
        F3 -->|false| F5["Archive\n(code node)"]
        F4 --> F6["Notify\n(code node)"]
        F5 --> F6
    end

    style Workflow fill:#e8f5e9
    style Playbook fill:#e3f2fd
    style Flow fill:#fce4ec

Event-Driven Automation

graph LR
    subgraph Triggers
        ChatDone["chat.completed"]
        Published["content.published"]
        Scanned["harvest.scan.completed"]
        JobDone["job.completed"]
    end

    subgraph Hooks
        H1["Hook: command\nsh -c 'notify.sh'"]
        H2["Hook: http\nPOST slack webhook"]
        H3["Hook: prompt\nclaude -p 'summarize'"]
    end

    subgraph Downstream
        Slack["Slack notification"]
        Flow2["Trigger a Flow"]
        Job["Enqueue a Job"]
    end

    ChatDone --> H1 & H2
    Published --> H2 & H3
    Scanned --> H1
    H1 --> Job
    H2 --> Slack
    H3 --> Flow2

Storage Map

EntityObjectStore KindScopeFiltered By
Agentagentsper-deptmetadata.engine
Skillskillsper-deptmetadata.engine
Rulerulesper-deptmetadata.engine
Hookhooksper-deptmetadata.engine
MCP Servermcp_serversper-deptmetadata.engine
Workflowworkflowsglobal
Playbookin-memory + storeglobal
Flowflow-engine storageglobal
Chat Historydept_msg_{dept}per-deptkey prefix
Dept Configdept_configper-deptkey
SessionSessionStoreglobal
RunSessionStoreper-sessionsession_id
ThreadSessionStoreper-runrun_id
EventEventStoreper-sessionsession_id
MemoryMemoryPort (FTS5)per-sessionsession_id
GoalObjectStoreper-sessionsession_id
MetricMetricStoreper-deptdepartment tag

When To Use What

NeedUseExample
Reusable prompt shortcutSkill/research {{input}} expands to full research prompt
Persistent behavior ruleRule“Always cite sources” injected into every chat
Specialized personaAgent@security-reviewer with infosec instructions
React to eventsHookon content.published → POST to Slack
Simple ordered stepsWorkflowdraft → review → publish
Mixed actions with approvalPlaybookAI analyzes → human approves → AI executes
Complex branching logicFlowDAG with conditions, parallel branches
External tool providerMCP ServerGitHub, Jira, or custom tools via stdio/http
Track work contextSessionProject with goals, budget, memory
Organize capabilitiesDepartment14 units, each with own agents/skills/rules

The Key Insights

  1. Department is the scoping boundary. Agents, skills, rules, hooks, MCP servers, and chat are all scoped by metadata.engine matching the department ID. Creating an agent in Content means it only appears in Content chat.

  2. Session is the work boundary. Events, memory, goals, and budget are scoped to a session. You talk to any department within a session, but the session tracks total cost and context.

  3. Events are the glue. Hooks react to events. Departments emit events. Jobs emit events. This is how automation chains together without departments knowing about each other.

  4. Three orchestration levels exist for a reason:

    • Workflow — when you know the exact steps and just need sequential execution
    • Playbook — when you need human-in-the-loop approval or mixed AI/Flow steps
    • Flow — when you need conditional logic, parallel branches, or complex DAGs
  5. Everything composes in the chat handler. A single chat request resolves skills, loads agents, injects rules, searches knowledge, runs the LLM, and dispatches hooks — all in one request/response cycle.

Departments

RUSVEL organizes business functions into 14 departments, each backed by a domain engine and an AI agent.

Wired (6): Forge, Code, Content, Harvest, GTM, Flow

Extended (8): Finance, Product, Growth, Distribution, Legal, Support, Infra, Messaging

Forge Department

Agent orchestration, goal planning, mission management, daily plans, reviews

Overview

The Forge Department is the meta-engine of RUSVEL. It orchestrates AI agents across all departments, manages strategic goals, generates prioritized daily mission plans, and produces periodic reviews. It also houses a catalog of 10 built-in agent personas that can be “hired” into any session, and a safety guard system (budget caps, concurrency slots, circuit breaker) to protect against runaway LLM spend. Forge is the department a solo builder uses to plan their day, track progress, and coordinate work across the entire virtual agency.

Engine (forge-engine)

  • Crate: crates/forge-engine/src/lib.rs
  • Lines: 483 (lib.rs) + 712 (mission.rs) + 264 (personas.rs) + 23 (events.rs) + submodules
  • Status: Wired (real business logic)

Public API

MethodSignatureDescription
newfn new(agent, events, memory, storage, jobs, session, config) -> SelfConstruct with all 7 injected port dependencies
list_personasfn list_personas(&self) -> &[AgentProfile]Return all 10 built-in personas
get_personafn get_persona(&self, name: &str) -> Option<&AgentProfile>Look up a persona by name (case-insensitive)
hire_personafn hire_persona(&self, name: &str, session_id: &SessionId) -> Result<AgentConfig>Build a spawn-ready AgentConfig from a named persona
set_goalasync fn set_goal(&self, session_id, title, description, timeframe) -> Result<Goal>Create and persist a new goal
list_goalsasync fn list_goals(&self, session_id: &SessionId) -> Result<Vec<Goal>>List all goals for a session
mission_todayasync fn mission_today(&self, session_id: &SessionId) -> Result<DailyPlan>Generate a prioritized daily plan via LLM from goals and recent events
reviewasync fn review(&self, session_id, period: Timeframe) -> Result<Review>Generate a periodic review (accomplishments, blockers, next actions)
generate_briefasync fn generate_brief(&self, session_id: &SessionId) -> Result<ExecutiveBrief>Cross-department daily digest with strategist summary
latest_briefasync fn latest_brief(&self, session_id: &SessionId) -> Result<Option<ExecutiveBrief>>Retrieve the most recently generated executive brief
run_agent_with_mission_safetyasync fn run_agent_with_mission_safety(&self, session_id, config, prompt) -> Result<AgentOutput>Run an LLM call with circuit breaker, budget check, and concurrency slot

Internal Structure

  • PersonaManager (personas.rs) – Manages the catalog of 10 built-in agent personas. Supports lookup by name (case-insensitive), filtering by capability, and runtime additions.
  • SafetyGuard (safety.rs) – Budget enforcement (soft ceiling with 80% warning threshold), concurrency slot limiter, and circuit breaker (opens after repeated failures).
  • Mission module (mission.rs) – Goals, daily plans, reviews, and executive briefs. All LLM calls go through run_agent_with_mission_safety.
  • Pipeline module (pipeline.rs) – Multi-step pipeline orchestration for forge workflows.
  • Artifacts module (artifacts.rs) – Persistent artifact records for pipeline outputs.
  • Events module (events.rs) – String constants for all emitted event kinds.

Department Wrapper (dept-forge)

  • Crate: crates/dept-forge/src/lib.rs
  • Lines: 276
  • Manifest: crates/dept-forge/src/manifest.rs

The wrapper creates a ForgeEngine during registration, wires all 7 ports, and registers 5 agent tools. It is a thin delegation layer with no business logic.

Manifest Declaration

System Prompt

You are the Forge department of RUSVEL.

Focus: agent orchestration, goal planning, mission management, daily plans, reviews.

Capabilities

  • planning
  • orchestration

Quick Actions

LabelPrompt
Daily planGenerate today’s mission plan based on active goals and priorities.
Review progressReview progress on all active goals. Summarize completed, in-progress, and blocked items.
Set new goalHelp me define a new strategic goal. Ask me for context and desired outcome.

Registered Tools

Tool NameParametersDescription
forge.mission.todaysession_id: string (required)Generate today’s prioritized mission plan from active goals and recent activity
forge.mission.list_goalssession_id: string (required)List all goals for a session
forge.mission.set_goalsession_id: string, title: string, description: string, timeframe: Day/Week/Month/Quarter (all required)Create a new goal for a session
forge.mission.reviewsession_id: string, period: Day/Week/Month/Quarter (required)Generate a periodic review (accomplishments, blockers, next actions)
forge.persona.hiresession_id: string, persona_name: string (required)Build an AgentConfig from a named Forge persona (spawn-ready profile)

Personas

NameRoleDefault ModelAllowed ToolsPurpose
CodeWritercode_writerollama:llama3.2file_write, file_read, shellWrite clean, well-structured code from specifications
Reviewercode_reviewerollama:llama3.2file_read, searchExamine code for bugs, anti-patterns, performance issues
Testertest_engineerollama:llama3.2file_write, file_read, shellWrite comprehensive tests, aim for edge cases
Debuggerdebuggerollama:llama3.2file_read, shell, searchDiagnose failures, trace execution, isolate root causes
Architectarchitectollama:llama3.2file_read, searchDesign high-level architecture, define module boundaries
Documentertechnical_writerollama:llama3.2file_write, file_readProduce README files, API docs, architecture guides
SecurityAuditorsecurity_auditorollama:llama3.2file_read, shell, searchAudit code for vulnerabilities, dependency CVEs
Refactorerrefactorerollama:llama3.2file_write, file_read, shellImprove code structure without changing behavior
ContentWritercontent_writerollama:llama3.2file_write, web_searchWrite blog posts, tweets, documentation, marketing copy
Researcherresearcherollama:llama3.2web_search, file_writeInvestigate topics, summarize findings, produce reports

Skills

NameDescriptionTemplate
Daily StandupSummarize progress and plan for the dayBased on recent activity, generate a standup summary: what was accomplished yesterday, what is planned for today, any blockers

Rules

NameContentEnabled
Forge safety – budgetMission and review flows use ForgeEngine safety: enforce aggregate spend against the configured cost limit before starting LLM work. If the budget would be exceeded, stop and surface a clear error.Yes
Forge safety – concurrency and circuit breakerMission LLM runs acquire a concurrency slot and respect the circuit breaker. After repeated failures the circuit opens; no further mission agent runs until reset. When the circuit opens, the engine emits forge.safety.circuit_open.Yes

Jobs

No jobs are declared in the Forge manifest. Mission and review work is executed synchronously via run_agent_with_mission_safety.

Events

Produced

Event KindWhen Emitted
forge.agent.createdA new agent run is created
forge.agent.startedAn agent run begins executing
forge.agent.completedAn agent run finishes successfully
forge.agent.failedAn agent run fails
forge.mission.plan_generatedmission_today() completes and a DailyPlan is built
forge.mission.goal_createdset_goal() persists a new goal
forge.mission.goal_updatedA goal’s status or progress is updated
forge.mission.review_completedreview() completes and a Review is built
forge.brief.generatedgenerate_brief() persists an executive brief
forge.persona.hiredA persona is hired into a session
forge.safety.budget_warningAggregate spend exceeds 80% of the configured cost limit
forge.safety.circuit_openCircuit breaker opens after repeated mission agent failures
forge.pipeline.startedA forge pipeline orchestration begins
forge.pipeline.step_startedA pipeline step begins executing
forge.pipeline.step_completedA pipeline step completes
forge.pipeline.completedA full pipeline orchestration completes
forge.pipeline.failedA pipeline orchestration fails

Consumed

The Forge department does not consume events from other departments. It is triggered by direct API/CLI calls and webhook-dispatched forge.pipeline.requested events (processed by the job worker in rusvel-app).

API Routes

MethodPathDescription
GET/api/sessions/{id}/mission/todayGenerate today’s prioritized mission plan for the session
GET/api/sessions/{id}/mission/goalsList goals for the session
POST/api/sessions/{id}/mission/goalsCreate a new goal for the session
GET/api/sessions/{id}/eventsQuery events for the session (includes forge mission and safety events)
GET/api/brief/latestReturn the most recently persisted executive brief for a session

CLI Commands

rusvel forge mission today       # Generate today's daily plan
rusvel forge mission goals       # List goals
rusvel forge mission goal <title> # Create a new goal
rusvel forge mission review      # Generate a periodic review (default: week)

Entity Auto-Discovery

Agents, skills, rules, hooks, and MCP servers scoped to the Forge department are stored in the object store with metadata.engine = "forge". The shared CRUD API routes (/api/dept/{id}/agents, /api/dept/{id}/skills, etc.) filter by this metadata key, so each department sees only its own entities.

Chat Flow

sequenceDiagram
    participant User
    participant API as rusvel-api
    participant Chat as Chat Handler
    participant Config as ConfigPort
    participant Skills as Skill Resolver
    participant Rules as Rule Loader
    participant Agent as AgentRuntime
    participant Forge as ForgeEngine
    participant Events as EventPort
    participant Hooks as Hook Dispatcher

    User->>API: POST /api/dept/forge/chat {message}
    API->>Chat: route to department chat handler
    Chat->>Config: load department config (model, effort, permissions)
    Chat->>Skills: resolve_skill() -- check for {{input}} interpolation
    Chat->>Chat: check agent override (custom agent for forge)
    Chat->>Rules: load_rules_for_engine("forge") -- append to system prompt
    Note over Chat: Inject capabilities: planning, orchestration
    Note over Chat: System prompt: "You are the Forge department..."
    Chat->>Agent: AgentRuntime::run_streaming(config, tools, prompt)
    Agent->>Forge: tool calls (forge.mission.today, forge.persona.hire, etc.)
    Forge->>Events: emit forge.* events
    Agent-->>Chat: SSE stream (AgentEvent chunks)
    Chat-->>User: SSE response
    Chat->>Events: emit chat completion event
    Chat->>Hooks: tokio::spawn hook dispatch

Extending This Department

1. Add a new tool

Register the tool in crates/dept-forge/src/lib.rs inside the register() method using ctx.tools.add("forge", "forge.new_tool", ...). Add a matching ToolContribution entry in forge_engine::mission::mission_tool_contributions_for_manifest() so the manifest stays consistent.

2. Add a new event kind

Add a new pub const in crates/forge-engine/src/events.rs. Emit it from the engine method using self.events.emit(...). Add the event kind string to the events_produced vec in crates/dept-forge/src/manifest.rs.

3. Add a new persona

Add a new profile(...) call in crates/forge-engine/src/personas.rs inside default_personas(). The persona will automatically appear in the manifest via persona_contributions_for_manifest().

4. Add a new skill

Add a SkillContribution entry in the skills vec in crates/dept-forge/src/manifest.rs. Use {{variable}} placeholders in the prompt_template field for runtime interpolation.

5. Add a new API route

Add a RouteContribution entry in forge_engine::mission::forge_route_contributions_for_manifest(). Implement the handler in crates/rusvel-api/src/engine_routes.rs (or a new handler module) and wire the route in crates/rusvel-api/src/lib.rs.

Port Dependencies

PortRequiredPurpose
AgentPortYesLLM agent execution for mission planning, reviews, briefs
EventPortYesEmit forge.* domain events
MemoryPortYesFTS5 session-namespaced memory search
StoragePortYesPersist goals, tasks, plans, reviews, briefs via ObjectStore
JobPortYesEnqueue background work (pipeline orchestration)
SessionPortYesCreate and load sessions for mission context
ConfigPortYesRead department configuration (budget ceiling, etc.)

Safety Guard System

The Forge engine includes a SafetyGuard that wraps all LLM calls made through run_agent_with_mission_safety():

Budget Enforcement

  • Every mission/review LLM call estimates cost via config.budget_limit (default: $0.10 per call).
  • Aggregate spend is tracked across the session lifetime.
  • When spend crosses 80% of the configured ceiling, the engine emits forge.safety.budget_warning.
  • When spend would exceed the ceiling, the call is rejected with a clear error before any LLM work begins.

Concurrency Slots

  • A semaphore limits concurrent mission agent runs (prevents parallel LLM calls from overwhelming providers).
  • Each call acquires a slot; the slot is released when the call completes or fails.

Circuit Breaker

  • Consecutive failures are tracked. After a threshold of repeated failures, the circuit opens.
  • When open, all subsequent mission agent calls are rejected immediately without contacting the LLM.
  • The engine emits forge.safety.circuit_open when the circuit opens.
  • Successful calls reset the failure counter and close the circuit.

Executive Brief System

The generate_brief() method produces a cross-department daily digest:

  1. Department scans: For each of the 14 department apps, the engine selects an appropriate persona (e.g., CodeWriter for code, Researcher for harvest/finance/legal) and runs a brief-section LLM call asking for status (green/yellow/red), highlights, and metrics.
  2. Strategist synthesis: An Architect persona processes all department sections and produces a 2-3 sentence executive summary plus 3-5 cross-cutting action items.
  3. Persistence: The brief is saved to ObjectStore as executive_brief kind and a forge.brief.generated event is emitted.
  4. Retrieval: latest_brief() returns the most recent brief, sorted by created_at.

Department-to-Persona Mapping

Department(s)Persona Used
codeCodeWriter
content, growth, distro, gtmContentWriter
harvest, finance, legalResearcher
supportDocumenter
forge, infra, productArchitect

Pipeline Orchestration

The Forge engine supports multi-step pipeline orchestration triggered by webhook events:

  • Webhook registration with event_kind = "forge.pipeline.requested" enqueues JobKind::Custom("forge.pipeline").
  • The job worker in rusvel-app calls ForgeEngine::orchestrate_pipeline().
  • Pipeline steps emit forge.pipeline.step_started and forge.pipeline.step_completed events.
  • The full pipeline emits forge.pipeline.started and forge.pipeline.completed (or forge.pipeline.failed).

Object Store Kinds

KindSchemaUsed By
goalGoal { id, session_id, title, description, timeframe, status, progress, metadata }set_goal(), list_goals()
taskTask { id, session_id, goal_id, title, status, due_at, priority, metadata }mission_today()
executive_briefExecutiveBriefStored { session_id, brief: ExecutiveBrief }generate_brief(), latest_brief()
flow_executionPipeline orchestration statePipeline module

UI Integration

The manifest declares a dashboard card and 7 tabs:

  • Dashboard card: “Mission Status” (large) – Active goals, today’s plan, recent reviews
  • Tabs: actions, engine, agents, workflows, skills, rules, events

Testing

cargo test -p forge-engine    # 15 tests (all use mock ports)

Key test scenarios:

  • Engine metadata (kind, name, capabilities)
  • Engine lifecycle (initialize, health, shutdown)
  • Set and list goals (persistence + event emission)
  • Mission today generates plan (LLM mock returns JSON, plan parsed)
  • Default personas (10 built-in, lookup by name)
  • Hire persona creates AgentConfig (session binding, tool list)
  • Safety guard initialization (budget check, circuit check)

Code Department

Code intelligence, implementation, debugging, testing, refactoring

Overview

The Code Department provides AI-powered code intelligence for RUSVEL. It parses Rust source files, builds a symbol dependency graph, computes project metrics (file counts, symbol counts, largest function), and exposes BM25-ranked symbol search. It is the department a developer uses to understand a codebase before making changes, and it produces code.analyzed events that the Content department consumes to auto-generate technical blog posts from code snapshots.

Engine (code-engine)

  • Crate: crates/code-engine/src/lib.rs
  • Lines: 307 (lib.rs) + submodules (parser, graph, metrics, search, error)
  • Status: Wired (real business logic)

Public API

MethodSignatureDescription
newfn new(storage: Arc<dyn StoragePort>, event_port: Arc<dyn EventPort>) -> SelfConstruct with storage and event port
analyzeasync fn analyze(&self, repo_path: &Path) -> Result<CodeAnalysis>Parse directory, build symbol graph, compute metrics, build search index, persist to ObjectStore, emit code.analyzed event
searchfn search(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>>BM25-ranked search over previously indexed symbols; returns error if analyze() not yet called

Internal Structure

  • parser (parser.rs) – Rust source file parser that extracts Symbol structs (functions, structs, enums, traits, impls) with name, kind, file path, line number, and body text.
  • graph (graph.rs) – SymbolGraph built from parsed symbols representing call/dependency relationships.
  • metrics (metrics.rs) – count_lines() per file and compute_project_metrics() aggregate: total files, total symbols, largest function.
  • search (search.rs) – SearchIndex with BM25 ranking. Built from (name, file_path, body, line) tuples.
  • error (error.rs) – Engine-specific error types.

Data Types

#![allow(unused)]
fn main() {
pub struct CodeAnalysis {
    pub snapshot: CodeSnapshotRef,   // ID + repo path + analyzed_at timestamp
    pub symbols: Vec<Symbol>,        // All parsed symbols
    pub graph: SymbolGraph,          // Dependency graph
    pub metrics: ProjectMetrics,     // Aggregate metrics
}
}

CodeAnalysis::summary() produces a CodeAnalysisSummary (snapshot_id, repo_path, total_files, total_symbols, top 10 function names, largest function) that the Content engine uses for code-to-content generation.

Department Wrapper (dept-code)

  • Crate: crates/dept-code/src/lib.rs
  • Lines: 154
  • Manifest: crates/dept-code/src/manifest.rs

The wrapper creates a CodeEngine during registration with StoragePort and EventPort, then registers 2 agent tools. No event handlers or job handlers are registered.

Manifest Declaration

System Prompt

You are the Code department of RUSVEL.

You have full access to Claude Code tools:

  • Read, Write, Edit files across all project directories
  • Run shell commands (build, test, git, pnpm, cargo, etc.)
  • Search codebases with grep and glob
  • Fetch web content and search the web
  • Spawn sub-agents for parallel work
  • Manage background tasks

Focus: code intelligence, implementation, debugging, testing, refactoring. When writing code, follow existing patterns. Be thorough.

Capabilities

  • code_analysis
  • tool_use

Quick Actions

LabelPrompt
Analyze codebaseAnalyze the codebase structure, dependencies, and code quality.
Run testsRun cargo test and report results. If any fail, show the errors.
Find TODOsFind all TODO, FIXME, and HACK comments across the codebase.
Self-improveRead docs/status/current-state.md and docs/status/gap-analysis.md. Identify the highest-impact fix you can make right now. Implement it, run tests, and verify.
Fix build warningsRun cargo build and fix any warnings. Then run cargo test to verify nothing broke.

Registered Tools

Tool NameParametersDescription
code.analyzepath: string (required)Analyze a codebase directory for symbols, metrics, and dependencies
code.searchquery: string (required), limit: integer (default: 10)Search previously indexed code symbols

Personas

NameRoleDefault ModelAllowed ToolsPurpose
code-engineerSenior software engineer with code intelligencesonnetcode.analyze, code.search, file_read, file_write, shellFull-stack code work with engine tools

Skills

NameDescriptionTemplate
Code ReviewAnalyze code quality and suggest improvementsAnalyze the code at: {{path}}. Focus on: code quality, patterns, potential bugs, and improvements.

Rules

No rules are declared for the Code department.

Jobs

Job KindDescriptionRequires Approval
code.analyzeRun code analysis on a directoryNo

Events

Produced

Event KindWhen Emitted
code.analyzedanalyze() completes successfully. Payload includes snapshot_id, total_symbols, total_files.
code.searchedA symbol search is executed (declared in manifest).

Consumed

The Code department does not consume events from other departments.

API Routes

MethodPathDescription
POST/api/dept/code/analyzeAnalyze a codebase directory. Body: {"path": "..."}. Returns full CodeAnalysis JSON.
GET/api/dept/code/searchSearch indexed symbols. Query params: query, limit. Returns ranked search results.

CLI Commands

rusvel code analyze [path]   # Analyze a codebase (default: current directory)
rusvel code search <query>   # Search indexed symbols

Entity Auto-Discovery

Agents, skills, rules, hooks, and MCP servers scoped to the Code department are stored with metadata.engine = "code". The shared CRUD API routes filter by this key so each department sees only its own entities.

Chat Flow

sequenceDiagram
    participant User
    participant API as rusvel-api
    participant Chat as Chat Handler
    participant Config as ConfigPort
    participant Skills as Skill Resolver
    participant Rules as Rule Loader
    participant Agent as AgentRuntime
    participant Code as CodeEngine
    participant Events as EventPort
    participant Hooks as Hook Dispatcher

    User->>API: POST /api/dept/code/chat {message}
    API->>Chat: route to department chat handler
    Chat->>Config: load department config (model, effort, add_dirs)
    Chat->>Skills: resolve_skill() -- check for {{input}} interpolation
    Chat->>Chat: check agent override (custom agent for code)
    Chat->>Rules: load_rules_for_engine("code") -- append to system prompt
    Note over Chat: Inject capabilities: code_analysis, tool_use
    Note over Chat: System prompt: "You are the Code department..."
    Note over Chat: Default config: effort=high, add_dirs=["."]
    Chat->>Agent: AgentRuntime::run_streaming(config, tools, prompt)
    Agent->>Code: tool calls (code.analyze, code.search)
    Code->>Events: emit code.analyzed
    Agent-->>Chat: SSE stream (AgentEvent chunks)
    Chat-->>User: SSE response
    Chat->>Events: emit chat completion event
    Chat->>Hooks: tokio::spawn hook dispatch

Extending This Department

1. Add a new tool

Register the tool in crates/dept-code/src/lib.rs inside the register() method using ctx.tools.add("code", "code.new_tool", ...). Add a matching ToolContribution entry in crates/dept-code/src/manifest.rs in the tools vec.

2. Add a new event kind

Add a new pub const in the events module inside crates/code-engine/src/lib.rs. Emit it from the engine method. Add the event kind string to events_produced in crates/dept-code/src/manifest.rs.

3. Add a new persona

Add a PersonaContribution entry in the personas vec in crates/dept-code/src/manifest.rs.

4. Add a new skill

Add a SkillContribution entry in the skills vec in crates/dept-code/src/manifest.rs. Use {{variable}} placeholders for runtime interpolation.

5. Add a new API route

Add a RouteContribution entry in the routes vec in crates/dept-code/src/manifest.rs. Implement the handler in crates/rusvel-api/src/engine_routes.rs and wire the route in crates/rusvel-api/src/lib.rs.

Port Dependencies

PortRequiredPurpose
StoragePortYesPersist code analysis results via ObjectStore
EventPortYesEmit code.analyzed and code.searched events

Default Configuration

The Code department ships with a non-default LayeredConfig:

  • effort: "high" – maximizes analysis thoroughness
  • permission_mode: "default"
  • add_dirs: ["."] – adds the current directory to the agent’s working set

Object Store Kinds

KindSchemaUsed By
code_analysisCodeAnalysis { snapshot, symbols, graph, metrics }analyze() persists, Content department reads for code-to-content

Cross-Department Integration

The Code department is a producer in the code-to-content pipeline:

  1. Code -> Content: When analyze() completes, it emits code.analyzed with snapshot_id, total_symbols, and total_files in the payload. The Content department’s event handler receives this and calls draft_blog_from_code_snapshot() to generate a technical blog post.

  2. Code -> Forge: The Forge department can use the CodeWriter persona to analyze code during executive brief generation for the code department section.

This is achieved entirely through the event system – the Code engine never imports the Content engine.

Parser Details

The parser module handles Rust source file parsing:

  • Scans .rs files in the given directory (recursive)
  • Extracts symbols: functions (fn), structs, enums, traits, impl blocks
  • Each Symbol includes: name, SymbolKind, file_path, line number, body text
  • Used by SymbolGraph::build() to construct a dependency graph
  • Used by SearchIndex::build() to create BM25-searchable index

Symbol Kinds

Function, Struct, Enum, Trait, Impl

Search Index

The BM25 search index is built from tuples of (name, file_path, body, line):

  • Index: Built once per analyze() call, stored in-memory behind a Mutex
  • Query: search(query, limit) returns ranked SearchResult entries
  • Error: Returns RusvelError::Internal if called before analyze()
#![allow(unused)]
fn main() {
pub struct SearchResult {
    pub symbol_name: String,
    pub file_path: String,
    pub line: usize,
    pub score: f64,
}
}

Metrics

The metrics module computes:

  • Per-file: count_lines() returns FileMetrics (total lines, code lines, comment lines, blank lines)
  • Project-wide: compute_project_metrics() aggregates across all files:
#![allow(unused)]
fn main() {
pub struct ProjectMetrics {
    pub total_files: usize,
    pub total_symbols: usize,
    pub largest_function: Option<String>,
    // ... additional fields
}
}

UI Integration

The manifest declares a dashboard card and 10 tabs:

  • Dashboard card: “Code Intelligence” (medium) – Symbol index, metrics, and search
  • Tabs: actions, engine, agents, workflows, skills, rules, mcp, hooks, dirs, events
  • has_settings: true (supports department-level settings)

Testing

cargo test -p code-engine    # Tests in lib.rs

Key test scenarios:

  • Analyze a temp directory with Rust files, verify symbol count
  • Search after analysis, verify results
  • Health returns healthy (with and without index)
  • Event emission on analyze
cargo test -p dept-code      # Department wrapper tests

Key test scenarios:

  • Department creates with correct manifest ID
  • Manifest declares 2 routes, 2 tools, 2 events
  • Manifest requires StoragePort and EventPort
  • Manifest serializes to valid JSON

Content Department

Content creation, platform adaptation, publishing strategy

Overview

The Content Department handles the full content lifecycle for RUSVEL: AI-powered drafting, multi-platform adaptation (LinkedIn, Twitter/X, DEV.to, Substack), calendar scheduling, human-approved publishing, and engagement analytics. It consumes code.analyzed events from the Code department to automatically generate technical blog posts from code snapshots, creating a seamless code-to-content pipeline. Every piece of content must pass a human approval gate (ADR-008) before it can be published.

Engine (content-engine)

  • Crate: crates/content-engine/src/lib.rs
  • Lines: 424 (lib.rs) + submodules (writer, calendar, analytics, adapters, platform, code_bridge)
  • Status: Wired (real business logic)

Public API

MethodSignatureDescription
newfn new(storage, event_bus, agent, jobs) -> SelfConstruct with 4 port dependencies
register_platformfn register_platform(&self, adapter: Arc<dyn PlatformAdapter>)Register a platform adapter for publishing
draftasync fn draft(&self, session_id, topic, kind: ContentKind) -> Result<ContentItem>Draft new content via AI agent
adaptasync fn adapt(&self, session_id, content_id, platform: Platform) -> Result<ContentItem>Adapt existing content for a target platform
publishasync fn publish(&self, session_id, content_id, platform) -> Result<PublishResult>Publish approved content to a platform (requires Approved status)
scheduleasync fn schedule(&self, session_id, content_id, platform, at: DateTime) -> Result<()>Schedule content for future publication
schedule_draftasync fn schedule_draft(&self, session_id, draft_id, platform, publish_at) -> Result<()>Alias for schedule (matches product spec wording)
approve_contentasync fn approve_content(&self, content_id) -> Result<ContentItem>Mark content as human-approved (ADR-008 gate)
draft_blog_from_code_snapshotasync fn draft_blog_from_code_snapshot(&self, session_id, snapshot_id) -> Result<ContentItem>Draft a blog post from a stored code_analysis snapshot
execute_content_publish_jobasync fn execute_content_publish_job(&self, job: Job) -> Result<Value>Execute a queued ContentPublish job (payload: content_id, platform)
list_contentasync fn list_content(&self, session_id, status_filter: Option<ContentStatus>) -> Result<Vec<ContentItem>>List content items, optionally filtered by status
list_scheduledasync fn list_scheduled(&self, session_id) -> Result<Vec<ScheduledPost>>List all scheduled posts (content calendar)
list_scheduled_in_rangeasync fn list_scheduled_in_range(&self, session_id, from, to) -> Result<Vec<ScheduledPost>>List scheduled posts within a date range
get_metricsasync fn get_metrics(&self, content_id) -> Result<Vec<(Platform, PostMetrics)>>Get engagement metrics for a content item

Internal Structure

  • ContentWriter (writer.rs) – AI-powered drafting and adaptation. Uses AgentPort to generate content. build_code_prompt() builds prompts from CodeAnalysisSummary for code-to-content.
  • ContentCalendar (calendar.rs) – Scheduling engine. Uses StoragePort for persistence and JobPort for scheduling future publish jobs.
  • ContentAnalytics (analytics.rs) – Engagement metrics tracking per content item per platform.
  • PlatformAdapter trait (platform.rs) – Interface for platform-specific publishing. Returns PublishResult with URL and timestamp. Has max_length() for character-limited platforms.
  • Platform adapters (adapters/) – Real implementations for LinkedIn, Twitter, and DEV.to. Each reads API credentials from ConfigPort.
  • code_bridge (code_bridge.rs) – Converts stored code_analysis JSON to CodeAnalysisSummary for the writer.

Content Kinds

LongForm, Tweet, Thread, LinkedInPost, Blog, VideoScript, Email, Proposal

Content Statuses

Draft, Adapted, Approved, Published, Cancelled

Supported Platforms

Twitter, LinkedIn, DevTo, Medium, YouTube, Substack, Email, Custom(String)

Department Wrapper (dept-content)

  • Crate: crates/dept-content/src/lib.rs
  • Lines: 131
  • Manifest: crates/dept-content/src/manifest.rs

The wrapper creates a ContentEngine, registers 3 real platform adapters (LinkedIn, Twitter, DEV.to), registers 2 agent tools, wires an event handler for code.analyzed, and registers a job handler for content.publish.

Registration Details

- Platform adapters: LinkedInAdapter, TwitterAdapter, DevToAdapter (all from content_engine::adapters)
- Tools: content.draft, content.adapt (via tools::register_tools)
- Event handler: "code.analyzed" -> triggers code-to-content pipeline
- Job handler: "content.publish" -> executes content_engine.execute_content_publish_job

Manifest Declaration

System Prompt

You are the Content department of RUSVEL.

Focus: content creation, platform adaptation, publishing strategy. Draft in Markdown. Adapt for LinkedIn, Twitter/X, DEV.to, Substack.

Capabilities

  • content_creation

Quick Actions

LabelPrompt
Draft blog postDraft a blog post. Ask me for the topic, audience, and key points.
Adapt for TwitterAdapt the latest content piece into a Twitter/X thread.
Content calendarShow the content calendar for this week with scheduled and draft posts.

Registered Tools

Tool NameParametersDescription
content.draftsession_id: string (required), topic: string (required), kind: string (enum: LongForm, Tweet, Thread, LinkedInPost, Blog, VideoScript, Email, Proposal)Draft a blog post or article on a given topic
content.adaptsession_id: string (required), content_id: string (required), platform: string (enum: twitter, linkedin, devto, medium, youtube, substack, email)Adapt existing content for a specific platform

Personas

NameRoleDefault ModelAllowed ToolsPurpose
content-strategistContent strategist and writersonnetcontent.draft, content.adapt, web_searchContent planning and execution

Skills

NameDescriptionTemplate
Blog DraftDraft a blog post from topic and key pointsWrite a blog post about: {{topic}}. Key points: {{points}}. Audience: {{audience}}

Rules

NameContentEnabled
Human Approval GateAll content must be approved before publishing. Never auto-publish.Yes

Jobs

Job KindDescriptionRequires Approval
content.publishPublish approved content to target platformsYes

Events

Produced

Event KindWhen Emitted
content.drafteddraft() creates a new content item
content.adaptedadapt() creates a platform-adapted version
content.scheduledschedule() schedules content for future publication. Payload includes platform and publish_at.
content.publishedpublish() successfully publishes to a platform
content.reviewedContent is reviewed (feedback cycle)
content.cancelledContent is cancelled
content.metrics_recordedEngagement metrics are recorded for a content item

Consumed

Event KindSourceAction
code.analyzedCode departmentTriggers draft_blog_from_code_snapshot() to auto-generate a blog post from the code analysis

API Routes

MethodPathDescription
POST/api/dept/content/draftDraft content from a topic
POST/api/dept/content/from-codeGenerate content from a code analysis snapshot
PATCH/api/dept/content/{id}/approveApprove content for publishing (ADR-008 gate)
POST/api/dept/content/publishPublish approved content to a platform
GET/api/dept/content/listList all content items

CLI Commands

rusvel content draft <topic>    # Draft content from a topic
rusvel content from-code        # Generate content from code analysis

Entity Auto-Discovery

Agents, skills, rules, hooks, and MCP servers scoped to the Content department are stored with metadata.engine = "content". The shared CRUD API routes filter by this key so each department sees only its own entities.

Chat Flow

sequenceDiagram
    participant User
    participant API as rusvel-api
    participant Chat as Chat Handler
    participant Config as ConfigPort
    participant Skills as Skill Resolver
    participant Rules as Rule Loader
    participant Agent as AgentRuntime
    participant Content as ContentEngine
    participant Events as EventPort
    participant Hooks as Hook Dispatcher

    User->>API: POST /api/dept/content/chat {message}
    API->>Chat: route to department chat handler
    Chat->>Config: load department config (model, platform tokens)
    Chat->>Skills: resolve_skill() -- check for {{input}} interpolation
    Chat->>Chat: check agent override (custom agent for content)
    Chat->>Rules: load_rules_for_engine("content") -- append "Human Approval Gate" rule
    Note over Chat: Inject capabilities: content_creation
    Note over Chat: System prompt: "You are the Content department..."
    Chat->>Agent: AgentRuntime::run_streaming(config, tools, prompt)
    Agent->>Content: tool calls (content.draft, content.adapt)
    Content->>Events: emit content.drafted / content.adapted
    Agent-->>Chat: SSE stream (AgentEvent chunks)
    Chat-->>User: SSE response
    Chat->>Events: emit chat completion event
    Chat->>Hooks: tokio::spawn hook dispatch

Code-to-Content Pipeline

sequenceDiagram
    participant Code as CodeEngine
    participant Events as EventPort
    participant ContentHandler as on_code_analyzed handler
    participant Content as ContentEngine

    Code->>Events: emit code.analyzed {snapshot_id}
    Events->>ContentHandler: dispatch event
    ContentHandler->>Content: draft_blog_from_code_snapshot(session_id, snapshot_id)
    Content->>Content: load code_analysis from ObjectStore
    Content->>Content: build CodeAnalysisSummary
    Content->>Content: draft(session_id, code_prompt, Blog)
    Content->>Events: emit content.drafted

Extending This Department

1. Add a new tool

Register the tool in crates/dept-content/src/tools.rs inside register_tools(). Add a matching ToolContribution entry in crates/dept-content/src/manifest.rs.

2. Add a new event kind

Add a new pub const in the events module inside crates/content-engine/src/lib.rs. Emit it from the engine method. Add the event kind string to events_produced in crates/dept-content/src/manifest.rs.

3. Add a new persona

Add a PersonaContribution entry in the personas vec in crates/dept-content/src/manifest.rs.

4. Add a new skill

Add a SkillContribution entry in the skills vec in crates/dept-content/src/manifest.rs.

5. Add a new API route

Add a RouteContribution entry in the routes vec in crates/dept-content/src/manifest.rs. Implement the handler in crates/rusvel-api/src/engine_routes.rs and wire the route in crates/rusvel-api/src/lib.rs.

6. Add a new platform adapter

Implement the PlatformAdapter trait in a new file under crates/content-engine/src/adapters/. Register it in crates/dept-content/src/lib.rs inside register() with engine.register_platform(Arc::new(NewAdapter::new(ctx.config.clone()))).

Port Dependencies

PortRequiredPurpose
AgentPortYesAI-powered content drafting and adaptation via ContentWriter
EventPortYesEmit content.* events and receive code.analyzed
StoragePortYesPersist content items, scheduled posts, analytics via ObjectStore
JobPortYesEnqueue and schedule content.publish jobs
ConfigPortNo (optional)Platform API credentials (devto_api_key, twitter_bearer_token, linkedin_bearer_token)

Object Store Kinds

KindSchemaUsed By
contentContentItem { id, session_id, title, body_markdown, status, approval, platform_targets, published_at, metadata }draft(), adapt(), publish(), approve_content(), list_content()
scheduled_postScheduledPost { content_id, platform, publish_at, session_id }schedule(), list_scheduled()
content_metricsPlatform engagement metrics per content itemget_metrics()

Platform Adapter Details

PlatformAdapter Trait

#![allow(unused)]
fn main() {
pub trait PlatformAdapter: Send + Sync + Debug {
    fn platform(&self) -> Platform;
    fn max_length(&self) -> Option<usize>;
    async fn publish(&self, item: &ContentItem) -> Result<PublishResult>;
}
}

Registered Adapters

PlatformAdapterConfig KeysMax Length
LinkedInLinkedInAdapterlinkedin_bearer_tokenNone
Twitter/XTwitterAdaptertwitter_bearer_token280 chars
DEV.toDevToAdapterdevto_api_keyNone

PublishResult

#![allow(unused)]
fn main() {
pub struct PublishResult {
    pub url: Option<String>,
    pub published_at: DateTime<Utc>,
    pub metadata: Value,
}
}

Content Approval Flow (ADR-008)

Content must be explicitly approved before it can be published:

stateDiagram-v2
    [*] --> Draft: draft()
    Draft --> Adapted: adapt()
    Draft --> Approved: approve_content()
    Adapted --> Approved: approve_content()
    Approved --> Published: publish()
    Draft --> Cancelled: cancel
    Adapted --> Cancelled: cancel

Attempting to call publish() on content that is not Approved or AutoApproved returns RusvelError::Validation.

Content Calendar

The ContentCalendar manages future publication scheduling:

  • schedule() creates a ScheduledPost and enqueues a content.publish job with a scheduled time
  • list_scheduled() returns all scheduled posts for a session
  • list_scheduled_in_range() filters by date range (inclusive)
  • Jobs are processed by the job worker, which calls execute_content_publish_job()

ContentWriter AI Integration

The ContentWriter uses AgentPort for two operations:

  1. Draft: Given a topic and content kind, generates markdown content via an AI agent
  2. Adapt: Given existing content and a target platform, rewrites to fit the platform’s format and length constraints

For code-to-content, build_code_prompt() constructs a rich prompt from CodeAnalysisSummary including repository path, symbol counts, top functions, and the largest function name.

UI Integration

The manifest declares a dashboard card and 6 tabs:

  • Dashboard card: “Content Pipeline” (medium) – Drafts, scheduled, and published content
  • Tabs: actions, engine, agents, skills, rules, events
  • has_settings: true (supports platform credential configuration)

Configuration Schema

{
  "devto_api_key": "string",
  "twitter_bearer_token": "string",
  "linkedin_bearer_token": "string",
  "default_format": "markdown | html"
}

Testing

cargo test -p content-engine    # 7 tests

Key test scenarios:

  • Draft creates content item and emits event
  • Adapt creates platform-adapted version
  • Publish requires approval status
  • Calendar scheduling
  • Code-to-content pipeline integration
cargo test -p dept-content      # Department wrapper tests

Key test scenarios:

  • Department creates with correct manifest ID
  • Manifest declares 5 routes, 2 tools, 7 events
  • Manifest requires AgentPort (non-optional)
  • Manifest serializes to valid JSON

Harvest Department

Opportunity discovery, scoring, proposal generation, pipeline management

Overview

The Harvest Department is RUSVEL’s freelance opportunity engine. It scans sources (Upwork, LinkedIn, GitHub, RSS feeds), scores discovered opportunities using keyword matching or LLM-based evaluation, generates tailored proposals via an AI agent, and manages a Kanban pipeline (Cold -> Warm -> Hot -> Won/Lost). It also records deal outcomes (won/lost/withdrawn) that feed back into the scoring model, and supports optional RAG via embedding and vector store for similarity-based scoring hints. Browser-captured data from CDP sessions (Upwork job listings, client profiles) is normalized into opportunities and CRM contacts.

Engine (harvest-engine)

  • Crate: crates/harvest-engine/src/lib.rs
  • Lines: 1013 (lib.rs) + submodules (source, scorer, proposal, pipeline, events, outcomes, cdp_source)
  • Status: Wired (real business logic)

Public API

MethodSignatureDescription
newfn new(storage: Arc<dyn StoragePort>) -> SelfConstruct with storage (minimal; other ports added via builder)
with_browserfn with_browser(self, b: Arc<dyn BrowserPort>) -> SelfAttach browser port for CDP capture
with_eventsfn with_events(self, events: Arc<dyn EventPort>) -> SelfAttach event port
with_agentfn with_agent(self, agent: Arc<dyn AgentPort>) -> SelfAttach agent port for LLM scoring/proposals
with_configfn with_config(self, config: HarvestConfig) -> SelfSet skills and min_budget filter
configure_ragfn configure_rag(&self, embedding, vector_store)Wire embedding + vector store for outcome-based scoring hints
harvest_skillsfn harvest_skills(&self) -> &[String]Skills used for scoring and RSS query expansion
scanasync fn scan(&self, session_id, source: &dyn HarvestSource) -> Result<Vec<Opportunity>>Scan a source, score results, persist, return opportunities
score_opportunityasync fn score_opportunity(&self, session_id, opportunity_id) -> Result<OpportunityScoreUpdate>Re-score an existing opportunity (keyword or LLM path)
generate_proposalasync fn generate_proposal(&self, session_id, opportunity_id, profile) -> Result<Proposal>Generate and persist a proposal for a stored opportunity
get_proposalsasync fn get_proposals(&self, session_id) -> Result<Vec<Proposal>>List persisted proposals for a session
pipelineasync fn pipeline(&self, session_id) -> Result<PipelineStats>Get pipeline statistics (total, by_stage counts)
list_opportunitiesasync fn list_opportunities(&self, session_id, stage: Option<&OpportunityStage>) -> Result<Vec<Opportunity>>List opportunities, optionally filtered by stage
advance_opportunityasync fn advance_opportunity(&self, opportunity_id, new_stage) -> Result<()>Move an opportunity to a new pipeline stage
record_opportunity_outcomeasync fn record_opportunity_outcome(&self, session_id, opportunity_id, result, notes) -> Result<HarvestOutcomeRecord>Record won/lost/withdrawn, feed into scorer hints, index in vector store
list_harvest_outcomesasync fn list_harvest_outcomes(&self, session_id, limit) -> Result<Vec<HarvestOutcomeRecord>>List recorded outcomes (newest first)
on_data_capturedasync fn on_data_captured(&self, session_id, event: BrowserEvent) -> Result<()>Normalize CDP-captured browser data into opportunities/contacts

Internal Structure

  • HarvestSource trait (source.rs) – Interface for scanning opportunity sources. Returns Vec<RawOpportunity>. Includes MockSource for testing.
  • CdpSource (cdp_source.rs) – CDP/browser-based source for Upwork. Includes default JavaScript extraction script.
  • OpportunityScorer (scorer.rs) – Scores raw opportunities. Two methods: ScoringMethod::Keyword (fast, pattern-based) and ScoringMethod::Llm (uses AgentPort for AI evaluation). Accepts outcome hints for calibration.
  • ProposalGenerator (proposal.rs) – Generates proposals using AgentPort. Produces Proposal with body, estimated value, tone, and metadata.
  • Pipeline (pipeline.rs) – Kanban pipeline management via ObjectStore. add(), list(), advance(), stats().
  • outcomes (outcomes.rs) – Deal outcome recording and retrieval. record_outcome(), list_outcomes(), recent_outcome_prompt_lines() for LLM hints.
  • events (events.rs) – Event kind constants.

Data Types

#![allow(unused)]
fn main() {
pub struct HarvestConfig {
    pub skills: Vec<String>,      // Skills to match (default: ["rust"])
    pub min_budget: Option<f64>,  // Minimum budget filter
}

pub struct OpportunityScoreUpdate {
    pub score: f64,
    pub reasoning: String,
}

pub enum HarvestDealOutcome { Won, Lost, Withdrawn }

pub struct HarvestOutcomeRecord {
    pub id: String,
    pub session_id: SessionId,
    pub opportunity_snapshot: Value,
    pub result: HarvestDealOutcome,
    pub notes: String,
}
}

RAG Integration

When configure_rag() is called with both an EmbeddingPort and VectorStorePort:

  1. Scoring hints: Before scoring, the engine embeds the opportunity title+description, searches the vector store for similar past outcomes, and includes the top 5 matches as prompt hints.
  2. Outcome indexing: When record_opportunity_outcome() is called, the outcome is embedded and upserted into the vector store with kind: "harvest_outcome" metadata.

Department Wrapper (dept-harvest)

  • Crate: crates/dept-harvest/src/lib.rs
  • Lines: 99
  • Manifest: crates/dept-harvest/src/manifest.rs

The wrapper creates a HarvestEngine with builder pattern (with_events, with_agent), calls configure_rag() to wire optional embedding and vector store from the registration context, and persists the engine reference.

Manifest Declaration

System Prompt

You are the Harvest department of RUSVEL.

Focus: finding opportunities, scoring gigs, drafting proposals. Sources: Upwork, LinkedIn, GitHub.

Capabilities

  • opportunity_discovery

Quick Actions

LabelPrompt
Scan opportunitiesScan for new freelance opportunities on Upwork, LinkedIn, and GitHub.
Score pipelineScore all opportunities in the pipeline by fit, budget, and probability.
Draft proposalDraft a proposal for an opportunity. Ask me for the gig details.

Registered Tools

Tool NameParametersDescription
harvest.scansession_id: string (required), source: string (optional)Scan sources for freelance opportunities
harvest.scoresession_id: string (required), opportunity_id: string (required)Re-score an opportunity
harvest.proposalsession_id: string (required), opportunity_id: string (required), profile: string (optional)Generate a proposal for an opportunity

Personas

NameRoleDefault ModelAllowed ToolsPurpose
opportunity-hunterFreelance opportunity scout and proposal writersonnetharvest.scan, harvest.score, harvest.proposal, web_searchDiscovery and proposal generation

Skills

NameDescriptionTemplate
Proposal DraftDraft a winning proposal for a freelance opportunityDraft a proposal for this opportunity: Title: {{title}}. Description: {{description}}. Highlight relevant skills and past experience.

Rules

NameContentEnabled
Human Approval GateAll proposals must be reviewed before submission. Never auto-submit.Yes

Jobs

Job KindDescriptionRequires Approval
harvest.scanScan opportunity sourcesNo

Events

Produced

Event KindWhen Emitted
harvest.scan.startedscan() begins scanning a source. Payload: {source: name}.
harvest.scan.completedscan() finishes. Payload: {count: N}.
harvest.opportunity.discoveredEach opportunity found during scan. Payload: {id, title}.
harvest.opportunity.scoredscore_opportunity() completes re-scoring. Payload: {id, score, reasoning}.
harvest.proposal.generatedgenerate_proposal() produces a proposal. Payload: {opportunity_id}.
harvest.proposal.persistedProposal is saved to ObjectStore. Payload: {key, opportunity_id}.
harvest.pipeline.advancedadvance_opportunity() moves an opportunity to a new stage.
harvest.outcome.recordedrecord_opportunity_outcome() records a deal result. Payload: {outcome_id, opportunity_id, result}.
harvest.contact.capturedon_data_captured() ingests a client profile from browser. Payload: {contact_id, name}.

Consumed

The Harvest department does not consume events from other departments. It is triggered by direct API/CLI calls and browser capture events.

API Routes

MethodPathDescription
POST/api/dept/harvest/scanScan sources for new opportunities
POST/api/dept/harvest/scoreRe-score an existing opportunity
POST/api/dept/harvest/proposalGenerate a proposal for an opportunity
GET/api/dept/harvest/pipelineGet pipeline statistics (total, by_stage)
GET/api/dept/harvest/listList opportunities (optional stage filter)

CLI Commands

rusvel harvest pipeline    # Show pipeline statistics
rusvel harvest status      # Department status
rusvel harvest list        # List items
rusvel harvest events      # Show recent events

Entity Auto-Discovery

Agents, skills, rules, hooks, and MCP servers scoped to the Harvest department are stored with metadata.engine = "harvest". The shared CRUD API routes filter by this key so each department sees only its own entities.

Chat Flow

sequenceDiagram
    participant User
    participant API as rusvel-api
    participant Chat as Chat Handler
    participant Config as ConfigPort
    participant Skills as Skill Resolver
    participant Rules as Rule Loader
    participant Agent as AgentRuntime
    participant Harvest as HarvestEngine
    participant Events as EventPort
    participant VectorStore as VectorStorePort
    participant Hooks as Hook Dispatcher

    User->>API: POST /api/dept/harvest/chat {message}
    API->>Chat: route to department chat handler
    Chat->>Config: load department config (skills, min_budget)
    Chat->>Skills: resolve_skill() -- check for {{input}} interpolation
    Chat->>Chat: check agent override (custom agent for harvest)
    Chat->>Rules: load_rules_for_engine("harvest") -- append "Human Approval Gate" rule
    Note over Chat: Inject capabilities: opportunity_discovery
    Note over Chat: System prompt: "You are the Harvest department..."
    Chat->>Agent: AgentRuntime::run_streaming(config, tools, prompt)
    Agent->>Harvest: tool calls (harvest.scan, harvest.score, harvest.proposal)
    Harvest->>VectorStore: similarity search for outcome hints (if RAG configured)
    Harvest->>Events: emit harvest.* events
    Agent-->>Chat: SSE stream (AgentEvent chunks)
    Chat-->>User: SSE response
    Chat->>Events: emit chat completion event
    Chat->>Hooks: tokio::spawn hook dispatch

Extending This Department

1. Add a new tool

Register the tool in crates/dept-harvest/src/lib.rs inside the register() method using ctx.tools.add("harvest", "harvest.new_tool", ...). Add a matching ToolContribution entry in crates/dept-harvest/src/manifest.rs.

2. Add a new event kind

Add a new pub const in crates/harvest-engine/src/events.rs. Emit it from the engine method. Add the event kind string to events_produced in crates/dept-harvest/src/manifest.rs.

3. Add a new persona

Add a PersonaContribution entry in the personas vec in crates/dept-harvest/src/manifest.rs.

4. Add a new skill

Add a SkillContribution entry in the skills vec in crates/dept-harvest/src/manifest.rs.

5. Add a new API route

Add a RouteContribution entry in the routes vec in crates/dept-harvest/src/manifest.rs. Implement the handler in crates/rusvel-api/src/engine_routes.rs and wire the route in crates/rusvel-api/src/lib.rs.

6. Add a new harvest source

Implement the HarvestSource trait in a new file under crates/harvest-engine/src/. Return Vec<RawOpportunity> from scan(). The engine’s scan() method will automatically score and persist results.

Port Dependencies

PortRequiredPurpose
StoragePortYesPersist opportunities, proposals, outcomes, contacts via ObjectStore
EventPortYesEmit harvest.* domain events
AgentPortYesLLM-based scoring and proposal generation
ConfigPortNo (optional)Skills list, min_budget, source configuration
BrowserPortNo (optional)CDP-captured browser data ingestion (Upwork)
EmbeddingPortNo (optional)Text embedding for RAG-based outcome hints
VectorStorePortNo (optional)Vector search for similar past outcomes

Configuration

{
  "skills": ["rust", "axum", "tokio"],
  "min_budget": 1000.0
}
  • skills – Skills to match when scoring opportunities and expanding RSS queries. Default: ["rust"].
  • min_budget – Minimum budget filter. Opportunities below this are scored lower.

Object Store Kinds

KindSchemaUsed By
opportunityOpportunity { id, session_id, source, title, url, description, score, stage, value_estimate, metadata }scan(), score_opportunity(), list_opportunities(), advance_opportunity()
proposalStoredProposalRecord { session_id, opportunity_id, proposal }generate_proposal(), get_proposals()
harvest_outcomeHarvestOutcomeRecord { id, session_id, opportunity_snapshot, result, notes }record_opportunity_outcome(), list_harvest_outcomes()
contactContact { id, session_id, name, emails, links, company, ... }on_data_captured() (Upwork client profiles)

Scoring System

The OpportunityScorer supports two scoring methods:

Keyword Scoring (ScoringMethod::Keyword)

Fast, pattern-based scoring used when no AgentPort is configured:

  • Matches opportunity title and description against configured skills
  • Applies budget multiplier based on min_budget threshold
  • Returns a normalized score (0.0 to 1.0)

LLM Scoring (ScoringMethod::Llm)

AI-powered scoring used when AgentPort is available:

  • Sends opportunity details plus configured skills to the agent
  • Includes outcome hints (recent outcomes + vector similarity matches) for calibration
  • Agent returns a score and reasoning

Outcome-Based Calibration

When RAG is configured, scoring includes:

  1. Recent outcomes: The last 12 HarvestOutcomeRecord entries for the session, formatted as prompt lines
  2. Vector similarity: The opportunity title+description is embedded and searched against indexed outcomes; top 5 matches with similarity scores are included

This creates a feedback loop: won/lost outcomes improve future scoring accuracy.

Pipeline Stages

Opportunities flow through a Kanban pipeline:

Cold -> Warm -> Hot -> Won
                   -> Lost
                   -> Withdrawn
  • Cold: Newly discovered, not yet evaluated
  • Warm: Scored positively, under consideration
  • Hot: Actively pursuing, proposal sent
  • Won: Deal closed successfully
  • Lost: Opportunity not won
  • Withdrawn: Withdrawn from consideration

advance_opportunity() moves an opportunity to any valid stage and emits harvest.pipeline.advanced.

Browser Data Capture (CDP)

The on_data_captured() method handles BrowserEvent::DataCaptured payloads from CDP sessions:

Upwork Job Listings (kind: "job_listing")

  • Accepts single job or {jobs: [...]} array
  • Extracts: title, description, url, budget, skills, posted_at
  • Runs through the scorer
  • Creates Opportunity in Cold stage
  • Emits harvest.opportunity.discovered

Upwork Client Profiles (kind: "client_profile")

  • Extracts: name, profile_url, company
  • Creates Contact in ObjectStore
  • Emits harvest.contact.captured

CDP Extract JavaScript

DEFAULT_CDP_EXTRACT_JS provides a default JavaScript snippet for extracting job listing cards from Upwork pages via Chrome DevTools Protocol.

UI Integration

The manifest declares a dashboard card and 6 tabs:

  • Dashboard card: “Opportunity Pipeline” (medium) – Cold, warm, and hot opportunities
  • Tabs: actions, engine, agents, skills, rules, events
  • has_settings: true (supports skills and budget configuration)

Testing

cargo test -p harvest-engine    # 12 tests

Key test scenarios:

  • Scan with mock source returns 3 opportunities
  • Pipeline stores and lists correctly
  • Pipeline stats match (all Cold after scan)
  • Health returns healthy
  • Proposals respect session filter
  • Outcome recording and retrieval
cargo test -p dept-harvest      # Department wrapper tests

Key test scenarios:

  • Department creates with correct manifest ID
  • Manifest declares 5 routes, 3 tools, 3 events
  • Manifest requires StoragePort, EventPort, AgentPort (non-optional) + ConfigPort (optional)
  • Manifest serializes to valid JSON

GoToMarket Department

CRM, outreach sequences, deal management, invoicing

Overview

The GoToMarket (GTM) Department handles the full sales pipeline for RUSVEL: contact management (CRM), multi-step outreach sequences with human approval gates, deal tracking through pipeline stages (Lead -> Qualified -> Proposal -> Negotiation -> Won/Lost), and invoicing with line items and payment tracking. Outreach sequences draft emails via the AI agent, hold them for human approval (ADR-008), then send via SMTP and auto-schedule the next step in the sequence. This is the department a solo builder uses to manage client relationships from first contact through paid invoice.

Engine (gtm-engine)

  • Crate: crates/gtm-engine/src/lib.rs
  • Lines: 710 (lib.rs) + submodules (crm, outreach, invoice, email)
  • Status: Wired (real business logic)

Public API

MethodSignatureDescription
newfn new(storage, events, agent, jobs) -> SelfConstruct with 4 port dependencies; creates CRM, Outreach, and Invoice managers
crmfn crm(&self) -> &CrmManagerAccess the CRM sub-manager
outreachfn outreach(&self) -> &OutreachManagerAccess the outreach sub-manager
invoicesfn invoices(&self) -> &InvoiceManagerAccess the invoice sub-manager
emit_eventasync fn emit_event(&self, kind, payload) -> Result<EventId>Emit a domain event on the event bus
process_outreach_send_jobasync fn process_outreach_send_job(&self, job, email: &dyn EmailAdapter) -> Result<OutreachSendDispatch>Process one OutreachSend job (draft -> approval -> SMTP send -> schedule next step)

CrmManager API

MethodSignatureDescription
add_contactasync fn add_contact(&self, session_id, contact: Contact) -> Result<ContactId>Add a new contact to the CRM
get_contactasync fn get_contact(&self, id: &ContactId) -> Result<Contact>Retrieve a contact by ID
list_contactsasync fn list_contacts(&self, session_id: SessionId) -> Result<Vec<Contact>>List all contacts for a session
add_dealasync fn add_deal(&self, session_id, deal: Deal) -> Result<DealId>Add a new deal to the pipeline
list_dealsasync fn list_deals(&self, session_id, stage: Option<DealStage>) -> Result<Vec<Deal>>List deals, optionally filtered by stage
advance_dealasync fn advance_deal(&self, deal_id, new_stage: DealStage) -> Result<()>Move a deal to a new pipeline stage

OutreachManager API

MethodSignatureDescription
create_sequenceasync fn create_sequence(&self, session_id, name, steps: Vec<SequenceStep>) -> Result<SequenceId>Create a multi-step outreach sequence
list_sequencesasync fn list_sequences(&self, session_id) -> Result<Vec<OutreachSequence>>List all sequences for a session
activate_sequenceasync fn activate_sequence(&self, seq_id: &SequenceId) -> Result<()>Activate a sequence (must be Active to execute)
execute_sequenceasync fn execute_sequence(&self, session_id, seq_id, contact_id) -> Result<JobId>Enqueue the first step of a sequence as an OutreachSend job
process_outreach_send_jobasync fn process_outreach_send_job(&self, job, events, email) -> Result<OutreachSendDispatch>Execute one step: draft email, hold for approval or send + schedule next

InvoiceManager API

MethodSignatureDescription
create_invoiceasync fn create_invoice(&self, session_id, contact_id, items, due_date) -> Result<InvoiceId>Create an invoice with line items
mark_paidasync fn mark_paid(&self, invoice_id: &InvoiceId) -> Result<()>Mark an invoice as paid
total_revenueasync fn total_revenue(&self, session_id) -> Result<f64>Sum of all paid invoices for a session

Internal Structure

  • CrmManager (crm.rs) – Contact and deal CRUD. Deals have stages: Lead, Qualified, Proposal, Negotiation, Won, Lost.
  • OutreachManager (outreach.rs) – Multi-step sequence management. Steps have delay_days, channel, and template. Sequences must be activated before execution. Execution enqueues JobKind::OutreachSend jobs.
  • InvoiceManager (invoice.rs) – Invoice creation with LineItem (description, quantity, unit_price). Tracks status: Draft, Sent, Paid, Overdue.
  • EmailAdapter trait (email.rs) – Interface for sending emails. Implementations: SmtpEmailAdapter (uses RUSVEL_SMTP_* env vars) and MockEmailAdapter (for testing).
  • OutreachSendDispatch – Result enum from processing an outreach job: HoldForApproval(result) (first pass, draft held), Complete { result, next } (approved, sent, next step scheduled if any).

Data Types

#![allow(unused)]
fn main() {
pub struct Deal {
    pub id: DealId,
    pub session_id: SessionId,
    pub contact_id: ContactId,
    pub title: String,
    pub value: f64,
    pub stage: DealStage,   // Lead | Qualified | Proposal | Negotiation | Won | Lost
    pub notes: String,
    pub created_at: DateTime<Utc>,
    pub metadata: Value,
}

pub struct OutreachSequence {
    pub id: SequenceId,
    pub session_id: SessionId,
    pub name: String,
    pub steps: Vec<SequenceStep>,
    pub status: SequenceStatus,  // Draft | Active | Paused | Completed
    pub metadata: Value,
}

pub struct SequenceStep {
    pub delay_days: u32,
    pub channel: String,     // "email", "linkedin", etc.
    pub template: String,
}

pub struct Invoice {
    pub id: InvoiceId,
    pub session_id: SessionId,
    pub contact_id: ContactId,
    pub items: Vec<LineItem>,
    pub status: InvoiceStatus,  // Draft | Sent | Paid | Overdue
    pub total: f64,
    pub due_date: DateTime<Utc>,
    pub metadata: Value,
}
}

Department Wrapper (dept-gtm)

  • Crate: crates/dept-gtm/src/lib.rs
  • Lines: 95
  • Manifest: crates/dept-gtm/src/manifest.rs
  • Tools: crates/dept-gtm/src/tools.rs

The wrapper creates a GtmEngine with all 4 ports during registration and registers 5 agent tools via tools::register().

Manifest Declaration

System Prompt

You are the GoToMarket department of RUSVEL.

Focus: CRM, outreach sequences, deal management, invoicing.

Capabilities

  • outreach
  • crm
  • invoicing

Quick Actions

LabelPrompt
List contactsList all contacts in the CRM. Show name, company, status, and last interaction.
Draft outreachDraft a multi-step outreach sequence for a prospect.
Deal pipelineShow the current deal pipeline with stages, values, and next actions.
Generate invoiceGenerate an invoice. Ask me for client details and line items.

Registered Tools

Tool NameParametersDescription
gtm.crm.add_contactsession_id: string, name: string, email: string (required); company: string, metadata: object (optional)Add a new contact to the CRM
gtm.crm.list_contactssession_id: string (required)List all contacts in the CRM for a session
gtm.crm.add_dealsession_id: string, contact_id: string, title: string, value: number, stage: string (required)Add a new deal to the CRM pipeline
gtm.outreach.create_sequencesession_id: string, name: string, steps: array (required)Create a multi-step outreach sequence
gtm.invoices.create_invoicesession_id: string, client_name: string, line_items: array, due_date: string (required)Create an invoice with line items

Personas

No personas are declared in the GTM manifest. The department uses the default agent configuration.

Skills

No skills are declared in the GTM manifest.

Rules

No rules are declared in the GTM manifest. Outreach approval is enforced at the job processing level (ADR-008 via OutreachSendDispatch::HoldForApproval).

Jobs

No jobs are declared in the GTM manifest. JobKind::OutreachSend is processed by the job worker in rusvel-app which calls GtmEngine::process_outreach_send_job().

Events

Produced

Event KindWhen Emitted
gtm.outreach.sentAn outreach email is sent after approval
gtm.email.sentAn email is sent via the email adapter
gtm.deal.updatedA deal is advanced to a new stage
gtm.contact.addedA new contact is added to the CRM
gtm.invoice.createdA new invoice is created
gtm.invoice.paidAn invoice is marked as paid
gtm.sequence.createdA new outreach sequence is created

Consumed

The GTM department does not consume events from other departments.

API Routes

The GTM manifest declares no route contributions. GTM functionality is accessed through:

  • Agent tools: The 5 registered tools are available during chat sessions
  • Generic department routes: /api/dept/gtm/status, /api/dept/gtm/list, /api/dept/gtm/events
  • Job worker: JobKind::OutreachSend processed in the rusvel-app job worker

CLI Commands

rusvel gtm status       # Department status
rusvel gtm list         # List items
rusvel gtm events       # Show recent events

Outreach Approval Flow

The GTM outreach system enforces human approval (ADR-008) through a two-pass job processing model:

sequenceDiagram
    participant User
    participant Outreach as OutreachManager
    participant Jobs as JobPort
    participant Worker as Job Worker
    participant GTM as GtmEngine
    participant Email as EmailAdapter
    participant Events as EventPort

    User->>Outreach: execute_sequence(session, seq_id, contact_id)
    Outreach->>Jobs: enqueue OutreachSend job (step 0)

    Note over Worker: Pass 1: Draft
    Worker->>GTM: process_outreach_send_job(job, email)
    GTM->>GTM: draft email via AgentPort
    GTM-->>Worker: HoldForApproval(result)
    Worker->>Jobs: hold_for_approval(job_id, result)

    Note over User: User reviews and approves
    User->>Jobs: approve(job_id)

    Note over Worker: Pass 2: Send
    Worker->>GTM: process_outreach_send_job(job_with_approval, email)
    GTM->>Email: send(to, subject, body)
    GTM->>Events: emit gtm.email.sent
    GTM->>Events: emit gtm.outreach.sent
    GTM-->>Worker: Complete { result, next: Some(step 1 job) }
    Worker->>Jobs: schedule next step (delay_days offset)

Entity Auto-Discovery

Agents, skills, rules, hooks, and MCP servers scoped to the GTM department are stored with metadata.engine = "gtm". The shared CRUD API routes filter by this key so each department sees only its own entities.

Chat Flow

sequenceDiagram
    participant User
    participant API as rusvel-api
    participant Chat as Chat Handler
    participant Config as ConfigPort
    participant Skills as Skill Resolver
    participant Rules as Rule Loader
    participant Agent as AgentRuntime
    participant GTM as GtmEngine
    participant Events as EventPort
    participant Hooks as Hook Dispatcher

    User->>API: POST /api/dept/gtm/chat {message}
    API->>Chat: route to department chat handler
    Chat->>Config: load department config
    Chat->>Skills: resolve_skill() -- check for {{input}} interpolation
    Chat->>Chat: check agent override (custom agent for gtm)
    Chat->>Rules: load_rules_for_engine("gtm") -- append to system prompt
    Note over Chat: Inject capabilities: outreach, crm, invoicing
    Note over Chat: System prompt: "You are the GoToMarket department..."
    Chat->>Agent: AgentRuntime::run_streaming(config, tools, prompt)
    Agent->>GTM: tool calls (gtm.crm.add_contact, gtm.outreach.create_sequence, etc.)
    GTM->>Events: emit gtm.* events
    Agent-->>Chat: SSE stream (AgentEvent chunks)
    Chat-->>User: SSE response
    Chat->>Events: emit chat completion event
    Chat->>Hooks: tokio::spawn hook dispatch

Extending This Department

1. Add a new tool

Register the tool in crates/dept-gtm/src/tools.rs inside register(). Add the tool ID to the GTM_TOOL_IDS constant. No manifest ToolContribution is needed since the GTM manifest currently declares an empty tools vec (tools are registered dynamically).

2. Add a new event kind

Add a new pub const in the events module inside crates/gtm-engine/src/lib.rs. Emit it from the engine method. Optionally add it to events_produced in crates/dept-gtm/src/manifest.rs.

3. Add a new persona

Add a PersonaContribution entry in the personas vec in crates/dept-gtm/src/manifest.rs.

4. Add a new skill

Add a SkillContribution entry in the skills vec in crates/dept-gtm/src/manifest.rs.

5. Add a new API route

Add a RouteContribution entry in the routes vec in crates/dept-gtm/src/manifest.rs. Implement the handler in crates/rusvel-api/src/engine_routes.rs and wire the route in crates/rusvel-api/src/lib.rs.

6. Add a new email adapter

Implement the EmailAdapter trait in crates/gtm-engine/src/email.rs. The trait requires a single send() method that takes an EmailMessage and returns Result<()>.

Port Dependencies

PortRequiredPurpose
StoragePortYesPersist contacts, deals, sequences, invoices via ObjectStore
EventPortYesEmit gtm.* domain events
AgentPortYesAI-powered email drafting in outreach sequences
JobPortYesEnqueue and process OutreachSend jobs

Environment Variables

VariablePurpose
RUSVEL_SMTP_HOSTSMTP server host for outreach emails
RUSVEL_SMTP_PORTSMTP server port
RUSVEL_SMTP_USERNAMESMTP authentication username
RUSVEL_SMTP_PASSWORDSMTP authentication password
RUSVEL_SMTP_FROMSender email address

Finance Department

Revenue tracking, expense management, tax optimization, runway forecasting.

FieldValue
IDfinance
Icon%
Colorgreen
Engine cratefinance-engine (~400 lines)
Dept cratedept-finance
StatusSkeleton – manager structures with CRUD, minimal business logic

Overview

The Finance department handles financial operations for a solo SaaS business: ledger transactions (income/expenses), tax estimation, and runway forecasting. The engine provides three manager subsystems, each backed by ObjectStore for persistence.


Current Status: Skeleton

The finance engine has manager structures but minimal business logic (~400 lines total across lib.rs and three manager modules). The managers provide CRUD operations:

  • LedgerManagerrecord(), balance(), list_transactions(). Records income and expense transactions, computes net balance.
  • TaxManageradd_estimate(), total_liability(), list_estimates(). Stores per-category tax estimates and sums liability (estimates minus deductions).
  • RunwayManagercalculate(), list_snapshots(). Computes runway from cash and burn rate, persists snapshots.

The department is fully registered and bootable – it appears in the department registry, responds to chat, and has 4 agent tools wired. However, it needs the following to be production-ready:

  • P&L report generation (aggregate by category/period)
  • Recurring transaction support
  • Multi-currency handling
  • Budget alerts and threshold notifications
  • Integration with real accounting data sources
  • Scheduled runway recalculation via job queue

Engine Details

Crate: finance-engine (~400 lines)

Struct: FinanceEngine

Constructor:

#![allow(unused)]
fn main() {
FinanceEngine::new(
    storage: Arc<dyn StoragePort>,
    events: Arc<dyn EventPort>,
    agent: Arc<dyn AgentPort>,
    jobs: Arc<dyn JobPort>,
)
}

Managers:

ManagerMethodsDescription
LedgerManagerrecord(sid, kind, amount, description, category), balance(sid), list_transactions(sid)Income/expense tracking
TaxManageradd_estimate(sid, category, amount, period), total_liability(sid), list_estimates(sid)Tax estimation by category
RunwayManagercalculate(sid, cash, burn_rate), list_snapshots(sid)Runway forecasting

Implements: rusvel_core::engine::Engine trait (kind: "finance", name: "Finance Engine")


Manifest

Declared in dept-finance/src/manifest.rs:

id:            "finance"
name:          "Finance Department"
description:   "Revenue tracking, expense management, tax optimization, runway forecasting, P&L reports, unit economics"
icon:          "%"
color:         "green"
capabilities:  ["ledger", "tax", "runway"]

System Prompt

You are the Finance department of RUSVEL.

Focus: revenue tracking, expense management, tax optimization, runway forecasting, P&L reports, unit economics.

Tools

Tools registered at runtime via dept-finance/src/lib.rs (4 tools):

ToolParametersDescription
finance.ledger.recordsession_id, kind (Income/Expense), amount, description, categoryRecord a transaction
finance.ledger.balancesession_idGet current balance (income minus expenses)
finance.tax.add_estimatesession_id, category (Income/SelfEmployment/Sales/Deduction), amount, periodAdd a tax estimate
finance.tax.total_liabilitysession_idCalculate total tax liability

Personas

None declared in the manifest. The department uses the default system prompt for chat interactions.


Skills

None declared in the manifest.


Rules

None declared in the manifest.


Jobs

None declared in the manifest. Future candidates:

  • Scheduled runway recalculation
  • Periodic P&L report generation
  • Tax deadline reminders

Events

Defined Constants (engine)

ConstantValue
INCOME_RECORDEDfinance.income.recorded
EXPENSE_RECORDEDfinance.expense.recorded
RUNWAY_CALCULATEDfinance.runway.calculated
TAX_ESTIMATEDfinance.tax.estimated

These constants are defined in the engine but are not yet emitted automatically by the manager methods (emitting requires calling engine.emit_event() explicitly). They are also not listed in the manifest’s events_produced.

Consumed

None.


API Routes

None declared in the manifest. The department is accessible via the standard parameterized routes:

  • GET /api/dept/finance/status – department status
  • POST /api/dept/finance/chat – SSE chat

CLI Commands

Standard department CLI:

rusvel finance status    # One-shot status
rusvel finance list      # List items
rusvel finance events    # Show events

Entity Auto-Discovery

The standard CRUD subsystems are available at /api/dept/finance/*:

  • Agents, Skills, Rules, Hooks, Workflows, MCP Servers

Chat Flow

sequenceDiagram
    participant U as User
    participant API as /api/dept/finance/chat
    participant Agent as AgentRuntime
    participant Tool as finance.* tools
    participant Engine as FinanceEngine
    participant Store as StoragePort

    U->>API: POST "Record $5000 income from consulting"
    API->>Agent: run_streaming(system_prompt, tools)
    Agent->>Tool: finance.ledger.record(kind=Income, amount=5000, ...)
    Tool->>Engine: ledger().record(sid, Income, 5000, ...)
    Engine->>Store: objects.put("transactions", id, value)
    Store-->>Engine: Ok
    Engine-->>Tool: transaction_id
    Tool-->>Agent: "Transaction recorded: {id}"
    Agent->>Tool: finance.ledger.balance(session_id)
    Tool->>Engine: ledger().balance(sid)
    Engine->>Store: objects.list("transactions")
    Store-->>Engine: Vec<Transaction>
    Engine-->>Tool: balance
    Tool-->>Agent: "5000.00"
    Agent-->>API: SSE chunks
    API-->>U: "Recorded $5000 income. Current balance: $5000.00"

Extending the Department

Adding P&L reports

  1. Add a PnlManager to finance-engine with generate_report(sid, period) method
  2. Register a finance.pnl.report tool in dept-finance/src/lib.rs
  3. Add a quick action to the manifest for “P&L report”
  4. Optionally add a scheduled job kind for periodic report generation

Adding event emission

The event constants are already defined. To wire them:

  1. Call self.emit_event(events::INCOME_RECORDED, payload) inside LedgerManager::record() when the transaction kind is Income
  2. Add the event kinds to the manifest’s events_produced vector
  3. Other departments can then subscribe via events_consumed

Adding API routes

  1. Add route contributions to the manifest in dept-finance/src/manifest.rs
  2. Implement handler functions in rusvel-api/src/engine_routes.rs
  3. Wire the routes in rusvel-api/src/lib.rs

Port Dependencies

PortRequiredUsage
StoragePortYesTransactions, tax estimates, runway snapshots (via ObjectStore)
EventPortYesDomain event emission (constants defined, not yet auto-emitted)
AgentPortYesLLM-powered financial analysis via chat
JobPortYesFuture: scheduled reports and calculations

Product Department

Product roadmaps, feature prioritization, pricing strategy, user feedback analysis.

FieldValue
IDproduct
Icon@
Colorrose
Engine crateproduct-engine (~388 lines)
Dept cratedept-product
StatusSkeleton – manager structures with CRUD, minimal business logic

Overview

The Product department manages the product lifecycle: roadmap features with priorities and milestones, pricing tier definitions, and user feedback collection. The engine provides three manager subsystems backed by ObjectStore.


Current Status: Skeleton

The product engine has manager structures but minimal business logic (~388 lines total across lib.rs and three manager modules). The managers provide CRUD operations:

  • RoadmapManageradd_feature(sid, title, description, priority, milestone), list_features(sid), mark_feature_done(sid, feature_id). Tracks features with Priority (Critical/High/Medium/Low) and FeatureStatus (Planned/InProgress/Done/Cancelled).
  • PricingManagercreate_tier(sid, name, price, annual_price, features), list_tiers(sid), update_tier(sid, tier_id, updates). Manages pricing tiers with monthly/annual pricing and feature lists.
  • FeedbackManageradd_feedback(sid, source, kind, content), list_feedback(sid), analyze_feedback(sid). Collects feedback categorized by FeedbackKind (FeatureRequest/Bug/Praise/Complaint).

The department is fully registered and bootable – it appears in the department registry, responds to chat, and has 4 agent tools wired. However, it needs the following to be production-ready:

  • Feature dependency tracking and critical path analysis
  • Milestone timeline visualization data
  • Pricing A/B test support
  • Feedback sentiment analysis via AgentPort
  • Feature voting and prioritization scoring
  • Integration with issue trackers (GitHub, Linear)

Engine Details

Crate: product-engine (~388 lines)

Struct: ProductEngine

Constructor:

#![allow(unused)]
fn main() {
ProductEngine::new(
    storage: Arc<dyn StoragePort>,
    events: Arc<dyn EventPort>,
    agent: Arc<dyn AgentPort>,
    jobs: Arc<dyn JobPort>,
)
}

Managers:

ManagerMethodsDescription
RoadmapManageradd_feature(), list_features(), mark_feature_done()Feature tracking with priority and status
PricingManagercreate_tier(), list_tiers(), update_tier()Pricing tier management
FeedbackManageradd_feedback(), list_feedback(), analyze_feedback()User feedback collection

Implements: rusvel_core::engine::Engine trait (kind: "product", name: "Product Engine")


Manifest

Declared in dept-product/src/manifest.rs:

id:            "product"
name:          "Product Department"
description:   "Product roadmaps, feature prioritization, pricing strategy, user feedback analysis, A/B testing"
icon:          "@"
color:         "rose"
capabilities:  ["roadmap", "pricing", "feedback"]

System Prompt

You are the Product department of RUSVEL.

Focus: product roadmaps, feature prioritization, pricing strategy, user feedback analysis, A/B testing.

Tools

Tools registered at runtime via dept-product/src/lib.rs (4 tools):

ToolParametersDescription
product.roadmap.add_featuresession_id, title, description, priority (Critical/High/Medium/Low), status (optional milestone)Add a feature to the roadmap
product.roadmap.list_featuressession_id, status (optional filter: Planned/InProgress/Done/Cancelled)List roadmap features
product.pricing.create_tiersession_id, name, price, features (array)Create a pricing tier
product.feedback.recordsession_id, source, sentiment (FeatureRequest/Bug/Praise/Complaint), contentRecord user feedback

Personas

None declared in the manifest.


Skills

None declared in the manifest.


Rules

None declared in the manifest.


Jobs

None declared in the manifest. Future candidates:

  • Periodic feedback analysis aggregation
  • Roadmap progress reports
  • Pricing optimization suggestions

Events

Defined Constants (engine)

ConstantValue
FEATURE_CREATEDproduct.feature.created
MILESTONE_REACHEDproduct.milestone.reached
PRICING_UPDATEDproduct.pricing.updated
FEEDBACK_RECEIVEDproduct.feedback.received

These constants are defined in the engine but are not yet emitted automatically by the manager methods. They are not listed in the manifest’s events_produced.

Consumed

None.


API Routes

None declared in the manifest. The department is accessible via the standard parameterized routes:

  • GET /api/dept/product/status – department status
  • POST /api/dept/product/chat – SSE chat

CLI Commands

Standard department CLI:

rusvel product status    # One-shot status
rusvel product list      # List items
rusvel product events    # Show events

Entity Auto-Discovery

The standard CRUD subsystems are available at /api/dept/product/*:

  • Agents, Skills, Rules, Hooks, Workflows, MCP Servers

Chat Flow

sequenceDiagram
    participant U as User
    participant API as /api/dept/product/chat
    participant Agent as AgentRuntime
    participant Tool as product.* tools
    participant Engine as ProductEngine
    participant Store as StoragePort

    U->>API: POST "Add OAuth2 feature, high priority, for v1.0"
    API->>Agent: run_streaming(system_prompt, tools)
    Agent->>Tool: product.roadmap.add_feature(title="OAuth2", priority="High", status="v1.0")
    Tool->>Engine: roadmap().add_feature(sid, "OAuth2", ..., High, Some("v1.0"))
    Engine->>Store: objects.put("features", id, value)
    Store-->>Engine: Ok
    Engine-->>Tool: feature_id
    Tool-->>Agent: "Feature created: {id}"
    Agent->>Tool: product.roadmap.list_features(session_id)
    Tool->>Engine: roadmap().list_features(sid)
    Engine->>Store: objects.list("features")
    Store-->>Engine: Vec<Feature>
    Engine-->>Tool: features JSON
    Tool-->>Agent: formatted list
    Agent-->>API: SSE chunks
    API-->>U: "Added OAuth2 (High) to v1.0 milestone. Roadmap now has N features."

Extending the Department

Adding feature dependency tracking

  1. Add a dependencies: Vec<FeatureId> field to the Feature struct in product-engine/src/roadmap.rs
  2. Add a critical_path(sid) method to RoadmapManager that computes the longest dependency chain
  3. Register a product.roadmap.critical_path tool in dept-product/src/lib.rs

Adding AI-powered feedback analysis

  1. Implement analyze_feedback(sid) in FeedbackManager to call AgentPort with all feedback items
  2. The agent can categorize, extract themes, and suggest feature priorities
  3. Register a product.feedback.analyze tool

Adding event emission

Wire the defined constants by calling self.emit_event() in the relevant manager methods. Add the event kinds to the manifest’s events_produced vector.


Port Dependencies

PortRequiredUsage
StoragePortYesFeatures, pricing tiers, feedback items (via ObjectStore)
EventPortYesDomain event emission (constants defined, not yet auto-emitted)
AgentPortYesLLM-powered product analysis via chat, future feedback analysis
JobPortYesFuture: scheduled reports and analysis

Growth Department

Funnel optimization, conversion tracking, cohort analysis, KPI dashboards.

FieldValue
IDgrowth
Icon&
Colororange
Engine crategrowth-engine (~375 lines)
Dept cratedept-growth
StatusSkeleton – manager structures with CRUD, minimal business logic

Overview

The Growth department tracks growth metrics for a solo SaaS business: conversion funnels with ordered stages, user cohorts for retention analysis, and KPI time-series recording. The engine provides three manager subsystems backed by ObjectStore.


Current Status: Skeleton

The growth engine has manager structures but minimal business logic (~375 lines total across lib.rs and three manager modules). The managers provide CRUD operations:

  • FunnelManageradd_stage(sid, name, order), list_stages(sid), record_conversion(sid, stage_id, count). Defines ordered funnel stages and records conversion counts per stage.
  • CohortManagercreate_cohort(sid, name, size), list_cohorts(sid), analyze_retention(sid, cohort_id). Creates named cohorts with initial size for retention tracking.
  • KpiManagerrecord_kpi(sid, name, value, unit), list_kpis(sid), get_kpi_trend(sid, name). Records time-series KPI measurements and computes simple two-point trend (delta between last two readings).

The department is fully registered and bootable – it appears in the department registry, responds to chat, and has 4 agent tools wired. However, it needs the following to be production-ready:

  • Funnel drop-off rate calculation and visualization data
  • Cohort retention matrix computation
  • KPI alerting (threshold-based notifications)
  • Churn prediction models via AgentPort
  • Historical trend analysis beyond two-point comparison
  • Dashboard data aggregation endpoints

Engine Details

Crate: growth-engine (~375 lines)

Struct: GrowthEngine

Constructor:

#![allow(unused)]
fn main() {
GrowthEngine::new(
    storage: Arc<dyn StoragePort>,
    events: Arc<dyn EventPort>,
    agent: Arc<dyn AgentPort>,
    jobs: Arc<dyn JobPort>,
)
}

Managers:

ManagerMethodsDescription
FunnelManageradd_stage(), list_stages(), record_conversion()Conversion funnel tracking
CohortManagercreate_cohort(), list_cohorts(), analyze_retention()User cohort analysis
KpiManagerrecord_kpi(), list_kpis(), get_kpi_trend()KPI time-series

Implements: rusvel_core::engine::Engine trait (kind: "growth", name: "Growth Engine")


Manifest

Declared in dept-growth/src/manifest.rs:

id:            "growth"
name:          "Growth Department"
description:   "Funnel optimization, conversion tracking, cohort analysis, churn prediction, retention strategies, KPI dashboards"
icon:          "&"
color:         "orange"
capabilities:  ["funnel", "cohort", "kpi"]

System Prompt

You are the Growth department of RUSVEL.

Focus: funnel optimization, conversion tracking, cohort analysis, churn prediction, retention strategies, KPI dashboards.

Tools

Tools registered at runtime via dept-growth/src/tools.rs (4 tools):

ToolParametersDescription
growth.funnel.add_stagesession_id, name, orderAdd a funnel stage for conversion tracking
growth.cohort.create_cohortsession_id, name, sizeCreate a user cohort for retention analysis
growth.kpi.record_kpisession_id, name, value, unitRecord a KPI measurement
growth.kpi.get_trendsession_id, kpi_nameCompare last two readings for a named metric (simple trend with delta)

Personas

None declared in the manifest.


Skills

None declared in the manifest.


Rules

None declared in the manifest.


Jobs

None declared in the manifest. Future candidates:

  • Scheduled KPI snapshot collection
  • Periodic cohort retention analysis
  • Churn detection scans

Events

Defined Constants (engine)

ConstantValue
FUNNEL_UPDATEDgrowth.funnel.updated
COHORT_ANALYZEDgrowth.cohort.analyzed
KPI_RECORDEDgrowth.kpi.recorded
CHURN_DETECTEDgrowth.churn.detected

These constants are defined in the engine but are not yet emitted automatically by the manager methods. They are not listed in the manifest’s events_produced.

Consumed

None.


API Routes

None declared in the manifest. The department is accessible via the standard parameterized routes:

  • GET /api/dept/growth/status – department status
  • POST /api/dept/growth/chat – SSE chat

CLI Commands

Standard department CLI:

rusvel growth status    # One-shot status
rusvel growth list      # List items
rusvel growth events    # Show events

Entity Auto-Discovery

The standard CRUD subsystems are available at /api/dept/growth/*:

  • Agents, Skills, Rules, Hooks, Workflows, MCP Servers

Chat Flow

sequenceDiagram
    participant U as User
    participant API as /api/dept/growth/chat
    participant Agent as AgentRuntime
    participant Tool as growth.* tools
    participant Engine as GrowthEngine
    participant Store as StoragePort

    U->>API: POST "Record MRR of $12,000"
    API->>Agent: run_streaming(system_prompt, tools)
    Agent->>Tool: growth.kpi.record_kpi(name="MRR", value=12000, unit="USD")
    Tool->>Engine: kpi().record_kpi(sid, "MRR", 12000.0, "USD")
    Engine->>Store: objects.put("kpis", id, value)
    Store-->>Engine: Ok
    Engine-->>Tool: kpi_id
    Tool-->>Agent: {"kpi_id": "..."}
    Agent->>Tool: growth.kpi.get_trend(kpi_name="MRR")
    Tool->>Engine: kpi().list_kpis(sid)
    Engine->>Store: objects.list("kpis")
    Store-->>Engine: Vec<KpiEntry>
    Tool->>Tool: filter by name, sort, compute delta
    Tool-->>Agent: {"previous": 10000, "latest": 12000, "delta": 2000}
    Agent-->>API: SSE chunks
    API-->>U: "MRR recorded at $12,000. Up $2,000 from last reading."

Extending the Department

Adding funnel drop-off analysis

  1. Add a drop_off_rates(sid) method to FunnelManager that computes conversion percentages between consecutive stages
  2. Register a growth.funnel.analyze tool in dept-growth/src/tools.rs
  3. Add a quick action to the manifest

Adding churn detection

  1. Implement a detect_churn(sid) method in CohortManager that identifies cohorts with declining retention
  2. Wire events::CHURN_DETECTED emission when thresholds are crossed
  3. Optionally use AgentPort for AI-powered churn prediction
  4. Add to the manifest’s events_produced

Adding dashboard data endpoints

  1. Add route contributions to the manifest for /api/dept/growth/dashboard
  2. Implement a handler that aggregates funnel, cohort, and KPI data into a single response
  3. Wire the route in rusvel-api

Port Dependencies

PortRequiredUsage
StoragePortYesFunnel stages, cohorts, KPI entries (via ObjectStore)
EventPortYesDomain event emission (constants defined, not yet auto-emitted)
AgentPortYesLLM-powered growth analysis via chat, future churn prediction
JobPortYesFuture: scheduled KPI collection and analysis

Distribution Department

Marketplace listings, SEO optimization, affiliate programs, partnerships.

FieldValue
IDdistro
Icon!
Colorteal
Engine cratedistro-engine (~390 lines)
Dept cratedept-distro
StatusSkeleton – manager structures with CRUD, minimal business logic

Overview

The Distribution department manages distribution channels for a solo SaaS business: marketplace listings across platforms, SEO keyword tracking with position monitoring, and affiliate partner management with commission tracking. The engine provides three manager subsystems backed by ObjectStore.


Current Status: Skeleton

The distro engine has manager structures but minimal business logic (~390 lines total across lib.rs and three manager modules). The managers provide CRUD operations:

  • MarketplaceManageradd_listing(sid, platform, name, url), list_listings(sid), publish_listing(sid, listing_id). Creates listings with ListingStatus (Draft/Published/Archived) and tracks revenue per listing.
  • SeoManageradd_keyword(sid, keyword, position, volume), list_keywords(sid), track_ranking(sid, keyword_id, new_position). Tracks keyword positions and search volume.
  • AffiliateManageradd_partner(sid, name, commission_rate), list_partners(sid), track_commission(sid, partner_id, amount). Manages affiliate partners with commission rates.

The department is fully registered and bootable – it appears in the department registry, responds to chat, and has 4 agent tools wired. However, it needs the following to be production-ready:

  • Automated marketplace listing sync with real platforms
  • SEO position history and trend analysis
  • Affiliate link generation and click tracking
  • Revenue attribution across channels
  • Automated SEO audit recommendations via AgentPort
  • Scheduled ranking checks via job queue

Engine Details

Crate: distro-engine (~390 lines)

Struct: DistroEngine

Constructor:

#![allow(unused)]
fn main() {
DistroEngine::new(
    storage: Arc<dyn StoragePort>,
    events: Arc<dyn EventPort>,
    agent: Arc<dyn AgentPort>,
    jobs: Arc<dyn JobPort>,
)
}

Managers:

ManagerMethodsDescription
MarketplaceManageradd_listing(), list_listings(), publish_listing()Marketplace listing management
SeoManageradd_keyword(), list_keywords(), track_ranking()SEO keyword position tracking
AffiliateManageradd_partner(), list_partners(), track_commission()Affiliate partner and commission management

Implements: rusvel_core::engine::Engine trait (kind: "distro", name: "Distribution Engine")


Manifest

Declared in dept-distro/src/manifest.rs:

id:            "distro"
name:          "Distribution Department"
description:   "Marketplace listings, SEO optimization, affiliate programs, partnerships, API distribution channels"
icon:          "!"
color:         "teal"
capabilities:  ["marketplace", "seo", "affiliate"]

System Prompt

You are the Distribution department of RUSVEL.

Focus: marketplace listings, SEO optimization, affiliate programs, partnerships, API distribution channels.

Tools

Tools registered at runtime via dept-distro/src/tools.rs (4 tools):

ToolParametersDescription
distro.seo.analyzesession_idSummarize tracked SEO keywords (positions, volume, average position)
distro.marketplace.listsession_idList all marketplace listings
distro.affiliate.create_linksession_id, name, commission_rate (0-1)Register an affiliate partner
distro.analytics.reportsession_idAggregate listings, affiliates, and SEO keywords into one JSON report

Personas

None declared in the manifest.


Skills

None declared in the manifest.


Rules

None declared in the manifest.


Jobs

None declared in the manifest. Future candidates:

  • Scheduled SEO position checks
  • Marketplace listing status sync
  • Affiliate commission payouts

Events

Defined Constants (engine)

ConstantValue
LISTING_PUBLISHEDdistro.listing.published
SEO_RANKEDdistro.seo.ranked
AFFILIATE_JOINEDdistro.affiliate.joined
PARTNERSHIP_CREATEDdistro.partnership.created

These constants are defined in the engine but are not yet emitted automatically by the manager methods. They are not listed in the manifest’s events_produced.

Consumed

None.


API Routes

None declared in the manifest. The department is accessible via the standard parameterized routes:

  • GET /api/dept/distro/status – department status
  • POST /api/dept/distro/chat – SSE chat

CLI Commands

Standard department CLI:

rusvel distro status    # One-shot status (alias: "distribution")
rusvel distro list      # List items
rusvel distro events    # Show events

Entity Auto-Discovery

The standard CRUD subsystems are available at /api/dept/distro/*:

  • Agents, Skills, Rules, Hooks, Workflows, MCP Servers

Chat Flow

sequenceDiagram
    participant U as User
    participant API as /api/dept/distro/chat
    participant Agent as AgentRuntime
    participant Tool as distro.* tools
    participant Engine as DistroEngine
    participant Store as StoragePort

    U->>API: POST "Show me an SEO report"
    API->>Agent: run_streaming(system_prompt, tools)
    Agent->>Tool: distro.seo.analyze(session_id)
    Tool->>Engine: seo().list_keywords(sid)
    Engine->>Store: objects.list("keywords")
    Store-->>Engine: Vec<Keyword>
    Tool->>Tool: compute avg position, aggregate
    Tool-->>Agent: {"keyword_count": 5, "avg_position": 12.4, "keywords": [...]}
    Agent->>Tool: distro.analytics.report(session_id)
    Tool->>Engine: marketplace().list_listings(sid)
    Tool->>Engine: affiliate().list_partners(sid)
    Tool->>Engine: seo().list_keywords(sid)
    Engine-->>Tool: aggregated data
    Tool-->>Agent: full analytics report JSON
    Agent-->>API: SSE chunks
    API-->>U: "SEO Report: 5 keywords tracked, avg position 12.4. ..."

Extending the Department

Adding automated SEO audits

  1. Add an audit(sid) method to SeoManager that calls AgentPort with keyword data for AI-powered recommendations
  2. Register a distro.seo.audit tool in dept-distro/src/tools.rs
  3. Add a scheduled job kind for periodic audits
  4. Emit distro.seo.ranked events when position changes are detected

Adding marketplace sync

  1. Implement platform-specific adapters (similar to content-engine’s LinkedIn/Twitter adapters)
  2. Add a sync_listing(sid, listing_id) method that fetches live data from the platform
  3. Wire a job kind for periodic sync

Adding revenue attribution

  1. Extend MarketplaceManager to track revenue per listing over time
  2. Extend AffiliateManager to compute total commissions earned per partner
  3. Add a distro.revenue.attribution tool that breaks down revenue by channel
  4. Wire the data into the analytics report tool

Port Dependencies

PortRequiredUsage
StoragePortYesListings, keywords, partners (via ObjectStore)
EventPortYesDomain event emission (constants defined, not yet auto-emitted)
AgentPortYesLLM-powered distribution analysis via chat, future SEO audits
JobPortYesFuture: scheduled ranking checks and sync

Legal Department

Contracts, IP protection, terms of service, GDPR compliance, licensing, privacy policies.

FieldValue
IDlegal
Icon§
Colorslate
Engine cratelegal-engine (~390 lines)
Wrapper cratedept-legal
StatusSkeleton

Overview

The Legal department manages the full contract lifecycle, compliance audits, and intellectual property tracking. It wraps legal-engine via the ADR-014 DepartmentApp pattern and is registered in the composition root alongside the other skeleton departments.

System Prompt

You are the Legal department of RUSVEL.

Focus: contracts, IP protection, terms of service, GDPR compliance, licensing, privacy policies.

Capabilities

CapabilityDescription
contractCreate, list, review, and sign contracts
complianceRecord and track compliance checks (GDPR, Privacy, Licensing, Tax)
ipFile and manage intellectual property assets (Patent, Trademark, Copyright, TradeSecret)

Quick Actions

LabelPrompt
Draft contract“Draft a contract. Ask me for type, parties, and terms.”
Compliance check“Run a compliance check for GDPR and privacy policy.”
IP review“Review intellectual property assets.”

Architecture

Three manager structs compose the engine, each backed by ObjectStore via StoragePort:

ManagerDomain TypeObject KindMethods
ContractManagerContractlegal_contractcreate_contract, list_contracts
ComplianceManagerComplianceChecklegal_complianceadd_check, list_checks
IpManagerIpAssetlegal_ipfile_asset, list_assets

Domain Types

ContractContractId (UUIDv7), ContractStatus enum (Draft, Sent, Signed, Expired, Cancelled), fields: title, counterparty, template, signed_at, expires_at, created_at, metadata.

ComplianceCheckComplianceCheckId (UUIDv7), ComplianceArea enum (GDPR, Privacy, Licensing, Tax), fields: description, passed, checked_at, notes, metadata.

IpAssetIpAssetId (UUIDv7), IpKind enum (Patent, Trademark, Copyright, TradeSecret), fields: name, description, filed_at, status, metadata.

  • LegalDepartment struct with OnceLock<Arc<LegalEngine>> for lazy initialization
  • register() creates the engine, stores it, and registers agent tools
  • shutdown() delegates to engine
  • 2 unit tests (department creation, manifest purity)

Registered Tools

Tool NameDescriptionParameters
legal.contracts.createCreate a new contract draftsession_id, title, counterparty, template
legal.contracts.listList contracts for a sessionsession_id
legal.compliance.checkRecord a compliance check outcomesession_id, area, description, passed, notes
legal.ip.registerFile an intellectual property assetsession_id, kind, name, description

Events

Event KindConstantDescription
legal.contract.createdCONTRACT_CREATEDA new contract draft was created
legal.compliance.checkedCOMPLIANCE_CHECKEDA compliance check was recorded
legal.ip.filedIP_FILEDAn IP asset was filed
legal.review.completedREVIEW_COMPLETEDA legal review was completed

Note: event constants are defined in legal_engine::events but emission is not yet wired into manager methods (skeleton status).

Required Ports

PortOptional
StoragePortNo
EventPortNo
AgentPortNo
JobPortNo

UI Contribution

Tabs: actions, agents, rules, events

No dashboard cards, settings panel, or custom components.

Chat Flow

sequenceDiagram
    participant User
    participant Frontend
    participant API as /api/dept/legal/chat
    participant Agent as AgentPort
    participant Engine as LegalEngine
    participant Store as ObjectStore

    User->>Frontend: "Draft an NDA for Acme Corp"
    Frontend->>API: POST (SSE)
    API->>Agent: run(system_prompt + user message)
    Agent->>Engine: legal.contracts.create(title, counterparty, template)
    Engine->>Store: objects().put("legal_contract", ...)
    Store-->>Engine: Ok
    Engine-->>Agent: { contract_id }
    Agent-->>API: SSE token stream
    API-->>Frontend: SSE events
    Frontend-->>User: "Created contract draft [id]"

CLI Usage

rusvel legal status          # Show department status
rusvel legal list             # List all legal items
rusvel legal list --kind contract  # List contracts only
rusvel legal events           # Show recent legal events

Testing

cargo test -p legal-engine    # Engine tests (contract CRUD, health check)
cargo test -p dept-legal      # Wrapper tests (manifest, department creation)

Current Status: Skeleton

The Legal department is fully registered and bootable within the RUSVEL department registry, but its business logic is minimal. Here is what exists and what remains to be built:

What exists:

  • Manager structures with basic CRUD operations (create + list for each domain)
  • Domain types with full serialization (Contract, ComplianceCheck, IpAsset)
  • 4 agent tools registered in the scoped tool registry
  • Event kind constants defined (but not yet emitted from manager methods)
  • Engine implements the Engine trait with health check
  • Unit tests for engine and wrapper

What needs to be built for production readiness:

  • Wire emit_event() calls into manager methods so domain events actually fire
  • Add review_contract, sign_contract operations to ContractManager
  • Add mark_passed operation to ComplianceManager
  • Add file_ip (full filing workflow with status transitions) to IpManager
  • Implement AI-assisted contract drafting via AgentPort (template expansion)
  • Add job kinds for async legal review workflows (e.g., JobKind::Custom("legal.review"))
  • Build compliance report generation (aggregate check results into a report)
  • Add engine-specific API routes (e.g., /api/dept/legal/contracts, /api/dept/legal/compliance)
  • Add engine-specific CLI commands (e.g., rusvel legal draft, rusvel legal audit)
  • Add personas for legal agent specialization
  • Add skills and rules for legal workflows

Source Files

FileLinesPurpose
crates/legal-engine/src/lib.rs390Engine struct, capabilities, tests
crates/legal-engine/src/contract.rs112Contract domain type + ContractManager
crates/legal-engine/src/compliance.rs108ComplianceCheck domain type + ComplianceManager
crates/legal-engine/src/ip.rs107IpAsset domain type + IpManager
crates/dept-legal/src/lib.rs89DepartmentApp implementation
crates/dept-legal/src/manifest.rs96Static manifest definition
crates/dept-legal/src/tools.rs184Agent tool registration

Support Department

Customer support tickets, knowledge base, NPS tracking, auto-triage, customer success.

FieldValue
IDsupport
Icon?
Coloryellow
Engine cratesupport-engine (~391 lines)
Wrapper cratedept-support
StatusSkeleton

Overview

The Support department handles customer-facing operations: ticket management with priority-based triage, a knowledge base for self-service articles, and Net Promoter Score (NPS) tracking for customer satisfaction measurement. It wraps support-engine via the ADR-014 DepartmentApp pattern.

System Prompt

You are the Support department of RUSVEL.

Focus: customer support tickets, knowledge base, NPS tracking, auto-triage, customer success.

Capabilities

CapabilityDescription
ticketCreate, list, resolve, and assign support tickets with priority levels
knowledgeManage knowledge base articles (add, list, search)
npsTrack NPS survey responses and calculate scores

Quick Actions

LabelPrompt
Open tickets“Show all open support tickets prioritized by urgency.”
Write KB article“Write a knowledge base article. Ask me for the topic.”
NPS survey“Analyze recent NPS survey results with score breakdown and themes.”

Architecture

Engine: support-engine

Three manager structs compose the engine, each backed by ObjectStore via StoragePort:

ManagerDomain TypeObject KindMethods
TicketManagerTicketsupport_ticketcreate_ticket, list_tickets
KnowledgeManagerArticlesupport_articleadd_article, list_articles
NpsManagerNpsResponsesupport_npsadd_response, list_responses, calculate_nps

Domain Types

TicketTicketId (UUIDv7), TicketStatus enum (Open, InProgress, Resolved, Closed), TicketPriority enum (Low, Medium, High, Urgent), fields: subject, description, requester_email, assignee, created_at, resolved_at, metadata.

ArticleArticleId (UUIDv7), fields: title, content, tags, published, created_at, metadata.

NpsResponseNpsResponseId (UUIDv7), fields: score (0-10), feedback, respondent, created_at, metadata.

Wrapper: dept-support

  • SupportDepartment struct with OnceLock<Arc<SupportEngine>> for lazy initialization
  • register() creates the engine, stores it, and registers agent tools
  • shutdown() delegates to engine
  • 2 unit tests (department creation, manifest purity)

Registered Tools

Tool NameDescriptionParameters
support.tickets.createCreate a support ticketsession_id, subject, description, priority, requester_email
support.tickets.listList support tickets for a sessionsession_id
support.knowledge.searchSearch knowledge base articles by keywordsession_id, query
support.nps.calculate_scoreCalculate Net Promoter Scoresession_id

Events

Event KindConstantDescription
support.ticket.createdTICKET_CREATEDA new support ticket was opened
support.ticket.resolvedTICKET_RESOLVEDA ticket was resolved
support.article.publishedARTICLE_PUBLISHEDA knowledge base article was published
support.nps.recordedNPS_RECORDEDAn NPS response was recorded

Note: event constants are defined in support_engine::events but emission is not yet wired into manager methods (skeleton status).

Required Ports

PortOptional
StoragePortNo
EventPortNo
AgentPortNo
JobPortNo

UI Contribution

Tabs: actions, agents, skills, rules, events

No dashboard cards, settings panel, or custom components.

Chat Flow

sequenceDiagram
    participant User
    participant Frontend
    participant API as /api/dept/support/chat
    participant Agent as AgentPort
    participant Engine as SupportEngine
    participant Store as ObjectStore

    User->>Frontend: "Show open tickets sorted by priority"
    Frontend->>API: POST (SSE)
    API->>Agent: run(system_prompt + user message)
    Agent->>Engine: support.tickets.list(session_id)
    Engine->>Store: objects().list("support_ticket", filter)
    Store-->>Engine: Vec<Ticket>
    Engine-->>Agent: ticket list JSON
    Agent-->>API: SSE token stream
    API-->>Frontend: SSE events
    Frontend-->>User: Formatted ticket list with priorities

CLI Usage

rusvel support status         # Show department status
rusvel support list            # List all support items
rusvel support list --kind ticket  # List tickets only
rusvel support events          # Show recent support events

Testing

cargo test -p support-engine   # Engine tests (ticket CRUD, health check)
cargo test -p dept-support     # Wrapper tests (manifest, department creation)

Current Status: Skeleton

The Support department is fully registered and bootable within the RUSVEL department registry, but its business logic is minimal. Here is what exists and what remains to be built:

What exists:

  • Manager structures with basic CRUD operations (create + list for tickets, articles, NPS)
  • Domain types with full serialization (Ticket, Article, NpsResponse)
  • NPS score calculation logic (promoters minus detractors percentage)
  • 4 agent tools registered in the scoped tool registry
  • Knowledge base search with keyword filtering (in-memory, case-insensitive)
  • Event kind constants defined (but not yet emitted from manager methods)
  • Engine implements the Engine trait with health check
  • Unit tests for engine and wrapper

What needs to be built for production readiness:

  • Wire emit_event() calls into manager methods so domain events actually fire
  • Add resolve_ticket, assign_ticket operations to TicketManager
  • Implement auto-triage: use AgentPort to classify incoming tickets by priority/category
  • Add search_articles with proper full-text search (currently keyword match only)
  • Build customer success metrics aggregation (ticket resolution time, first-response time)
  • Add job kinds for async support workflows (e.g., JobKind::Custom("support.auto_triage"))
  • Implement AI-assisted KB article generation via AgentPort
  • Add engine-specific API routes (e.g., /api/dept/support/tickets, /api/dept/support/nps)
  • Add engine-specific CLI commands (e.g., rusvel support triage, rusvel support kb)
  • Add personas for support agent specialization (triage agent, KB writer)
  • Add skills and rules for support workflows
  • Build NPS trend analysis and reporting

Source Files

FileLinesPurpose
crates/support-engine/src/lib.rs391Engine struct, capabilities, tests
crates/support-engine/src/ticket.rsTicket domain type + TicketManager
crates/support-engine/src/knowledge.rsArticle domain type + KnowledgeManager
crates/support-engine/src/nps.rsNpsResponse domain type + NpsManager
crates/dept-support/src/lib.rs89DepartmentApp implementation
crates/dept-support/src/manifest.rs97Static manifest definition
crates/dept-support/src/tools.rs163Agent tool registration

Infra Department

CI/CD pipelines, deployments, monitoring, incident response, performance, cost analysis.

FieldValue
IDinfra
Icon>
Colorred
Engine crateinfra-engine (~390 lines)
Wrapper cratedept-infra
StatusSkeleton

Overview

The Infra department manages infrastructure operations: deployment tracking, health monitoring, and incident management. It wraps infra-engine via the ADR-014 DepartmentApp pattern and provides tools for recording deployments, running health checks, and managing incidents with severity levels.

System Prompt

You are the Infrastructure department of RUSVEL.

Focus: CI/CD pipelines, deployments, monitoring, incident response, performance, cost analysis.

Capabilities

CapabilityDescription
deployRecord and track deployments across services and environments
monitorManage health checks with status tracking (Up, Down, Degraded)
incidentOpen, track, and resolve incidents with severity levels (P1-P4)

Quick Actions

LabelPrompt
Deploy status“Show current deployment status across all services and environments.”
Health check“Run health checks on all monitored services.”
Incident report“Show open incidents with severity, timeline, and resolution status.”

Architecture

Engine: infra-engine

Three manager structs compose the engine, each backed by ObjectStore via StoragePort:

ManagerDomain TypeObject KindMethods
DeployManagerDeploymentinfra_deployrecord_deployment, list_deployments
MonitorManagerHealthCheckinfra_healthcheckadd_check, list_checks
IncidentManagerIncidentinfra_incidentopen_incident, list_incidents

Domain Types

DeploymentDeploymentId (UUIDv7), DeployStatus enum (Pending, InProgress, Deployed, Failed, RolledBack), fields: service, version, environment, deployed_at, created_at, metadata.

HealthCheckHealthCheckId (UUIDv7), CheckStatus enum (Up, Down, Degraded), fields: service, endpoint, status, last_checked, response_ms, metadata.

IncidentIncidentId (UUIDv7), IncidentStatus enum (Open, Investigating, Mitigated, Resolved), Severity enum (P1, P2, P3, P4), fields: title, description, severity, status, opened_at, resolved_at, metadata.

Wrapper: dept-infra

  • InfraDepartment struct with OnceLock<Arc<InfraEngine>> for lazy initialization
  • register() creates the engine, stores it, and registers agent tools
  • shutdown() delegates to engine
  • 2 unit tests (department creation, manifest purity)

Registered Tools

Tool NameDescriptionParameters
infra.deploy.triggerRecord a deployment (starts as Pending)session_id, service, version, environment
infra.monitor.statusList health checks with status summarysession_id
infra.incidents.createOpen an incidentsession_id, title, description, severity
infra.incidents.listList incidents for a sessionsession_id

Events

Event KindConstantDescription
infra.deploy.completedDEPLOY_COMPLETEDA deployment was completed
infra.alert.firedALERT_FIREDA monitoring alert was triggered
infra.incident.openedINCIDENT_OPENEDA new incident was opened
infra.incident.resolvedINCIDENT_RESOLVEDAn incident was resolved

Note: event constants are defined in infra_engine::events but emission is not yet wired into manager methods (skeleton status).

Required Ports

PortOptional
StoragePortNo
EventPortNo
AgentPortNo
JobPortNo

UI Contribution

Tabs: actions, agents, rules, events

No dashboard cards, settings panel, or custom components.

Chat Flow

sequenceDiagram
    participant User
    participant Frontend
    participant API as /api/dept/infra/chat
    participant Agent as AgentPort
    participant Engine as InfraEngine
    participant Store as ObjectStore

    User->>Frontend: "Deploy rusvel-api v0.3.0 to production"
    Frontend->>API: POST (SSE)
    API->>Agent: run(system_prompt + user message)
    Agent->>Engine: infra.deploy.trigger(service, version, environment)
    Engine->>Store: objects().put("infra_deploy", ...)
    Store-->>Engine: Ok
    Engine-->>Agent: { deployment_id }
    Agent->>Engine: infra.monitor.status(session_id)
    Engine->>Store: objects().list("infra_healthcheck", filter)
    Store-->>Engine: Vec<HealthCheck>
    Engine-->>Agent: health summary JSON
    Agent-->>API: SSE token stream
    API-->>Frontend: SSE events
    Frontend-->>User: "Deployment recorded. All services healthy."

CLI Usage

rusvel infra status           # Show department status
rusvel infra list              # List all infra items
rusvel infra list --kind deploy  # List deployments only
rusvel infra events            # Show recent infra events

Testing

cargo test -p infra-engine     # Engine tests (deployment CRUD, health check)
cargo test -p dept-infra       # Wrapper tests (manifest, department creation)

Current Status: Skeleton

The Infra department is fully registered and bootable within the RUSVEL department registry, but its business logic is minimal. Here is what exists and what remains to be built:

What exists:

  • Manager structures with basic CRUD operations (create + list for each domain)
  • Domain types with full serialization (Deployment, HealthCheck, Incident)
  • Health check status summary tool (counts down/degraded services)
  • 4 agent tools registered in the scoped tool registry
  • Event kind constants defined (but not yet emitted from manager methods)
  • Engine implements the Engine trait with health check
  • Unit tests for engine and wrapper

What needs to be built for production readiness:

  • Wire emit_event() calls into manager methods so domain events actually fire
  • Add mark_deployed, rollback operations to DeployManager
  • Add run_health_check (actual HTTP probe) to MonitorManager
  • Add resolve_incident, status transition logic to IncidentManager
  • Implement deployment pipeline orchestration via AgentPort + JobPort
  • Add job kinds for async infra workflows (e.g., JobKind::Custom("infra.deploy"), JobKind::Custom("infra.health_scan"))
  • Build real monitoring: periodic health check scheduling via job queue
  • Integrate with rusvel-deploy crate for actual deployment execution
  • Add incident timeline tracking (status change history)
  • Add engine-specific API routes (e.g., /api/dept/infra/deploys, /api/dept/infra/incidents)
  • Add engine-specific CLI commands (e.g., rusvel infra deploy, rusvel infra health)
  • Add personas for infra agent specialization (SRE agent, incident commander)
  • Add skills and rules for incident response runbooks
  • Build cost analysis and performance metrics aggregation

Source Files

FileLinesPurpose
crates/infra-engine/src/lib.rs390Engine struct, capabilities, tests
crates/infra-engine/src/deploy.rsDeployment domain type + DeployManager
crates/infra-engine/src/monitor.rsHealthCheck domain type + MonitorManager
crates/infra-engine/src/incident.rsIncident domain type + IncidentManager
crates/dept-infra/src/lib.rs89DepartmentApp implementation
crates/dept-infra/src/manifest.rs96Static manifest definition
crates/dept-infra/src/tools.rs178Agent tool registration

Flow Department

DAG-based workflow automation – create, execute, and monitor directed acyclic graph workflows.

FieldValue
IDflow
Icon~
Colorsky
Engine crateflow-engine (~600 lines)
Dept cratedept-flow
StatusWired – full business logic with DAG execution, checkpointing, and resume

Overview

The Flow department provides a general-purpose DAG workflow engine built on petgraph. Workflows are defined as directed acyclic graphs of typed nodes (code, condition, agent, browser, parallel) connected by edges. The engine supports:

  • Topological execution with parallel branch support
  • Condition-based branching (true/false output ports)
  • Agent nodes that delegate to AgentPort for LLM-driven steps
  • Browser trigger and action nodes (CDP integration)
  • Parallel evaluate nodes for multi-agent comparison
  • Checkpoint persistence for resume after failure
  • Single-node retry from checkpoint state

Engine Details

Crate: flow-engine (~600 lines in lib.rs, plus submodules for executor, expression, nodes, templates)

Struct: FlowEngine

Constructor:

#![allow(unused)]
fn main() {
FlowEngine::new(
    storage: Arc<dyn StoragePort>,
    events: Arc<dyn EventPort>,
    agent: Arc<dyn AgentPort>,
    terminal: Option<Arc<dyn TerminalPort>>,
    browser: Option<Arc<dyn BrowserPort>>,
)
}

Public methods:

MethodDescription
node_types()List registered node type names
save_flow(flow)Persist a FlowDef to storage
get_flow(id)Retrieve a flow definition by ID
list_flows()List all saved flow definitions
delete_flow(id)Remove a flow definition
run_flow(id, trigger_data)Execute a flow, return FlowExecution
resume_flow(execution_id)Resume from checkpoint (same execution ID)
retry_node(execution_id, node_id)Re-run a single node using checkpoint state
get_checkpoint(execution_id)Load the current checkpoint for an execution
get_execution(id)Get an execution record by ID
list_executions(flow_id)List all executions for a flow

Storage keys: flows, flow_executions, flow_checkpoints

Node Types

TypeModuleDescription
codenodes::codeReturns a literal value from parameters
conditionnodes::conditionBranches on a boolean result (true/false output ports)
agentnodes::agentDelegates to AgentPort for LLM execution
browser_triggernodes::browserTriggers browser automation via CDP
browser_actionnodes::browserExecutes browser actions via BrowserPort
parallel_evaluatenodes::parallelMulti-agent parallel evaluation (gated by RUSVEL_FLOW_PARALLEL_EVALUATE=1)

Manifest

Declared in dept-flow/src/manifest.rs:

id:            "flow"
name:          "Flow Department"
description:   "DAG workflow automation -- create, execute, and monitor workflows"
icon:          "~"
color:         "sky"
capabilities:  ["workflow_automation"]

System Prompt

You are the Flow department of RUSVEL.

Focus: DAG-based workflow automation.
Create, execute, and monitor directed acyclic graph workflows
with code, condition, and agent nodes.

Tools

Tools registered at runtime via dept-flow/src/tools.rs (7 tools):

ToolParametersDescription
flow.saveflow (FlowDef JSON)Save a flow definition
flow.getflow_idLoad a flow definition by ID
flow.list(none)List all saved flow definitions
flow.runflow_id, trigger_dataRun a flow with trigger payload
flow.resumeexecution_idResume a flow from checkpoint
flow.get_executionexecution_idGet an execution record by ID
flow.list_executionsflow_idList executions for a flow

Manifest also declares 3 tool contributions for discovery:

  • flow.create – Create a new DAG workflow definition
  • flow.execute – Execute a saved workflow by ID
  • flow.list – List all saved workflows

Personas

NameRoleDefault ModelAllowed Tools
workflow-architectDAG workflow designer and automation expertsonnetflow.create, flow.execute, flow.list

Skills

NameDescriptionTemplate
Workflow DesignDesign a DAG workflow from requirementsDesign a workflow for: {{goal}}\n\nBreak it into discrete steps with conditions and dependencies.

Rules

None declared in the manifest.


Jobs

KindDescriptionRequires Approval
flow.executeExecute a DAG workflowNo

Events

Produced (manifest)

  • flow.started
  • flow.completed
  • flow.failed

Emitted (engine runtime)

  • flow.execution.completed – emitted after run_flow() and resume_flow() with payload { flow_id, execution_id, status }

Consumed

None.


API Routes

Declared in manifest (7 routes):

MethodPathDescription
GET/api/flowsList all flow definitions
POST/api/flowsCreate a new flow definition
GET/api/flows/{id}Get a flow definition by ID
PUT/api/flows/{id}Update a flow definition
DELETE/api/flows/{id}Delete a flow definition
POST/api/flows/{id}/executeExecute a flow
GET/api/flows/{id}/executionsList executions for a flow

Additional route in rusvel-api (not in manifest):

  • GET /api/flows/node-types – returns available node types (filters parallel_evaluate unless RUSVEL_FLOW_PARALLEL_EVALUATE=1)

CLI Commands

Manifest declares one command:

CommandArgsDescription
executeflow_id (required)Execute a flow by ID

Entity Auto-Discovery

The standard CRUD subsystems are available for the Flow department at the parameterized /api/dept/flow/* routes:

  • Agents, Skills, Rules, Hooks, Workflows, MCP Servers

These are auto-discovered from the department registry. No additional engine-specific entities.


Chat Flow

sequenceDiagram
    participant U as User
    participant API as /api/dept/flow/chat
    participant Agent as AgentRuntime
    participant Tool as flow.* tools
    participant Engine as FlowEngine
    participant Store as StoragePort
    participant Bus as EventPort

    U->>API: POST message (SSE)
    API->>Agent: run_streaming(system_prompt, tools)
    Agent->>Tool: flow.list()
    Tool->>Engine: list_flows()
    Engine->>Store: objects.list("flows")
    Store-->>Engine: Vec<FlowDef>
    Engine-->>Tool: flows
    Tool-->>Agent: JSON response
    Agent->>Tool: flow.run(flow_id, trigger_data)
    Tool->>Engine: run_flow(id, data)
    Engine->>Store: get_flow(id)
    Engine->>Engine: execute DAG (topological order)
    Engine->>Store: put execution record
    Engine->>Bus: emit flow.execution.completed
    Bus-->>Engine: EventId
    Engine-->>Tool: FlowExecution
    Tool-->>Agent: execution result
    Agent-->>API: SSE chunks
    API-->>U: streamed response

Extending the Department

Adding a new node type

  1. Create a new module under flow-engine/src/nodes/ implementing the FlowNode trait
  2. Register it in FlowEngine::new() via registry.register(Arc::new(YourNode))
  3. The node becomes available in node_types() and usable in flow definitions

Adding new tools

Add tool registrations in dept-flow/src/tools.rs inside the register() function. Follow the existing pattern of cloning the engine Arc and using ctx.tools.add().

Adding events

Define event kind constants in the engine and emit them via self.events.emit(Event { ... }). Add the event kind strings to the manifest’s events_produced vector.


Port Dependencies

PortRequiredUsage
StoragePortYesFlow definitions, executions, checkpoints (via ObjectStore)
EventPortYesEmit flow.execution.completed events
AgentPortYesAgent node execution within flows
JobPortYesBackground flow execution via job queue

Optional ports (not in manifest requires_ports but accepted by engine constructor):

  • TerminalPort – PTY management for terminal-based nodes
  • BrowserPort – CDP browser automation for browser trigger/action nodes

Messaging Department

Outbound notifications and channel adapters (e.g. Telegram) when configured.

FieldValue
IDmessaging
Icon@
Colorviolet
Engine crateNone (wrapper-only)
Wrapper cratedept-messaging (~52 lines)
StatusWrapper-only

Overview

The Messaging department is a lightweight department shell with no dedicated engine crate. It exists to represent outbound notification capabilities in the department registry and provide a namespace for future multi-channel routing. Channel composition (Telegram, future Slack/Discord/email) is handled at the rusvel-app composition root, not inside an engine.

System Prompt

You are the Messaging department of RUSVEL.

Focus: outbound notifications, channel configuration, delivery status, and integration with external chat platforms when available.

Capabilities

CapabilityDescription
notifySend outbound notifications via configured channels
channelConfigure and manage messaging channel adapters

Quick Actions

LabelPrompt
Notify status“Summarize configured outbound channels and recent notification delivery health.”
Channel setup“Explain how to configure outbound messaging (e.g. Telegram) via environment and config.”

Why There Is No Engine

Unlike other departments that wrap a dedicated *-engine crate, Messaging has no messaging-engine. This is an intentional architectural choice:

  1. Channel composition belongs at the app level. The ChannelPort trait is defined in rusvel-core and implemented by adapter crates (e.g., rusvel-channel). The composition root (rusvel-app/src/main.rs) wires the channel adapter into AppState based on environment variables. This follows the hexagonal pattern: the port lives in core, the adapter lives in its own crate, and wiring happens at the composition root.

  2. No domain logic yet. Sending a notification is a single send_message() call on ChannelPort. There is no complex domain state (no entities to CRUD, no workflows to orchestrate) that would justify an engine crate.

  3. Registered last. dept-messaging is the last entry in installed_departments() in rusvel-app/src/boot.rs, ensuring all domain departments are registered before the channel shell.

ChannelPort and rusvel-channel

The Port (rusvel-core)

#![allow(unused)]
fn main() {
// crates/rusvel-core/src/ports.rs
#[async_trait]
pub trait ChannelPort: Send + Sync {
    fn channel_kind(&self) -> &'static str;
    async fn send_message(&self, session_id: &SessionId, payload: serde_json::Value) -> Result<()>;
}
}

ChannelPort is one of the 22 port traits in rusvel-core. It defines a minimal interface: identify the channel kind (e.g., "telegram") and send a message with a JSON payload scoped to a session.

The Adapter: TelegramChannel (rusvel-channel)

rusvel-channel provides TelegramChannel, the first ChannelPort implementation. It sends messages via the Telegram Bot API (sendMessage endpoint).

Configuration (environment variables):

VariableRequiredDescription
RUSVEL_TELEGRAM_BOT_TOKENYesTelegram Bot API token (from @BotFather)
RUSVEL_TELEGRAM_CHAT_IDNoDefault destination chat ID; can be overridden per-message in payload

Initialization: TelegramChannel::from_env() returns Option<Arc<TelegramChannel>>. It returns None when RUSVEL_TELEGRAM_BOT_TOKEN is unset or empty, so the app gracefully degrades when Telegram is not configured.

Payload format:

{
  "text": "Message body (required; 'message' is also accepted as alias)",
  "chat_id": "Optional override for default chat ID"
}

Message format sent to Telegram: [session <session_id>] <text>

Wiring in the Composition Root

In rusvel-app/src/main.rs, the channel is wired into AppState:

#![allow(unused)]
fn main() {
let channel: Option<Arc<dyn ChannelPort>> = TelegramChannel::from_env()
    .map(|t| t as Arc<dyn ChannelPort>);
}

The AppState.channel field is Option<Arc<dyn ChannelPort>>, making it available to API handlers.

The Notify API Endpoint

POST /api/system/notify

Request body:

{
  "session_id": "uuid",
  "text": "Notification message"
}

Behavior:

  • If state.channel is Some, wraps text into {"text": text} and calls channel.send_message()
  • If state.channel is None, returns 503 Service Unavailable with "notify channel not configured"
  • On Telegram API failure, returns 502 Bad Gateway with the error message
  • On success, returns 204 No Content

Example:

curl -X POST http://localhost:3000/api/system/notify \
  -H "Content-Type: application/json" \
  -d '{"session_id": "...", "text": "Build completed successfully"}'

Required Ports

PortOptional
StoragePortNo
EventPortNo
AgentPortNo
JobPortNo

These ports are inherited defaults from the DepartmentManifest structure. The messaging department does not currently use any of them directly – it has no register() logic beyond logging.

UI Contribution

Tabs: actions, agents, rules, events

No dashboard cards, settings panel, or custom components.

Chat Flow

sequenceDiagram
    participant User
    participant Frontend
    participant API as /api/dept/messaging/chat
    participant Agent as AgentPort

    User->>Frontend: "What channels are configured?"
    Frontend->>API: POST (SSE)
    API->>Agent: run(system_prompt + user message)
    Note over Agent: No engine tools available.<br/>Agent answers from system prompt<br/>and general knowledge.
    Agent-->>API: SSE token stream
    API-->>Frontend: SSE events
    Frontend-->>User: Channel configuration explanation

Note: since no engine tools are registered, the messaging agent operates purely through its system prompt and general knowledge. It cannot currently inspect actual channel configuration at runtime.

Notify Flow (System Endpoint)

sequenceDiagram
    participant Caller
    participant API as POST /api/system/notify
    participant State as AppState
    participant Channel as ChannelPort
    participant Telegram as Telegram API

    Caller->>API: { session_id, text }
    API->>State: state.channel?
    alt Channel configured
        State-->>API: Some(channel)
        API->>Channel: send_message(session_id, {text})
        Channel->>Telegram: POST /bot<token>/sendMessage
        Telegram-->>Channel: 200 OK
        Channel-->>API: Ok
        API-->>Caller: 204 No Content
    else No channel
        State-->>API: None
        API-->>Caller: 503 Service Unavailable
    end

Boot Order

installed_departments() order:
  1. forge       (core)
  2. code        (core)
  3. content     (cross-dept events)
  4. harvest     (cross-dept events)
  5. flow        (cross-dept events)
  6. gtm         (progressive enhancement)
  7. finance     (progressive enhancement)
  8. product     (progressive enhancement)
  9. growth      (progressive enhancement)
 10. distro      (progressive enhancement)
 11. legal       (skeleton)
 12. support     (skeleton)
 13. infra       (skeleton)
 14. messaging   <-- registered last

CLI Usage

rusvel messaging status       # Show department status (via generic dept handler)
rusvel messaging list          # List items (empty -- no domain objects)
rusvel messaging events        # Show recent messaging events (none emitted currently)

Testing

cargo test -p dept-messaging   # Wrapper test (manifest ID check, port requirements)

No engine tests since there is no engine crate.

Future: Multi-Channel Routing

This is where multi-channel routing would live as the department matures:

  • Slack adapter – Implement ChannelPort for Slack Webhook/API, register in rusvel-channel
  • Discord adapter – Implement ChannelPort for Discord Webhook
  • Email adapter – Implement ChannelPort for SMTP (complement to the RUSVEL_SMTP_* config already used by GTM outreach)
  • Channel router – A MultiChannelRouter that wraps multiple ChannelPort implementations and routes based on message type, urgency, or user preference
  • Messaging engine – Once routing logic becomes complex enough, extract a messaging-engine crate with delivery queuing, retry policies, and delivery status tracking
  • Delivery events – Emit messaging.sent, messaging.failed, messaging.delivered events for observability
  • Department tools – Register tools like messaging.send, messaging.channels.list, messaging.delivery.status

Source Files

FileLinesPurpose
crates/dept-messaging/src/lib.rs52DepartmentApp implementation (wrapper-only)
crates/dept-messaging/src/manifest.rs101Static manifest definition
crates/rusvel-channel/src/lib.rs7ChannelPort re-export + module declaration
crates/rusvel-channel/src/telegram.rs107TelegramChannel adapter implementation
crates/rusvel-core/src/ports.rs(line 542)ChannelPort trait definition
crates/rusvel-api/src/system.rs(line 66)POST /api/system/notify handler

Reference

Technical reference documentation for RUSVEL’s interfaces.

Repository status

The canonical metrics, feature inventory, and gaps for the RUSVEL codebase live in the main repository under docs/. This page summarizes what shipped as of the last audit and links to the full Markdown (always current on main).

Canonical sources (GitHub)

DocumentPurpose
docs/status/current-state.mdSingle written source of truth — numbers, what works E2E, gaps, re-verify commands
docs/README.mdDocumentation index — which folder is truth vs plans vs scratch
docs/status/verification-log-2026-03-30.mdClaim → evidence for the latest metrics snapshot

When this mdBook page and the repo diverge, trust the repo files above and refresh the tables below on the next audit.


Metric definitions (abbrev.)

TermMeaning
Workspace membersPackages in root Cargo.toml [workspace].members
HTTP route chainsLines with .route( in crates/rusvel-api/src/lib.rs (one line can register get().post())
API modules*.rs files in rusvel-api/src/ except lib.rs

Numbers at a glance (2026-03-30)

MetricCount
Workspace members55
Rust LOC (crates/*.rs)~73,058
Rust source files (crates/)301
Tests (approx., full cargo test)~645
HTTP route chains in API router153
API modules (rusvel-api, excl. lib.rs)39
Port traits (rusvel-core/src/ports.rs)22
Departments / dept-* crates14 / 14
Engines13 (6 wired + 7 skeletons)

Gaps (explicit)

  • GTM / CRM depth — OutreachSend job path is wired (approval-gated, gtm-engine); more CRM surfaces and polish remain.
  • Auth — not full API middleware; env/in-memory style for many paths.
  • Depth — Several business engines are thinner than Forge/Code/Content/Harvest/Flow; department chat still works via DepartmentApp.

How to re-verify

cargo build
cargo test
cargo metadata --format-version 1 --no-deps | python3 -c "import json,sys; print(len(json.load(sys.stdin)['workspace_members']))"
find crates -name '*.rs' | wc -l
wc -l $(find crates -name '*.rs') | tail -1
rg '\.route\(' crates/rusvel-api/src/lib.rs | wc -l

Some tests (e.g. terminal PTY) may fail in restricted environments.

CLI

Overview

RUSVEL provides a three-tier CLI using Clap 4. The binary is rusvel (or cargo run -- during development).

rusvel <dept> <action>     # Tier 1: One-shot commands (12 depts; 14 apps in API/UI — see below)
rusvel shell               # Tier 2: Interactive REPL (reedline, autocomplete, history)
rusvel --tui               # Tier 3: TUI dashboard (ratatui, 4-panel layout)

Global Options

FlagDescription
--mcpStart the MCP server (stdio JSON-RPC) instead of the web server
--tuiLaunch the TUI dashboard (ratatui, 4-panel layout)
--helpShow help information
--versionShow version

Starting the Server

rusvel (no subcommand)

Starts the API web server on 0.0.0.0:3000. Serves the REST API and the embedded SvelteKit frontend.

cargo run

rusvel --mcp

Starts the MCP (Model Context Protocol) server over stdio. Used for integration with Claude Desktop and other MCP clients.

cargo run -- --mcp

rusvel --tui

Launches the TUI dashboard with 4 panels: Tasks, Goals, Pipeline, Events. Press q to exit.

cargo run -- --tui

Tier 1: Department One-Shot Commands

These one-shot actions are registered for 12 departments (Tier 1). The web app and API register 14 DepartmentApp instances (flow and messaging are not in this Tier 1 list).

rusvel <dept> status              # Show department status summary
rusvel <dept> list [--kind X]     # List department items
rusvel <dept> events              # Show recent events for department

Tier 1 departments: forge, code, content, harvest, gtm, finance, product, growth, distro, legal, support, infra

Engine-Specific Commands

Some departments have additional commands powered by their wired engines:

# Code department
rusvel code analyze [path]        # Analyze code: parser, dependency graph, metrics
rusvel code search <query>        # BM25 search across codebase

# Content department
rusvel content draft <topic>      # Draft content on a topic
rusvel content from-code          # Generate content from code analysis

# Harvest department
rusvel harvest pipeline           # Show opportunity pipeline

Forge Commands

rusvel forge mission today        # Generate a prioritized daily plan
rusvel forge mission goals        # List all goals for active session
rusvel forge mission goal add <title>  # Add a new goal
rusvel forge mission review       # Generate a periodic review

Options for goal add:

FlagDefaultDescription
--description""Goal description
--timeframemonthOne of: day, week, month, quarter

Options for review:

FlagDefaultDescription
--periodweekOne of: day, week, month, quarter

Tier 2: Interactive REPL

rusvel shell

Launches an interactive shell powered by reedline with:

  • Tab completion for commands and department names
  • Ctrl+R history search
  • use <dept> to switch department context
  • All Tier 1 commands available without the rusvel prefix

Session Management

rusvel session create <name>      # Create a new session
rusvel session list               # List all sessions
rusvel session switch <id>        # Switch active session

The CLI stores the active session ID in ~/.rusvel/active_session. All forge commands operate on this session.

If no active session is set, commands that require one will error:

Error: No active session. Run `rusvel session create <name>` first.

Active Session

The CLI stores the active session ID in ~/.rusvel/active_session. All forge commands operate on this session. Change it with session switch.

API

Overview

RUSVEL exposes a JSON REST API on port 3000 via Axum. All endpoints use the /api/ prefix. CORS is enabled for all origins.

Scale (verify on main): the main router in crates/rusvel-api/src/lib.rs registers 153 .route( chains. Handler logic is split across 39 modules (one *.rs per module, excluding lib.rs). That is not the same as “153 HTTP methods” — a single chain can register get().post().

Router modules: agents, analytics, approvals, auth, browser, build_cmd, capability, chat, config, cron, db_routes, department, engine_routes, flow_routes, help, hook_dispatch, hooks, jobs, kits, knowledge, mcp_servers, pipeline_runner, playbooks, routes, rules, skills, system, terminal, visual_report, webhooks, workflows.

For canonical metrics and gaps, see Repository status.


Core

MethodPathDescription
GET/api/healthHealth check
GET/api/briefExecutive brief
POST/api/brief/generateGenerate brief
GET/api/configGlobal configuration
PUT/api/configUpdate configuration
GET/api/config/modelsList LLM models
GET/api/config/toolsList tools
GET/api/analyticsAnalytics dashboard data
GET/api/profileUser profile
PUT/api/profileUpdate profile

Sessions

MethodPathDescription
GET/api/sessionsList sessions
POST/api/sessionsCreate session
GET/api/sessions/{id}Get session
GET/api/sessions/{id}/mission/todayDaily plan
GET/api/sessions/{id}/mission/goalsList goals
POST/api/sessions/{id}/mission/goalsCreate goal
GET/api/sessions/{id}/eventsQuery events

Chat (God agent)

MethodPathDescription
POST/api/chatSend message (SSE)
GET/api/chat/conversationsList conversations
GET/api/chat/conversations/{id}Conversation history

Departments

Replace {dept} with: forge, code, content, harvest, gtm, finance, product, growth, distro, legal, support, infra, flow, messaging.

MethodPathDescription
GET/api/departmentsList department definitions
POST/api/dept/{dept}/chatDepartment chat (SSE)
GET/api/dept/{dept}/chat/conversationsList conversations
GET/api/dept/{dept}/chat/conversations/{id}History
GET/api/dept/{dept}/configDepartment config
PUT/api/dept/{dept}/configUpdate config
GET/api/dept/{dept}/eventsDepartment events

Engine-specific routes

MethodPathDescription
POST/api/dept/code/analyzeCode analysis
GET/api/dept/code/searchBM25 search
POST/api/dept/content/draftDraft content
POST/api/dept/content/from-codeContent from code analysis
PATCH/api/dept/content/{id}/approveApprove content item
POST/api/dept/content/publishPublish (may require approval)
GET/api/dept/content/listList content items
POST/api/dept/harvest/scoreScore opportunity
POST/api/dept/harvest/scanScan sources
POST/api/dept/harvest/proposalGenerate proposal
GET/api/dept/harvest/pipelinePipeline
GET/api/dept/harvest/listList harvest items

Flow engine (DAG)

MethodPathDescription
GET / POST/api/flowsList / create flows
GET / PUT / DELETE/api/flows/{id}Get / update / delete
POST/api/flows/{id}/runRun flow
GET/api/flows/{id}/executionsList executions
GET/api/flows/{id}/executions/{exec_id}/panesExecution panes
GET/api/flows/executions/{id}Get execution
POST/api/flows/executions/{id}/resumeResume
POST/api/flows/executions/{id}/retry/{node_id}Retry node
GET/api/flows/executions/{id}/checkpointCheckpoint
GET/api/flows/node-typesList node types

Playbooks

MethodPathDescription
GET/api/playbooks/runsList runs
GET/api/playbooks/runs/{run_id}Get run
GET / POST/api/playbooksList / create playbook
GET/api/playbooks/{id}Get playbook
POST/api/playbooks/{id}/runRun playbook

Starter kits

MethodPathDescription
GET/api/kitsList kits
GET/api/kits/{id}Get kit
POST/api/kits/{id}/installInstall kit

Knowledge / RAG

MethodPathDescription
GET/api/knowledgeList entries
POST/api/knowledge/ingestIngest
POST/api/knowledge/searchSemantic search
POST/api/knowledge/hybrid-searchHybrid search
GET/api/knowledge/statsStats
GET/api/knowledge/relatedRelated entries
DELETE/api/knowledge/{id}Delete entry

Database (RusvelBase)

MethodPathDescription
GET/api/db/tablesList tables
GET/api/db/tables/{table}/schemaTable schema
GET/api/db/tables/{table}/rowsTable rows
POST/api/db/sqlRun SQL

Approvals

MethodPathDescription
GET/api/approvalsPending jobs
POST/api/approvals/{id}/approveApprove
POST/api/approvals/{id}/rejectReject

Agents, skills, rules, workflows, MCP, hooks

Standard REST under /api/agents, /api/skills, /api/rules, /api/workflows, /api/mcp-servers, /api/hooks. Individual resource endpoints include GET /api/mcp-servers/{id} and GET /api/hooks/{id}. Workflows: POST /api/workflows/{id}/run. Hooks: GET /api/hooks/events for event types.

Capability and help

MethodPathDescription
POST/api/capability/buildCapability / !build bundle
POST/api/helpAI help

System and visual regression

MethodPathDescription
POST/api/system/testRun tests
POST/api/system/buildRun build
GET/api/system/statusStatus
POST/api/system/fixSelf-fix
POST/api/system/ingest-docsIngest docs
GET / POST/api/system/visual-reportVisual reports
POST/api/system/visual-report/self-correctSelf-correct from diffs
POST/api/system/visual-testRun visual tests

Terminal

MethodPathDescription
GET/api/terminal/dept/{dept_id}Dept terminal pane
GET/api/terminal/runs/{run_id}/panesRun panes
GET/api/terminal/wsTerminal WebSocket

Browser (CDP)

MethodPathDescription
GET/api/browser/statusStatus
POST/api/browser/connectConnect
GET/api/browser/tabsTabs
POST/api/browser/observe/{tab}Observe
GET/api/browser/capturesCaptures
GET/api/browser/captures/streamCapture stream
POST/api/browser/actAction

Configuration

Directory Structure

RUSVEL stores its configuration and data in ~/.rusvel/:

~/.rusvel/
├── active_session       # UUID of the currently active session
├── config.toml          # Global configuration
├── profile.toml         # User profile (name, email, bio)
└── rusvel.db            # SQLite database (WAL mode)

Config Hierarchy

Configuration follows a three-layer cascade. More specific layers override less specific ones:

Global config  →  Department config  →  Session config

For example, you can set a default model globally, override it for the Code department to use a more capable model, and further override it for a specific session.

Global Configuration (config.toml)

# Default LLM model for all departments
model = "claude-sonnet-4-20250514"

# Default effort level: "low", "medium", "high"
effort = "medium"

# API server settings
[server]
host = "0.0.0.0"
port = 3000

# LLM provider configuration
[llm]
default_provider = "ollama"

[llm.ollama]
base_url = "http://localhost:11434"

[llm.openai]
# Key loaded from OPENAI_API_KEY env var
model = "gpt-4o"

[llm.claude]
# Key loaded from ANTHROPIC_API_KEY env var
model = "claude-sonnet-4-20250514"

User Profile (profile.toml)

name = "Your Name"
email = "you@example.com"
bio = "Solo founder building products with AI"
company = "Your Company"

The profile is used by agents for context – the Content department knows your name for bylines, the GTM department uses your company name in outreach, etc.

Department Configuration

Department-level config is set via the API or the Settings page in the UI:

# Set Code department to use high effort and a specific model
curl -X PUT http://localhost:3000/api/dept/code/config \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-20250514",
    "effort": "high",
    "permission_mode": "default",
    "add_dirs": ["."]
  }'

Department Config Options

KeyTypeDescription
modelstringLLM model to use for this department
effortstringResponse depth: low, medium, high
permission_modestringTool permissions: default, restricted
add_dirsstring[]Working directories (for Code department)
system_promptstringOverride the department system prompt
max_turnsnumberMax conversation turns per request

Department Registry (DepartmentApp Pattern)

Since ADR-014, departments are no longer configured via TOML files. Each department is a self-contained dept-* crate implementing the DepartmentApp trait, which declares its capabilities via a DepartmentManifest:

#![allow(unused)]
fn main() {
pub struct DepartmentManifest {
    pub id: String,              // e.g., "code"
    pub name: String,            // e.g., "Code"
    pub title: String,           // e.g., "Code Intelligence"
    pub icon: String,            // e.g., "terminal"
    pub color: String,           // e.g., "emerald"
    pub system_prompt: String,   // Department-specific system prompt
    pub capabilities: Vec<String>,
    pub tabs: Vec<String>,
    pub quick_actions: Vec<QuickAction>,
}
}

The host (rusvel-app) collects manifests from all 14 dept-* crates at boot to generate the DepartmentRegistry. Adding a new department means adding a new dept-* crate – zero changes to rusvel-core.

Environment Variables

VariablePurpose
ANTHROPIC_API_KEYClaude API authentication
OPENAI_API_KEYOpenAI API authentication
RUSVEL_DB_PATHNot implemented in Rust binary — DB file is {data_dir}/rusvel.db (default ~/.rusvel/rusvel.db).
RUSVEL_CONFIG_DIROverride config directory (default: ~/.rusvel)
RUST_LOGLogging level (e.g., info, debug, rusvel_api=debug)
RUSVEL_RATE_LIMITAPI rate limit in requests/second (default: 100)
RUSVEL_API_READ_TOKENRead-only API bearer token (GET/HEAD/OPTIONS only)

Database

RUSVEL uses SQLite in WAL (Write-Ahead Logging) mode for concurrent reads. The database file is at ~/.rusvel/rusvel.db by default.

The database stores: sessions, goals, events, conversations, agents, skills, rules, workflows, hooks, MCP server configs, and analytics data. Migrations run automatically on startup.

Architecture

RUSVEL’s hexagonal architecture and design decisions.

  • Overview — Ports & adapters, layers, dependency graph
  • Decisions (ADRs) — 14 architecture decision records with rationale

Overview

Design Philosophy

RUSVEL follows hexagonal architecture (ports and adapters). The core principle: domain logic never depends on infrastructure. Engines express what they need through port traits; adapters provide the implementations.

Four Layers

┌─────────────────── SURFACES ───────────────────┐
│  CLI (Clap)  │  TUI (Ratatui)  │  Web (Svelte) │
│                  MCP Server                      │
└──────────────────────┬─────────────────────────┘
                       │
┌──────────────────────┴─────────────────────────┐
│              DOMAIN ENGINES (13)                 │
│                                                 │
│  Forge         │ Code       │ Harvest           │
│  (+ Mission)   │            │                   │
│                │            │                   │
│  Content       │ GoToMarket                     │
│                │ (Ops+Connect+Outreach)          │
└──────────────────────┬─────────────────────────┘
                       │ uses (traits only)
┌──────────────────────┴─────────────────────────┐
│              FOUNDATION                          │
│                                                 │
│  ┌──────────── rusvel-core ──────────────┐      │
│  │  Port Traits + Shared Domain Types    │      │
│  └───────────────────────────────────────┘      │
│                                                 │
│  ┌──────────── Adapters ─────────────────┐      │
│  │  rusvel-llm     (model providers)     │      │
│  │  rusvel-agent   (agent runtime)       │      │
│  │  rusvel-db      (SQLite + 5 stores)   │      │
│  │  rusvel-event   (event bus + persist)  │      │
│  │  rusvel-memory  (context + search)    │      │
│  │  rusvel-tool    (tool registry)       │      │
│  │  rusvel-jobs    (central job queue)   │      │
│  │  rusvel-auth    (credentials)         │      │
│  │  rusvel-config  (settings)            │      │
│  └───────────────────────────────────────┘      │
└─────────────────────────────────────────────────┘

Layer 1: rusvel-core (Foundation)

The heart of the system. Contains:

  • 22 port traits in ports.rs — includes five *Store subtraits under the storage model, BrowserPort, ChannelPort, RusvelBasePort, and primary ports (LlmPort, AgentPort, ToolPort, EventPort, StoragePort, MemoryPort, JobPort, SessionPort, AuthPort, ConfigPort, EmbeddingPort, VectorStorePort, DeployPort, TerminalPort). DepartmentApp is defined under department/.
  • ~114 pub struct / pub enum in domain.rs, plus shared types — Session, Goal, Event, Agent, Content, Opportunity, Contact, Task, DepartmentManifest, etc. (see docs/status/current-state.md §1).
  • Zero framework dependencies

Layer 2: Adapters

Concrete implementations of the port traits:

CrateImplementsNotes
rusvel-llmLlmPort4 providers: Ollama, OpenAI, Claude API, Claude CLI
rusvel-agentAgentPortWraps LLM + Tool + Memory into orchestration
rusvel-dbStoragePortSQLite WAL with 5 sub-stores, migrations
rusvel-eventEventPortEvent bus with persistence
rusvel-memoryMemoryPortFTS5 session-namespaced search
rusvel-toolToolPortTool registry with JSON Schema
rusvel-jobsJobPortCentral SQLite job queue
rusvel-authAuthPortIn-memory credential storage from env
rusvel-configConfigPortTOML config with per-session overrides
rusvel-embedEmbeddingPortLocal embeddings via fastembed
rusvel-vectorVectorStorePortLanceDB vector store
rusvel-deployDeployPortDeployment adapter
rusvel-terminalTerminalPortTerminal interaction adapter
rusvel-builtin-toolsBuilt-in tools for agents (file, shell, git, etc.) + optional tool_search meta-tool
rusvel-cdpChrome DevTools Protocol client (Browser/CDP wiring)
rusvel-engine-toolsEngine-specific tool wiring
rusvel-mcp-clientMCP client for external MCP servers
rusvel-schemaDatabase schema introspection (RusvelBase)

Layer 3: Engines

Domain logic crates. Each engine depends only on rusvel-core traits:

EngineFocus
forge-engineAgent orchestration + Mission (goals, planning, reviews)
code-engineCode intelligence: parser, dependency graph, BM25 search, metrics
harvest-engineOpportunity discovery: scanning, scoring, proposals, pipeline
content-engineContent creation: writer, calendar, platform adapters, analytics
gtm-engineGoToMarket: CRM, outreach sequences, invoicing, deal stages
finance-engineLedger, runway calculator, tax estimation
product-engineRoadmap, pricing analysis, feedback aggregation
growth-engineFunnel analysis, cohort tracking, KPI dashboard
distro-engineSEO, marketplace listings, affiliate channels
legal-engineContract drafting, compliance checks, IP management
support-engineTicket management, knowledge base, NPS tracking
infra-engineDeployment, monitoring, incident response
flow-engineDAG workflow engine: petgraph, code/condition/agent nodes

Each engine also has a corresponding dept-* wrapper crate (e.g. dept-forge, dept-code, dept-finance) that implements the DepartmentApp trait (ADR-014), declaring the department’s manifest, tools, and registration logic. There are 14 dept-* crates (13 engine-backed departments + dept-messaging). Of the 13 engines, 6 are fully wired (Forge, Code, Content, Harvest, GTM, Flow) and 7 are skeletons (Finance, Product, Growth, Distro, Legal, Support, Infra).

Layer 4: Surfaces

User-facing interfaces that wire adapters into engines:

SurfaceTechnologyStatus
rusvel-apiAxum HTTPActive
rusvel-cliClap 4Active
rusvel-mcpstdio JSON-RPCActive
rusvel-tuiRatatuiActive
frontend/SvelteKit 5 + Tailwind 4Active

Composition Root: rusvel-app

The binary entry point. It constructs all adapters, injects them into engines, and starts the chosen surface (web server, CLI, or MCP). This is the only place where concrete types meet.

Workspace Layout

rusvel/
├── crates/                   55 workspace members
│   ├── rusvel-core/          22 port traits in ports.rs + domain types + DepartmentApp
│   ├── rusvel-db/            SQLite WAL + 5 canonical stores
│   ├── rusvel-llm/           4 LLM providers
│   ├── rusvel-agent/         Agent runtime (LLM+Tool+Memory)
│   ├── rusvel-event/         Event bus + persistence
│   ├── rusvel-memory/        FTS5 session-namespaced search
│   ├── rusvel-tool/          Tool registry + JSON Schema
│   ├── rusvel-builtin-tools/ Built-in + meta tools for agent execution
│   ├── rusvel-engine-tools/  Engine-specific tool wiring
│   ├── rusvel-mcp-client/    MCP client for external servers
│   ├── rusvel-jobs/          Central job queue
│   ├── rusvel-auth/          Credential storage
│   ├── rusvel-config/        TOML config + overrides
│   ├── rusvel-deploy/        Deployment port adapter
│   ├── rusvel-embed/         Text embedding (fastembed)
│   ├── rusvel-vector/        Vector store (LanceDB)
│   ├── rusvel-schema/        Database schema introspection
│   ├── rusvel-terminal/      Terminal interaction adapter
│   ├── rusvel-cdp/           CDP client (browser automation)
│   ├── forge-engine/         Agent orchestration + Mission
│   ├── code-engine/          Code intelligence
│   ├── harvest-engine/       Opportunity discovery
│   ├── content-engine/       Content creation + publishing
│   ├── gtm-engine/           GoToMarket (CRM + outreach)
│   ├── finance-engine/       Ledger, runway, tax
│   ├── product-engine/       Roadmap, pricing, feedback
│   ├── growth-engine/        Funnel, cohorts, KPIs
│   ├── distro-engine/        SEO, marketplace, affiliates
│   ├── legal-engine/         Contracts, compliance, IP
│   ├── support-engine/       Tickets, knowledge base, NPS
│   ├── infra-engine/         Deploy, monitor, incidents
│   ├── flow-engine/          DAG workflow engine
│   ├── dept-forge/           DepartmentApp wrapper for Forge
│   ├── dept-code/            DepartmentApp wrapper for Code
│   ├── dept-harvest/         ... (14 dept-* wrappers total)
│   ├── dept-flow/            DepartmentApp wrapper for Flow
│   ├── rusvel-api/           Axum HTTP API
│   ├── rusvel-cli/           Clap CLI + REPL
│   ├── rusvel-tui/           Ratatui TUI
│   ├── rusvel-mcp/           MCP server (stdio JSON-RPC)
│   └── rusvel-app/           Binary entry point
├── frontend/                 SvelteKit 5 + Tailwind 4
├── Cargo.toml                Workspace manifest (55 members)
└── CLAUDE.md                 Project conventions

Key Rules

  1. Engines never import adapter crates. They receive port implementations via constructor injection.
  2. Engines never call LlmPort directly. They use AgentPort, which wraps LLM + Tool + Memory (ADR-009).
  3. All domain types have metadata: serde_json::Value for schema evolution without migrations (ADR-007).
  4. Event.kind is a String, not an enum. Engines define their own constants (ADR-005).
  5. Single job queue for all async work. No per-engine scheduling (ADR-003).
  6. Human approval gates on content publishing and outreach sending (ADR-008).
  7. Each crate stays under 2000 lines. Single responsibility.

Port traits (rusvel-core/src/ports.rs)

There are 21 pub trait definitions in ports.rs (including the five *Store subtraits and ChannelPort). The table below lists the main contracts; see the source for the exact set.

PortResponsibility
LlmPortRaw model access: generate, stream, embed
AgentPortAgent orchestration: create, run, stop, status
ToolPortTool registry + execution
EventPortSystem-wide typed event bus (append-only)
StoragePort5 canonical sub-stores (see below)
EventStoreAppend-only event log
ObjectStoreCRUD for domain objects
SessionStoreSession/Run/Thread hierarchy
JobStoreJob queue persistence
MetricStoreTime-series metrics
MemoryPortContext, knowledge, semantic search
JobPortCentral job queue with approval support
SessionPortSession hierarchy management
AuthPortOpaque credential handles
ConfigPortSettings and preferences
EmbeddingPortText embedding (fastembed)
VectorStorePortVector storage and search (LanceDB)
DeployPortDeployment operations
TerminalPortTerminal interaction and display
BrowserPortBrowser/CDP observation and actions
ChannelPortOutbound notification channels (Telegram, etc.)

Decisions (ADRs)

Overview

RUSVEL’s architecture is guided by 14 Architecture Decision Records (ADRs). These capture the “why” behind key design choices. All were informed by cross-validation with multiple AI systems and real-world constraints of a solo founder product.

ADR-001: Merge Ops + Connect into GoToMarket Engine

Status: Accepted

Context: Original design had 7 engines including separate Ops (CRM, invoicing, SOPs) and Connect (outreach, sequences, follow-ups) engines. Perplexity review flagged that 7 is too many for a solo founder product.

Decision: Merge into single GoToMarket engine. CRM, outreach, pipeline, invoicing all live together.

Consequence: 5 engines instead of 7. GoToMarket engine is larger but cohesive around “find and close business.”

ADR-002: Fold Mission into Forge Engine

Status: Accepted

Context: Mission (goals, daily planning, reviews) was a separate engine. Mission is really “orchestrated agents” – goal-setting, planning, and reviews are all agent tasks.

Decision: Mission becomes a sub-module of the Forge engine.

Consequence: rusvel forge mission today instead of rusvel mission today. Forge is the meta-engine that orchestrates everything.

ADR-003: Single Central Job Queue

Status: Accepted

Context: Original design had AutomationPort (workflows), SchedulePort (cron/triggers), plus Forge (agent orchestration) and per-engine scheduling. This created 4 overlapping workflow substrates.

Decision: Single JobPort with SQLite-backed job queue. All async work goes through one queue. Worker pool routes jobs to engines by JobKind.

Consequence: One place to see all pending/running/completed work. Simpler debugging.

ADR-004: StoragePort with 5 Canonical Stores

Status: Accepted

Context: The original StoragePort was “persist anything” with generic put/get/delete/query. Too broad – would re-invent a typed repository layer.

Decision: StoragePort exposes 5 sub-stores: EventStore (append-only), ObjectStore (CRUD), SessionStore (hierarchy), JobStore (queue semantics), MetricStore (time-series).

Consequence: Clear boundaries. Each store optimized for its access pattern.

ADR-005: Event.kind as String, not Enum

Status: Accepted

Context: The original EventKind was a giant enum forcing rusvel-core to know every event type.

Decision: Event.kind is a String. Engines define their own event kind constants (e.g., forge::AGENT_CREATED).

Consequence: rusvel-core stays minimal. New engines define new event kinds without modifying core.

ADR-006: HarvestPort and PublishPort Are Engine-Internal

Status: Accepted

Context: The original design had HarvestPort and PublishPort as core ports. But scraping and publishing are domain-specific, not cross-cutting concerns.

Decision: Move to engine-internal traits. Harvest engine defines its own source adapters. Content engine defines its own platform adapters.

Consequence: Core ports reduced from 13 to 10 (later grew to 19 with new ports). Cleaner separation.

ADR-007: metadata: serde_json::Value on All Domain Types

Status: Accepted

Context: Schema evolution concern – domain types will iterate frequently.

Decision: All domain types include metadata: serde_json::Value for extensibility without migrations.

Consequence: Base columns + metadata JSON pattern. Huge flexibility for evolution.

ADR-008: Human-in-the-Loop Approval Model

Status: Accepted

Context: For a solo founder, the most valuable pattern is “agent proposes, human approves.” Without this, the system either does too much (scary) or too little (useless).

Decision: ApprovalStatus and ApprovalPolicy are core domain types. Content publishing and outreach require approval by default. Agents can be auto-approved below cost thresholds.

Consequence: JobStatus includes AwaitingApproval. The UI shows an approval queue. Agents pause and present results for review.

ADR-009: Engines Never Call LlmPort Directly

Status: Accepted

Context: Unclear boundaries between LlmPort (raw model access) and AgentPort (orchestration). If engines call both, prompting/retries/tool selection logic gets scattered.

Decision: LlmPort is raw (generate, stream, embed). AgentPort wraps LlmPort + ToolPort + MemoryPort. Engines only use AgentPort.

Consequence: All prompt construction, tool selection, retries, and memory injection happen in one place (rusvel-agent).

ADR-010: Engines Depend Only on rusvel-core Traits

Status: Accepted

Context: Hexagonal architecture rule. Engines must not import concrete adapter types.

Decision: Engines depend on rusvel-core. They receive port implementations via constructor injection. rusvel-app (composition root) wires concrete adapters to engines.

Consequence: Any adapter can be swapped without touching engine code. Engines are testable with mock ports.

ADR-011: Department Registry — Dynamic Departments Replace Hardcoded Routing

Status: Accepted (superseded by ADR-014 for registration mechanism)

Context: Scaling from 5 engines to many departments (the registry now holds 14 apps) with hardcoded routes meant 72+ routes and touching 7 files to add one department.

Decision: DepartmentRegistry holds DepartmentDef structs. 6 parameterized /api/dept/{dept}/* routes replace all per-department routes. Frontend uses a single dynamic [dept] route.

Consequence: Adding a department = adding a DepartmentDef entry. Zero route changes. Three-layer config cascade (Global -> Department -> Session).

ADR-012: shadcn/ui Design Tokens — oklch Color System

Status: Accepted

Context: Frontend needed a consistent design system. Tailwind 4 supports oklch natively.

Decision: Adopt shadcn/ui design tokens with oklch color values. Each department gets a color token from the registry.

Consequence: Consistent theming across all department UIs. Dark mode is a CSS variable swap.

ADR-013: Capability Engine — AI-Driven Entity Creation

Status: Accepted

Context: Users need to extend the system with new agents, skills, rules, MCP servers, hooks, and workflows. Manually configuring each is tedious.

Decision: POST /api/capability/build accepts natural language, uses Claude with WebSearch/WebFetch to discover resources, generates a bundle of entities. Also available via !build <description> in chat.

Consequence: One-shot system extension. “Install a GitHub code review agent” creates agent, skills, hooks, and MCP config in one call.

ADR-014: DepartmentApp Pattern — Departments as Self-Contained Crates

Status: Accepted

Context: The EngineKind enum forced rusvel-core to know about every department, violating the Open/Closed Principle. Adding a department touched 5+ files.

Decision: Introduce DepartmentApp trait and DepartmentManifest struct. Each department lives in its own dept-* crate. EngineKind enum is removed entirely; departments use string IDs. 14 dept-* crates including dept-messaging (registered last at boot).

Consequence: Adding a department = adding a dept-* crate. Zero changes to rusvel-core. Each department declares its own routes, tools, capabilities, and system prompt via DepartmentManifest. Supersedes the registration mechanism of ADR-011.

Testing

Manual and automated testing guides for every RUSVEL surface.

Manual Testing Playbook

Complete feature-by-feature testing guide with expected behavior for every RUSVEL surface.

RUSVEL exposes 6 surfaces — each needs manual verification:

SurfaceEntry pointRequires Ollama
CLI One-Shotcargo run -- <command>Some commands
REPL Shellcargo run -- shellNo
TUI Dashboardcargo run -- --tuiNo
Web APIcurl localhost:3000/api/*Chat endpoints
Web FrontendBrowser at localhost:3000Chat pages
MCP Servercargo run -- --mcpSome tools

Testing Strategy

  1. Start with the Smoke Test Checklist — 30 steps, covers all surfaces in ~15 minutes
  2. Deep-dive per surface — Use the dedicated pages for thorough testing
  3. Engine-specific testsCode, Content, Harvest, GTM, Flow each have unique endpoints
  4. Cross-surface verification — Perform an action in one surface, verify it in another:
    • Create a session via CLI -> verify via curl /api/sessions -> verify in browser UI
    • Chat via web UI -> verify conversation via API -> check events in TUI

Quick Reference

WhatWhere
Build & prerequisitesSetup
CLI, REPL, TUICLI Testing
REST API (130+ routes)API Testing
Browser UI (30+ pages)Frontend Testing
Code, Content, Harvest, GTM, FlowEngine Testing
30-step release gateSmoke Checklist

Setup & Prerequisites

Build the Binary

cargo build

Expected: Compiles 55 workspace members. Zero errors. Warnings are acceptable.

Run Automated Tests First

cargo test

Expected: ~645 tests pass (workspace sum), 0 failures. Some tests may be ignored (PTY tests in sandboxed environments).

Required Services

ServiceHow to startRequired for
Ollamaollama serveLLM-powered features (chat, mission, draft, etc.)
Ollama modelollama pull llama3.2Must have at least one model pulled

Optional Environment Variables

VariablePurposeDefault
RUST_LOGTracing levelinfo
RUSVEL_API_TOKENBearer token for API authNone (open)
RUSVEL_RATE_LIMITAPI rate limit (req/sec)100
RUSVEL_SMTP_HOSTSMTP for outreach emailsMock adapter
RUSVEL_SMTP_PORTSMTP port
RUSVEL_SMTP_USERSMTP username
RUSVEL_SMTP_PASSWORDSMTP password
RUSVEL_TELEGRAM_BOT_TOKENTelegram notificationsDisabled
RUSVEL_FLOW_PARALLEL_EVALUATEEnable parallel flow nodes0
ANTHROPIC_API_KEYClaude API providerClaude CLI fallback
OPENAI_API_KEYOpenAI provider

Data Directory

RUSVEL stores data in ~/.rusvel/:

  • rusvel.db – SQLite database
  • config.toml – configuration
  • shell_history.txt – REPL history
  • lance/ – vector store data

To reset to clean state: rm -rf ~/.rusvel/ (destructive – removes all data).

Docker Alternative

docker compose up

Expected: Starts RUSVEL on :3000 + Ollama on :11434. No local install needed.

Environment Combos

Minimal (no AI)

cargo run
# Test all non-LLM features: CRUD, DB browser, config, etc.

Full Local

ollama serve &
ollama pull llama3.2
cargo run
# All features available

With Claude API

export ANTHROPIC_API_KEY=sk-ant-...
cargo run
# Uses Claude for higher-quality responses

With Notifications

export RUSVEL_TELEGRAM_BOT_TOKEN=...
cargo run
# POST /api/system/notify sends Telegram messages

Testing Tools

ToolUse forInstall
curlAPI testingPre-installed on macOS
jqJSON formattingbrew install jq
websocatWebSocket testingbrew install websocat
httpieFriendlier API callsbrew install httpie
justTask runner recipesbrew install just
watchexecFile-watching re-runscargo install watchexec-cli

httpie Examples (alternative to curl)

# GET
http :3000/api/health
http :3000/api/departments

# POST
http POST :3000/api/sessions name=test kind=General

# SSE streaming
http --stream POST :3000/api/chat message="hello"

CLI, REPL & TUI Testing

CLI One-Shot Commands

Help

cargo run -- --help

Expected: Shows usage, subcommands (session, forge, shell, brief, browser, finance, growth, distro, legal, support, infra, product, code, harvest, content, gtm), and flags (--mcp, --mcp-http, --tui).

Session Management

# Create a session
cargo run -- session create "test-project"

Expected: Prints session ID (UUID). Session is now active.

# List sessions
cargo run -- session list

Expected: Table showing session ID, name, kind, created_at. Should include “test-project”.

# Switch session
cargo run -- session switch <session-id>

Expected: Confirms switch to the specified session.

Forge / Mission

# Daily mission plan (requires Ollama)
cargo run -- forge mission today

Expected: AI-generated daily plan with prioritized tasks. Streams text to stdout. May take 5-30s depending on model.

# List goals
cargo run -- forge mission goals

Expected: Table of goals (may be empty on fresh install).

# Add a goal
cargo run -- forge mission goal add "Launch MVP" \
  --description "Ship v1 to production" --timeframe month

Expected: Prints goal ID. Goal appears in subsequent goals listing.

# Periodic review
cargo run -- forge mission review --period week

Expected: AI-generated weekly review (requires Ollama).

Executive Brief

cargo run -- brief

Expected: Generates an executive daily digest summarizing activity across departments.

Department Commands (Generic)

Every department supports status, list, and events:

# Status (repeat for each department)
cargo run -- finance status
cargo run -- growth status
cargo run -- code status
cargo run -- content status
cargo run -- harvest status
cargo run -- gtm status
cargo run -- product status
cargo run -- distro status
cargo run -- legal status
cargo run -- support status
cargo run -- infra status

Expected: Each prints a status summary. May show “No active items” on fresh install.

# List with kind filter
cargo run -- finance list --kind transactions
cargo run -- support list --kind tickets
cargo run -- legal list --kind contracts
cargo run -- growth list --kind funnel_stages
cargo run -- product list --kind features

Expected: Table of domain objects. Empty on fresh install, but no error.

# Events
cargo run -- finance events --limit 5

Expected: Recent events for that department. Empty on fresh install.

Engine-Specific Commands

# Code: analyze current directory
cargo run -- code analyze .

Expected: Prints analysis results (file count, complexity metrics, dependency info). Works without Ollama.

# Code: search symbols
cargo run -- code search "DepartmentApp"

Expected: BM25 search results showing matching symbols/definitions.

# Content: draft a topic (requires Ollama)
cargo run -- content draft "Why Rust is great for CLI tools"

Expected: AI-generated markdown draft. Streams to stdout.

# Harvest: pipeline stats
cargo run -- harvest pipeline

Expected: Pipeline stage counts (Cold, Contacted, Qualified, etc.). All zeros on fresh install.

Browser Commands

# Check browser status (no Chrome needed)
cargo run -- browser status

Expected: Shows “Not connected” or connection info.

# Connect to Chrome (requires Chrome with --remote-debugging-port=9222)
cargo run -- browser connect

Expected: Connects to Chrome DevTools Protocol endpoint.


Interactive REPL Shell

cargo run -- shell

Expected: Launches interactive prompt: rusvel>

REPL Commands

CommandExpected Behavior
helpLists all available commands
statusOverview across all departments
use financeSwitches context -> prompt becomes rusvel:finance>
status (in dept)Shows finance-specific status
list transactionsLists finance transactions
eventsShows finance events
backReturns to top-level rusvel>
use codeSwitches to code department
session listLists all sessions
session create fooCreates a new session
exit or Ctrl+DExits the REPL

REPL Features

FeatureHow to testExpected
Tab completionType us then press TabCompletes to use
Department completionType use then TabShows all 14 departments
HistoryType a command, exit, re-enter shellUp arrow recalls previous commands
History searchPress Ctrl+R, type partial commandFinds matching history entry

TUI Dashboard

cargo run -- --tui

Expected: Full-screen terminal dashboard with 4 panels.

TUI Panels

PanelContentExpected
Tasks (top-left)Active tasks with priority markersShows tasks or “No active tasks”
Goals (top-right)Goals with progress barsShows goals or “No goals”
Pipeline (bottom-left)Opportunity counts by stageShows stages with counts
Events (bottom-right)Recent system eventsShows recent events or empty

TUI Keybindings

KeyExpected
q or EscExits TUI cleanly, returns to terminal
tToggles terminal pane focus
Arrow keys (in terminal mode)Navigate between terminal panes

TUI Verification

  • No crash on empty data – Fresh install should render all panels without panic
  • Resize handling – Resize terminal window; panels should reflow
  • Clean exit – Terminal should restore to normal state after q

API Testing

Start the server first:

cargo run

Expected: Server starts on http://localhost:3000. Logs show boot sequence: database init, department registration (14 departments), tool registration, job worker started.

Health & System

curl -s http://localhost:3000/api/health | jq .

Expected: {"status": "ok"} or similar health response.

curl -s http://localhost:3000/api/system/status | jq .

Expected: JSON with system info (uptime, departments, sessions, etc.).

Sessions

# Create session
curl -s -X POST http://localhost:3000/api/sessions \
  -H 'Content-Type: application/json' \
  -d '{"name": "api-test", "kind": "General"}' | jq .

Expected: Returns JSON with id (UUID), name, kind, created_at.

# List sessions
curl -s http://localhost:3000/api/sessions | jq .

Expected: Array of session objects.

# Get single session
curl -s http://localhost:3000/api/sessions/<id> | jq .

Expected: Single session object with full details.

Departments

# List all departments
curl -s http://localhost:3000/api/departments | jq .

Expected: Array of 14 department objects, each with id, name, description, icon, color, capabilities, quick_actions.

# Department config
curl -s http://localhost:3000/api/dept/code/config | jq .

Expected: Code department configuration (system prompt, model, etc.).

# Department events
curl -s http://localhost:3000/api/dept/forge/events | jq .

Expected: Array of events (may be empty).

Agents CRUD

# List agents
curl -s http://localhost:3000/api/agents | jq .

Expected: Array with seeded agents (rust-engine, svelte-ui, test-writer, arch-reviewer, etc.).

# Create agent
curl -s -X POST http://localhost:3000/api/agents \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-agent",
    "role": "Test",
    "instructions": "You are a test agent",
    "model": "llama3.2"
  }' | jq .

Expected: Returns created agent with id.

# Get / Update / Delete
curl -s http://localhost:3000/api/agents/<id> | jq .

curl -s -X PUT http://localhost:3000/api/agents/<id> \
  -H 'Content-Type: application/json' \
  -d '{"name": "updated-agent", "instructions": "Updated"}' | jq .

curl -s -X DELETE http://localhost:3000/api/agents/<id>

Expected: Standard CRUD responses (200 with entity, 204 on delete).

Skills CRUD

# List skills
curl -s http://localhost:3000/api/skills | jq .

Expected: Array with seeded skills (/analyze-architecture, /check-crate-sizes, etc.).

# Create skill
curl -s -X POST http://localhost:3000/api/skills \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-skill",
    "description": "A test skill",
    "action": "echo {{input}}"
  }' | jq .

Expected: Returns created skill with id. The {{input}} placeholder is preserved.

Rules CRUD

curl -s http://localhost:3000/api/rules | jq .

Expected: Array with seeded rules (architecture boundaries, crate size, etc.).

curl -s -X POST http://localhost:3000/api/rules \
  -H 'Content-Type: application/json' \
  -d '{"name": "test-rule", "description": "Test", "content": "Always test"}' | jq .

Hooks CRUD

curl -s http://localhost:3000/api/hooks | jq .
# Create a command hook
curl -s -X POST http://localhost:3000/api/hooks \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-hook",
    "event_kind": "code.chat.completed",
    "hook_type": "command",
    "action": "echo hook fired"
  }' | jq .
# List hook events (what events can hooks fire on)
curl -s http://localhost:3000/api/hooks/events | jq .

MCP Servers CRUD

curl -s http://localhost:3000/api/mcp-servers | jq .

curl -s -X POST http://localhost:3000/api/mcp-servers \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-mcp",
    "command": "node",
    "args": ["./mcp-server.js"],
    "description": "Test MCP server"
  }' | jq .

Workflows CRUD + Execution

# List workflows
curl -s http://localhost:3000/api/workflows | jq .

Expected: Array with seeded self-improvement workflow.

# Create workflow
curl -s -X POST http://localhost:3000/api/workflows \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-workflow",
    "steps": [
      {"name": "step1", "action": "echo step 1"},
      {"name": "step2", "action": "echo step 2"}
    ]
  }' | jq .
# Run workflow (requires Ollama for agent steps)
curl -s -X POST http://localhost:3000/api/workflows/<id>/run | jq .

Expected: Returns run status. Job is enqueued.

Chat (God Agent) – SSE Streaming

curl -N http://localhost:3000/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "Hello, what can you do?"}'

Expected SSE events:

data: {"type":"text_delta","text":"I"}
data: {"type":"text_delta","text":" can"}
data: {"type":"text_delta","text":" help"}
...
data: {"type":"done","output":"I can help you with..."}

Verify: Stream starts within 2-5s. Text deltas arrive incrementally. Stream ends with a done event.

Department Chat – SSE Streaming

curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "What files are in this project?"}'

Expected: SSE stream with department-scoped response. The agent uses code-related tools.

# List conversations
curl -s http://localhost:3000/api/dept/code/chat/conversations | jq .

# Get conversation history
curl -s http://localhost:3000/api/dept/code/chat/conversations/<id> | jq .

Agent Tool-Use Loop

The core loop: LLM generates -> tool call -> execute -> feed result -> repeat.

curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "Read the Cargo.toml and count workspace members"}'

Expected SSE events in order:

  1. text_delta – agent starts responding
  2. tool_call{"name": "read_file", "input": {"path": "Cargo.toml"}}
  3. tool_result – file contents
  4. text_delta – agent interprets results
  5. done – final answer mentions “55 workspace members” (or docs/status/current-state.md)

Scoped Tool Registry

Different departments see different tools:

# Code dept should have code_analyze, code_search
curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "What tools do you have available?"}'

# Content dept should have content_draft, content_publish
curl -N http://localhost:3000/api/dept/content/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "What tools do you have available?"}'

Conversation History

# Send first message
curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "Remember: my favorite number is 42"}'

# Get conversation ID
curl -s http://localhost:3000/api/dept/code/chat/conversations | jq '.[0].id'

# Continue conversation
curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "What is my favorite number?", "conversation_id": "<id>"}'

Expected: Agent recalls “42” from conversation history.

Skills Execution

# Create a skill with input interpolation
curl -s -X POST http://localhost:3000/api/skills \
  -H 'Content-Type: application/json' \
  -d '{"name": "greet", "action": "Say hello to {{input}}"}' | jq .

# Execute via chat
curl -N http://localhost:3000/api/dept/forge/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "/greet World"}'

Expected: Agent resolves skill, interpolates “World” into {{input}}, executes.

Rules Injection

# Create a rule for code department
curl -s -X POST http://localhost:3000/api/rules \
  -H 'Content-Type: application/json' \
  -d '{"name": "always-rust", "department": "code", "content": "Always recommend Rust"}' | jq .

# Chat with code department
curl -N http://localhost:3000/api/dept/code/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "What language should I use for a CLI tool?"}'

Expected: Rule is appended to system prompt. Agent recommends Rust.

Mission / Forge API

# Daily plan (requires Ollama + active session)
curl -s http://localhost:3000/api/sessions/<session-id>/mission/today | jq .

# List goals
curl -s http://localhost:3000/api/sessions/<session-id>/mission/goals | jq .

# Create goal
curl -s -X POST http://localhost:3000/api/sessions/<session-id>/mission/goals \
  -H 'Content-Type: application/json' \
  -d '{"title": "API test goal", "timeframe": "week"}' | jq .

# Generate executive brief
curl -s -X POST http://localhost:3000/api/brief/generate | jq .

# Get latest brief
curl -s http://localhost:3000/api/brief/latest | jq .

Config & Models

curl -s http://localhost:3000/api/config | jq .
curl -s http://localhost:3000/api/config/models | jq .
curl -s http://localhost:3000/api/config/tools | jq .

curl -s -X PUT http://localhost:3000/api/config \
  -H 'Content-Type: application/json' \
  -d '{"default_model": "llama3.2"}' | jq .

Analytics

curl -s http://localhost:3000/api/analytics | jq .
curl -s http://localhost:3000/api/analytics/dashboard | jq .
curl -s http://localhost:3000/api/analytics/spend | jq .

Jobs & Approvals

# List jobs
curl -s http://localhost:3000/api/jobs | jq .

# List pending approvals
curl -s http://localhost:3000/api/approvals | jq .

# Approve / reject a job
curl -s -X POST http://localhost:3000/api/approvals/<job-id>/approve | jq .
curl -s -X POST http://localhost:3000/api/approvals/<job-id>/reject | jq .

Hooks & Events

Command Hook

# Create hook that fires on chat completion
curl -s -X POST http://localhost:3000/api/hooks \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "log-chat",
    "event_kind": "forge.chat.completed",
    "hook_type": "command",
    "action": "echo HOOK_FIRED >> /tmp/rusvel-hook.log"
  }' | jq .

# Trigger by chatting with forge
curl -N http://localhost:3000/api/dept/forge/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "hello"}'

# Verify hook fired
cat /tmp/rusvel-hook.log

Expected: File contains “HOOK_FIRED”.

HTTP Hook

curl -s -X POST http://localhost:3000/api/hooks \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "notify-hook",
    "event_kind": "content.published",
    "hook_type": "http",
    "action": "http://localhost:8080/webhook"
  }' | jq .

Expected: When content is published, POST request sent to the URL with event payload.

User Profile

curl -s http://localhost:3000/api/profile | jq .

curl -s -X PUT http://localhost:3000/api/profile \
  -H 'Content-Type: application/json' \
  -d '{"name": "Mehdi", "skills": {"primary": ["rust", "sveltekit"]}}' | jq .

Help (AI-Powered)

curl -s -X POST http://localhost:3000/api/help \
  -H 'Content-Type: application/json' \
  -d '{"question": "How do I create a content draft?"}' | jq .

Database Browser

# List tables
curl -s http://localhost:3000/api/db/tables | jq .

# Get table schema
curl -s http://localhost:3000/api/db/tables/events/schema | jq .

# Get table rows
curl -s "http://localhost:3000/api/db/tables/events/rows?limit=10" | jq .

# Execute SQL
curl -s -X POST http://localhost:3000/api/db/sql \
  -H 'Content-Type: application/json' \
  -d '{"sql": "SELECT COUNT(*) as count FROM events"}' | jq .

Security check: Destructive SQL should be rejected or run in read-only mode:

curl -s -X POST http://localhost:3000/api/db/sql \
  -H 'Content-Type: application/json' \
  -d '{"sql": "DROP TABLE events"}' | jq .

Knowledge / RAG

# Ingest knowledge
curl -s -X POST http://localhost:3000/api/knowledge/ingest \
  -H 'Content-Type: application/json' \
  -d '{"content": "Rust is a systems programming language.", "source": "manual"}' | jq .

# Search knowledge
curl -s -X POST http://localhost:3000/api/knowledge/search \
  -H 'Content-Type: application/json' \
  -d '{"query": "programming language safety"}' | jq .

# Hybrid search (keyword + semantic)
curl -s -X POST http://localhost:3000/api/knowledge/hybrid-search \
  -H 'Content-Type: application/json' \
  -d '{"query": "Rust safety"}' | jq .

# Stats, related, delete
curl -s http://localhost:3000/api/knowledge/stats | jq .
curl -s "http://localhost:3000/api/knowledge/related?id=<id>" | jq .
curl -s -X DELETE http://localhost:3000/api/knowledge/<id>

Webhook & Cron

Webhooks

# Create webhook
curl -s -X POST http://localhost:3000/api/webhooks \
  -H 'Content-Type: application/json' \
  -d '{"event_kind": "forge.pipeline.requested"}' | jq .

# List / trigger
curl -s http://localhost:3000/api/webhooks | jq .
curl -s -X POST http://localhost:3000/api/webhooks/<id> \
  -H 'Content-Type: application/json' \
  -d '{"session_id": "<session-id>"}' | jq .

Cron Scheduling

# Create schedule
curl -s -X POST http://localhost:3000/api/cron \
  -H 'Content-Type: application/json' \
  -d '{"name": "daily-brief", "cron": "0 9 * * *", "job_kind": "Custom", "payload": {"action": "brief"}}' | jq .

# List / tick / get / update / delete
curl -s http://localhost:3000/api/cron | jq .
curl -s -X POST http://localhost:3000/api/cron/tick | jq .
curl -s http://localhost:3000/api/cron/<id> | jq .
curl -s -X PUT http://localhost:3000/api/cron/<id> -H 'Content-Type: application/json' -d '{"cron": "0 10 * * *"}' | jq .
curl -s -X DELETE http://localhost:3000/api/cron/<id>

Playbooks

# List playbooks
curl -s http://localhost:3000/api/playbooks | jq .

# Create playbook
curl -s -X POST http://localhost:3000/api/playbooks \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "code-to-blog",
    "steps": [
      {"engine": "code", "action": "analyze", "params": {"path": "."}},
      {"engine": "content", "action": "from-code", "params": {"topic": "architecture"}}
    ]
  }' | jq .

# Run / check status
curl -s -X POST http://localhost:3000/api/playbooks/<id>/run | jq .
curl -s http://localhost:3000/api/playbooks/runs | jq .
curl -s http://localhost:3000/api/playbooks/runs/<run-id> | jq .

Starter Kits

curl -s http://localhost:3000/api/kits | jq .
curl -s http://localhost:3000/api/kits/<id> | jq .
curl -s -X POST http://localhost:3000/api/kits/<id>/install | jq .

Terminal & Browser Ports

Terminal

# WebSocket terminal connection
websocat ws://localhost:3000/api/terminal/ws

# Get department terminal pane
curl -s http://localhost:3000/api/terminal/dept/code | jq .

Browser (CDP)

Requires Chrome running with --remote-debugging-port=9222.

curl -s http://localhost:3000/api/browser/status | jq .

curl -s -X POST http://localhost:3000/api/browser/connect \
  -H 'Content-Type: application/json' \
  -d '{"endpoint": "http://localhost:9222"}' | jq .

curl -s http://localhost:3000/api/browser/tabs | jq .

curl -s -X POST http://localhost:3000/api/browser/observe/0 | jq .

curl -s -X POST http://localhost:3000/api/browser/act \
  -H 'Content-Type: application/json' \
  -d '{"action": "click", "selector": "#submit-button"}' | jq .

Notification System

# Requires RUSVEL_TELEGRAM_BOT_TOKEN
curl -s -X POST http://localhost:3000/api/system/notify \
  -H 'Content-Type: application/json' \
  -d '{"message": "Test notification from RUSVEL"}' | jq .

MCP Server

Stdio Transport

cargo run -- --mcp

Expected: Server enters stdio JSON-RPC mode. Reads JSON from stdin, writes to stdout.

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}' \
  | cargo run -- --mcp

HTTP Transport

cargo run -- --mcp-http

Expected: MCP server starts on HTTP with /mcp (POST) and /mcp/sse (GET) endpoints.

MCP Tools

ToolInputExpected
session_list{}Array of sessions
session_create{"name": "mcp-test"}New session with ID
mission_today{"session_id": "<id>"}Daily plan text
mission_goals{"session_id": "<id>"}Array of goals
mission_add_goal{"session_id": "<id>", "title": "MCP goal"}Goal ID
visual_inspect{}Visual test results

Frontend Testing

Start the server (cargo run) and open http://localhost:3000 in a browser.

Pages to Check

URLWhat to verify
/Dashboard loads. Shows department cards, system status, recent activity
/chatChat interface loads. Can type messages, SSE stream works
/dept/forgeForge overview. Shows mission, goals, tasks
/dept/codeCode department. Shows analysis tools
/dept/contentContent department. Shows drafting UI
/dept/harvestHarvest department. Shows pipeline
/dept/gtmGTM department. Shows CRM
/dept/financeFinance department. Shows ledger
/dept/[id]/chatDepartment-scoped chat works for each department
/dept/[id]/agentsAgent CRUD UI – list, create, edit, delete
/dept/[id]/skillsSkills CRUD UI
/dept/[id]/rulesRules CRUD UI
/dept/[id]/hooksHooks CRUD UI
/dept/[id]/mcpMCP server management
/dept/[id]/workflowsWorkflow management
/dept/[id]/configDepartment configuration
/dept/[id]/eventsEvent log
/dept/[id]/actionsQuick actions
/dept/[id]/engineEngine-specific UI
/dept/[id]/terminalTerminal pane
/dept/content/calendarContent calendar view
/dept/harvest/pipelineOpportunity pipeline view
/dept/gtm/contactsCRM contacts
/dept/gtm/dealsCRM deals
/dept/gtm/outreachOutreach sequences
/dept/gtm/invoicesInvoicing
/flowsFlow builder – create/edit DAG workflows
/knowledgeKnowledge base – ingest, search
/approvalsApproval queue – pending human approvals (sidebar badge)
/settingsGlobal settings
/settings/spendCost analytics
/terminalTerminal view
/database/tablesDatabase browser – list tables
/database/schemaSchema viewer
/database/sqlSQL runner

Feature Checklist

FeatureHow to testExpected
NavigationClick each sidebar itemPage loads without error
Department switchingClick different departmentsURL updates, content refreshes
Dark modeToggle theme (if available)CSS variables swap, no broken colors
Chat streamingSend a message in /chatText appears character by character
Tool call cardsChat with code dept, ask about filesToolCallCard renders with tool name + result
Approval cardsCreate content draft needing approvalApprovalCard appears in sidebar badge
Department colorsVisit each departmentEach has its own oklch color accent
Responsive layoutResize browser windowLayout adapts, no overflow
Error handlingVisit /dept/nonexistentError page or redirect, no crash
Command paletteKeyboard shortcut (Cmd+K or similar)CommandPalette opens
OnboardingFresh install, first visitOnboardingChecklist or ProductTour appears

Visual Regression Tests

cd frontend
pnpm install
pnpm test:visual

Expected: Playwright takes screenshots and compares against baselines.

# Update baselines
pnpm test:e2e:update
# AI-powered diff analysis
pnpm test:analyze

Expected: Claude Vision analyzes visual diffs and reports issues.

# Via API
curl -s -X POST http://localhost:3000/api/system/visual-test | jq .

# Self-correction loop
curl -s -X POST http://localhost:3000/api/system/visual-report/self-correct | jq .

Expected: Auto-generates fix skills/rules based on visual diff analysis.

Engine-Specific Testing

Each wired engine has dedicated API endpoints and CLI commands beyond the generic department CRUD.

Code Engine

# Analyze codebase
curl -s -X POST http://localhost:3000/api/dept/code/analyze \
  -H 'Content-Type: application/json' \
  -d '{"path": "."}' | jq .

Expected: Returns analysis with file count, LOC, complexity metrics.

# Search symbols
curl -s "http://localhost:3000/api/dept/code/search?query=DepartmentApp" | jq .

Expected: BM25 ranked results with file paths and line numbers.

# CLI equivalents
cargo run -- code analyze .
cargo run -- code search "DepartmentApp"

Content Engine

# Draft content (requires Ollama)
curl -s -X POST http://localhost:3000/api/dept/content/draft \
  -H 'Content-Type: application/json' \
  -d '{"topic": "Building CLI tools in Rust"}' | jq .

Expected: Returns markdown draft with title, body, metadata.

# Code-to-content pipeline
curl -s -X POST http://localhost:3000/api/dept/content/from-code \
  -H 'Content-Type: application/json' \
  -d '{"path": ".", "topic": "architecture"}' | jq .

Expected: Analyzes code first, then generates content based on analysis.

# List content
curl -s http://localhost:3000/api/dept/content/list | jq .

Expected: Array of content items with status (Draft, Adapted, Scheduled, Published).

# Approve content
curl -s -X PATCH http://localhost:3000/api/dept/content/<id>/approve | jq .

Expected: Content status changes. Approval status updated.

# Schedule content
curl -s -X POST http://localhost:3000/api/dept/content/schedule \
  -H 'Content-Type: application/json' \
  -d '{"content_id": "<id>", "scheduled_at": "2026-04-01T09:00:00Z"}' | jq .

Expected: Content marked as Scheduled with timestamp.

# Publish content
curl -s -X POST http://localhost:3000/api/dept/content/publish \
  -H 'Content-Type: application/json' \
  -d '{"content_id": "<id>"}' | jq .

Expected: Content published (or job created with approval gate).

# List scheduled content
curl -s http://localhost:3000/api/dept/content/scheduled | jq .
# CLI
cargo run -- content draft "test topic"

Harvest Engine

# Scan for opportunities (requires Ollama)
curl -s -X POST http://localhost:3000/api/dept/harvest/scan | jq .

Expected: Scans configured sources, returns discovered opportunities.

# Score an opportunity
curl -s -X POST http://localhost:3000/api/dept/harvest/score \
  -H 'Content-Type: application/json' \
  -d '{"opportunity_id": "<id>"}' | jq .

Expected: Returns score (0.0-1.0) with scoring rationale.

# Generate proposal
curl -s -X POST http://localhost:3000/api/dept/harvest/proposal \
  -H 'Content-Type: application/json' \
  -d '{"opportunity_id": "<id>"}' | jq .

Expected: AI-generated proposal draft (approval-gated).

# Pipeline stats
curl -s http://localhost:3000/api/dept/harvest/pipeline | jq .

Expected: Counts by stage (Cold, Contacted, Qualified, ProposalSent, Won, Lost).

# List opportunities
curl -s http://localhost:3000/api/dept/harvest/list | jq .
# Advance opportunity
curl -s -X POST http://localhost:3000/api/dept/harvest/advance \
  -H 'Content-Type: application/json' \
  -d '{"opportunity_id": "<id>", "stage": "Contacted"}' | jq .

Expected: Opportunity stage updated.

# Record outcome
curl -s -X POST http://localhost:3000/api/dept/harvest/outcome \
  -H 'Content-Type: application/json' \
  -d '{"opportunity_id": "<id>", "outcome": "Won", "value": 5000.0}' | jq .

Expected: Outcome recorded with value.

# List outcomes
curl -s http://localhost:3000/api/dept/harvest/outcomes | jq .
# CLI
cargo run -- harvest pipeline

GTM Engine

# Create contact
curl -s -X POST http://localhost:3000/api/dept/gtm/contacts \
  -H 'Content-Type: application/json' \
  -d '{"name": "Alice", "emails": ["alice@example.com"], "company": "Acme"}' | jq .

Expected: Contact created with ID.

# List contacts
curl -s http://localhost:3000/api/dept/gtm/contacts | jq .

# List deals
curl -s http://localhost:3000/api/dept/gtm/deals | jq .

# Advance deal
curl -s -X POST http://localhost:3000/api/dept/gtm/deals/advance \
  -H 'Content-Type: application/json' \
  -d '{"deal_id": "<id>", "stage": "Qualified"}' | jq .

Outreach Sequences

# Create sequence
curl -s -X POST http://localhost:3000/api/dept/gtm/outreach/sequences \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "cold-intro",
    "steps": [
      {"kind": "email", "template": "Hi {{name}}, ..."},
      {"kind": "follow_up", "delay_days": 3, "template": "Following up..."}
    ]
  }' | jq .

# List sequences
curl -s http://localhost:3000/api/dept/gtm/outreach/sequences | jq .

# Activate sequence
curl -s -X POST http://localhost:3000/api/dept/gtm/outreach/sequences/<id>/activate | jq .

# Execute outreach (approval-gated)
curl -s -X POST http://localhost:3000/api/dept/gtm/outreach/execute | jq .

Invoicing

# Create invoice
curl -s -X POST http://localhost:3000/api/dept/gtm/invoices \
  -H 'Content-Type: application/json' \
  -d '{
    "contact_id": "<id>",
    "amount": 5000.00,
    "description": "Consulting services",
    "due_date": "2026-04-15"
  }' | jq .

# Get invoice
curl -s http://localhost:3000/api/dept/gtm/invoices/<id> | jq .

# List invoices
curl -s http://localhost:3000/api/dept/gtm/invoices | jq .

# Update status
curl -s -X POST http://localhost:3000/api/dept/gtm/invoices/<id>/status \
  -H 'Content-Type: application/json' \
  -d '{"status": "sent"}' | jq .

Flow Engine (DAG Workflows)

CRUD

# Create a flow
curl -s -X POST http://localhost:3000/api/flows \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "test-flow",
    "nodes": [
      {"id": "start", "type": "code", "config": {"command": "echo start"}},
      {"id": "check", "type": "condition", "config": {"expression": "true"}},
      {"id": "end", "type": "code", "config": {"command": "echo done"}}
    ],
    "edges": [
      {"from": "start", "to": "check"},
      {"from": "check", "to": "end"}
    ]
  }' | jq .

Expected: Flow created with ID. DAG is validated (no cycles).

curl -s http://localhost:3000/api/flows | jq .
curl -s http://localhost:3000/api/flows/<id> | jq .
curl -s -X PUT http://localhost:3000/api/flows/<id> \
  -H 'Content-Type: application/json' \
  -d '{"name": "updated-flow"}' | jq .
curl -s -X DELETE http://localhost:3000/api/flows/<id>

Execution

# Run a flow
curl -s -X POST http://localhost:3000/api/flows/<id>/run | jq .

Expected: Returns execution ID. Nodes execute in topological order.

# List executions
curl -s http://localhost:3000/api/flows/<id>/executions | jq .

# Get execution details
curl -s http://localhost:3000/api/flows/executions/<exec-id> | jq .

# Get checkpoint
curl -s http://localhost:3000/api/flows/executions/<exec-id>/checkpoint | jq .

# Resume suspended execution
curl -s -X POST http://localhost:3000/api/flows/executions/<exec-id>/resume | jq .

# Retry a failed node
curl -s -X POST http://localhost:3000/api/flows/executions/<exec-id>/retry/<node-id> | jq .

Node Types

curl -s http://localhost:3000/api/flows/node-types | jq .

Expected: ["code", "condition", "agent"]. If RUSVEL_FLOW_PARALLEL_EVALUATE=1, also includes parallel_evaluate.

Templates

curl -s http://localhost:3000/api/flows/templates/cross-engine-handoff | jq .

Expected: Pre-built flow template showing cross-engine data handoff.

Capability Engine (!build)

Via API

curl -s -X POST http://localhost:3000/api/capability/build \
  -H 'Content-Type: application/json' \
  -d '{"description": "A GitHub PR review agent"}' | jq .

Expected: Returns bundle of created entities (agent, skills, rules, hooks, MCP config).

Via Chat

curl -N http://localhost:3000/api/dept/forge/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "!build a daily standup summary agent"}'

Expected: Agent detects !build prefix, invokes capability engine, creates entities, reports what was installed.

Smoke Test Checklist

Quick pass to verify everything works after a build. Run these in order – takes ~15 minutes.

Phase 1: Build & Boot (no Ollama needed)

#CommandPass if
1cargo buildCompiles without errors
2cargo test~645 tests pass (workspace sum; see docs/status/current-state.md)
3cargo run -- --helpShows help with all subcommands
4cargo run -- session create smoke-testPrints session UUID
5cargo run -- session listShows the smoke-test session
6cargo run -- finance statusPrints status (no crash)
7cargo run -- code analyze .Prints analysis results
8cargo run -- harvest pipelinePrints pipeline (all zeros OK)

Phase 2: Web Server (no Ollama needed)

Start cargo run in a terminal, then in another:

#CommandPass if
9curl -s localhost:3000/api/healthReturns 200
10curl -s localhost:3000/api/departments | jq lengthReturns 14
11curl -s localhost:3000/api/agents | jq lengthReturns >= 7 (seeded)
12curl -s localhost:3000/api/skills | jq lengthReturns >= 5 (seeded)
13curl -s localhost:3000/api/rules | jq lengthReturns >= 5 (seeded)
14curl -s localhost:3000/api/config/tools | jq lengthReturns >= 22
15curl -s localhost:3000/api/db/tables | jq lengthReturns >= 5
16Open http://localhost:3000 in browserDashboard renders

Phase 3: LLM Features (requires Ollama)

#CommandPass if
17cargo run -- forge mission todayStreams daily plan
18curl -N localhost:3000/api/chat -H 'Content-Type: application/json' -d '{"message":"hi"}'SSE stream with text deltas
19curl -N localhost:3000/api/dept/code/chat -H 'Content-Type: application/json' -d '{"message":"list files"}'SSE stream with tool calls
20cargo run -- content draft "test"Streams content draft

Phase 4: Interactive Surfaces

#ActionPass if
21cargo run -- shell -> type help -> type exitREPL starts, shows help, exits cleanly
22cargo run -- shell -> use finance -> status -> backContext switching works
23cargo run -- --tui -> press qTUI renders 4 panels, exits cleanly

Phase 5: CRUD Cycle

Pick any entity (agents, skills, rules, hooks, workflows) and verify the full lifecycle:

#ActionPass if
24POST createReturns 200 with ID
25GET by IDReturns created entity
26GET listEntity appears in list
27PUT updateReturns updated entity
28GET by ID againShows updated fields
29DELETEReturns 200/204
30GET by ID againReturns 404

Example CRUD cycle (agents):

# Create
ID=$(curl -s -X POST localhost:3000/api/agents \
  -H 'Content-Type: application/json' \
  -d '{"name":"smoke-agent","role":"Test","instructions":"test"}' | jq -r '.id')
echo "Created: $ID"

# Read
curl -s localhost:3000/api/agents/$ID | jq .name
# Expected: "smoke-agent"

# Update
curl -s -X PUT localhost:3000/api/agents/$ID \
  -H 'Content-Type: application/json' \
  -d '{"name":"updated-agent","instructions":"updated"}' | jq .name
# Expected: "updated-agent"

# List (verify it's there)
curl -s localhost:3000/api/agents | jq '.[].name' | grep updated-agent
# Expected: "updated-agent"

# Delete
curl -s -X DELETE localhost:3000/api/agents/$ID -w "\n%{http_code}\n"
# Expected: 200 or 204

# Verify gone
curl -s -o /dev/null -w "%{http_code}" localhost:3000/api/agents/$ID
# Expected: 404

Automated Test Suite

For reference, the full automated suite:

# Full workspace
cargo test

# Individual crates
cargo test -p rusvel-core
cargo test -p rusvel-db
cargo test -p rusvel-api
cargo test -p rusvel-llm
cargo test -p forge-engine
cargo test -p content-engine
cargo test -p harvest-engine
cargo test -p code-engine
cargo test -p flow-engine
cargo test -p rusvel-tool
cargo test -p rusvel-agent

# Line coverage
./scripts/coverage.sh

# Benchmark
cargo bench -p rusvel-app --bench boot

Engineering

Internal engineering documentation, patterns, and plans.

Concept Hierarchy

The domain model spine – the ontology that every crate, API route, and UI component maps onto. All refactoring decisions trace back to which level of this hierarchy they affect.

Five levels

Level 0: Platform (RUSVEL)
Level 1: Session (workspace scope)
Level 2: Department (bounded context)
Level 3: Domain Entity (per-department)
Level 4: Cross-cutting Primitive (shared infrastructure)

Level 0: Platform

RUSVEL
|- Identity: single binary, single human, infinite leverage
|- Constraint: SQLite WAL, tokio async, Rust + SvelteKit
'- Invariant: every action traceable to a Session

The platform is the composition root (rusvel-app), the port traits (rusvel-core), and the adapter crates. This level changes only when fundamental infrastructure changes.

Level 1: Session

Session
|- Owns: Runs, Threads, Goals, Events, Jobs, Config overrides
|- Scopes: all state is session-namespaced
|- Lifecycle: create -> active -> archived
'- Future: Session becomes Workspace when multi-user

The session is the unit of isolation. All queries, all tool calls, all agent runs are scoped to a session. This is RUSVEL’s equivalent of tenancy.

Level 2: Department

Department (DepartmentApp)
|- Identity: string ID, manifest, icon, color
|- Owns: Engine, Tools, Skills, Rules, Hooks, Agents, Workflows
|- Communicates via: Events (pub/sub), Jobs (async work), ObjectStore
|- Never: imports another department's crate
'- Types:
    |- Wired (6): forge, code, harvest, content, gtm, flow
    |- Skeleton (7): finance, product, growth, distro, legal, support, infra
    '- Shell (1): messaging

Each department is a bounded context. Departments communicate only through cross-cutting primitives (Level 4), never by importing each other’s types.

Level 3: Domain entities

Each department owns its entities. Entities from different departments reference each other by ID, never by owned struct.

DepartmentEntities
ForgeGoal, Task, Plan, Review, Persona, AgentProfile
CodeRepository, SymbolGraph, Symbol, Metric, SearchResult
HarvestOpportunity, Proposal, Pipeline, Source, Score
ContentContentItem, CalendarEntry, PlatformAdapter, PublishResult
GTMContact, Deal, OutreachSequence, Step, Invoice
FlowWorkflow, Node, Edge, Execution, Checkpoint, NodeResult
FinanceLedger, Transaction, TaxEstimate, RunwayForecast
ProductRoadmap, Feature, PricingTier, FeedbackItem
GrowthFunnel, Cohort, KPI, Experiment
DistroListing, SEOProfile, AffiliateProgram, Partnership
LegalContract, ComplianceCheck, IPRecord, LicenseAgreement
SupportTicket, KBArticle, NPSSurvey, AutoTriageRule
InfraDeployment, Monitor, Incident, Pipeline

Level 4: Cross-cutting primitives

Shared infrastructure that all departments use through port traits:

PrimitivePurposeMutability
EventImmutable record of what happenedAppend-only
JobAsync work item with state machineMutable (state transitions)
ToolRegistered capability with handlerImmutable after registration
SkillStored prompt templateMutable (CRUD)
RuleSystem prompt fragmentMutable (CRUD)
HookEvent-triggered automationMutable (CRUD)
AgentLLM + tools + persona + memoryStateful per run
ApprovalHuman gate on job or publishingMutable (approve/reject)

Level 5: Infrastructure ports

The port traits that connect departments to the outside world:

LlmPort         -> raw model access (generate, stream, embed)
AgentPort        -> orchestrated LLM (tool loop, memory, verification)
StoragePort      -> 5 sub-stores (events, objects, sessions, jobs, metrics)
EventPort        -> pub/sub + persistence
JobPort          -> central async work queue
ToolPort         -> tool registry + execution + permission
MemoryPort       -> session-scoped context + FTS5 search
ConfigPort       -> layered settings (global -> dept -> session)
AuthPort         -> opaque credential handles
EmbeddingPort    -> text -> dense vectors
VectorStorePort  -> similarity search
ChannelPort      -> outbound notifications
TerminalPort     -> PTY multiplexer
BrowserPort      -> Chrome DevTools Protocol
DeployPort       -> deployment operations
SessionPort      -> session lifecycle

Design rules

  1. Concept ownership: every concept belongs to exactly one level
  2. Cross-level references: use IDs (newtypes), never owned structs
  3. A Department never owns a Session. They reference each other via SessionId and department string ID
  4. Shared kernel: types in rusvel-core/src/domain.rs are the small set all departments agree on
  5. Anti-corruption: if a department needs another department’s data, it goes through ObjectStore or Events – never through direct type imports

Visual map

                    ┌─────────────────────┐
                    │    RUSVEL (L0)       │
                    │  Single Binary       │
                    └──────────┬──────────┘
                               │
                    ┌──────────┴──────────┐
                    │    Session (L1)      │
                    │  Scope + Isolation   │
                    └──────────┬──────────┘
                               │
          ┌────────┬───────────┼───────────┬────────┐
          │        │           │           │        │
       ┌──┴──┐ ┌──┴──┐    ┌──┴──┐    ┌──┴──┐ ┌──┴──┐
       │Forge│ │Code │    │GTM  │    │Flow │ │ ... │
       │(L2) │ │(L2) │    │(L2) │    │(L2) │ │(L2) │
       └──┬──┘ └──┬──┘    └──┬──┘    └──┬──┘ └─────┘
          │       │           │          │
       Goals   Symbols    Contacts    Nodes    (L3)
       Tasks   Metrics    Deals      Edges
       Plans   Repos      Invoices   Checkpoints
          │       │           │          │
          └───────┴─────┬─────┴──────────┘
                        │
              ┌─────────┴─────────┐
              │  Cross-cutting    │
              │  Primitives (L4)  │
              │  Events, Jobs,    │
              │  Tools, Skills    │
              └─────────┬─────────┘
                        │
              ┌─────────┴─────────┐
              │   Port Traits     │
              │   (L5 infra)      │
              │   LLM, Storage,   │
              │   Agent, Event    │
              └───────────────────┘

Design Patterns

How RUSVEL applies Gang of Four patterns, SOLID principles, and modern system design – all adapted to Rust’s type system. See also the Architecture overview and ADRs.

GoF patterns in Rust

Rust has no inheritance. Each GoF pattern either translates via traits/generics, collapses into an enum, or becomes unnecessary because ownership solves the underlying problem.

Creational

PatternRust TranslationRUSVEL Usage
BuilderMethod chaining, typestate for required fieldsDepartmentManifest, LlmRequest, AgentConfig
Factory MethodTrait with create() methodDepartmentApp::register()
Abstract FactoryComposition root returns trait objectsrusvel-app/src/main.rs wires all adapters
SingletonArc<T> passed at construction (no statics)All ports are Arc<dyn Port>
Prototype#[derive(Clone)]DepartmentManifest

Structural

PatternRust TranslationRUSVEL Usage
AdapterStruct wraps foreign type, implements local traitEvery rusvel-* adapter crate
Decoratortower::Layer wraps ServiceCostTrackingLlm, planned: TimeoutLayer, RetryLayer
FacadeStruct composes multiple portsAgentRuntime over LLM + Tool + Memory (ADR-009)
ProxySame trait, forwarding + filteringScopedToolRegistry per-department filtering
CompositeEnum with recursive Box<Self> variantsFlow DAG nodes (planned: SubFlowNode)
BridgeTrait separates abstraction from implementationAll port traits in rusvel-core
FlyweightArc<T> for shared immutable stateArc<dyn LlmPort> shared across engines

Behavioral

PatternRust TranslationRUSVEL Usage
StrategyGeneric <S: Strategy> (static) or dyn Strategy (dynamic)Port trait dispatch; ModelTier routing
Observertokio::sync::broadcast / mpsc channelsAgentEvent stream, EventPort broadcast
CommandEnum variants + match dispatchJobKind in worker, ToolHandler closures
Chain of Responsibilitytower::Layer middleware chainAxum middleware stack
StateEnum (runtime) or typestate (compile-time)JobStatus, FlowExecutionStatus
Template MethodTrait with default method calling abstract methodsLlmPort::stream() defaults to generate()
VisitorTrait with visit_* methods, separate traversalPlanned: SymbolVisitor in code-engine
MediatorCentral event bus or registryEventPort as cross-department mediator
IteratorIterator trait (first-class in Rust)Standard throughout

When NOT to use GoF in Rust

  • Singleton: Never use static Mutex<T>. Pass Arc<T> via constructors.
  • Trait objects for closed sets: If all variants are known, use an enum – faster (no vtable), exhaustively matchable.
  • Observer with callback vectors: Use async channels instead – they compose with tokio’s scheduler.
  • Visitor: Unnecessary when match on exhaustive enums covers all cases.

SOLID in Rust

Single Responsibility

Each crate has one reason to change. The <2000 lines per crate rule enforces this. Crates that grow too large are split.

Open/Closed

Traits are Rust’s extension mechanism. Adding a new LlmPort implementation requires zero changes to rusvel-core. The DepartmentApp pattern (ADR-014) is the purest OCP expression – adding a department means adding a dept-* crate, not modifying existing code.

Liskov Substitution

Any Arc<dyn LlmPort> must behave according to the LlmPort contract. Rust enforces signature compatibility; integration tests validate semantic contracts.

Interface Segregation

StoragePort splits into 5 focused sub-stores (ADR-004):

StoragePort
  |- EventStore    (append-only events)
  |- ObjectStore   (CRUD domain objects)
  |- SessionStore  (session lifecycle)
  |- JobStore      (job queue semantics)
  '- MetricStore   (time-series metrics)

Engines depend only on the sub-stores they use.

Dependency Inversion

The hexagonal architecture IS dependency inversion. Engines depend on abstractions (rusvel-core traits), not on concrete adapters. rusvel-app is the only place that depends on concrete types. Verified via just check-boundaries.

Rust-specific patterns

Typestate

Encode operation ordering in the type system. The build() method is only available when required fields are set:

#![allow(unused)]
fn main() {
pub struct ManifestBuilder<Id, Prompt> {
    id: Id,
    prompt: Prompt,
    // ...
}

// Can't call build() until both id and system_prompt are set
impl ManifestBuilder<HasId, HasPrompt> {
    pub fn build(self) -> DepartmentManifest { /* ... */ }
}
}

Used for: DepartmentManifest, AgentConfig, LlmRequest. See Typestate Pattern – Cliffle.

Newtype

Prevents mixing IDs at compile time:

#![allow(unused)]
fn main() {
pub struct SessionId(Uuid);  // Cannot be confused with...
pub struct AgentId(Uuid);    // ...this at the call site
}

Used throughout rusvel-core for all domain identifiers.

Tower Service

The foundational middleware abstraction in the Rust async ecosystem. poll_ready() implements backpressure – the caller must check readiness before sending:

#![allow(unused)]
fn main() {
let service = ServiceBuilder::new()
    .layer(TimeoutLayer::new(Duration::from_secs(30)))
    .layer(RateLimitLayer::new(10, Duration::from_secs(1)))
    .service(provider);
}

Used in: rusvel-api (Axum middleware). Planned for: LlmPort, AgentPort, ToolPort.

Modern system design patterns

DepartmentApp as Microkernel

RUSVEL’s DepartmentApp pattern is a microkernel at the application level:

  • Kernel: rusvel-app + rusvel-core port traits + composition root
  • Plugins: Each dept-* crate is a self-contained service
  • Message passing: EventPort (domain events), JobPort (async work)
  • Capability declaration: DepartmentManifest declares what each plugin provides

Event-Driven Architecture

Three EDA patterns in use:

  1. Event Notification – engines emit events, listeners react (hook dispatch)
  2. Central Job Queue – orchestration-style async work (ADR-003)
  3. Event Persistence – append-only EventStore for audit trail (ADR-005)

Planned additions:

  • Saga pattern – compensation edges in flow-engine DAGs
  • Projections – denormalized read views rebuilt from event streams

Agent Orchestration Hierarchy

ForgeEngine (supervisor)
  |-- DelegateAgentTool --> ContentEngine agent
  |-- DelegateAgentTool --> HarvestEngine agent
  '-- DelegateAgentTool --> CodeEngine agent

Delegation is depth-limited (max 3 levels) with per-department tool scoping.

Self-Improving System

The feedback loop substrate:

  1. Agent runs –> emit success/failure events
  2. Failure reflection agent analyzes transcript
  3. Generates skill/rule suggestions –> stored as drafts
  4. Human approves –> skill/rule promoted to active
  5. Successful tool sequences mined as reusable templates

References

Testing Plan

RUSVEL targets ~1200+ tests across Rust and frontend, with staged workspace coverage toward 55-65%. This page summarizes the strategy; the full checklist lives in docs/plans/comprehensive-testing-plan.md.

Current state

AreaCountNotes
Rust tests (workspace sum)~645Sum of running N tests from cargo test --workspace; see docs/status/current-state.md
Rust integration tests~92Strong API smoke tests, engine round-trips
Benchmarks2Criterion: DB open + registry load
Frontend visual tests27 routesPlaywright screenshot comparison
Frontend unit tests0No Vitest setup yet
Property-based tests0No proptest yet
Fuzz tests0No cargo-fuzz targets yet
Snapshot tests0No insta snapshots yet

CI coverage floor: 42% (cargo-llvm-cov, --fail-under-lines).

Testing pyramid

                    /\
                   /  \          E2E (Playwright)
                  / 27  \        Visual regression + interaction flows
                 /--------\
                /          \     Integration Tests
               /   ~150     \    API handlers, engine pipelines, cross-crate
              /--------------\
             /                \   Unit Tests
            /     ~800+        \  Domain types, port impls, utils, components
           /--------------------\
          /                      \  Property + Fuzz
         /        ~50+            \  Parsers, serialization, scoring
        /__________________________\

Most tests at the unit level. Integration tests for cross-boundary flows. E2E for critical user journeys only.

Sprint overview

SprintThemeNew Tests
0Baseline capture0 (measurement)
1Rust unit tests (rusvel-core to 90%, all crates > 50%)~200
2Frontend Vitest setup + api.ts + stores~100
3API negative path tests (400/401/404/405 for all routes)~120
4Rust engine contract tests (13 engines)~80
5Snapshot tests (insta) + property-based tests (proptest)~70
6Frontend component tests (@testing-library/svelte)~60
7Benchmarks (Criterion) + CI hardening~15
8Fuzz testing (cargo-fuzz, nightly)~8

Coverage targets by layer

These align with docs/testing/coverage-strategy.md:

LayerCurrentTarget
rusvel-core~60%85-95%
Engines (13)~50%70-90%
Adapters~35%60-80%
rusvel-api~45%50-70%
rusvel-app~25%30-50%
Frontend (lib/)0%50%
Workspace total~42%~55-65%

Test infrastructure

Rust

  • Test harness: crates/rusvel-api/tests/common/mod.rs provides TestHarness with temp SQLite, stub LLM, mock platform adapters
  • Mock pattern: Port-based mocking via FakeStorage, RecordingEvents, StubLlm structs
  • Framework: tokio async runtime, tempfile for isolation, criterion for benchmarks
  • Coverage: cargo-llvm-cov with HTML reports via ./scripts/coverage.sh

Frontend

  • Visual regression: Playwright + Claude Vision analysis (27 routes)
  • Unit tests (planned): Vitest + @testing-library/svelte
  • Test data: Global setup seeds sessions and goals via API

Key principles

  1. Port-based mocking – engines receive Arc<dyn Trait> mocks, never real adapters in unit tests
  2. No mocking the database in integration tests – use temp SQLite for realistic behavior
  3. Negative paths are required – every API endpoint needs 400, 401, 404, 405 tests
  4. Property tests for parsers – proptest on code-engine parser, harvest scoring, cron expressions
  5. Snapshots for serialization – insta catches accidental field renames in API responses

Running tests

# Rust
cargo test                          # Full workspace
cargo test -p rusvel-core           # Single crate
cargo bench -p rusvel-app --bench boot  # Benchmarks
./scripts/coverage.sh               # HTML coverage report

# Frontend
cd frontend
pnpm test                           # Vitest unit tests (planned)
pnpm test:visual                    # Playwright visual regression
pnpm test:e2e                       # All Playwright tests
pnpm test:coverage                  # Vitest coverage (planned)

Refactoring Plan

Structural refactoring for design pattern alignment, extensibility, and scalability. The full checklist lives in docs/plans/comprehensive-refactoring-plan.md.

Strategic themes

  1. Type safety over stringly-typed – replace magic strings (event kinds, job kinds, tool names) with compile-time-safe constants and validated registries
  2. GoF patterns as Rust idioms – Builder (typestate), Strategy (generics), Observer (typed channels), Decorator (tower layers), Command (typed enums), Visitor (AST traversal)
  3. Scalability substrate – saga orchestration, association graphs, self-improvement loops, Tower Service composability

What is NOT changing: hexagonal port/adapter boundary, DepartmentApp trait, composition root, single-binary constraint, SQLite WAL, or any ADR decisions.

Current patterns (verified)

PatternWhereQuality
StrategyPort traits (LlmPort, AgentPort, etc.)Excellent
Abstract FactoryComposition root (rusvel-app)Excellent
FacadeAgentRuntime over LLM+Tool+MemoryExcellent
AdapterEvery rusvel-* crateExcellent
ObserverAgentEvent over mpsc, EventPort broadcastGood
CommandJobKind dispatch, ToolHandler closuresGood
BuilderDepartmentManifest::new()Partial
ProxyScopedToolRegistryGood

Gaps to address

PatternGapSprint
Typestate BuilderNo compile-time field enforcement2
Decorator (Tower)No composable middleware on ports3
Visitorcode-engine has no visitor trait4
Typed ObserverEvent subscriptions use string matching4
State (Typestate)Job/flow status transitions unchecked4
CompositeFlow nodes are flat (no nesting)8
MediatorNo cross-department agent delegation9
SagaNo compensation logic in multi-step flows6

Sprint overview

SprintThemeKey Deliverables
1Type safetyEvent kind constants, tool collision detection, job registry
2Builders & factoriesTypestate DepartmentManifest, PlatformFactory trait, TestFactory
3StructuralAppState decomposition (25 fields -> 5 sub-states), Tower layers on LlmPort
4BehavioralTypedEvent system, JobCommand trait, SymbolVisitor, DelegateAgentTool
5DDDAggregateRoot trait, ObjectStore associations, value object enforcement
6EventsSaga compensation, lightweight projections, event replay
7PluginsManifest validation, port requirement checks, capability tokens
8WorkflowsCheckpoint/resume, SubFlow/Parallel/Loop nodes, per-node retry
9AgentsHierarchical delegation, supervisor pattern, blackboard cross-engine
10ScalabilityMagic number extraction, graceful shutdown, data-driven CLI
11FrontendTyped API client, command/query stores, error boundaries
12Self-improvementFailure reflection loop, skill accumulation, experience replay

Structural issues being fixed

IssueSeveritySprint
Monolithic AppState (~25 fields)High3
Tool name collisions silently overwriteHigh1
Event kinds are string literalsMedium1
Job results stored in untyped metadataMedium4
No association graph between domain objectsMedium5
Magic numbers scattered in codeLow10
Job worker has no graceful shutdownMedium10
CLI department variants repeated 9xLow10