初始化提交
Some checks failed
CI / Check / macos-latest (push) Has been cancelled
CI / Check / ubuntu-latest (push) Has been cancelled
CI / Check / windows-latest (push) Has been cancelled
CI / Test / macos-latest (push) Has been cancelled
CI / Test / ubuntu-latest (push) Has been cancelled
CI / Test / windows-latest (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Secrets Scan (push) Has been cancelled
CI / Install Script Smoke Test (push) Has been cancelled

This commit is contained in:
iven
2026-03-01 16:24:24 +08:00
commit 92e5def702
492 changed files with 211343 additions and 0 deletions

105
docs/README.md Normal file
View File

@@ -0,0 +1,105 @@
# OpenFang Documentation
Welcome to the OpenFang documentation. OpenFang is the open-source Agent Operating System -- 14 Rust crates, 40 channels, 60 skills, 20 LLM providers, 76 API endpoints, and 16 security systems in a single binary.
---
## Getting Started
| Guide | Description |
|-------|-------------|
| [Getting Started](getting-started.md) | Installation, first agent, first chat session |
| [Configuration](configuration.md) | Complete `config.toml` reference with every field |
| [CLI Reference](cli-reference.md) | Every command and subcommand with examples |
| [Troubleshooting](troubleshooting.md) | Common issues, FAQ, diagnostics |
## Core Concepts
| Guide | Description |
|-------|-------------|
| [Architecture](architecture.md) | 12-crate structure, kernel boot, agent lifecycle, memory substrate |
| [Agent Templates](agent-templates.md) | 30 pre-built agents across 4 performance tiers |
| [Workflows](workflows.md) | Multi-agent pipelines with branching, fan-out, loops, and triggers |
| [Security](security.md) | 16 defense-in-depth security systems |
## Integrations
| Guide | Description |
|-------|-------------|
| [Channel Adapters](channel-adapters.md) | 40 messaging channels -- setup, configuration, custom adapters |
| [LLM Providers](providers.md) | 20 providers, 51 models, 23 aliases -- setup and model routing |
| [Skills](skill-development.md) | 60 bundled skills, custom skill development, FangHub marketplace |
| [MCP & A2A](mcp-a2a.md) | Model Context Protocol and Agent-to-Agent protocol integration |
## Reference
| Guide | Description |
|-------|-------------|
| [API Reference](api-reference.md) | All 76 REST/WS/SSE endpoints with request/response examples |
| [Desktop App](desktop.md) | Tauri 2.0 native app -- build, features, architecture |
## Release & Operations
| Guide | Description |
|-------|-------------|
| [Production Checklist](production-checklist.md) | Every step before tagging v0.1.0 -- signing keys, secrets, verification |
## Additional Resources
| Resource | Description |
|----------|-------------|
| [CONTRIBUTING.md](../CONTRIBUTING.md) | Development setup, code style, PR guidelines |
| [MIGRATION.md](../MIGRATION.md) | Migrating from OpenClaw, LangChain, or AutoGPT |
| [SECURITY.md](../SECURITY.md) | Security policy and vulnerability reporting |
| [CHANGELOG.md](../CHANGELOG.md) | Release notes and version history |
---
## Quick Reference
### Start in 30 Seconds
```bash
export GROQ_API_KEY="your-key"
openfang init && openfang start
# Open http://127.0.0.1:4200
```
### Key Numbers
| Metric | Count |
|--------|-------|
| Crates | 14 |
| Agent templates | 30 |
| Messaging channels | 40 |
| Bundled skills | 60 |
| Built-in tools | 38 |
| LLM providers | 20 |
| Models in catalog | 51 |
| Model aliases | 23 |
| API endpoints | 76 |
| Security systems | 16 |
| Tests | 967 |
### Important Paths
| Path | Description |
|------|-------------|
| `~/.openfang/config.toml` | Main configuration file |
| `~/.openfang/data/openfang.db` | SQLite database |
| `~/.openfang/skills/` | Installed skills |
| `~/.openfang/daemon.json` | Daemon PID and port info |
| `agents/` | Agent template manifests |
### Key Environment Variables
| Variable | Provider |
|----------|----------|
| `ANTHROPIC_API_KEY` | Anthropic (Claude) |
| `OPENAI_API_KEY` | OpenAI (GPT-4o) |
| `GEMINI_API_KEY` | Google Gemini |
| `GROQ_API_KEY` | Groq (fast Llama/Mixtral) |
| `DEEPSEEK_API_KEY` | DeepSeek |
| `XAI_API_KEY` | xAI (Grok) |
Only one provider key is needed to get started. Groq offers a free tier.

976
docs/agent-templates.md Normal file
View File

@@ -0,0 +1,976 @@
# Agent Templates Catalog
OpenFang ships with **30 pre-built agent templates** organized into 4 performance tiers. Each template is a ready-to-spawn `agent.toml` manifest located in the `agents/` directory. Templates cover software engineering, business operations, personal productivity, and everyday tasks.
## Quick Start
Spawn any template from the CLI:
```bash
openfang spawn orchestrator
openfang spawn coder
openfang spawn --template agents/writer/agent.toml
```
Spawn via the REST API:
```bash
# Spawn from a built-in template name
curl -X POST http://localhost:4200/api/agents \
-H "Content-Type: application/json" \
-d '{"template": "coder"}'
# Spawn with overrides
curl -X POST http://localhost:4200/api/agents \
-H "Content-Type: application/json" \
-d '{"template": "writer", "model": "gemini-2.5-flash"}'
```
Send a message to a running agent:
```bash
curl -X POST http://localhost:4200/api/agents/{id}/message \
-H "Content-Type: application/json" \
-d '{"content": "Write unit tests for the auth module"}'
```
---
## Template Tiers
Templates are organized into 4 tiers based on task complexity and the LLM models they use. Higher tiers use more capable (and more expensive) models for tasks that require deep reasoning.
### Tier 1 -- Frontier (DeepSeek)
For tasks requiring the deepest reasoning: multi-agent orchestration, system architecture, and security analysis.
| Template | Provider | Model |
|----------|----------|-------|
| orchestrator | deepseek | deepseek-chat |
| architect | deepseek | deepseek-chat |
| security-auditor | deepseek | deepseek-chat |
All Tier 1 agents fall back to `groq/llama-3.3-70b-versatile` if the DeepSeek API key is unavailable.
### Tier 2 -- Smart (Gemini 2.5 Flash)
For tasks requiring strong analytical and coding abilities: software engineering, data science, research, testing, and legal review.
| Template | Provider | Model |
|----------|----------|-------|
| coder | gemini | gemini-2.5-flash |
| code-reviewer | gemini | gemini-2.5-flash |
| data-scientist | gemini | gemini-2.5-flash |
| debugger | gemini | gemini-2.5-flash |
| researcher | gemini | gemini-2.5-flash |
| analyst | gemini | gemini-2.5-flash |
| test-engineer | gemini | gemini-2.5-flash |
| legal-assistant | gemini | gemini-2.5-flash |
All Tier 2 agents fall back to `groq/llama-3.3-70b-versatile` if the Gemini API key is unavailable.
### Tier 3 -- Balanced (Groq + Gemini Fallback)
For everyday business and productivity tasks: planning, writing, email, customer support, sales, recruiting, and meetings.
| Template | Provider | Model | Fallback |
|----------|----------|-------|----------|
| planner | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| writer | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| doc-writer | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| devops-lead | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| assistant | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| email-assistant | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| social-media | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| customer-support | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| sales-assistant | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| recruiter | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
| meeting-assistant | groq | llama-3.3-70b-versatile | gemini/gemini-2.0-flash |
### Tier 4 -- Fast (Groq Only)
For lightweight, high-speed tasks: ops monitoring, translation, tutoring, wellness tracking, budgeting, travel, and home automation. No fallback model configured (except `ops` which uses a smaller 8B model for speed).
| Template | Provider | Model |
|----------|----------|-------|
| ops | groq | llama-3.1-8b-instant |
| hello-world | groq | llama-3.3-70b-versatile |
| translator | groq | llama-3.3-70b-versatile |
| tutor | groq | llama-3.3-70b-versatile |
| health-tracker | groq | llama-3.3-70b-versatile |
| personal-finance | groq | llama-3.3-70b-versatile |
| travel-planner | groq | llama-3.3-70b-versatile |
| home-automation | groq | llama-3.3-70b-versatile |
---
## Template Catalog
### orchestrator
**Tier 1 -- Frontier** | `deepseek/deepseek-chat` | Fallback: `groq/llama-3.3-70b-versatile`
> Meta-agent that decomposes complex tasks, delegates to specialist agents, and synthesizes results.
The orchestrator is the command center of the agent fleet. It analyzes user requests, breaks them into subtasks, uses `agent_list` to discover available specialists, delegates work via `agent_send`, spawns new agents when needed, and synthesizes all responses into a coherent final answer. It explains its delegation strategy before executing and avoids delegating trivially simple tasks.
- **Tags**: none
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 500,000/hour
- **Schedule**: Continuous check every 120 seconds
- **Tools**: `agent_send`, `agent_spawn`, `agent_list`, `agent_kill`, `memory_store`, `memory_recall`, `file_read`, `file_write`
- **Capabilities**: `agent_spawn = true`, `agent_message = ["*"]`, `memory_read = ["*"]`, `memory_write = ["*"]`
```bash
openfang spawn orchestrator
# "Plan and execute a full security audit of the codebase"
```
---
### architect
**Tier 1 -- Frontier** | `deepseek/deepseek-chat` | Fallback: `groq/llama-3.3-70b-versatile`
> System architect. Designs software architectures, evaluates trade-offs, creates technical specifications.
Designs systems following principles of separation of concerns, performance-aware design, simplicity over cleverness, and designing for change without over-engineering. Clarifies requirements, identifies key components, defines interfaces and data flow, evaluates trade-offs (latency, throughput, complexity, maintainability), and documents decisions with rationale. Outputs use clear headings, ASCII diagrams, and structured reasoning.
- **Tags**: `architecture`, `design`, `planning`
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Tools**: `file_read`, `file_list`, `memory_store`, `memory_recall`, `agent_send`
- **Capabilities**: `agent_message = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn architect
# "Design a microservices architecture for the payment processing system"
```
---
### security-auditor
**Tier 1 -- Frontier** | `deepseek/deepseek-chat` | Fallback: `groq/llama-3.3-70b-versatile`
> Security specialist. Reviews code for vulnerabilities, checks configurations, performs threat modeling.
Focuses on OWASP Top 10, input validation, auth flaws, cryptographic misuse, injection attacks (SQL, command, XSS, SSTI), insecure deserialization, secrets management, dependency vulnerabilities, race conditions, and privilege escalation. Maps the attack surface, traces data flow from untrusted inputs, checks trust boundaries, reviews error handling, and assesses cryptographic implementations. Reports findings with severity levels (CRITICAL/HIGH/MEDIUM/LOW/INFO) in the format: Finding, Impact, Evidence, Remediation.
- **Tags**: `security`, `audit`, `vulnerability`
- **Temperature**: 0.2
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Schedule**: Proactive on `event:agent_spawned`, `event:agent_terminated`
- **Tools**: `file_read`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`
- **Shell access**: `cargo audit *`, `cargo tree *`, `git log *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn security-auditor
# "Audit the authentication module for vulnerabilities"
```
---
### coder
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Expert software engineer. Reads, writes, and analyzes code.
Writes clean, production-quality code with a step-by-step reasoning approach. Reads files first to understand context, then makes precise changes. Always writes tests for produced code. Supports Rust, Python, JavaScript, and other languages.
- **Tags**: `coding`, `implementation`, `rust`, `python`
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Max concurrent tools**: 10
- **Tools**: `file_read`, `file_write`, `file_list`, `shell_exec`
- **Shell access**: `cargo *`, `rustc *`, `git *`, `npm *`, `python *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*"]`
```bash
openfang spawn coder
# "Implement a rate limiter using the token bucket algorithm in Rust"
```
---
### code-reviewer
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Senior code reviewer. Reviews PRs, identifies issues, suggests improvements with production standards.
Reviews code by priority: correctness, security, performance, maintainability, style. Groups feedback by file with severity tags: `[MUST FIX]`, `[SHOULD FIX]`, `[NIT]`, `[PRAISE]`. Explains WHY, not just WHAT. Suggests specific code for proposed changes. Acknowledges good code, avoids bikeshedding on style when formatters exist.
- **Tags**: `review`, `code-quality`, `best-practices`
- **Temperature**: 0.3
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`
- **Shell access**: `cargo clippy *`, `cargo fmt *`, `git diff *`, `git log *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn code-reviewer
# "Review the changes in the last 3 commits for production readiness"
```
---
### data-scientist
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Data scientist. Analyzes datasets, builds models, creates visualizations, performs statistical analysis.
Follows a structured methodology: understand the question, explore data (shape, distributions, missing values), analyze with appropriate statistical methods, build predictive models when needed, and communicate findings clearly. Toolkit includes descriptive stats, hypothesis testing (t-test, chi-squared, ANOVA), correlation/regression, time series, clustering, dimensionality reduction, and A/B test design.
- **Tags**: none
- **Temperature**: 0.3
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`
- **Shell access**: `python *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn data-scientist
# "Analyze this CSV dataset and identify the top 3 factors correlated with churn"
```
---
### debugger
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Expert debugger. Traces bugs, analyzes stack traces, performs root cause analysis.
Follows a strict methodology: reproduce, isolate (binary search through code/data), identify root cause (not just symptoms), fix (minimal correct fix), verify (regression tests). Looks for common patterns: off-by-one, null/None, race conditions, resource leaks. Checks error handling paths and recent changes. Presents findings as Bug Report, Root Cause, Fix, Prevention.
- **Tags**: none
- **Temperature**: 0.2
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`
- **Shell access**: `cargo *`, `git log *`, `git diff *`, `git show *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn debugger
# "The API returns 500 on POST /api/agents when the name contains unicode -- find the root cause"
```
---
### researcher
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Research agent. Fetches web content and synthesizes information.
Fetches web pages, reads documents, and synthesizes findings into clear, structured reports. Always cites sources, separates facts from analysis, and flags uncertainty. Breaks research tasks into sub-questions and investigates each systematically.
- **Tags**: `research`, `analysis`, `web`
- **Temperature**: 0.5
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `web_fetch`, `file_read`, `file_write`, `file_list`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn researcher
# "Research the current state of WebAssembly component model and summarize the key proposals"
```
---
### analyst
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Data analyst. Processes data, generates insights, creates reports.
Analyzes data, finds patterns, generates insights, and creates structured reports. Shows methodology, uses numbers and evidence to support conclusions. Reads files first to understand data structure, then presents findings with summary, key metrics, detailed analysis, and recommendations.
- **Tags**: none
- **Temperature**: 0.4
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`, `shell_exec`
- **Shell access**: `python *`, `cargo *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn analyst
# "Analyze the server access logs and report traffic patterns by hour and endpoint"
```
---
### test-engineer
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Quality assurance engineer. Designs test strategies, writes tests, validates correctness.
Tests document behavior, not implementation. Prefers fast, deterministic tests. Designs unit tests, integration tests, property-based tests, edge case tests, and regression tests. Follows the Arrange-Act-Assert pattern with descriptive test names (`test_X_when_Y_should_Z`). Reviews test coverage to identify untested paths and missing edge cases.
- **Tags**: `testing`, `qa`, `validation`
- **Temperature**: 0.3
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`
- **Shell access**: `cargo test *`, `cargo check *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn test-engineer
# "Write comprehensive tests for the rate limiter module covering edge cases"
```
---
### legal-assistant
**Tier 2 -- Smart** | `gemini/gemini-2.5-flash` | Fallback: `groq/llama-3.3-70b-versatile`
> Legal assistant for contract review, legal research, compliance checking, and document drafting.
Systematically reviews contracts covering parties, termination provisions, payment terms, indemnification, IP provisions, confidentiality, governing law, and force majeure. Drafts NDAs, service agreements, terms of service, privacy policies, and employment agreements. Checks compliance against GDPR, SOC 2, HIPAA, PCI DSS, CCPA/CPRA, ADA, and OSHA. Always includes a disclaimer that output does not constitute legal advice.
- **Tags**: `legal`, `contracts`, `compliance`, `research`, `review`, `documents`
- **Temperature**: 0.2
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn legal-assistant
# "Review this NDA and flag any one-sided or problematic clauses"
```
---
### planner
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Project planner. Creates project plans, breaks down epics, estimates effort, identifies risks and dependencies.
Follows a structured methodology: scope (in/out), decompose (epics to stories to tasks), sequence (dependencies and critical path), estimate (S/M/L/XL with rationale), risk (technical and schedule), milestones (with acceptance criteria). Estimates ranges (best/likely/worst), tackles riskiest parts first, and builds in 20-30% buffer for unknowns.
- **Tags**: none
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Tools**: `file_read`, `file_list`, `memory_store`, `memory_recall`, `agent_send`
- **Capabilities**: `agent_message = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn planner
# "Create a project plan for migrating our monolith to microservices over 6 months"
```
---
### writer
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Content writer. Creates documentation, articles, and technical writing.
Excels at documentation, technical writing, blog posts, and clear communication. Writes concisely with active voice, structures content with headers and bullet points. Reads existing files for context and writes output to files when asked.
- **Tags**: none
- **Temperature**: 0.7
- **Max tokens**: 4096
- **Token quota**: 100,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*"]`
```bash
openfang spawn writer
# "Write a blog post about the benefits of agent-based architectures"
```
---
### doc-writer
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Technical writer. Creates documentation, README files, API docs, tutorials, and architecture guides.
Writes for the reader: starts with WHY, then WHAT, then HOW. Uses progressive disclosure (overview to details). Creates READMEs, API docs, architecture docs, tutorials, reference docs, and Architecture Decision Records (ADRs). Uses active voice, short sentences, and includes code examples for every non-trivial concept.
- **Tags**: none
- **Temperature**: 0.4
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn doc-writer
# "Write API documentation for all the /api/agents endpoints"
```
---
### devops-lead
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> DevOps lead. Manages CI/CD, infrastructure, deployments, monitoring, and incident response.
Covers CI/CD pipeline design, container orchestration (Docker, Kubernetes), Infrastructure as Code (Terraform, Pulumi), monitoring and observability (Prometheus, Grafana, OpenTelemetry), incident response, security hardening, and capacity planning. Designs pipelines with fast feedback loops, immutable artifacts, and automated rollback.
- **Tags**: none
- **Temperature**: 0.2
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Tools**: `file_read`, `file_write`, `file_list`, `shell_exec`, `memory_store`, `memory_recall`, `agent_send`
- **Shell access**: `docker *`, `git *`, `cargo *`, `kubectl *`
- **Capabilities**: `agent_message = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn devops-lead
# "Design a CI/CD pipeline for our Rust workspace with staging and production environments"
```
---
### assistant
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> General-purpose assistant. The default OpenFang agent for everyday tasks, questions, and conversations.
The versatile default agent covering conversational intelligence, task execution, research and synthesis, writing and communication, problem solving, agent delegation (routes specialized tasks to the right specialist), knowledge management, and creative brainstorming. Acts as the user's trusted first point of contact -- handles most tasks directly and delegates to specialists when they would do better.
- **Tags**: `general`, `assistant`, `default`, `multipurpose`, `conversation`, `productivity`
- **Temperature**: 0.5
- **Max tokens**: 8192
- **Token quota**: 300,000/hour
- **Max concurrent tools**: 10
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`, `shell_exec`, `agent_send`, `agent_list`
- **Shell access**: `python *`, `cargo *`, `git *`, `npm *`
- **Capabilities**: `network = ["*"]`, `agent_message = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn assistant
# "Help me plan my week and draft replies to these three emails"
```
---
### email-assistant
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Email triage, drafting, scheduling, and inbox management agent.
Rapidly triages incoming email by urgency, category, and required action. Drafts professional emails adapted to recipient and situation. Manages email-based scheduling and follow-up obligations. Recognizes recurring email patterns and generates reusable templates. Produces concise digests for long threads and high-volume inboxes.
- **Tags**: `email`, `communication`, `triage`, `drafting`, `scheduling`, `productivity`
- **Temperature**: 0.4
- **Max tokens**: 8192
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn email-assistant
# "Triage these 15 emails and draft responses for the urgent ones"
```
---
### social-media
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Social media content creation, scheduling, and engagement strategy agent.
Crafts platform-optimized content for Twitter/X, LinkedIn, Instagram, Facebook, TikTok, Reddit, Mastodon, Bluesky, and Threads. Plans content calendars, designs engagement strategies, analyzes engagement data, defines brand voice guidelines, and optimizes hashtags and SEO. Adapts tone from professional thought leadership to casual and punchy depending on platform.
- **Tags**: `social-media`, `content`, `marketing`, `engagement`, `scheduling`, `analytics`
- **Temperature**: 0.7
- **Max tokens**: 4096
- **Token quota**: 120,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn social-media
# "Create a week of LinkedIn posts about our open-source launch"
```
---
### customer-support
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Customer support agent for ticket handling, issue resolution, and customer communication.
Triages support tickets by category, severity, product area, and customer tier. Follows systematic troubleshooting workflows for issue diagnosis. Writes empathetic, solution-oriented customer responses. Manages knowledge base content and escalation handoffs. Monitors customer sentiment and generates support metrics summaries.
- **Tags**: `support`, `customer-service`, `tickets`, `helpdesk`, `communication`, `resolution`
- **Temperature**: 0.3
- **Max tokens**: 4096
- **Token quota**: 200,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn customer-support
# "Triage this batch of support tickets and draft responses for the top 5 urgent ones"
```
---
### sales-assistant
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Sales assistant for CRM updates, outreach drafting, pipeline management, and deal tracking.
Drafts personalized cold outreach emails using the AIDA framework. Manages CRM data with structured updates. Analyzes sales pipelines with weighted values, at-risk deals, and conversion rates. Prepares pre-call briefs with prospect research. Builds competitive battle cards and performs win/loss analysis.
- **Tags**: `sales`, `crm`, `outreach`, `pipeline`, `prospecting`, `deals`
- **Temperature**: 0.5
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn sales-assistant
# "Draft a 3-touch outreach sequence for CTOs at mid-market SaaS companies"
```
---
### recruiter
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Recruiting agent for resume screening, candidate outreach, job description writing, and hiring pipeline management.
Evaluates resumes against job requirements with structured match scoring. Writes inclusive, searchable job descriptions. Drafts personalized candidate outreach sequences. Prepares structured interview guides with STAR-format behavioral questions. Tracks candidates through hiring pipeline stages and generates reports. Actively supports inclusive hiring practices.
- **Tags**: `recruiting`, `hiring`, `resume`, `outreach`, `talent`, `hr`
- **Temperature**: 0.4
- **Max tokens**: 4096
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn recruiter
# "Screen these 10 resumes against the senior backend engineer job requirements"
```
---
### meeting-assistant
**Tier 3 -- Balanced** | `groq/llama-3.3-70b-versatile` | Fallback: `gemini/gemini-2.0-flash`
> Meeting notes, action items, agenda preparation, and follow-up tracking agent.
Creates structured, time-boxed agendas. Transforms raw meeting notes or transcripts into clean, structured minutes with executive summaries, key discussion points, decisions, and action items. Extracts every commitment with owner, deadline, and priority. Drafts follow-up emails and schedules reminders. Synthesizes across multiple related meetings to identify themes and gaps.
- **Tags**: `meetings`, `notes`, `action-items`, `agenda`, `follow-up`, `productivity`
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn meeting-assistant
# "Process this meeting transcript and extract all action items with owners and deadlines"
```
---
### ops
**Tier 4 -- Fast** | `groq/llama-3.1-8b-instant` | No fallback
> DevOps agent. Monitors systems, runs diagnostics, manages deployments.
Monitors system health, runs diagnostics, and helps with deployments. Precise and cautious -- explains what a command does before running it. Prefers read-only operations unless explicitly asked to make changes. Reports in structured format: status, details, recommended action. Uses the smallest model in the fleet (8B) for maximum speed on routine ops checks.
- **Tags**: none
- **Temperature**: 0.2
- **Max tokens**: 2048
- **Token quota**: 50,000/hour
- **Schedule**: Periodic every 5 minutes
- **Tools**: `shell_exec`, `file_read`, `file_list`
- **Shell access**: `docker *`, `git *`, `cargo *`, `systemctl *`, `ps *`, `df *`, `free *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*"]`
```bash
openfang spawn ops
# "Check disk usage, memory, and running containers"
```
---
### hello-world
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> A friendly greeting agent that can read files and fetch web pages.
The simplest agent template -- a minimal starter agent with basic read-only capabilities. No system prompt, no tags, no shell access. Useful as a starting point for custom agents or for testing that the agent system is working.
- **Tags**: none
- **Temperature**: default
- **Max tokens**: default
- **Token quota**: 100,000/hour
- **Tools**: `file_read`, `file_list`, `web_fetch`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*"]`, `agent_spawn = false`
```bash
openfang spawn hello-world
# "Hello! What can you do?"
```
---
### translator
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Multi-language translation agent for document translation, localization, and cross-cultural communication.
Translates between 20+ major languages with high fidelity to meaning, tone, and intent. Handles contextual and cultural adaptation, document format preservation, software localization (JSON, YAML, PO/POT, XLIFF), technical/specialized translation, translation quality assurance (back-translation, consistency checks), and glossary management. Flags ambiguous phrases with multiple translation options.
- **Tags**: `translation`, `languages`, `localization`, `multilingual`, `communication`, `i18n`
- **Temperature**: 0.3
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn translator
# "Translate this README from English to Japanese and Spanish, preserving code blocks"
```
---
### tutor
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Teaching and explanation agent for learning, tutoring, and educational content creation.
Explains concepts at the learner's level using the Feynman Technique. Uses Socratic questioning to guide discovery. Teaches across mathematics, computer science, natural sciences, humanities, social sciences, and professional skills. Walks through problems step-by-step showing reasoning, not just solutions. Creates structured learning plans with spaced repetition. Provides practice questions with detailed, constructive feedback.
- **Tags**: `education`, `teaching`, `tutoring`, `learning`, `explanation`, `knowledge`
- **Temperature**: 0.5
- **Max tokens**: 8192
- **Token quota**: 200,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `shell_exec`, `web_fetch`
- **Shell access**: `python *`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn tutor
# "Teach me how binary search trees work, starting from the basics"
```
---
### health-tracker
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Wellness tracking agent for health metrics, medication reminders, fitness goals, and lifestyle habits.
Tracks weight, blood pressure, heart rate, sleep, water intake, steps, mood, and custom metrics. Manages medication schedules with dosage, timing, and refill dates. Sets SMART fitness goals with progressive training plans. Logs meals and estimates nutritional content. Applies evidence-based habit formation principles. Generates periodic wellness reports. Always includes a disclaimer that it is not a medical professional.
- **Tags**: `health`, `wellness`, `fitness`, `medication`, `habits`, `tracking`
- **Temperature**: 0.3
- **Max tokens**: 4096
- **Token quota**: 100,000/hour
- **Max concurrent tools**: 5
- **Schedule**: Periodic every 1 hour
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*"]`
```bash
openfang spawn health-tracker
# "Log today's metrics: weight 175lbs, sleep 7.5 hours, mood 8/10, 8000 steps"
```
---
### personal-finance
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Personal finance agent for budget tracking, expense analysis, savings goals, and financial planning.
Creates detailed budgets using frameworks like 50/30/20, zero-based budgeting, and envelope method. Processes expense data in any format (CSV, manual lists) and categorizes transactions. Defines and tracks savings goals with projected timelines. Analyzes debt portfolios and models avalanche vs. snowball payoff strategies. Produces financial health reports with net worth, debt-to-income ratio, and savings rate. Always disclaims that output is not financial advice.
- **Tags**: `finance`, `budget`, `expenses`, `savings`, `planning`, `money`
- **Temperature**: 0.2
- **Max tokens**: 8192
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `shell_exec`
- **Shell access**: `python *`
- **Capabilities**: `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn personal-finance
# "Analyze this month's expense CSV and show me where I'm over budget"
```
---
### travel-planner
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Trip planning agent for itinerary creation, booking research, budget estimation, and travel logistics.
Builds day-by-day itineraries with estimated times, transportation, meal recommendations, and contingency plans. Provides comprehensive destination guides covering best times to visit, attractions, customs, safety, cuisine, and visa requirements. Creates detailed travel budgets at multiple price tiers. Recommends accommodations by type, neighborhood, and budget. Plans transportation logistics including flights, trains, and local transit. Generates customized packing lists.
- **Tags**: `travel`, `planning`, `itinerary`, `booking`, `logistics`, `vacation`
- **Temperature**: 0.5
- **Max tokens**: 8192
- **Token quota**: 150,000/hour
- **Max concurrent tools**: 5
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `web_fetch`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn travel-planner
# "Plan a 10-day trip to Japan for 2 people, mid-range budget, mix of culture and food"
```
---
### home-automation
**Tier 4 -- Fast** | `groq/llama-3.3-70b-versatile` | No fallback
> Smart home control agent for IoT device management, automation rules, and home monitoring.
Manages smart home devices (lights, thermostats, security, appliances, sensors). Designs automation workflows using event-condition-action patterns. Configures multi-device scenes for common scenarios (morning routine, movie night, bedtime, away mode). Monitors energy consumption and recommends optimizations. Configures home security workflows. Troubleshoots IoT connectivity and bridges different ecosystems (Home Assistant, HomeKit, SmartThings). Understands Matter/Thread protocol adoption.
- **Tags**: `smart-home`, `iot`, `automation`, `devices`, `monitoring`, `home`
- **Temperature**: 0.2
- **Max tokens**: 4096
- **Token quota**: 100,000/hour
- **Max concurrent tools**: 10
- **Tools**: `file_read`, `file_write`, `file_list`, `memory_store`, `memory_recall`, `shell_exec`, `web_fetch`
- **Shell access**: `curl *`, `python *`, `ping *`
- **Capabilities**: `network = ["*"]`, `memory_read = ["*"]`, `memory_write = ["self.*", "shared.*"]`
```bash
openfang spawn home-automation
# "Create a bedtime automation: lock doors, arm cameras, dim lights, set thermostat to 68F"
```
---
## Custom Templates
The `agents/custom/` directory is reserved for your own agent templates. Create a new `agent.toml` file following the manifest format below.
### Manifest Format
```toml
# Required fields
name = "my-agent"
version = "0.1.0"
description = "What this agent does in one sentence."
author = "your-name"
module = "builtin:chat"
# Optional metadata
tags = ["tag1", "tag2"]
# Model configuration (required)
[model]
provider = "gemini" # Provider: gemini, deepseek, groq, openai, anthropic, etc.
model = "gemini-2.5-flash" # Model identifier
api_key_env = "GEMINI_API_KEY" # Env var holding the API key
max_tokens = 4096 # Max output tokens per response
temperature = 0.3 # Creativity (0.0 = deterministic, 1.0 = creative)
system_prompt = """Your agent's personality, capabilities, and instructions go here.
Be specific about what the agent should and should not do."""
# Optional fallback model (used when primary is unavailable)
[[fallback_models]]
provider = "groq"
model = "llama-3.3-70b-versatile"
api_key_env = "GROQ_API_KEY"
# Optional schedule (for autonomous/background agents)
[schedule]
periodic = { cron = "every 5m" } # Periodic execution
# continuous = { check_interval_secs = 120 } # Continuous loop
# proactive = { conditions = ["event:agent_spawned"] } # Event-triggered
# Resource limits
[resources]
max_llm_tokens_per_hour = 150000 # Token budget per hour
max_concurrent_tools = 5 # Max parallel tool executions
# Capability grants (principle of least privilege)
[capabilities]
tools = ["file_read", "file_write", "file_list", "shell_exec",
"memory_store", "memory_recall", "web_fetch",
"agent_send", "agent_list", "agent_spawn", "agent_kill"]
network = ["*"] # Network access patterns
memory_read = ["*"] # Memory namespaces agent can read
memory_write = ["self.*"] # Memory namespaces agent can write
agent_spawn = true # Can this agent spawn other agents?
agent_message = ["*"] # Which agents can it message?
shell = ["python *", "cargo *"] # Allowed shell command patterns (whitelist)
```
### Available Tools
| Tool | Description |
|------|-------------|
| `file_read` | Read file contents |
| `file_write` | Write/create files |
| `file_list` | List directory contents |
| `shell_exec` | Execute shell commands (restricted by `shell` whitelist) |
| `memory_store` | Persist key-value data to memory |
| `memory_recall` | Retrieve data from memory |
| `web_fetch` | Fetch content from URLs (SSRF-protected) |
| `agent_send` | Send a message to another agent |
| `agent_list` | List all running agents |
| `agent_spawn` | Spawn a new agent |
| `agent_kill` | Terminate a running agent |
### Tips for Custom Agents
1. **Start minimal**. Grant only the tools and capabilities the agent actually needs. You can always add more later.
2. **Write a clear system prompt**. The system prompt is the most important part of the template. Be specific about the agent's role, methodology, output format, and limitations.
3. **Set appropriate temperature**. Use 0.2 for precise/analytical tasks, 0.5 for balanced tasks, 0.7+ for creative tasks.
4. **Use shell whitelists**. Never grant `shell = ["*"]`. Whitelist specific command patterns like `shell = ["python *", "cargo test *"]`.
5. **Set token budgets**. Use `max_llm_tokens_per_hour` to prevent runaway costs. Start with 100,000 and adjust based on usage.
6. **Add fallback models**. If your primary model has rate limits or availability issues, add a `[[fallback_models]]` entry.
7. **Use memory for continuity**. Grant `memory_store` and `memory_recall` so the agent can persist context across sessions.
---
## Spawning Agents
### CLI
```bash
# Spawn by template name
openfang spawn coder
# Spawn with a custom name
openfang spawn coder --name "backend-coder"
# Spawn from a TOML file path
openfang spawn --template agents/custom/my-agent.toml
# List running agents
openfang agents
# Send a message
openfang message <agent-id> "Write a function to parse TOML files"
# Kill an agent
openfang kill <agent-id>
```
### REST API
```bash
# Spawn from template
POST /api/agents
{"template": "coder"}
# Spawn with overrides
POST /api/agents
{"template": "coder", "name": "backend-coder", "model": "deepseek-chat"}
# Send message
POST /api/agents/{id}/message
{"content": "Implement the auth module"}
# WebSocket (streaming)
WS /api/agents/{id}/ws
# List agents
GET /api/agents
# Delete agent
DELETE /api/agents/{id}
```
### OpenAI-Compatible API
```bash
# Use any agent through the OpenAI-compatible endpoint
POST /v1/chat/completions
{
"model": "openfang:coder",
"messages": [{"role": "user", "content": "Write a Rust HTTP server"}],
"stream": true
}
# List available models
GET /v1/models
```
### Orchestrator Delegation
The orchestrator agent can spawn and delegate to any other agent programmatically:
```
User: "Build a REST API with tests and documentation"
Orchestrator:
1. agent_send(coder, "Implement the REST API endpoints")
2. agent_send(test-engineer, "Write integration tests for these endpoints")
3. agent_send(doc-writer, "Document the API endpoints")
4. Synthesize all results into a final report
```
---
## Environment Variables
Set the following API keys to enable the corresponding model providers:
| Variable | Provider | Used By |
|----------|----------|---------|
| `DEEPSEEK_API_KEY` | DeepSeek | Tier 1 (orchestrator, architect, security-auditor) |
| `GEMINI_API_KEY` | Google Gemini | Tier 2 primary, Tier 3 fallback |
| `GROQ_API_KEY` | Groq | Tier 3 primary, Tier 1/2 fallback, Tier 4 |
At minimum, set `GROQ_API_KEY` to enable all Tier 3 and Tier 4 agents. Add `GEMINI_API_KEY` for Tier 2 agents. Add `DEEPSEEK_API_KEY` for Tier 1 frontier agents.

2234
docs/api-reference.md Normal file

File diff suppressed because it is too large Load Diff

927
docs/architecture.md Normal file
View File

@@ -0,0 +1,927 @@
# OpenFang Architecture
This document describes the internal architecture of OpenFang, the open-source Agent Operating System built in Rust. It covers the crate structure, kernel boot sequence, agent lifecycle, memory substrate, LLM driver abstraction, capability-based security model, the OFP wire protocol, the security hardening stack, the channel and skill systems, and the agent stability subsystems.
## Table of Contents
- [Crate Structure](#crate-structure)
- [Kernel Boot Sequence](#kernel-boot-sequence)
- [Agent Lifecycle](#agent-lifecycle)
- [Agent Loop Stability](#agent-loop-stability)
- [Memory Substrate](#memory-substrate)
- [LLM Driver Abstraction](#llm-driver-abstraction)
- [Model Catalog](#model-catalog)
- [Capability-Based Security Model](#capability-based-security-model)
- [Security Hardening](#security-hardening)
- [Channel System](#channel-system)
- [Skill System](#skill-system)
- [MCP and A2A Protocols](#mcp-and-a2a-protocols)
- [Wire Protocol (OFP)](#wire-protocol-ofp)
- [Desktop Application](#desktop-application)
- [Subsystem Diagram](#subsystem-diagram)
---
## Crate Structure
OpenFang is organized as a Cargo workspace with 14 crates (13 code crates + xtask). Dependencies flow downward (lower crates depend on nothing above them).
```
openfang-cli CLI interface, daemon auto-detect, MCP server
|
openfang-desktop Tauri 2.0 desktop app (WebView + system tray)
|
openfang-api REST/WS/SSE API server (Axum 0.8), 76 endpoints
|
openfang-kernel Kernel: assembles all subsystems, workflow engine, RBAC, metering
|
+-- openfang-runtime Agent loop, 3 LLM drivers, 23 tools, WASM sandbox, MCP, A2A
+-- openfang-channels 40 channel adapters, bridge, formatter, rate limiter
+-- openfang-wire OFP peer-to-peer networking with HMAC-SHA256 auth
+-- openfang-migrate Migration engine (OpenClaw YAML->TOML)
+-- openfang-skills 60 bundled skills, FangHub marketplace, ClawHub client
|
openfang-memory SQLite memory substrate, sessions, semantic search, usage tracking
|
openfang-types Shared types: Agent, Capability, Event, Memory, Message, Tool, Config,
Taint, ManifestSigning, ModelCatalog, MCP/A2A config, Web config
```
### Crate Responsibilities
| Crate | Description |
|-------|-------------|
| **openfang-types** | Core type definitions used across all crates. Defines `AgentManifest`, `AgentId`, `Capability`, `Event`, `ToolDefinition`, `KernelConfig`, `OpenFangError`, taint tracking (`TaintLabel`, `TaintSet`), Ed25519 manifest signing, model catalog types (`ModelCatalogEntry`, `ProviderInfo`, `ModelTier`), tool compatibility mappings (21 OpenClaw-to-OpenFang), MCP/A2A config types, and web config types. All config structs use `#[serde(default)]` for forward-compatible TOML parsing. |
| **openfang-memory** | SQLite-backed memory substrate (schema v5). Uses `Arc<Mutex<Connection>>` with `spawn_blocking` for async bridge. Provides structured KV storage, semantic search with vector embeddings, knowledge graph (entities and relations), session management, task board, usage event persistence (`usage_events` table, `UsageStore`), and canonical sessions for cross-channel memory. Five schema versions: V1 core, V2 collab, V3 embeddings, V4 usage, V5 canonical_sessions. |
| **openfang-runtime** | Agent execution engine. Contains the agent loop (`run_agent_loop`, `run_agent_loop_streaming`), 3 native LLM drivers (Anthropic, Gemini, OpenAI-compatible covering 20 providers), 23 built-in tools, WASM sandbox (Wasmtime with dual fuel+epoch metering), MCP client/server (JSON-RPC 2.0 over stdio/SSE), A2A protocol (AgentCard, task management), web search engine (4 providers: Tavily/Brave/Perplexity/DuckDuckGo), web fetch with SSRF protection, loop guard (SHA256-based tool loop detection), session repair (history validation), LLM session compactor (block-aware), Merkle hash chain audit trail, and embedding driver. Defines the `KernelHandle` trait that enables inter-agent tools without circular crate dependencies. |
| **openfang-kernel** | The central coordinator. `OpenFangKernel` assembles all subsystems: `AgentRegistry`, `AgentScheduler`, `CapabilityManager`, `EventBus`, `Supervisor`, `WorkflowEngine`, `TriggerEngine`, `BackgroundExecutor`, `WasmSandbox`, `ModelCatalog`, `MeteringEngine`, `ModelRouter`, `AuthManager` (RBAC), `HeartbeatMonitor`, `SetupWizard`, `SkillRegistry`, MCP connections, and `WebToolsContext`. Implements `KernelHandle` for inter-agent operations. Handles agent spawn/kill, message dispatch, workflow execution, trigger evaluation, capability inheritance validation, and graceful shutdown with state persistence. |
| **openfang-api** | HTTP API server built on Axum 0.8 with 76 endpoints. Routes for agents, workflows, triggers, memory, channels, templates, models, providers, skills, ClawHub, MCP, health, status, version, and shutdown. WebSocket handler for real-time agent chat with streaming. SSE endpoint for streaming responses. OpenAI-compatible endpoints (`POST /v1/chat/completions`, `GET /v1/models`). A2A endpoints (`/.well-known/agent.json`, `/a2a/*`). Middleware: Bearer token auth, request ID injection, structured request logging, GCRA rate limiter (cost-aware), security headers (CSP, X-Frame-Options, etc.), health endpoint redaction. |
| **openfang-channels** | Channel bridge layer with 40 adapters. Each adapter implements the `ChannelAdapter` trait. Includes: Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Email, SMS, Webhook, Teams, Mattermost, IRC, Google Chat, Twitch, Rocket.Chat, Zulip, XMPP, LINE, Viber, Messenger, Reddit, Mastodon, Bluesky, Feishu, Revolt, Nextcloud, Guilded, Keybase, Threema, Nostr, Webex, Pumble, Flock, Twist, Mumble, DingTalk, Discourse, Gitter, Ntfy, Gotify, LinkedIn. Features: `AgentRouter` for message routing, `BridgeManager` for lifecycle coordination, `ChannelRateLimiter` (per-user DashMap tracking), `formatter.rs` (Markdown to TelegramHTML/SlackMrkdwn/PlainText), `ChannelOverrides` (model/system_prompt/dm_policy/group_policy/rate_limit/threading/output_format), DM/group policy enforcement. |
| **openfang-wire** | OpenFang Protocol (OFP) for peer-to-peer agent communication. JSON-framed messages over TCP with HMAC-SHA256 mutual authentication (nonce + constant-time verify via `subtle`). `PeerNode` listens for connections and manages peers. `PeerRegistry` tracks known remote peers and their agents. |
| **openfang-cli** | Clap-based CLI. Supports all commands: `init`, `start`, `status`, `doctor`, `agent spawn/list/chat/kill`, `workflow list/create/run`, `trigger list/create/delete`, `migrate`, `skill install/list/remove/search/create`, `channel list/setup/test/enable/disable`, `config show/edit`, `chat`, `mcp`. Daemon auto-detect: checks `~/.openfang/daemon.json` and health pings; uses HTTP when a daemon is running, boots an in-process kernel as fallback. Built-in MCP server mode. |
| **openfang-desktop** | Tauri 2.0 native desktop application. Boots the kernel in-process, runs the axum server on a background thread, and points a WebView at `http://127.0.0.1:{random_port}`. Features: system tray (Show/Browser/Status/Quit), single-instance enforcement, desktop notifications, hide-to-tray on close. IPC commands: `get_port`, `get_status`. Mobile-ready with `#[cfg(desktop)]` guards. |
| **openfang-migrate** | Migration engine. Supports OpenClaw (`~/.openclaw/`). Converts YAML configs to TOML, maps tool names, maps provider names, imports agent manifests, copies memory files, converts channel configs. Produces a `MigrationReport` with imported items, skipped items, and warnings. |
| **openfang-skills** | Skill system for pluggable tool bundles. 60 bundled skills compiled via `include_str!()`. Skills are `skill.toml` + Python/WASM/Node.js/PromptOnly code. `SkillManifest` defines metadata, runtime config, provided tools, and requirements. `SkillRegistry` manages installed and bundled skills. `FangHubClient` connects to FangHub marketplace. `ClawHubClient` connects to clawhub.ai for cross-ecosystem skill discovery. `SKILL.md` parser for OpenClaw compatibility (YAML frontmatter + Markdown body). `SkillVerifier` with SHA256 verification. Prompt injection scanner (`scan_prompt_content()`) detects override attempts, data exfiltration, and shell references. |
| **xtask** | Build automation tasks (cargo-xtask pattern). |
---
## Kernel Boot Sequence
When `OpenFangKernel::boot_with_config()` is called (either by the daemon or in-process by the CLI/desktop app), the following sequence executes:
```
1. Load configuration
- Read ~/.openfang/config.toml (or specified path)
- Apply #[serde(default)] defaults for missing fields
- Validate config and log warnings (missing API keys, etc.)
2. Create data directory
- Ensure ~/.openfang/data/ exists
3. Initialize memory substrate
- Open SQLite database (openfang.db)
- Run schema migrations (up to v5)
- Set memory decay rate
4. Initialize LLM driver
- Read API key from environment variable
- Create driver for the configured provider
- Validate driver config
5. Initialize model catalog
- Build ModelCatalog with 51 builtin models, 20+ aliases, 20 providers
- Run detect_auth() to check env var presence (never reads secrets)
- Store as kernel.model_catalog
6. Initialize metering engine
- Create MeteringEngine with cost catalog (20+ model families)
- Wire to model catalog for pricing source
7. Initialize model router
- Create ModelRouter with TaskComplexity scoring
- Validate configured models and resolve aliases
8. Initialize core subsystems
- AgentRegistry (DashMap-based concurrent agent store)
- CapabilityManager (DashMap-based capability grants)
- EventBus (async broadcast channel)
- AgentScheduler (quota tracking per agent, hourly window reset)
- Supervisor (health monitoring, panic/restart counters)
- WorkflowEngine (workflow registration and execution, run eviction cap 200)
- TriggerEngine (event pattern matching)
- BackgroundExecutor (continuous/periodic agent loops)
- WasmSandbox (Wasmtime engine, dual fuel+epoch metering)
9. Initialize RBAC auth manager
- Create AuthManager with UserRole hierarchy
- Set up channel identity resolution
10. Initialize skill registry
- Load 60 bundled skills via parse_bundled()
- Load user-installed skills from disk
- Wire skill tools into tool_runner fallback chain
- Inject PromptOnly skill context into system prompts
11. Initialize web tools context
- Create WebSearchEngine (4-provider cascading: Tavily->Brave->Perplexity->DDG)
- Create WebFetchEngine (SSRF-protected)
- Bundle as WebToolsContext
12. Restore persisted agents
- Load all agents from SQLite
- Re-register in memory (registry, capabilities, scheduler)
- Set state to Running
13. Publish KernelStarted event
14. Return kernel instance
```
When the daemon wraps the kernel in `Arc`, additional steps occur:
```
15. Set self-handle (weak Arc reference for trigger dispatch)
16. Connect to MCP servers
- Background connect to configured MCP servers (stdio/SSE)
- Namespace tools as mcp_{server}_{tool}
- Store connections in kernel.mcp_connections
17. Start heartbeat monitor
- Background tokio task for agent health checks
- Publishes HealthCheckFailed events on anomalies
18. Start background agent loops (continuous, periodic, proactive)
```
---
## Agent Lifecycle
### States
```
spawn message/tick kill
| | |
v v v
[Running] <------------> [Running] ---------> [Terminated]
| ^
| shutdown |
+----------> [Suspended] ---------------------+
| reboot/restore
+------> [Running]
```
- **Running**: Agent is active and can receive messages.
- **Suspended**: Agent is paused (e.g., during daemon shutdown). Persisted to SQLite for restore on next boot.
- **Terminated**: Agent has been killed. Removed from registry and persistent storage.
### Spawn Flow
1. Generate new `AgentId` (UUID v4) and `SessionId`.
2. Create a session in the memory substrate.
3. Parse the manifest and extract capabilities.
4. Validate capability inheritance (`validate_capability_inheritance()` prevents privilege escalation).
5. Grant capabilities via `CapabilityManager`.
6. Register with the `AgentScheduler` (quota tracking).
7. Create `AgentEntry` and register in `AgentRegistry`.
8. Persist to SQLite via `memory.save_agent()`.
9. If agent has a parent, update parent's children list.
10. Register proactive triggers (if schedule mode is `Proactive`).
11. Publish `Lifecycle::Spawned` event and evaluate triggers.
### Message Flow
1. **RBAC check**: `AuthManager` resolves channel identity and checks user role permissions.
2. **Channel policy check**: `ChannelBridgeHandle.authorize_channel_user()` enforces DM/group policy.
3. **Quota check**: `AgentScheduler` verifies the agent has not exceeded its token-per-hour limit.
4. **Entry lookup**: Fetch `AgentEntry` from the registry.
5. **Module dispatch**: Based on `manifest.module`:
- `builtin:chat` or unrecognized: LLM agent loop
- `wasm:path/to/module.wasm`: WASM sandbox execution
- `python:path/to/script.py`: Python subprocess execution (env_clear() + selective vars)
6. **LLM agent loop** (for `builtin:chat`):
a. Load or create session from memory.
b. Load canonical context summary (cross-channel memory) into system prompt.
c. Append stability guidelines to system prompt.
d. Resolve LLM driver (per-agent override or kernel default).
e. Gather available tools (filtered by capabilities + skill tools + MCP tools).
f. Initialize loop guard (tool loop detection).
g. Run session repair (validate and fix message history).
h. Run iterative loop: send messages to LLM, execute tool calls, accumulate results.
i. Auto-compact session if threshold exceeded (block-aware compaction).
j. Save updated session and canonical session back to memory.
7. **Cost estimation**: `MeteringEngine.estimate_cost_with_catalog()` computes cost in USD.
8. **Record usage**: Update quota tracking with token counts; persist usage event.
9. **Return result**: `AgentLoopResult` with response text, token usage, iteration count, and `cost_usd`.
### Kill Flow
1. Check caller has `AgentKill(target_name)` capability.
2. Remove from `AgentRegistry`.
3. Stop background loops via `BackgroundExecutor`.
4. Unregister from `AgentScheduler`.
5. Revoke all capabilities.
6. Unsubscribe from `EventBus`.
7. Remove triggers.
8. Remove from persistent storage (SQLite).
---
## Agent Loop Stability
The agent loop includes multiple hardening layers to prevent runaway behavior:
### Loop Guard
`LoopGuard` detects when an agent is stuck calling the same tool with the same parameters. Uses SHA256 hashing of `(tool_name, params)` to identify repetition.
- **Warn threshold** (default 3): Logs a warning and injects a hint to the LLM.
- **Block threshold** (default 5): Refuses the tool call and returns an error to the LLM.
- **Circuit breaker** (default 30): Terminates the agent loop entirely.
Configured via `LoopGuardConfig`.
### Session Repair
`validate_and_repair()` runs before each agent loop iteration to ensure message history consistency:
- Drops orphaned `ToolResult` messages (no matching `ToolUse`).
- Removes empty messages.
- Merges consecutive same-role messages.
### Tool Result Truncation
`truncate_tool_result()` enforces a 50,000 character hard cap on tool output. Truncated results include a marker showing the original size.
### Tool Timeout
All tool executions are wrapped in a universal 60-second `tokio::time::timeout`. Tools that exceed this limit return a timeout error to the LLM rather than hanging indefinitely.
### Max Continuations
`MAX_CONTINUATIONS = 3` prevents infinite "Please continue" loops. After 3 continuation attempts, the agent returns its partial response rather than requesting another round.
### Inter-Agent Depth Limit
`MAX_AGENT_CALL_DEPTH = 5` enforced via `tokio::task_local!` in the tool runner. Prevents unbounded recursive agent-to-agent calls.
### Stability Guidelines
`STABILITY_GUIDELINES` are appended to every agent's system prompt. These contain anti-loop and anti-retry behavioral rules that the LLM follows to avoid degenerate patterns.
### Block-Aware Compaction
The session compactor handles all content block types (Text, ToolUse, ToolResult, Image) rather than assuming text-only messages. Auto-compaction triggers when the session exceeds the configured threshold (default 80% of context window), keeping the most recent messages (default 20).
---
## Memory Substrate
The memory substrate (`openfang-memory`) provides six layers of storage:
### 1. Structured KV Store
Per-agent key-value storage backed by SQLite. Keys are strings, values are JSON. Used by the `memory_store` and `memory_recall` tools.
A shared memory namespace (fixed agent ID `00000000-...01`) enables cross-agent data sharing.
```
agent_id | key | value
---------|-------------|------------------
uuid-a | preferences | {"theme": "dark"}
uuid-b | state | {"step": 3}
shared | project | {"name": "foo"}
```
### 2. Semantic Search
Vector embeddings for similarity-based memory retrieval. Documents are embedded using the configured embedding driver and stored with their vectors. Queries are embedded at search time and matched by cosine similarity.
### 3. Knowledge Graph
Entity-relation storage for structured knowledge. Agents can store entities (with types and properties) and relations between them. Supports graph traversal queries.
### 4. Session Manager
Conversation history storage. Each agent has a session containing its message history (user, assistant, tool use, tool result, image). Sessions track context window token counts. Sessions are persisted to SQLite and restored on kernel reboot.
### 5. Task Board
A shared task queue for multi-agent collaboration:
- `task_post`: Create a task with title, description, and optional assignee.
- `task_claim`: Claim the next available task.
- `task_complete`: Mark a task as done with a result.
- `task_list`: List tasks filtered by status (pending, claimed, completed).
### 6. Usage and Canonical Sessions
- **Usage tracking**: `usage_events` table persists token counts, cost estimates, and model usage per agent. `UsageStore` provides query and aggregation APIs.
- **Canonical sessions**: Cross-channel memory. `CanonicalSession` tracks a user's conversation context across multiple channels. Compaction produces summaries that are injected into system prompts. Stored in `canonical_sessions` table (schema v5).
### SQLite Architecture
All memory operations go through `Arc<Mutex<Connection>>` with Tokio's `spawn_blocking` for async bridging. This ensures thread safety without requiring an async SQLite driver. Schema migrations run automatically through five versions.
---
## LLM Driver Abstraction
The `LlmDriver` trait (`openfang-runtime`) provides a unified interface for all LLM providers:
```rust
#[async_trait]
pub trait LlmDriver: Send + Sync {
async fn send_message(
&self,
model: &str,
system_prompt: &str,
messages: &[Message],
tools: &[ToolDefinition],
) -> Result<LlmResponse, OpenFangError>;
async fn send_message_streaming(
&self,
model: &str,
system_prompt: &str,
messages: &[Message],
tools: &[ToolDefinition],
tx: mpsc::Sender<StreamEvent>,
) -> Result<LlmResponse, OpenFangError>;
fn key_required(&self) -> bool;
}
```
### Provider Architecture
Three native driver implementations cover all 20 providers with 51 models:
1. **AnthropicDriver**: Native Anthropic Messages API. Handles Claude-specific features (content blocks including images, tool use blocks, streaming deltas). Supports `ContentBlock::Image` with media type validation and 5MB cap.
2. **GeminiDriver**: Native Google Gemini API (v1beta). Uses `x-goog-api-key` auth, `systemInstruction`, `functionDeclarations`, `streamGenerateContent?alt=sse`. Maps Gemini function call responses to the unified `ToolUse` stop reason.
3. **OpenAiCompatDriver**: OpenAI-compatible Chat Completions API. Works with any provider that implements the OpenAI API format. Configured with different base URLs per provider. Covers 18+ providers including OpenAI, DeepSeek, Groq, Mistral, Together, and local runners.
### Provider Configuration
| Provider | Driver | Base URL | Key Required |
|----------|--------|----------|--------------|
| `anthropic` | Anthropic | `https://api.anthropic.com` | Yes |
| `gemini` | Gemini | `https://generativelanguage.googleapis.com` | Yes |
| `openai` | OpenAI-compat | `https://api.openai.com` | Yes |
| `deepseek` | OpenAI-compat | `https://api.deepseek.com` | Yes |
| `groq` | OpenAI-compat | `https://api.groq.com/openai` | Yes |
| `openrouter` | OpenAI-compat | `https://openrouter.ai/api` | Yes |
| `mistral` | OpenAI-compat | `https://api.mistral.ai` | Yes |
| `together` | OpenAI-compat | `https://api.together.xyz` | Yes |
| `fireworks` | OpenAI-compat | `https://api.fireworks.ai/inference` | Yes |
| `perplexity` | OpenAI-compat | `https://api.perplexity.ai` | Yes |
| `cohere` | OpenAI-compat | `https://api.cohere.ai` | Yes |
| `ai21` | OpenAI-compat | `https://api.ai21.com` | Yes |
| `cerebras` | OpenAI-compat | `https://api.cerebras.ai` | Yes |
| `sambanova` | OpenAI-compat | `https://api.sambanova.ai` | Yes |
| `huggingface` | OpenAI-compat | `https://api-inference.huggingface.co` | Yes |
| `xai` | OpenAI-compat | `https://api.x.ai` | Yes |
| `replicate` | OpenAI-compat | `https://api.replicate.com` | Yes |
| `ollama` | OpenAI-compat | `http://localhost:11434` | No |
| `vllm` | OpenAI-compat | `http://localhost:8000` | No |
| `lmstudio` | OpenAI-compat | `http://localhost:1234` | No |
### Per-Agent Driver Resolution
Each agent can override the kernel's default provider:
```toml
[model]
provider = "openai" # Different from kernel default
model = "gpt-4o"
api_key_env = "OPENAI_API_KEY" # Custom key env var
base_url = "https://custom.api.com" # Optional custom endpoint
```
When resolving the driver for an agent:
1. If the agent uses the same provider as the kernel default (and no custom key/URL), reuse the kernel's shared driver instance.
2. Otherwise, create a dedicated driver for that agent.
### Retry and Rate Limiting
LLM calls use exponential backoff for rate-limited (429) and overloaded (529) responses. The retry logic is built into the driver layer. All API key fields use `Zeroizing<String>` for automatic memory wipe on drop.
---
## Model Catalog
The `ModelCatalog` (`openfang-runtime/src/model_catalog.rs`) provides a registry of all known models, providers, and aliases.
### Registry Contents
- **51 builtin models** across 20+ model families (Claude, GPT, Gemini, DeepSeek, Llama, Mixtral, Command, Jamba, Grok, etc.)
- **20+ aliases** for convenience (e.g., `claude` -> `claude-sonnet-4-20250514`, `grok` -> `grok-2`)
- **20 providers** with authentication status detection
### Types
- `ModelCatalogEntry`: Model ID, display name, provider, tier, context window, cost rates.
- `ProviderInfo`: Provider name, driver type, base URL, key env var, auth status.
- `ModelTier`: Frontier, Smart, Balanced, Fast (maps to cost and capability tiers).
- `AuthStatus`: Detected, NotDetected (based on env var presence without reading secrets).
### Integration Points
- **Metering**: `estimate_cost_with_catalog()` uses catalog entries as the pricing source.
- **Router**: `ModelRouter.validate_models()` and `resolve_aliases()` reference the catalog.
- **API**: 4 endpoints (`/api/models`, `/api/models/{id}`, `/api/models/aliases`, `/api/providers`).
- **Channels**: `/models` and `/providers` chat commands via `ChannelBridgeHandle`.
---
## Capability-Based Security Model
Every agent operation is subject to capability checks. Capabilities are declared in the agent manifest and enforced at runtime.
### Capability Types
```rust
pub enum Capability {
// Tool access
ToolInvoke(String), // Access to a specific tool (e.g., "file_read")
ToolAll, // Access to all tools
// Memory access
MemoryRead(String), // Read scope (e.g., "*", "self.*")
MemoryWrite(String), // Write scope
// Network access
NetConnect(String), // Connect to host (e.g., "api.example.com", "*")
// Agent operations
AgentSpawn, // Can spawn new agents
AgentMessage(String), // Can message agents matching pattern
AgentKill(String), // Can kill agents matching pattern
// Shell access
ShellExec(String), // Can execute shell commands matching pattern
// OFP networking
OfpDiscover, // Can discover remote peers
OfpConnect(String), // Can connect to specific peers
OfpAdvertise, // Can advertise to peers
}
```
### Capability Inheritance Validation
`validate_capability_inheritance()` prevents privilege escalation when agents spawn child agents. A child agent can never receive capabilities that its parent does not hold. This is enforced at spawn time before any capabilities are granted.
### Manifest Declaration
```toml
[capabilities]
tools = ["file_read", "file_list", "web_fetch"]
memory_read = ["*"]
memory_write = ["self.*"]
network = ["api.anthropic.com"]
shell = []
agent_spawn = false
agent_message = ["coder", "researcher"]
agent_kill = []
ofp_discover = false
ofp_connect = []
```
### Enforcement Flow
```
Tool invocation request
|
v
CapabilityManager.check(agent_id, ToolInvoke("file_read"))
|
+-- Granted --> Validate path (traversal check) --> Execute tool
|
+-- Denied --> Return "Permission denied" error to LLM
```
The `CapabilityManager` uses a `DashMap<AgentId, Vec<Capability>>` for lock-free concurrent access. Capabilities are granted at spawn time (after inheritance validation) and revoked at kill time.
The tool runner also enforces capabilities by filtering the tool list before passing it to the LLM. If the LLM hallucinates a tool name outside the agent's granted list, the tool runner rejects it with a permission error.
---
## Security Hardening
OpenFang implements 16 security systems organized into critical fixes and state-of-the-art defenses:
### Path Traversal Prevention
`safe_resolve_path()` and `safe_resolve_parent()` in WASM host functions prevent directory traversal attacks. Path validation in `tool_runner.rs` (`validate_path`) protects file tools. Capability check runs BEFORE path resolution (deny first, then validate).
### Subprocess Isolation
`subprocess_sandbox.rs` provides a secure execution environment for Python/Node skill runtimes. All subprocess invocations use `cmd.env_clear()` followed by selective environment variable injection, preventing secret leakage.
### SSRF Protection
`is_ssrf_target()` and `is_private_ip()` block requests to private IPs and cloud metadata endpoints (169.254.169.254, etc.). DNS resolution is checked to prevent DNS rebinding attacks. Applied in `host_net_fetch` and `web_fetch.rs`.
### WASM Dual Metering
WASM sandbox uses both Wasmtime fuel metering (instruction count) and epoch interruption (wall-clock timeout via watchdog thread). This prevents both CPU-bound and time-bound runaway modules.
### Merkle Audit Trail
`audit.rs` implements a Merkle hash chain where each audit entry includes a hash of the previous entry. This provides tamper-evident logging of all agent actions.
### Information Flow Taint Tracking
`taint.rs` in `openfang-types` implements taint labels and taint sets. Data from external sources carries taint labels that propagate through operations, enabling information flow analysis.
### Ed25519 Manifest Signing
`manifest_signing.rs` provides Ed25519 digital signatures for agent manifests. Ensures manifest integrity and authenticity.
### OFP HMAC-SHA256 Mutual Auth
Wire protocol authentication uses `hmac_sign(secret, nonce + node_id)` on both handshake sides. Nonce prevents replay attacks. Constant-time verification via the `subtle` crate prevents timing attacks.
### Security Headers Middleware
CSP, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, and Permissions-Policy headers on all API responses.
### GCRA Rate Limiter
Generic Cell Rate Algorithm with cost-aware token buckets. Per-IP tracking with stale entry cleanup. Configurable burst and sustained rates.
### Health Endpoint Redaction
Public health endpoint (`/api/health`) returns minimal status. Detailed health (`/api/health/detail`) requires authentication and shows database stats, agent counts, and subsystem status.
### Prompt Injection Scanner
`scan_prompt_content()` in the skills crate detects override attempts, data exfiltration patterns, and shell references in skill content. Applied to all bundled and installed skills and to SKILL.md auto-conversion.
### Secret Zeroization
All LLM driver API key fields use `Zeroizing<String>` from the `zeroize` crate. Keys are automatically wiped from memory when the driver is dropped. `Debug` impls on config structs redact secret fields.
### Localhost-Only Fallback
When no API key is configured, the system falls back to localhost-only mode, preventing accidental exposure of unauthenticated endpoints.
### Loop Guard and Session Repair
See [Agent Loop Stability](#agent-loop-stability) above.
### Security Dependencies
`sha2`, `hmac`, `hex`, `subtle`, `ed25519-dalek`, `rand`, `zeroize`, `governor`
---
## Channel System
The channel system (`openfang-channels`) provides 40 adapters for messaging platform integration.
### Adapter List
| Wave | Channels |
|------|----------|
| **Original (15)** | Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Email, SMS, Webhook, Teams, Mattermost, IRC, Google Chat, Twitch, Rocket.Chat |
| **Wave 2 (8)** | Zulip, XMPP, LINE, Viber, Messenger, Reddit, Mastodon, Bluesky |
| **Wave 3 (8)** | Feishu, Revolt, Nextcloud, Guilded, Keybase, Threema, Nostr, Webex |
| **Wave 4 (9)** | Pumble, Flock, Twist, Mumble, DingTalk, Discourse, Gitter, Ntfy, Gotify, LinkedIn |
### Channel Features
- **Channel Overrides**: Per-channel configuration of model, system prompt, DM policy, group policy, rate limit, threading, and output format.
- **DM/Group Policy**: `DmPolicy` and `GroupPolicy` enums enforce who can interact with agents in direct messages vs. group chats.
- **Formatter**: `formatter.rs` converts Markdown to platform-specific formats (TelegramHTML, SlackMrkdwn, PlainText).
- **Rate Limiter**: `ChannelRateLimiter` with per-user DashMap tracking prevents message flooding.
- **Threading**: `send_in_thread()` trait method for platforms that support threaded conversations.
- **Chat Commands**: `/models`, `/providers`, `/new`, `/compact`, `/model`, `/stop`, `/usage`, `/think` handled by `ChannelBridgeHandle`.
---
## Skill System
The skill system (`openfang-skills`) provides 60 bundled skills and supports external skill installation.
### Skill Types
- **Python**: Python scripts executed in subprocess sandbox.
- **Node.js**: Node.js scripts (OpenClaw compatibility).
- **WASM**: WebAssembly modules executed in the WASM sandbox.
- **PromptOnly**: Skills that inject context into the LLM system prompt without code execution.
### Bundled Skills (60)
Compiled into the binary via `include_str!()` in `bundled.rs`. Three tiers:
- **Tier 1 (8)**: github, docker, web-search, code-reviewer, sql-analyst, git-expert, sysadmin, writing-coach
- **Tier 2 (6)**: kubernetes, terraform, aws, jira, data-analyst, api-tester
- **Tier 3 (6)**: pdf-reader, slack-tools, notion, sentry, mongodb, regex-expert
- **Plus 40 additional skills** added in the expansion phase
### Security Pipeline
All skills pass through a security pipeline before activation:
1. **SHA256 verification** (`SkillVerifier`): Ensures skill content matches its declared hash.
2. **Prompt injection scan** (`scan_prompt_content()`): Detects malicious patterns in skill prompts and descriptions.
3. **Trust boundary markers**: Skill-injected context in system prompts is wrapped with trust boundary markers.
4. **Subprocess env_clear()**: Skill code execution uses environment isolation.
### Ecosystem Bridges
- **FangHub**: Native OpenFang marketplace (`FangHubClient`).
- **ClawHub**: Cross-ecosystem compatibility (`ClawHubClient` connects to clawhub.ai).
- **SKILL.md Parser**: Auto-converts OpenClaw SKILL.md format (YAML frontmatter + Markdown body) to `skill.toml`.
- **Tool Compat**: 21 OpenClaw-to-OpenFang tool name mappings in `tool_compat.rs`.
---
## MCP and A2A Protocols
### Model Context Protocol (MCP)
OpenFang implements both MCP client and server:
- **MCP Client** (`mcp.rs`): JSON-RPC 2.0 over stdio or SSE transports. Connects to external MCP servers. Tools are namespaced as `mcp_{server}_{tool}` to prevent collisions. Background connection in `start_background_agents()`.
- **MCP Server** (`mcp_server.rs`): Exposes OpenFang's 23 built-in tools via the MCP protocol. Enables external tools to use OpenFang as a tool provider.
- **Configuration**: `KernelConfig.mcp_servers` (Vec of `McpServerConfigEntry` with name, command, args, env, transport).
- **API**: `/api/mcp/servers` returns configured and connected servers with their tool lists.
### Agent-to-Agent Protocol (A2A)
Google's A2A protocol for inter-system agent communication:
- **A2A Server** (`a2a.rs`): Publishes `AgentCard` at `/.well-known/agent.json`. Handles task lifecycle (send, get, cancel).
- **A2A Client** (`a2a.rs`): Discovers and communicates with remote A2A-compatible agents.
- **Endpoints**: `/.well-known/agent.json`, `/a2a/agents`, `/a2a/tasks/send`, `/a2a/tasks/{id}`, `/a2a/tasks/{id}/cancel`.
- **Configuration**: `KernelConfig.a2a` (optional `A2aConfig`).
---
## Wire Protocol (OFP)
The OpenFang Protocol (OFP) enables peer-to-peer agent communication across machines.
### Architecture
```
Machine A Machine B
+-----------+ +-----------+
| PeerNode | ---TCP (JSON)------> | PeerNode |
| port 4200 | <---TCP (JSON)------ | port 4200 |
+-----------+ +-----------+
| PeerRegistry | | PeerRegistry |
| - Known peers | | - Known peers |
| - Remote agents | | - Remote agents |
+---------------+ +---------------+
```
### HMAC-SHA256 Mutual Authentication
Before any protocol messages are exchanged, both peers authenticate:
1. Initiator sends `{nonce, node_id, hmac_sign(shared_secret, nonce + node_id)}`.
2. Responder verifies HMAC using constant-time comparison (`subtle` crate).
3. Responder sends its own `{nonce, node_id, hmac}` challenge.
4. Initiator verifies.
5. On mutual success, the connection is established.
Configured via `PeerConfig.shared_secret` (required) and `NetworkConfig.shared_secret` in `config.toml`.
### Protocol Messages
All messages are JSON-framed (newline-delimited JSON over TCP):
```
WireMessage {
id: UUID,
sender: PeerId,
payload: WireRequest | WireResponse
}
```
**Request types:**
- `Discover` -- Request peer information and agent list
- `Advertise` -- Announce local agents to a peer
- `RouteMessage` -- Send a message to a remote agent
- `Ping` -- Keepalive
**Response types:**
- `DiscoverResponse` -- Peer info and agent list
- `RouteResponse` -- Agent's response to a routed message
- `Pong` -- Keepalive response
### PeerRegistry
Tracks all known peers and their advertised agents:
```rust
pub struct PeerEntry {
pub id: PeerId,
pub addr: SocketAddr,
pub agents: Vec<RemoteAgent>,
pub last_seen: Instant,
}
pub struct RemoteAgent {
pub agent_id: String,
pub name: String,
pub description: String,
pub tags: Vec<String>,
}
```
### Capability Gating
OFP operations require capabilities:
- `OfpDiscover` -- Required to send discover requests
- `OfpConnect(addr)` -- Required to connect to a specific peer
- `OfpAdvertise` -- Required to advertise agents to peers
---
## Desktop Application
The desktop app (`openfang-desktop`) wraps the full OpenFang stack in a native Tauri 2.0 application.
### Architecture
```
+-------------------------------------------+
| Tauri 2.0 Shell |
| +---------------------------------------+ |
| | WebView (WebKit/WebView2) | |
| | -> http://127.0.0.1:{random_port} | |
| +---------------------------------------+ |
| +---------------------------------------+ |
| | System Tray | |
| | Show | Browser | Status | Quit | |
| +---------------------------------------+ |
| +---------------------------------------+ |
| | Background Thread | |
| | +- Own Tokio Runtime | |
| | +- OpenFangKernel (in-process) | |
| | +- Axum Server (build_router()) | |
| | +- ServerHandle { port, shutdown } | |
| +---------------------------------------+ |
+-------------------------------------------+
```
### Features
- **In-process kernel**: No separate daemon needed. The kernel boots inside the app process.
- **Random port**: Avoids port conflicts. Port communicated via IPC command `get_port`.
- **System tray**: Show Window, Open in Browser, Status indicator, Quit. Double-click to show.
- **Single instance**: `tauri-plugin-single-instance` prevents multiple app instances.
- **Notifications**: `tauri-plugin-notification` for desktop alerts.
- **Hide to tray**: Window close hides to tray instead of quitting (desktop platforms).
- **Mobile ready**: `#[cfg(desktop)]` guards on tray and single-instance; `#[cfg_attr(mobile, tauri::mobile_entry_point)]`.
---
## Subsystem Diagram
```
+-------------------------------------------------------------------+
| openfang-cli |
| [init] [start] [agent] [workflow] [trigger] [skill] [channel] |
| [migrate] [config] [chat] [status] [doctor] [mcp] |
+-------------------------------------------------------------------+
| |
| (HTTP/daemon) | (in-process)
v v
+-------------------------------------------------------------------+
| openfang-api |
| +-------------+ +----------+ +--------+ +------------------+ |
| | REST Routes | | WS Chat | | SSE | | OpenAI /v1/ | |
| | (76 endpts) | +----------+ +--------+ +------------------+ |
| +-------------+ +------------------+ +-----------------------+ |
| | Auth+RBAC | | Security Headers | | GCRA Rate Limiter | |
| +-------------+ +------------------+ +-----------------------+ |
| +---------------------+ +------------------------------------+ |
| | A2A Endpoints | | Health Redaction | |
| +---------------------+ +------------------------------------+ |
+-------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------+
| openfang-kernel |
| +----------------+ +------------------+ +-------------------+ |
| | AgentRegistry | | AgentScheduler | | CapabilityManager | |
| | (DashMap) | | (quota+metering) | | (DashMap+inherit) | |
| +----------------+ +------------------+ +-------------------+ |
| +----------------+ +------------------+ +-------------------+ |
| | EventBus | | Supervisor | | AuthManager | |
| | (broadcast) | | (health monitor) | | (RBAC multi-user) | |
| +----------------+ +------------------+ +-------------------+ |
| +----------------+ +------------------+ +-------------------+ |
| | WorkflowEngine | | TriggerEngine | | BackgroundExec | |
| | (pipelines) | | (event patterns) | | (continuous/cron) | |
| +----------------+ +------------------+ +-------------------+ |
| +----------------+ +------------------+ +-------------------+ |
| | ModelCatalog | | MeteringEngine | | ModelRouter | |
| | (51 models) | | (cost tracking) | | (auto-select) | |
| +----------------+ +------------------+ +-------------------+ |
| +----------------+ +------------------+ +-------------------+ |
| | HeartbeatMon | | SetupWizard | | SkillRegistry | |
| | (agent health) | | (NL agent setup) | | (60 bundled) | |
| +----------------+ +------------------+ +-------------------+ |
| +----------------+ +------------------+ |
| | MCP Connections| | WebToolsContext | |
| | (stdio/SSE) | | (search+fetch) | |
| +----------------+ +------------------+ |
+-------------------------------------------------------------------+
|
+----+-------------------+------------------+---------+
| | | |
v v v v
+------------------+ +--------------+ +--------+ +-----------+
| openfang-runtime | | openfang- | | open- | | openfang- |
| | | channels | | fang- | | skills |
| +------------+ | | | | wire | | |
| | Agent Loop | | | +----------+| | | | +-------+ |
| | +LoopGuard | | | | 40 Chan || | +----+ | | |60 Bun| |
| | +SessRepair| | | | Adapters || | |OFP | | | |Skills | |
| +------------+ | | +----------+| | |HMAC| | | +-------+ |
| +------------+ | | +----------+| | +----+ | | +-------+ |
| | 3 LLM Drv | | | |Formatter || | +----+ | | |FangHub| |
| | (20 provs) | | | |Rate Lim || | |Peer| | | |ClawHub| |
| +------------+ | | |DM/Group || | |Reg | | | +-------+ |
| +------------+ | | +----------+| | +----+ | | +-------+ |
| | 23 Tools | | | +----------+| +--------+ | |Verify | |
| +------------+ | | |AgentRouter| | |Inject | |
| +------------+ | | +----------+| | |Scan | |
| | WASM Sand | | +--------------+ | +-------+ |
| | (dual meter)| | +-----------+
| +------------+ |
| +------------+ |
| | MCP Client | |
| | MCP Server | |
| +------------+ |
| +------------+ |
| | A2A Proto | |
| +------------+ |
| +------------+ |
| | Web Search | | 4 engines: Tavily/Brave/Perplexity/DDG
| | Web Fetch | | SSRF protection + TTL cache
| +------------+ |
| +------------+ |
| | Audit Trail| | Merkle hash chain
| | Compactor | | Block-aware session compaction
| +------------+ |
| +------------+ |
| | KernelHandl| | (trait defined here,
| | (trait) | | implemented in kernel)
| +------------+ |
+------------------+
|
v
+------------------+
| openfang-memory |
| +------------+ |
| | KV Store | | Per-agent + shared namespace
| +------------+ |
| +------------+ |
| | Semantic | | Vector embeddings + cosine similarity
| +------------+ |
| +------------+ |
| | Knowledge | | Entity-relation graph
| | Graph | |
| +------------+ |
| +------------+ |
| | Sessions | | Conversation history + token tracking
| +------------+ |
| +------------+ |
| | Task Board | | Shared task queue for collaboration
| +------------+ |
| +------------+ |
| | Usage Store| | Token counts, costs, model usage
| +------------+ |
| +------------+ |
| | Canonical | | Cross-channel session memory
| | Sessions | |
| +------------+ |
| +------------+ |
| | SQLite v5 | | Arc<Mutex<Connection>> + spawn_blocking
| +------------+ |
+------------------+
|
v
+------------------+
| openfang-types |
| Agent, Capability|
| Event, Memory |
| Message, Tool |
| Config, Error |
| Taint, Signing |
| ModelCatalog |
| MCP/A2A Config |
| Web Config |
+------------------+
```

View File

@@ -0,0 +1,164 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 640" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
<defs>
<linearGradient id="kern" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#10b981"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="rt" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6"/>
<stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
<linearGradient id="api" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8b5cf6"/>
<stop offset="100%" stop-color="#7c3aed"/>
</linearGradient>
<linearGradient id="ch" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f59e0b"/>
<stop offset="100%" stop-color="#d97706"/>
</linearGradient>
<linearGradient id="mem" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ec4899"/>
<stop offset="100%" stop-color="#db2777"/>
</linearGradient>
<linearGradient id="ext" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#06b6d4"/>
<stop offset="100%" stop-color="#0891b2"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="900" height="640" rx="12" fill="#0f172a"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" fill="#f1f5f9" font-size="18" font-weight="700">OpenFang — Agent Operating System Architecture</text>
<text x="450" y="50" text-anchor="middle" fill="#64748b" font-size="11">14 crates | 137K lines of Rust | single binary</text>
<!-- === User Layer === -->
<rect x="30" y="65" width="840" height="55" rx="8" fill="#1e293b" stroke="#334155" stroke-width="1"/>
<text x="50" y="82" fill="#94a3b8" font-size="9" font-weight="600" letter-spacing="1">USER SPACE</text>
<rect x="50" y="90" width="120" height="24" rx="4" fill="#334155"/>
<text x="110" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">Desktop (Tauri)</text>
<rect x="185" y="90" width="90" height="24" rx="4" fill="#334155"/>
<text x="230" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">CLI + TUI</text>
<rect x="290" y="90" width="90" height="24" rx="4" fill="#334155"/>
<text x="335" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">Web Dashboard</text>
<rect x="395" y="90" width="90" height="24" rx="4" fill="#334155"/>
<text x="440" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">JS/Python SDK</text>
<rect x="500" y="90" width="130" height="24" rx="4" fill="#334155"/>
<text x="565" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">OpenAI-compat API</text>
<rect x="645" y="90" width="70" height="24" rx="4" fill="#334155"/>
<text x="680" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">MCP</text>
<rect x="730" y="90" width="60" height="24" rx="4" fill="#334155"/>
<text x="760" y="106" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">A2A</text>
<!-- Arrow down -->
<line x1="450" y1="120" x2="450" y2="135" stroke="#475569" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- === API Layer === -->
<rect x="30" y="135" width="840" height="70" rx="8" fill="url(#api)" opacity="0.15" stroke="#8b5cf6" stroke-width="1"/>
<text x="50" y="153" fill="#a78bfa" font-size="9" font-weight="600" letter-spacing="1">API GATEWAY — openfang-api</text>
<text x="50" y="172" fill="#c4b5fd" font-size="10">140 REST endpoints | WebSocket streaming | SSE events | GCRA rate limiter</text>
<text x="50" y="189" fill="#c4b5fd" font-size="10">Security headers (CSP/HSTS) | CORS | Health redaction | File uploads | Approval queue</text>
<!-- Arrow down -->
<line x1="450" y1="205" x2="450" y2="218" stroke="#475569" stroke-width="2"/>
<!-- === Kernel Layer === -->
<rect x="30" y="218" width="840" height="85" rx="8" fill="url(#kern)" opacity="0.15" stroke="#10b981" stroke-width="1.5"/>
<text x="50" y="237" fill="#34d399" font-size="9" font-weight="600" letter-spacing="1">KERNEL — openfang-kernel</text>
<rect x="50" y="245" width="95" height="20" rx="3" fill="#10b98133"/>
<text x="97" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Agent Registry</text>
<rect x="152" y="245" width="75" height="20" rx="3" fill="#10b98133"/>
<text x="189" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Scheduler</text>
<rect x="234" y="245" width="85" height="20" rx="3" fill="#10b98133"/>
<text x="276" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Capabilities</text>
<rect x="326" y="245" width="85" height="20" rx="3" fill="#10b98133"/>
<text x="368" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Workflow Eng</text>
<rect x="418" y="245" width="70" height="20" rx="3" fill="#10b98133"/>
<text x="453" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">RBAC Auth</text>
<rect x="495" y="245" width="75" height="20" rx="3" fill="#10b98133"/>
<text x="532" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Metering</text>
<rect x="577" y="245" width="70" height="20" rx="3" fill="#10b98133"/>
<text x="612" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">EventBus</text>
<rect x="654" y="245" width="70" height="20" rx="3" fill="#10b98133"/>
<text x="689" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Heartbeat</text>
<rect x="731" y="245" width="60" height="20" rx="3" fill="#10b98133"/>
<text x="761" y="259" text-anchor="middle" fill="#a7f3d0" font-size="9">Wizard</text>
<rect x="50" y="272" width="95" height="20" rx="3" fill="#10b98133"/>
<text x="97" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Hot Reload</text>
<rect x="152" y="272" width="95" height="20" rx="3" fill="#10b98133"/>
<text x="199" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Budget Manager</text>
<rect x="254" y="272" width="85" height="20" rx="3" fill="#10b98133"/>
<text x="296" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Approvals</text>
<rect x="346" y="272" width="100" height="20" rx="3" fill="#10b98133"/>
<text x="396" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Device Pairing</text>
<rect x="453" y="272" width="105" height="20" rx="3" fill="#10b98133"/>
<text x="505" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Graceful Shutdown</text>
<rect x="565" y="272" width="110" height="20" rx="3" fill="#10b98133"/>
<text x="620" y="286" text-anchor="middle" fill="#a7f3d0" font-size="9">Circuit Breakers</text>
<!-- Arrow down to runtime and channels -->
<line x1="300" y1="303" x2="300" y2="318" stroke="#475569" stroke-width="2"/>
<line x1="600" y1="303" x2="600" y2="318" stroke="#475569" stroke-width="2"/>
<!-- === Runtime (left) === -->
<rect x="30" y="318" width="420" height="110" rx="8" fill="url(#rt)" opacity="0.12" stroke="#3b82f6" stroke-width="1"/>
<text x="50" y="337" fill="#60a5fa" font-size="9" font-weight="600" letter-spacing="1">RUNTIME — openfang-runtime</text>
<text x="50" y="354" fill="#93c5fd" font-size="10">Agent loop (streaming) | 3 LLM drivers | 53 tools</text>
<text x="50" y="369" fill="#93c5fd" font-size="10">WASM sandbox (dual metering) | MCP client/server</text>
<text x="50" y="384" fill="#93c5fd" font-size="10">Loop guard | Session repair | Auth cooldown</text>
<text x="50" y="399" fill="#93c5fd" font-size="10">Web search (4 engines) | Browser automation</text>
<text x="50" y="414" fill="#93c5fd" font-size="10">TTS/STT | Image gen | Docker sandbox | Compactor</text>
<!-- === Channels + Skills (right) === -->
<rect x="460" y="318" width="200" height="110" rx="8" fill="url(#ch)" opacity="0.12" stroke="#f59e0b" stroke-width="1"/>
<text x="480" y="337" fill="#fbbf24" font-size="9" font-weight="600" letter-spacing="1">CHANNELS</text>
<text x="480" y="354" fill="#fde68a" font-size="10">40 adapters</text>
<text x="480" y="369" fill="#fde68a" font-size="10">Bridge + Router</text>
<text x="480" y="384" fill="#fde68a" font-size="10">Rate limiter</text>
<text x="480" y="399" fill="#fde68a" font-size="10">DM/group policy</text>
<text x="480" y="414" fill="#fde68a" font-size="10">Formatter</text>
<rect x="670" y="318" width="200" height="110" rx="8" fill="url(#ext)" opacity="0.12" stroke="#06b6d4" stroke-width="1"/>
<text x="690" y="337" fill="#22d3ee" font-size="9" font-weight="600" letter-spacing="1">SKILLS + HANDS</text>
<text x="690" y="354" fill="#a5f3fc" font-size="10">60 bundled skills</text>
<text x="690" y="369" fill="#a5f3fc" font-size="10">7 autonomous Hands</text>
<text x="690" y="384" fill="#a5f3fc" font-size="10">SKILL.md parser</text>
<text x="690" y="399" fill="#a5f3fc" font-size="10">FangHub marketplace</text>
<text x="690" y="414" fill="#a5f3fc" font-size="10">Injection scanner</text>
<!-- Arrow down to memory -->
<line x1="450" y1="428" x2="450" y2="443" stroke="#475569" stroke-width="2"/>
<!-- === Memory Layer === -->
<rect x="30" y="443" width="540" height="60" rx="8" fill="url(#mem)" opacity="0.12" stroke="#ec4899" stroke-width="1"/>
<text x="50" y="462" fill="#f472b6" font-size="9" font-weight="600" letter-spacing="1">MEMORY — openfang-memory</text>
<text x="50" y="479" fill="#fbcfe8" font-size="10">SQLite (KV + relational) | Vector embeddings | Usage tracking | Canonical sessions | JSONL mirror</text>
<text x="50" y="494" fill="#fbcfe8" font-size="10">Schema v7 | Session compaction | Knowledge graphs</text>
<!-- === Extensions Layer === -->
<rect x="580" y="443" width="290" height="60" rx="8" fill="#1e293b" stroke="#475569" stroke-width="1"/>
<text x="600" y="462" fill="#94a3b8" font-size="9" font-weight="600" letter-spacing="1">EXTENSIONS</text>
<text x="600" y="479" fill="#cbd5e1" font-size="10">25 MCP templates | AES-256 vault</text>
<text x="600" y="494" fill="#cbd5e1" font-size="10">OAuth2 PKCE | Health monitor</text>
<!-- === Foundation Layer === -->
<rect x="30" y="515" width="840" height="55" rx="8" fill="#1e293b" stroke="#334155" stroke-width="1"/>
<text x="50" y="534" fill="#94a3b8" font-size="9" font-weight="600" letter-spacing="1">FOUNDATION</text>
<rect x="50" y="541" width="110" height="22" rx="3" fill="#334155"/>
<text x="105" y="556" text-anchor="middle" fill="#e2e8f0" font-size="9">openfang-types</text>
<rect x="170" y="541" width="100" height="22" rx="3" fill="#334155"/>
<text x="220" y="556" text-anchor="middle" fill="#e2e8f0" font-size="9">openfang-wire</text>
<rect x="280" y="541" width="120" height="22" rx="3" fill="#334155"/>
<text x="340" y="556" text-anchor="middle" fill="#e2e8f0" font-size="9">openfang-migrate</text>
<rect x="410" y="541" width="110" height="22" rx="3" fill="#334155"/>
<text x="465" y="556" text-anchor="middle" fill="#e2e8f0" font-size="9">openfang-hands</text>
<rect x="530" y="541" width="130" height="22" rx="3" fill="#334155"/>
<text x="595" y="556" text-anchor="middle" fill="#e2e8f0" font-size="9">openfang-extensions</text>
<!-- Footer -->
<text x="450" y="598" text-anchor="middle" fill="#475569" font-size="10">Taint tracking | Ed25519 manifests | Merkle audit | SSRF protection | Zeroizing secrets</text>
<text x="450" y="614" text-anchor="middle" fill="#475569" font-size="10">OFP mutual auth | Path traversal prevention | Subprocess sandbox | Prompt injection scanning</text>
<text x="450" y="630" text-anchor="middle" fill="#334155" font-size="9">16 independent security systems woven through every layer</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,116 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
<defs>
<linearGradient id="of" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#10b981"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
<linearGradient id="oc" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6366f1"/>
<stop offset="100%" stop-color="#4f46e5"/>
</linearGradient>
<linearGradient id="lc" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f59e0b"/>
<stop offset="100%" stop-color="#d97706"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="800" height="480" rx="12" fill="#0f172a"/>
<!-- Title -->
<text x="400" y="36" text-anchor="middle" fill="#f1f5f9" font-size="16" font-weight="700">Feature Coverage Comparison</text>
<text x="400" y="54" text-anchor="middle" fill="#94a3b8" font-size="11">Counted from public documentation and source code analysis</text>
<!-- Legend -->
<rect x="220" y="68" width="12" height="12" rx="2" fill="url(#of)"/>
<text x="237" y="79" fill="#e2e8f0" font-size="11" font-weight="600">OpenFang</text>
<rect x="320" y="68" width="12" height="12" rx="2" fill="url(#oc)"/>
<text x="337" y="79" fill="#e2e8f0" font-size="11">OpenClaw</text>
<rect x="420" y="68" width="12" height="12" rx="2" fill="url(#lc)"/>
<text x="437" y="79" fill="#e2e8f0" font-size="11">LangChain</text>
<!-- Grid lines -->
<line x1="100" y1="105" x2="760" y2="105" stroke="#1e293b" stroke-width="1"/>
<line x1="100" y1="400" x2="760" y2="400" stroke="#334155" stroke-width="1"/>
<!-- Category labels (bottom) -->
<text x="165" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">Channels</text>
<text x="275" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">Skills</text>
<text x="385" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">Tools</text>
<text x="495" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">Providers</text>
<text x="605" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">API Endpoints</text>
<text x="715" y="425" text-anchor="middle" fill="#94a3b8" font-size="10">Tests</text>
<!-- Scale markers -->
<text x="95" y="403" text-anchor="end" fill="#475569" font-size="9">0</text>
<text x="95" y="109" text-anchor="end" fill="#475569" font-size="9">max</text>
<!-- === Channels (max ~40) === -->
<!-- OpenFang: 40 -->
<rect x="130" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="142" y="107" text-anchor="middle" fill="#10b981" font-size="11" font-weight="700">40</text>
<!-- OpenClaw: 38 -->
<rect x="158" y="126" width="24" height="270" rx="3" fill="url(#oc)" opacity="0.85"/>
<text x="170" y="121" text-anchor="middle" fill="#818cf8" font-size="10">38</text>
<!-- LangChain: 0 -->
<rect x="186" y="396" width="24" height="0" rx="3" fill="url(#lc)"/>
<text x="198" y="393" text-anchor="middle" fill="#fbbf24" font-size="10">0</text>
<!-- === Skills (max ~60) === -->
<!-- OpenFang: 60 -->
<rect x="240" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="252" y="107" text-anchor="middle" fill="#10b981" font-size="11" font-weight="700">60</text>
<!-- OpenClaw: 57 -->
<rect x="268" y="126" width="24" height="270" rx="3" fill="url(#oc)" opacity="0.85"/>
<text x="280" y="121" text-anchor="middle" fill="#818cf8" font-size="10">57</text>
<!-- LangChain: 0 -->
<rect x="296" y="396" width="24" height="0" rx="3" fill="url(#lc)"/>
<text x="308" y="393" text-anchor="middle" fill="#fbbf24" font-size="10">0</text>
<!-- === Tools (max ~53) === -->
<!-- OpenFang: 53 -->
<rect x="350" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="362" y="107" text-anchor="middle" fill="#10b981" font-size="11" font-weight="700">53</text>
<!-- OpenClaw: 15 -->
<rect x="378" y="318" width="24" height="78" rx="3" fill="url(#oc)" opacity="0.85"/>
<text x="390" y="313" text-anchor="middle" fill="#818cf8" font-size="10">15</text>
<!-- LangChain: varies -->
<rect x="406" y="352" width="24" height="44" rx="3" fill="url(#lc)" opacity="0.85"/>
<text x="418" y="347" text-anchor="middle" fill="#fbbf24" font-size="10">~8</text>
<!-- === Providers (max ~27) === -->
<!-- OpenFang: 27 -->
<rect x="460" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="472" y="107" text-anchor="middle" fill="#10b981" font-size="11" font-weight="700">27</text>
<!-- OpenClaw: 3 -->
<rect x="488" y="365" width="24" height="31" rx="3" fill="url(#oc)" opacity="0.85"/>
<text x="500" y="360" text-anchor="middle" fill="#818cf8" font-size="10">3</text>
<!-- LangChain: many -->
<rect x="516" y="195" width="24" height="201" rx="3" fill="url(#lc)" opacity="0.85"/>
<text x="528" y="190" text-anchor="middle" fill="#fbbf24" font-size="10">~20</text>
<!-- === API Endpoints (max ~140) === -->
<!-- OpenFang: 140 -->
<rect x="570" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="582" y="107" text-anchor="middle" fill="#10b981" font-size="11" font-weight="700">140</text>
<!-- OpenClaw: ~30 -->
<rect x="598" y="339" width="24" height="57" rx="3" fill="url(#oc)" opacity="0.85"/>
<text x="610" y="334" text-anchor="middle" fill="#818cf8" font-size="10">~30</text>
<!-- LangChain: N/A -->
<rect x="626" y="396" width="24" height="0" rx="3" fill="url(#lc)"/>
<text x="638" y="393" text-anchor="middle" fill="#fbbf24" font-size="10">N/A</text>
<!-- === Tests (max ~1759) === -->
<!-- OpenFang: 1759 -->
<rect x="680" y="112" width="24" height="284" rx="3" fill="url(#of)" opacity="0.95"/>
<text x="692" y="107" text-anchor="middle" fill="#10b981" font-size="10" font-weight="700">1759</text>
<!-- OpenClaw: unknown -->
<rect x="708" y="370" width="24" height="26" rx="3" fill="url(#oc)" opacity="0.5"/>
<text x="720" y="365" text-anchor="middle" fill="#818cf8" font-size="10">?</text>
<!-- LangChain: unknown -->
<rect x="736" y="370" width="24" height="26" rx="3" fill="url(#lc)" opacity="0.5"/>
<text x="748" y="365" text-anchor="middle" fill="#fbbf24" font-size="10">?</text>
<!-- Bottom note -->
<text x="400" y="460" text-anchor="middle" fill="#64748b" font-size="10">All counts verified from source code. OpenClaw and LangChain counts from public documentation.</text>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
<defs>
<linearGradient id="pf" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#10b981"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
<linearGradient id="pg" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#6b7280"/>
<stop offset="100%" stop-color="#9ca3af"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="800" height="420" rx="12" fill="#0f172a"/>
<!-- Title -->
<text x="400" y="36" text-anchor="middle" fill="#f1f5f9" font-size="16" font-weight="700">Runtime Performance — Cold Start &amp; Resource Usage</text>
<text x="400" y="54" text-anchor="middle" fill="#94a3b8" font-size="11">Native Rust binary vs. Python runtime overhead</text>
<!-- === Section 1: Startup Time === -->
<text x="50" y="90" fill="#e2e8f0" font-size="13" font-weight="600">Cold Start Time</text>
<text x="50" y="104" fill="#64748b" font-size="10">Time from process launch to first API response</text>
<text x="175" y="132" text-anchor="end" fill="#e2e8f0" font-size="11">OpenFang</text>
<rect x="180" y="118" width="52" height="22" rx="3" fill="url(#pf)"/>
<text x="238" y="133" fill="#10b981" font-size="12" font-weight="700">&lt;200ms</text>
<text x="175" y="162" text-anchor="end" fill="#cbd5e1" font-size="11">OpenClaw</text>
<rect x="180" y="148" width="320" height="22" rx="3" fill="url(#pg)"/>
<text x="506" y="163" fill="#9ca3af" font-size="12" font-weight="600">~3.2s</text>
<text x="175" y="192" text-anchor="end" fill="#cbd5e1" font-size="11">AutoGPT</text>
<rect x="180" y="178" width="520" height="22" rx="3" fill="url(#pg)"/>
<text x="706" y="193" fill="#9ca3af" font-size="12" font-weight="600">~5.4s</text>
<!-- === Section 2: Binary / Install Size === -->
<text x="50" y="240" fill="#e2e8f0" font-size="13" font-weight="600">Install Footprint</text>
<text x="50" y="254" fill="#64748b" font-size="10">Total disk space required for full installation</text>
<text x="175" y="282" text-anchor="end" fill="#e2e8f0" font-size="11">OpenFang</text>
<rect x="180" y="268" width="36" height="22" rx="3" fill="url(#pf)"/>
<text x="222" y="283" fill="#10b981" font-size="12" font-weight="700">~32 MB</text>
<text x="290" y="283" fill="#475569" font-size="10">(single binary)</text>
<text x="175" y="312" text-anchor="end" fill="#cbd5e1" font-size="11">OpenClaw</text>
<rect x="180" y="298" width="230" height="22" rx="3" fill="url(#pg)"/>
<text x="416" y="313" fill="#9ca3af" font-size="12" font-weight="600">~200 MB</text>
<text x="490" y="313" fill="#475569" font-size="10">(Python + deps)</text>
<text x="175" y="342" text-anchor="end" fill="#cbd5e1" font-size="11">LangChain</text>
<rect x="180" y="328" width="400" height="22" rx="3" fill="url(#pg)"/>
<text x="586" y="343" fill="#9ca3af" font-size="12" font-weight="600">~350 MB</text>
<text x="660" y="343" fill="#475569" font-size="10">(Python + deps)</text>
<!-- Bottom note -->
<text x="400" y="390" text-anchor="middle" fill="#64748b" font-size="10">OpenFang compiled with Rust 1.93 (release profile, LTO enabled). Python frameworks measured with pip install + dependencies.</text>
<text x="400" y="404" text-anchor="middle" fill="#64748b" font-size="10">Startup = time until HTTP health endpoint responds. Install size = du -sh after clean install.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 400" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
<defs>
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#10b981"/>
<stop offset="100%" stop-color="#34d399"/>
</linearGradient>
<linearGradient id="gg" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#6b7280"/>
<stop offset="100%" stop-color="#9ca3af"/>
</linearGradient>
<filter id="ds">
<feDropShadow dx="0" dy="1" stdDeviation="2" flood-opacity="0.1"/>
</filter>
</defs>
<!-- Background -->
<rect width="720" height="400" rx="12" fill="#0f172a"/>
<!-- Title -->
<text x="360" y="38" text-anchor="middle" fill="#f1f5f9" font-size="16" font-weight="700">Security Defense Layers — Independent Systems Count</text>
<text x="360" y="58" text-anchor="middle" fill="#94a3b8" font-size="11">Measured: discrete, independently testable security mechanisms per framework</text>
<!-- Y-axis labels -->
<text x="145" y="108" text-anchor="end" fill="#e2e8f0" font-size="13" font-weight="600">OpenFang</text>
<text x="145" y="158" text-anchor="end" fill="#cbd5e1" font-size="13">OpenClaw</text>
<text x="145" y="208" text-anchor="end" fill="#cbd5e1" font-size="13">LangChain</text>
<text x="145" y="258" text-anchor="end" fill="#cbd5e1" font-size="13">AutoGPT</text>
<text x="145" y="308" text-anchor="end" fill="#cbd5e1" font-size="13">CrewAI</text>
<!-- Bars -->
<!-- OpenFang: 16 layers -->
<rect x="155" y="90" width="480" height="26" rx="4" fill="url(#sg)" filter="url(#ds)"/>
<text x="642" y="108" fill="#10b981" font-size="14" font-weight="700">16</text>
<!-- OpenClaw: 3 (config-based ACL, basic auth, env isolation) -->
<rect x="155" y="140" width="90" height="26" rx="4" fill="url(#gg)" filter="url(#ds)"/>
<text x="252" y="158" fill="#9ca3af" font-size="14" font-weight="600">3</text>
<!-- LangChain: 1 (basic callbacks) -->
<rect x="155" y="190" width="30" height="26" rx="4" fill="url(#gg)" filter="url(#ds)"/>
<text x="192" y="208" fill="#9ca3af" font-size="14" font-weight="600">1</text>
<!-- AutoGPT: 2 (workspace sandbox, rate limits) -->
<rect x="155" y="240" width="60" height="26" rx="4" fill="url(#gg)" filter="url(#ds)"/>
<text x="222" y="258" fill="#9ca3af" font-size="14" font-weight="600">2</text>
<!-- CrewAI: 1 (role-based) -->
<rect x="155" y="290" width="30" height="26" rx="4" fill="url(#gg)" filter="url(#ds)"/>
<text x="192" y="308" fill="#9ca3af" font-size="14" font-weight="600">1</text>
<!-- Legend items -->
<text x="155" y="352" fill="#94a3b8" font-size="10">Layers include: capability gates, WASM sandbox, taint tracking, Merkle audit, Ed25519 manifests, SSRF protection,</text>
<text x="155" y="366" fill="#94a3b8" font-size="10">secret zeroization, OFP mutual auth, security headers, GCRA rate limiter, path traversal prevention,</text>
<text x="155" y="380" fill="#94a3b8" font-size="10">subprocess sandbox, prompt injection scanner, loop guard, session repair, health redaction</text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

725
docs/channel-adapters.md Normal file
View File

@@ -0,0 +1,725 @@
# Channel Adapters
OpenFang connects to messaging platforms through **40 channel adapters**, allowing users to interact with their agents across every major communication platform. Adapters span consumer messaging, enterprise collaboration, social media, community platforms, privacy-focused protocols, and generic webhooks.
All adapters share a common foundation: graceful shutdown via `watch::channel`, exponential backoff on connection failures, `Zeroizing<String>` for secrets, automatic message splitting for platform limits, per-channel model/prompt overrides, DM/group policy enforcement, per-user rate limiting, and output formatting (Markdown, TelegramHTML, SlackMrkdwn, PlainText).
## Table of Contents
- [All 40 Channels](#all-40-channels)
- [Channel Configuration](#channel-configuration)
- [Channel Overrides](#channel-overrides)
- [Formatter, Rate Limiter, and Policies](#formatter-rate-limiter-and-policies)
- [Telegram](#telegram)
- [Discord](#discord)
- [Slack](#slack)
- [WhatsApp](#whatsapp)
- [Signal](#signal)
- [Matrix](#matrix)
- [Email](#email)
- [WebChat (Built-in)](#webchat-built-in)
- [Agent Routing](#agent-routing)
- [Writing Custom Adapters](#writing-custom-adapters)
---
## All 40 Channels
### Core (7)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Telegram | Bot API long-polling | `TELEGRAM_BOT_TOKEN` | `Telegram` |
| Discord | Gateway WebSocket v10 | `DISCORD_BOT_TOKEN` | `Discord` |
| Slack | Socket Mode WebSocket | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` | `Slack` |
| WhatsApp | Cloud API webhook | `WA_ACCESS_TOKEN`, `WA_PHONE_ID`, `WA_VERIFY_TOKEN` | `WhatsApp` |
| Signal | signal-cli REST/JSON-RPC | _(system service)_ | `Signal` |
| Matrix | Client-Server API `/sync` | `MATRIX_TOKEN` | `Matrix` |
| Email | IMAP + SMTP | `EMAIL_PASSWORD` | `Email` |
### Enterprise (8)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Microsoft Teams | Bot Framework v3 webhook + OAuth2 | `TEAMS_APP_ID`, `TEAMS_APP_SECRET` | `Teams` |
| Mattermost | WebSocket + REST v4 | `MATTERMOST_TOKEN`, `MATTERMOST_URL` | `Mattermost` |
| Google Chat | Service account webhook | `GOOGLE_CHAT_SA_KEY`, `GOOGLE_CHAT_SPACE` | `Custom("google_chat")` |
| Webex | Bot SDK WebSocket | `WEBEX_BOT_TOKEN` | `Custom("webex")` |
| Feishu / Lark | Open Platform webhook | `FEISHU_APP_ID`, `FEISHU_APP_SECRET` | `Custom("feishu")` |
| Rocket.Chat | REST polling | `ROCKETCHAT_TOKEN`, `ROCKETCHAT_URL` | `Custom("rocketchat")` |
| Zulip | Event queue long-polling | `ZULIP_EMAIL`, `ZULIP_API_KEY`, `ZULIP_URL` | `Custom("zulip")` |
| XMPP | XMPP protocol (stub) | `XMPP_JID`, `XMPP_PASSWORD`, `XMPP_SERVER` | `Custom("xmpp")` |
### Social (8)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| LINE | Messaging API webhook | `LINE_CHANNEL_SECRET`, `LINE_CHANNEL_TOKEN` | `Custom("line")` |
| Viber | Bot API webhook | `VIBER_AUTH_TOKEN` | `Custom("viber")` |
| Facebook Messenger | Platform API webhook | `MESSENGER_PAGE_TOKEN`, `MESSENGER_VERIFY_TOKEN` | `Custom("messenger")` |
| Mastodon | Streaming API WebSocket | `MASTODON_TOKEN`, `MASTODON_INSTANCE` | `Custom("mastodon")` |
| Bluesky | AT Protocol WebSocket | `BLUESKY_HANDLE`, `BLUESKY_APP_PASSWORD` | `Custom("bluesky")` |
| Reddit | OAuth2 polling | `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET`, `REDDIT_USERNAME`, `REDDIT_PASSWORD` | `Custom("reddit")` |
| LinkedIn | Messaging API polling | `LINKEDIN_ACCESS_TOKEN` | `Custom("linkedin")` |
| Twitch | IRC gateway | `TWITCH_TOKEN`, `TWITCH_CHANNEL` | `Custom("twitch")` |
### Community (6)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| IRC | Raw TCP PRIVMSG | `IRC_SERVER`, `IRC_NICK`, `IRC_PASSWORD` | `Custom("irc")` |
| Guilded | WebSocket | `GUILDED_BOT_TOKEN` | `Custom("guilded")` |
| Revolt | WebSocket | `REVOLT_BOT_TOKEN` | `Custom("revolt")` |
| Keybase | Bot API polling | `KEYBASE_USERNAME`, `KEYBASE_PAPERKEY` | `Custom("keybase")` |
| Discourse | REST polling | `DISCOURSE_API_KEY`, `DISCOURSE_URL` | `Custom("discourse")` |
| Gitter | Streaming API | `GITTER_TOKEN` | `Custom("gitter")` |
### Self-hosted (1)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Nextcloud Talk | REST polling | `NEXTCLOUD_TOKEN`, `NEXTCLOUD_URL` | `Custom("nextcloud")` |
### Privacy (3)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Threema | Gateway API webhook | `THREEMA_ID`, `THREEMA_SECRET` | `Custom("threema")` |
| Nostr | NIP-01 relay WebSocket | `NOSTR_PRIVATE_KEY`, `NOSTR_RELAY` | `Custom("nostr")` |
| Mumble | TCP text protocol | `MUMBLE_SERVER`, `MUMBLE_USERNAME`, `MUMBLE_PASSWORD` | `Custom("mumble")` |
### Workplace (4)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Pumble | Webhook | `PUMBLE_WEBHOOK_URL`, `PUMBLE_TOKEN` | `Custom("pumble")` |
| Flock | Webhook | `FLOCK_TOKEN` | `Custom("flock")` |
| Twist | API v3 polling | `TWIST_TOKEN` | `Custom("twist")` |
| DingTalk | Robot API webhook | `DINGTALK_TOKEN`, `DINGTALK_SECRET` | `Custom("dingtalk")` |
### Notification (2)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| ntfy | SSE pub/sub | `NTFY_TOPIC`, `NTFY_SERVER` | `Custom("ntfy")` |
| Gotify | WebSocket | `GOTIFY_TOKEN`, `GOTIFY_URL` | `Custom("gotify")` |
### Integration (1)
| Channel | Protocol | Env Vars | ChannelType Variant |
|---------|----------|----------|---------------------|
| Webhook | Generic HTTP with HMAC-SHA256 | `WEBHOOK_URL`, `WEBHOOK_SECRET` | `Custom("webhook")` |
---
## Channel Configuration
All channel configurations live in `~/.openfang/config.toml` under the `[channels]` section. Each channel is a subsection:
```toml
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
allowed_users = ["123456789"]
[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
# Enterprise example
[channels.teams]
app_id_env = "TEAMS_APP_ID"
app_secret_env = "TEAMS_APP_SECRET"
default_agent = "ops"
# Social example
[channels.mastodon]
token_env = "MASTODON_TOKEN"
instance = "https://mastodon.social"
default_agent = "social-media"
```
### Common Fields
- `bot_token_env` / `token_env` -- The environment variable holding the bot/access token. OpenFang reads the token from this env var at startup. All secrets are stored as `Zeroizing<String>` and wiped from memory on drop.
- `default_agent` -- The agent name (or ID) that receives messages when no specific routing applies.
- `allowed_users` -- Optional list of platform user IDs allowed to interact. Empty means allow all.
- `overrides` -- Optional per-channel behavior overrides (see [Channel Overrides](#channel-overrides) below).
### Environment Variables Reference (Core Channels)
| Channel | Required Env Vars |
|---------|-------------------|
| Telegram | `TELEGRAM_BOT_TOKEN` |
| Discord | `DISCORD_BOT_TOKEN` |
| Slack | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` |
| WhatsApp | `WA_ACCESS_TOKEN`, `WA_PHONE_ID`, `WA_VERIFY_TOKEN` |
| Matrix | `MATRIX_TOKEN` |
| Email | `EMAIL_PASSWORD` |
Env vars for all other channels are listed in the [All 40 Channels](#all-40-channels) tables above.
---
## Channel Overrides
Every channel adapter supports `ChannelOverrides`, which let you customize behavior per channel without modifying the agent manifest. Add an `[channels.<name>.overrides]` section in `config.toml`:
```toml
[channels.telegram.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Telegram assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "telegram_html"
usage_footer = "compact"
```
### Override Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `model` | `Option<String>` | Agent default | Override the LLM model for this channel. |
| `system_prompt` | `Option<String>` | Agent default | Override the system prompt for this channel. |
| `dm_policy` | `DmPolicy` | `Respond` | How to handle direct messages. |
| `group_policy` | `GroupPolicy` | `MentionOnly` | How to handle group/channel messages. |
| `rate_limit_per_user` | `u32` | `0` (unlimited) | Max messages per minute per user. |
| `threading` | `bool` | `false` | Send replies as thread responses (platforms that support it). |
| `output_format` | `Option<OutputFormat>` | `Markdown` | Output format for this channel. |
| `usage_footer` | `Option<UsageFooterMode>` | None | Whether to append token usage to responses. |
---
## Formatter, Rate Limiter, and Policies
### Output Formatter
The `formatter` module (`openfang-channels/src/formatter.rs`) converts Markdown output from the LLM into platform-native formats:
| OutputFormat | Target | Notes |
|-------------|--------|-------|
| `Markdown` | Standard Markdown | Default; passed through as-is. |
| `TelegramHtml` | Telegram HTML subset | Converts `**bold**` to `<b>`, `` `code` `` to `<code>`, etc. |
| `SlackMrkdwn` | Slack mrkdwn | Converts `**bold**` to `*bold*`, links to `<url\|text>`, etc. |
| `PlainText` | Plain text | Strips all formatting. |
### Per-User Rate Limiter
The `ChannelRateLimiter` (`openfang-channels/src/rate_limiter.rs`) uses a `DashMap` to track per-user message counts. When `rate_limit_per_user` is set on a channel's overrides, the limiter enforces a sliding-window cap of N messages per minute. Excess messages receive a polite rejection.
### DM Policy
Controls how the adapter handles direct messages:
| DmPolicy | Behavior |
|----------|----------|
| `Respond` | Respond to all DMs (default). |
| `AllowedOnly` | Only respond to DMs from users in `allowed_users`. |
| `Ignore` | Silently drop all DMs. |
### Group Policy
Controls how the adapter handles messages in group chats, channels, and rooms:
| GroupPolicy | Behavior |
|-------------|----------|
| `All` | Respond to every message in the group. |
| `MentionOnly` | Only respond when the bot is @mentioned (default). |
| `CommandsOnly` | Only respond to `/command` messages. |
| `Ignore` | Silently ignore all group messages. |
Policy enforcement happens in `dispatch_message()` before the message reaches the agent loop. This means ignored messages consume zero LLM tokens.
---
## Telegram
### Prerequisites
- A Telegram bot token (from [@BotFather](https://t.me/botfather))
### Setup
1. Open Telegram and message `@BotFather`.
2. Send `/newbot` and follow the prompts to create a new bot.
3. Copy the bot token.
4. Set the environment variable:
```bash
export TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
```
5. Add to config:
```toml
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
# Optional: restrict to specific Telegram user IDs
# allowed_users = ["123456789"]
[channels.telegram.overrides]
# Optional: Telegram-native HTML formatting
# output_format = "telegram_html"
# group_policy = "mention_only"
```
6. Restart the daemon:
```bash
openfang start
```
### How It Works
The Telegram adapter uses long-polling via the `getUpdates` API. It polls every few seconds with a 30-second long-poll timeout. On API failures, it applies exponential backoff (starting at 1 second, up to 60 seconds). Shutdown is coordinated via a `watch::channel`.
Messages from authorized users are converted to `ChannelMessage` events and routed to the configured agent. Responses are sent back via the `sendMessage` API. Long responses are automatically split into multiple messages to respect Telegram's 4096-character limit using the shared `split_message()` utility.
### Interactive Setup
```bash
openfang channel setup telegram
```
This walks you through the setup interactively.
---
## Discord
### Prerequisites
- A Discord application and bot (from the [Discord Developer Portal](https://discord.com/developers/applications))
### Setup
1. Go to [Discord Developer Portal](https://discord.com/developers/applications).
2. Click "New Application" and name it.
3. Go to the **Bot** section and click "Add Bot".
4. Copy the bot token.
5. Under **Privileged Gateway Intents**, enable:
- **Message Content Intent** (required to read message content)
6. Go to **OAuth2 > URL Generator**:
- Select scopes: `bot`
- Select permissions: `Send Messages`, `Read Message History`
- Copy the generated URL and open it to invite the bot to your server.
7. Set the environment variable:
```bash
export DISCORD_BOT_TOKEN=MTIzNDU2Nzg5.ABCDEF.ghijklmnop
```
8. Add to config:
```toml
[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"
```
9. Restart the daemon.
### How It Works
The Discord adapter connects to the Discord Gateway via WebSocket (v10). It listens for `MESSAGE_CREATE` events and routes messages to the configured agent. Responses are sent via the REST API's `channels/{id}/messages` endpoint.
The adapter handles Gateway reconnection, heartbeating, and session resumption automatically.
---
## Slack
### Prerequisites
- A Slack app with Socket Mode enabled
### Setup
1. Go to [Slack API](https://api.slack.com/apps) and click "Create New App" > "From Scratch".
2. Enable **Socket Mode** (Settings > Socket Mode):
- Generate an App-Level Token with scope `connections:write`.
- Copy the token (`xapp-...`).
3. Go to **OAuth & Permissions** and add Bot Token Scopes:
- `chat:write`
- `app_mentions:read`
- `im:history`
- `im:read`
- `im:write`
4. Install the app to your workspace.
5. Copy the Bot User OAuth Token (`xoxb-...`).
6. Set the environment variables:
```bash
export SLACK_APP_TOKEN=xapp-1-...
export SLACK_BOT_TOKEN=xoxb-...
```
7. Add to config:
```toml
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
[channels.slack.overrides]
# Optional: Slack-native mrkdwn formatting
# output_format = "slack_mrkdwn"
# threading = true
```
8. Restart the daemon.
### How It Works
The Slack adapter uses Socket Mode, which establishes a WebSocket connection to Slack's servers. This avoids the need for a public webhook URL. The adapter receives events (app mentions, direct messages) and routes them to the configured agent. Responses are posted via the `chat.postMessage` Web API. When `threading = true`, replies are sent to the message's thread via `thread_ts`.
---
## WhatsApp
### Prerequisites
- A Meta Business account with WhatsApp Cloud API access
### Setup
1. Go to [Meta for Developers](https://developers.facebook.com/).
2. Create a Business App.
3. Add the WhatsApp product.
4. Set up a test phone number (or use a production one).
5. Copy:
- Phone Number ID
- Permanent Access Token
- Choose a Verify Token (any string you choose)
6. Set environment variables:
```bash
export WA_PHONE_ID=123456789012345
export WA_ACCESS_TOKEN=EAABs...
export WA_VERIFY_TOKEN=my-secret-verify-token
```
7. Add to config:
```toml
[channels.whatsapp]
mode = "cloud_api"
phone_number_id_env = "WA_PHONE_ID"
access_token_env = "WA_ACCESS_TOKEN"
verify_token_env = "WA_VERIFY_TOKEN"
webhook_port = 8443
default_agent = "assistant"
```
8. Set up a webhook in the Meta dashboard pointing to your server's public URL:
- URL: `https://your-domain.com:8443/webhook/whatsapp`
- Verify Token: the value you chose above
- Subscribe to: `messages`
9. Restart the daemon.
### How It Works
The WhatsApp adapter runs an HTTP server (on the configured `webhook_port`) that receives incoming webhooks from the WhatsApp Cloud API. It handles webhook verification (GET) and message reception (POST). Responses are sent via the Cloud API's `messages` endpoint.
---
## Signal
### Prerequisites
- Signal CLI installed and linked to a phone number
### Setup
1. Install [signal-cli](https://github.com/AsamK/signal-cli).
2. Register or link a phone number.
3. Add to config:
```toml
[channels.signal]
signal_cli_path = "/usr/local/bin/signal-cli"
phone_number = "+1234567890"
default_agent = "assistant"
```
4. Restart the daemon.
### How It Works
The Signal adapter spawns `signal-cli` as a subprocess in daemon mode and communicates via JSON-RPC. Incoming messages are read from the signal-cli output stream and routed to the configured agent.
---
## Matrix
### Prerequisites
- A Matrix homeserver account and access token
### Setup
1. Create a bot account on your Matrix homeserver.
2. Generate an access token.
3. Set the environment variable:
```bash
export MATRIX_TOKEN=syt_...
```
4. Add to config:
```toml
[channels.matrix]
homeserver_url = "https://matrix.org"
access_token_env = "MATRIX_TOKEN"
user_id = "@openfang-bot:matrix.org"
default_agent = "assistant"
```
5. Invite the bot to the rooms you want it to monitor.
6. Restart the daemon.
### How It Works
The Matrix adapter uses the Matrix Client-Server API. It syncs with the homeserver using long-polling (`/sync` with a timeout) and processes new messages from joined rooms. Responses are sent via the `/rooms/{roomId}/send` endpoint.
---
## Email
### Prerequisites
- An email account with IMAP and SMTP access
### Setup
1. For Gmail, create an [App Password](https://myaccount.google.com/apppasswords).
2. Set the environment variable:
```bash
export EMAIL_PASSWORD=abcd-efgh-ijkl-mnop
```
3. Add to config:
```toml
[channels.email]
imap_host = "imap.gmail.com"
imap_port = 993
smtp_host = "smtp.gmail.com"
smtp_port = 587
username = "you@gmail.com"
password_env = "EMAIL_PASSWORD"
poll_interval = 30
default_agent = "email-assistant"
```
4. Restart the daemon.
### How It Works
The email adapter polls the IMAP inbox at the configured interval. New emails are parsed (subject + body) and routed to the configured agent. Responses are sent as reply emails via SMTP, preserving the subject line threading.
---
## WebChat (Built-in)
The WebChat UI is embedded in the daemon and requires no configuration. When the daemon is running:
```
http://127.0.0.1:4200/
```
Features:
- Real-time chat via WebSocket
- Streaming responses (text deltas as they arrive)
- Agent selection (switch between running agents)
- Token usage display
- No authentication required on localhost (protected by CORS)
---
## Agent Routing
The `AgentRouter` determines which agent receives an incoming message. The routing logic is:
1. **Per-channel default**: Each channel config has a `default_agent` field. Messages from that channel go to that agent.
2. **User-agent binding**: If a user has previously been associated with a specific agent (via commands or configuration), messages from that user route to that agent.
3. **Command prefix**: Users can switch agents by sending a command like `/agent coder` in the chat. Subsequent messages will be routed to the "coder" agent.
4. **Fallback**: If no routing applies, messages go to the first available agent.
---
## Writing Custom Adapters
To add support for a new messaging platform, implement the `ChannelAdapter` trait. The trait is defined in `crates/openfang-channels/src/types.rs`.
### The ChannelAdapter Trait
```rust
pub trait ChannelAdapter: Send + Sync {
/// Human-readable name of this adapter.
fn name(&self) -> &str;
/// The channel type this adapter handles.
fn channel_type(&self) -> ChannelType;
/// Start receiving messages. Returns a stream of incoming messages.
async fn start(
&self,
) -> Result<Pin<Box<dyn Stream<Item = ChannelMessage> + Send>>, Box<dyn std::error::Error>>;
/// Send a response back to a user on this channel.
async fn send(
&self,
user: &ChannelUser,
content: ChannelContent,
) -> Result<(), Box<dyn std::error::Error>>;
/// Send a typing indicator (optional -- default no-op).
async fn send_typing(&self, _user: &ChannelUser) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
/// Stop the adapter and clean up resources.
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>>;
/// Get the current health status of this adapter (optional -- default returns disconnected).
fn status(&self) -> ChannelStatus {
ChannelStatus::default()
}
/// Send a response as a thread reply (optional -- default falls back to `send()`).
async fn send_in_thread(
&self,
user: &ChannelUser,
content: ChannelContent,
_thread_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
self.send(user, content).await
}
}
```
### 1. Define Your Adapter
Create `crates/openfang-channels/src/myplatform.rs`:
```rust
use crate::types::{
ChannelAdapter, ChannelContent, ChannelMessage, ChannelStatus, ChannelType, ChannelUser,
};
use futures::stream::{self, Stream};
use std::pin::Pin;
use tokio::sync::watch;
use zeroize::Zeroizing;
pub struct MyPlatformAdapter {
token: Zeroizing<String>,
client: reqwest::Client,
shutdown: watch::Receiver<bool>,
}
impl MyPlatformAdapter {
pub fn new(token: String, shutdown: watch::Receiver<bool>) -> Self {
Self {
token: Zeroizing::new(token),
client: reqwest::Client::new(),
shutdown,
}
}
}
impl ChannelAdapter for MyPlatformAdapter {
fn name(&self) -> &str {
"MyPlatform"
}
fn channel_type(&self) -> ChannelType {
ChannelType::Custom("myplatform".to_string())
}
async fn start(
&self,
) -> Result<Pin<Box<dyn Stream<Item = ChannelMessage> + Send>>, Box<dyn std::error::Error>> {
// Return a stream that yields ChannelMessage items.
// Use self.shutdown to detect when the daemon is stopping.
// Apply exponential backoff on connection failures.
let stream = stream::empty(); // Replace with your polling/WebSocket logic
Ok(Box::pin(stream))
}
async fn send(
&self,
user: &ChannelUser,
content: ChannelContent,
) -> Result<(), Box<dyn std::error::Error>> {
// Send the response back to the platform.
// Use split_message() if the platform has message length limits.
// Use self.client and self.token to call the platform's API.
Ok(())
}
async fn stop(&self) -> Result<(), Box<dyn std::error::Error>> {
// Clean shutdown: close connections, stop polling.
Ok(())
}
fn status(&self) -> ChannelStatus {
ChannelStatus::default()
}
}
```
**Key points for new adapters:**
- Use `ChannelType::Custom("myplatform".to_string())` for the channel type. Only the 9 most common channels have named `ChannelType` variants (`Telegram`, `WhatsApp`, `Slack`, `Discord`, `Signal`, `Matrix`, `Email`, `Teams`, `Mattermost`). All others use `Custom(String)`.
- Wrap secrets in `Zeroizing<String>` so they are wiped from memory on drop.
- Accept a `watch::Receiver<bool>` for coordinated shutdown with the daemon.
- Use exponential backoff for resilience on connection failures.
- Use the shared `split_message(text, max_len)` utility for platforms with message length limits.
### 2. Register the Module
In `crates/openfang-channels/src/lib.rs`:
```rust
pub mod myplatform;
```
### 3. Wire It Into the Bridge
In `crates/openfang-api/src/channel_bridge.rs`, add initialization logic for your adapter alongside the existing adapters.
### 4. Add Config Support
In `openfang-types`, add a config struct:
```rust
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MyPlatformConfig {
pub token_env: String,
pub default_agent: Option<String>,
#[serde(default)]
pub overrides: ChannelOverrides,
}
```
Add it to the `ChannelsConfig` struct and `config.toml` parsing. The `overrides` field gives your channel automatic support for model/prompt overrides, DM/group policies, rate limiting, threading, and output format selection.
### 5. Add CLI Setup Wizard
In `crates/openfang-cli/src/main.rs`, add a case to `cmd_channel_setup` with step-by-step instructions for your platform.
### 6. Test
Write integration tests. Use the `ChannelMessage` type to simulate incoming messages without connecting to the real platform.

1354
docs/cli-reference.md Normal file

File diff suppressed because it is too large Load Diff

1480
docs/configuration.md Normal file

File diff suppressed because it is too large Load Diff

412
docs/desktop.md Normal file
View File

@@ -0,0 +1,412 @@
# OpenFang Desktop App
The OpenFang Desktop App is a native desktop wrapper built with [Tauri 2.0](https://v2.tauri.app/) that packages the entire OpenFang Agent OS into a single, installable application. Instead of running a CLI daemon and opening a browser, users get a native window with system tray integration, OS notifications, and single-instance enforcement -- all powered by the same kernel and API server that the headless deployment uses.
**Crate:** `openfang-desktop`
**Identifier:** `ai.openfang.desktop`
**Product name:** OpenFang
---
## Architecture
The desktop app follows a straightforward embedded-server pattern:
```
+-------------------------------------------+
| Tauri 2.0 Process |
| |
| +-----------+ +--------------------+ |
| | Main | | Background Thread | |
| | Thread | | ("openfang-server")| |
| | | | | |
| | WebView | | tokio runtime | |
| | Window |--->| axum API server | |
| | (main) | | channel bridges | |
| | | | background agents | |
| | System | | | |
| | Tray | | OpenFang Kernel | |
| +-----------+ +--------------------+ |
| | | |
| | http://127.0.0.1:{port} |
| +------------------------------------
+-------------------------------------------+
```
### Startup Sequence
1. **Tracing init** -- `tracing_subscriber` is configured with `RUST_LOG` env, defaulting to `openfang=info,tauri=info`.
2. **Kernel boot** -- `OpenFangKernel::boot(None)` loads the default configuration (from `config.toml` or defaults), wrapped in `Arc`. `set_self_handle()` is called to enable self-referencing kernel operations.
3. **Port binding** -- A `std::net::TcpListener` binds to `127.0.0.1:0` on the main thread, which lets the OS assign a random free port. This ensures the port number is known before any window is created.
4. **Server thread** -- A dedicated OS thread named `"openfang-server"` is spawned. It creates its own `tokio::runtime::Builder::new_multi_thread()` runtime and runs:
- `kernel.start_background_agents()` -- heartbeat monitor, autonomous agents, etc.
- `run_embedded_server()` -- builds the axum router via `openfang_api::server::build_router()`, converts the `std::net::TcpListener` to a `tokio::net::TcpListener`, and serves with graceful shutdown.
5. **Tauri app** -- The Tauri builder is assembled with plugins, managed state, IPC commands, system tray, and a WebView window pointing at `http://127.0.0.1:{port}`.
6. **Event loop** -- Tauri runs its native event loop. On exit, `server_handle.shutdown()` is called to stop the embedded server and kernel.
### ServerHandle
The `ServerHandle` struct (defined in `src/server.rs`) manages the embedded server lifecycle:
```rust
pub struct ServerHandle {
pub port: u16,
pub kernel: Arc<OpenFangKernel>,
shutdown_tx: watch::Sender<bool>,
server_thread: Option<std::thread::JoinHandle<()>>,
}
```
- **`port`** -- The port the embedded server is listening on.
- **`kernel`** -- Shared reference to the kernel, also used by the Tauri app for IPC commands and notifications.
- **`shutdown_tx`** -- A `tokio::sync::watch` channel. Sending `true` triggers graceful shutdown of the axum server.
- **`server_thread`** -- Join handle for the background thread. `shutdown()` joins it to ensure clean termination.
Calling `shutdown()` sends the shutdown signal, joins the background thread, and calls `kernel.shutdown()`. The `Drop` implementation sends the shutdown signal as a best-effort fallback but does not block on the thread join.
### Graceful Shutdown
The axum server uses `with_graceful_shutdown()` wired to the watch channel:
```rust
let server = axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(async move {
let _ = shutdown_rx.wait_for(|v| *v).await;
});
```
After the server shuts down, channel bridges (Telegram, Slack, etc.) are stopped via `bridge.stop().await`.
---
## Features
### System Tray
The system tray (defined in `src/tray.rs`) provides quick access without bringing up the main window:
| Menu Item | Behavior |
|-----------|----------|
| **Show Window** | Calls `show()`, `unminimize()`, and `set_focus()` on the main WebView window |
| **Open in Browser** | Reads the port from managed `PortState` and opens `http://127.0.0.1:{port}` in the default browser |
| **Agents: N running** | Disabled (info only) — shows current agent count |
| **Status: Running (uptime)** | Disabled (info only) — shows uptime in human-readable format |
| **Launch at Login** | Checkbox — toggles OS-level auto-start via `tauri-plugin-autostart` |
| **Check for Updates...** | Checks for updates, downloads, installs, and restarts if available. Shows notifications for progress/success/failure |
| **Open Config Directory** | Opens `~/.openfang/` in the OS file manager |
| **Quit OpenFang** | Logs the quit event and calls `app.exit(0)` |
The tray tooltip reads **"OpenFang Agent OS"**.
**Left-click on tray icon** shows the main window (same as "Show Window" menu item). This is implemented via `on_tray_icon_event` listening for `MouseButton::Left` with `MouseButtonState::Up`.
### Single-Instance Enforcement
On desktop platforms, `tauri-plugin-single-instance` prevents multiple copies of OpenFang from running simultaneously. When a second instance attempts to launch, the existing instance's main window is shown, unminimized, and focused:
```rust
#[cfg(desktop)]
{
builder = builder.plugin(tauri_plugin_single_instance::init(
|app, _args, _cwd| {
if let Some(w) = app.get_webview_window("main") {
let _ = w.show();
let _ = w.unminimize();
let _ = w.set_focus();
}
},
));
}
```
### Hide-to-Tray on Close
Closing the window does not quit the application. Instead, the window is hidden and the close event is suppressed:
```rust
.on_window_event(|window, event| {
#[cfg(desktop)]
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
let _ = window.hide();
api.prevent_close();
}
})
```
To actually quit, use the **"Quit OpenFang"** option in the system tray menu.
### Native OS Notifications
The app subscribes to the kernel's event bus and forwards critical events as native desktop notifications using `tauri-plugin-notification`:
| Event | Notification Title | Body |
|-------|-------------------|------|
| `LifecycleEvent::Crashed` | "Agent Crashed" | `Agent {id} crashed: {error}` |
| `LifecycleEvent::Spawned` | "Agent Started" | `Agent "{name}" is now running` |
| `SystemEvent::HealthCheckFailed` | "Health Check Failed" | `Agent {id} unresponsive for {secs}s` |
All other events are silently skipped. The notification listener runs as an async task spawned via `tauri::async_runtime::spawn` and handles broadcast lag gracefully (logs a warning and continues).
---
## IPC Commands
Eleven Tauri IPC commands are registered, callable from the WebView frontend via `invoke()`:
### `get_port`
Returns the port number (`u16`) the embedded server is listening on.
```typescript
// Frontend usage
const port: number = await invoke("get_port");
```
### `get_status`
Returns a JSON object with runtime status:
```json
{
"status": "running",
"port": 8042,
"agents": 5,
"uptime_secs": 3600
}
```
- `agents` -- count of registered agents from `kernel.registry.list()`.
- `uptime_secs` -- seconds since the kernel state was initialized (via `Instant::now()` at startup).
### `get_agent_count`
Returns the number of registered agents (`usize`) as a simple integer.
```typescript
const count: number = await invoke("get_agent_count");
```
### `import_agent_toml`
Opens a native file picker for `.toml` files. Validates the selected file as an `AgentManifest`, copies it to `~/.openfang/agents/{name}/agent.toml`, and spawns the agent. Returns the agent name on success.
### `import_skill_file`
Opens a native file picker for skill files (`.md`, `.toml`, `.py`, `.js`, `.wasm`). Copies the file to `~/.openfang/skills/` and triggers a hot-reload of the skill registry.
### `get_autostart` / `set_autostart`
Check or toggle whether OpenFang launches at OS login. Uses `tauri-plugin-autostart` (launchd on macOS, registry on Windows, systemd on Linux).
### `check_for_updates`
Checks for available updates without installing. Returns an `UpdateInfo` object:
```json
{ "available": true, "version": "0.2.0", "body": "Release notes..." }
```
### `install_update`
Downloads and installs the latest update, then restarts the app. The command does not return on success (the app restarts). Returns an error string on failure.
```typescript
await invoke("install_update"); // App restarts if update succeeds
```
### `open_config_dir` / `open_logs_dir`
Opens `~/.openfang/` or `~/.openfang/logs/` in the OS file manager.
---
## Window Configuration
The main window is created programmatically in the `setup` closure (not via `tauri.conf.json`, which declares an empty `windows: []` array):
| Property | Value |
|----------|-------|
| Window label | `"main"` |
| Title | `"OpenFang"` |
| URL | `http://127.0.0.1:{port}` (external) |
| Inner size | 1280 x 800 |
| Minimum inner size | 800 x 600 |
| Position | Centered |
The window uses `WebviewUrl::External(...)` rather than a bundled frontend, because the WebView renders the axum-served UI.
### Auto-Updater
The app checks for updates 10 seconds after startup. If an update is available, it is downloaded, installed, and the app restarts automatically. Users can also trigger a manual check via the system tray.
**Flow:**
1. Startup check (10s delay) → `check_for_update()` → if available → notify user → `download_and_install_update()` → app restarts
2. Tray "Check for Updates" → same flow, with failure notification if install fails
**Configuration** (in `tauri.conf.json`):
- `plugins.updater.pubkey` — Ed25519 public key (must match the signing private key)
- `plugins.updater.endpoints` — URL to `latest.json` (hosted on GitHub Releases)
- `plugins.updater.windows.installMode``"passive"` (install without full UI)
**Signing:** Every release bundle is signed with `TAURI_SIGNING_PRIVATE_KEY` (GitHub Secret). The `tauri-action` generates `latest.json` containing download URLs and signatures for each platform.
See [Production Checklist](production-checklist.md) for key generation and setup instructions.
### CSP
The `tauri.conf.json` configures a Content Security Policy that allows connections to the local embedded server:
```
default-src 'self' http://127.0.0.1:* ws://127.0.0.1:*;
img-src 'self' data: http://127.0.0.1:*;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline'
```
This permits the WebView to load content from the localhost API server while blocking external resource loading. The axum API server provides additional security headers middleware.
---
## Building
### Prerequisites
- **Rust** (stable toolchain)
- **Tauri CLI v2**: `cargo install tauri-cli --version "^2"`
- **Platform-specific dependencies**:
- **Windows**: WebView2 (included in Windows 10/11), Visual Studio Build Tools
- **macOS**: Xcode Command Line Tools
- **Linux**: `libwebkit2gtk-4.1-dev`, `libappindicator3-dev`, `librsvg2-dev`, `libssl-dev`, `build-essential`
### Development
```bash
cd crates/openfang-desktop
cargo tauri dev
```
This launches the app with hot-reload support. The console window is visible in debug builds for tracing output.
### Production Build
```bash
cd crates/openfang-desktop
cargo tauri build
```
This produces platform-specific installers:
- **Windows**: `.msi` and `.exe` (NSIS) installers
- **macOS**: `.dmg` and `.app` bundle
- **Linux**: `.deb`, `.rpm`, and `.AppImage`
The release binary suppresses the console window on Windows via:
```rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
```
### Bundle Configuration
From `tauri.conf.json`:
```json
{
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/icon.png",
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png"
]
}
}
```
The `"targets": "all"` setting generates every available package format for the current platform. Icons are provided at multiple resolutions, plus an `icon.ico` for Windows.
---
## Plugins
| Plugin | Version | Purpose |
|--------|---------|---------|
| `tauri-plugin-notification` | 2 | Native OS notifications for kernel events and update progress |
| `tauri-plugin-shell` | 2 | Shell/process access from the WebView |
| `tauri-plugin-dialog` | 2 | Native file picker for agent/skill import |
| `tauri-plugin-single-instance` | 2 | Prevents multiple instances (desktop only) |
| `tauri-plugin-autostart` | 2 | Launch at OS login (desktop only) |
| `tauri-plugin-updater` | 2 | Signed auto-updates from GitHub Releases (desktop only) |
| `tauri-plugin-global-shortcut` | 2 | Ctrl+Shift+O/N/C shortcuts (desktop only) |
### Capabilities
The default capability set (defined in `capabilities/default.json`) grants:
```json
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"notification:default",
"shell:default",
"dialog:default",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"global-shortcut:allow-is-registered",
"autostart:default",
"updater:default"
]
}
```
Only the `"main"` window receives these permissions.
---
## Mobile Ready
The codebase includes conditional compilation guards for mobile platform support:
- **Entry point**: The `run()` function is annotated with `#[cfg_attr(mobile, tauri::mobile_entry_point)]`, allowing Tauri to use it as the mobile entry point.
- **Desktop-only features**: System tray setup, single-instance enforcement, and hide-to-tray on close are all gated behind `#[cfg(desktop)]` so they compile out on mobile targets.
- **Mobile targets**: iOS and Android builds are structurally supported by the Tauri 2.0 framework, though the kernel and API server would still boot in-process on the device.
---
## File Structure
```
crates/openfang-desktop/
build.rs # tauri_build::build()
Cargo.toml # Crate dependencies and metadata
tauri.conf.json # Tauri app configuration
capabilities/
default.json # Permission grants for the main window
gen/
schemas/ # Auto-generated Tauri schemas
icons/
icon.png # Source icon (327 KB)
icon.ico # Windows icon
32x32.png # Small icon
128x128.png # Standard icon
128x128@2x.png # HiDPI icon
src/
main.rs # Binary entry point (calls lib::run())
lib.rs # Tauri app builder, state types, event listener
commands.rs # IPC command handlers (get_port, get_status, get_agent_count)
server.rs # ServerHandle, kernel boot, embedded axum server
tray.rs # System tray menu and event handlers
```
---
## Environment Variables
| Variable | Effect |
|----------|--------|
| `RUST_LOG` | Controls tracing verbosity. Defaults to `openfang=info,tauri=info` if unset. |
All other OpenFang environment variables (API keys, configuration) apply as normal since the desktop app boots the same kernel as the headless daemon.

376
docs/getting-started.md Normal file
View File

@@ -0,0 +1,376 @@
# Getting Started with OpenFang
This guide walks you through installing OpenFang, configuring your first LLM provider, spawning an agent, and chatting with it.
## Table of Contents
- [Installation](#installation)
- [Configuration](#configuration)
- [Spawn Your First Agent](#spawn-your-first-agent)
- [Chat with an Agent](#chat-with-an-agent)
- [Start the Daemon](#start-the-daemon)
- [Using the WebChat UI](#using-the-webchat-ui)
- [Next Steps](#next-steps)
---
## Installation
### Option 1: Desktop App (Windows / macOS / Linux)
Download the installer for your platform from the [latest release](https://github.com/RightNow-AI/openfang/releases/latest):
| Platform | File |
|---|---|
| Windows | `.msi` installer |
| macOS | `.dmg` disk image |
| Linux | `.AppImage` or `.deb` |
The desktop app includes the full OpenFang system with a native window, system tray, auto-updates, and OS notifications. Updates are installed automatically in the background.
### Option 2: Shell Installer (Linux / macOS)
```bash
curl -sSf https://openfang.sh | sh
```
This downloads the latest CLI binary and installs it to `~/.openfang/bin/`.
### Option 3: PowerShell Installer (Windows)
```powershell
irm https://openfang.sh/install.ps1 | iex
```
Downloads the latest CLI binary, verifies its SHA256 checksum, and adds it to your user PATH.
### Option 4: Cargo Install (Any Platform)
Requires Rust 1.75+:
```bash
cargo install --git https://github.com/RightNow-AI/openfang openfang-cli
```
Or build from source:
```bash
git clone https://github.com/RightNow-AI/openfang.git
cd openfang
cargo install --path crates/openfang-cli
```
### Option 5: Docker
```bash
docker pull ghcr.io/RightNow-AI/openfang:latest
docker run -d \
--name openfang \
-p 4200:4200 \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
-v openfang-data:/data \
ghcr.io/RightNow-AI/openfang:latest
```
Or use Docker Compose:
```bash
git clone https://github.com/RightNow-AI/openfang.git
cd openfang
# Set your API keys in environment or .env file
docker compose up -d
```
### Verify Installation
```bash
openfang --version
```
---
## Configuration
### Initialize
Run the init command to create the `~/.openfang/` directory and a default config file:
```bash
openfang init
```
This creates:
```
~/.openfang/
config.toml # Main configuration
data/ # Database and runtime data
agents/ # Agent manifests (optional)
```
### Set Up an API Key
OpenFang needs at least one LLM provider API key. Set it as an environment variable:
```bash
# Anthropic (Claude)
export ANTHROPIC_API_KEY=sk-ant-...
# Or OpenAI
export OPENAI_API_KEY=sk-...
# Or Groq (free tier available)
export GROQ_API_KEY=gsk_...
```
Add the export to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.) to persist it.
### Edit the Config
The default config uses Anthropic. To change the provider, edit `~/.openfang/config.toml`:
```toml
[default_model]
provider = "groq" # anthropic, openai, groq, ollama, etc.
model = "llama-3.3-70b-versatile" # Model identifier for the provider
api_key_env = "GROQ_API_KEY" # Env var holding the API key
[memory]
decay_rate = 0.05 # Memory confidence decay rate
[network]
listen_addr = "127.0.0.1:4200" # OFP listen address
```
### Verify Your Setup
```bash
openfang doctor
```
This checks that your config exists, API keys are set, and the toolchain is available.
---
## Spawn Your First Agent
### Using a Built-in Template
OpenFang ships with 30 agent templates. Spawn the hello-world agent:
```bash
openfang agent spawn agents/hello-world/agent.toml
```
Output:
```
Agent spawned successfully!
ID: a1b2c3d4-e5f6-...
Name: hello-world
```
### Using a Custom Manifest
Create your own `my-agent.toml`:
```toml
name = "my-assistant"
version = "0.1.0"
description = "A helpful assistant"
author = "you"
module = "builtin:chat"
[model]
provider = "groq"
model = "llama-3.3-70b-versatile"
[capabilities]
tools = ["file_read", "file_list", "web_fetch"]
memory_read = ["*"]
memory_write = ["self.*"]
```
Then spawn it:
```bash
openfang agent spawn my-agent.toml
```
### List Running Agents
```bash
openfang agent list
```
Output:
```
ID NAME STATE PROVIDER MODEL
-----------------------------------------------------------------------------------------------
a1b2c3d4-e5f6-... hello-world Running groq llama-3.3-70b-versatile
```
---
## Chat with an Agent
Start an interactive chat session using the agent ID:
```bash
openfang agent chat a1b2c3d4-e5f6-...
```
Or use the quick chat command (picks the first available agent):
```bash
openfang chat
```
Or specify an agent by name:
```bash
openfang chat hello-world
```
Example session:
```
Chat session started (daemon mode). Type 'exit' or Ctrl+C to quit.
you> Hello! What can you do?
agent> I'm the hello-world agent running on OpenFang. I can:
- Read files from the filesystem
- List directory contents
- Fetch web pages
Try asking me to read a file or look up something on the web!
[tokens: 142 in / 87 out | iterations: 1]
you> List the files in the current directory
agent> Here are the files in the current directory:
- Cargo.toml
- Cargo.lock
- README.md
- agents/
- crates/
- docs/
...
you> exit
Chat session ended.
```
---
## Start the Daemon
For persistent agents, multi-user access, and the WebChat UI, start the daemon:
```bash
openfang start
```
Output:
```
Starting OpenFang daemon...
OpenFang daemon running on http://127.0.0.1:4200
Press Ctrl+C to stop.
```
The daemon provides:
- **REST API** at `http://127.0.0.1:4200/api/`
- **WebSocket** endpoint at `ws://127.0.0.1:4200/api/agents/{id}/ws`
- **WebChat UI** at `http://127.0.0.1:4200/`
- **OFP networking** on port 4200
### Check Status
```bash
openfang status
```
### Stop the Daemon
Press `Ctrl+C` in the terminal running the daemon, or:
```bash
curl -X POST http://127.0.0.1:4200/api/shutdown
```
---
## Using the WebChat UI
With the daemon running, open your browser to:
```
http://127.0.0.1:4200/
```
The embedded WebChat UI allows you to:
- View all running agents
- Chat with any agent in real-time (via WebSocket)
- See streaming responses as they are generated
- View token usage per message
---
## Next Steps
Now that you have OpenFang running:
- **Explore agent templates**: Browse the `agents/` directory for 30 pre-built agents (coder, researcher, writer, ops, analyst, security-auditor, and more).
- **Create custom agents**: Write your own `agent.toml` manifests. See the [Architecture guide](architecture.md) for details on capabilities and scheduling.
- **Set up channels**: Connect any of 40 messaging platforms (Telegram, Discord, Slack, WhatsApp, LINE, Mastodon, and 34 more). See [Channel Adapters](channel-adapters.md).
- **Use bundled skills**: 60 expert knowledge skills are pre-installed (GitHub, Docker, Kubernetes, security audit, prompt engineering, etc.). See [Skill Development](skill-development.md).
- **Build custom skills**: Extend agents with Python, WASM, or prompt-only skills. See [Skill Development](skill-development.md).
- **Use the API**: 76 REST/WS/SSE endpoints, including an OpenAI-compatible `/v1/chat/completions`. See [API Reference](api-reference.md).
- **Switch LLM providers**: 20 providers supported (Anthropic, OpenAI, Gemini, Groq, DeepSeek, xAI, Ollama, and more). Per-agent model overrides.
- **Set up workflows**: Chain multiple agents together. Use `openfang workflow create` with a TOML workflow definition.
- **Use MCP**: Connect to external tools via Model Context Protocol. Configure in `config.toml` under `[[mcp_servers]]`.
- **Migrate from OpenClaw**: Run `openfang migrate --from openclaw`. See [MIGRATION.md](../MIGRATION.md).
- **Desktop app**: Run `cargo tauri dev` for a native desktop experience with system tray.
- **Run diagnostics**: `openfang doctor` checks your entire setup.
### Useful Commands Reference
```bash
openfang init # Initialize ~/.openfang/
openfang start # Start the daemon
openfang status # Check daemon status
openfang doctor # Run diagnostic checks
openfang agent spawn <manifest.toml> # Spawn an agent
openfang agent list # List all agents
openfang agent chat <id> # Chat with an agent
openfang agent kill <id> # Kill an agent
openfang workflow list # List workflows
openfang workflow create <file.json> # Create a workflow
openfang workflow run <id> <input> # Run a workflow
openfang trigger list # List event triggers
openfang trigger create <args> # Create a trigger
openfang trigger delete <id> # Delete a trigger
openfang skill install <source> # Install a skill
openfang skill list # List installed skills
openfang skill search <query> # Search FangHub
openfang skill create # Scaffold a new skill
openfang channel list # List channel status
openfang channel setup <channel> # Interactive setup wizard
openfang config show # Show current config
openfang config edit # Open config in editor
openfang chat [agent] # Quick chat (alias)
openfang migrate --from openclaw # Migrate from OpenClaw
openfang mcp # Start MCP server (stdio)
```

462
docs/launch-roadmap.md Normal file
View File

@@ -0,0 +1,462 @@
# OpenFang Launch Roadmap
> Competitive gap analysis vs OpenClaw. Organized into 4 sprints.
> Each item has: what, why, files to touch, and done criteria.
---
## Sprint 1 — Stop the Bleeding (3-4 days)
These are showstoppers. The app literally crashes or looks broken without them.
### 1.1 Fix Token Bloat (agents crash after 3 messages) -- DONE
**Status: COMPLETE** -- All 13 items implemented across compactor.rs, context_overflow.rs, context_budget.rs, agent_loop.rs, kernel.rs, agent.rs, and prompt_builder.rs.
**Problem (was):** A single chat message consumed ~45K input tokens (tool definitions + system prompt). By message 3, it hit the 100K quota and crashed with "Token quota exceeded."
**What to do:**
1. **Add token estimation & context guard** (`crates/openfang-runtime/src/compactor.rs`)
- Add `estimate_token_count(messages, system_prompt, tools)` — chars/4 heuristic
- Add `needs_compaction_by_tokens(estimated, context_window)` — triggers at 70% capacity
- Add `token_threshold_ratio: f64` (default 0.7) and `context_window_tokens: usize` (default 200_000) to `CompactionConfig`
- Lower message threshold from 80 to 30
2. **Add in-loop token guard** (`crates/openfang-runtime/src/agent_loop.rs`)
- Before each LLM call: estimate tokens vs context window
- Over 70%: emergency-trim old messages (keep last 10), log warning
- Over 90%: aggressive trim to last 4 messages + inject summary
- Lower `MAX_HISTORY_MESSAGES` from 40 to 20
- Lower `MAX_TOOL_RESULT_CHARS` from 50,000 to 15,000
3. **Filter tools by profile in kernel** (`crates/openfang-kernel/src/kernel.rs`)
- In `available_tools()`: use manifest's `tool_profile` to filter
- Call `tool_profile.tools()` for allowed tool names, filter `builtin_tool_definitions()`
- Only send ALL tools if profile is `Full` AND agent has `ToolAll` capability
- This alone cuts default chat from 41 tools to ~8 tools (saves ~15-20K tokens)
4. **Raise default token quota** (`crates/openfang-types/src/agent.rs`)
- Change `max_llm_tokens_per_hour` from 100_000 to 1_000_000
- 100K is too low — a single system prompt is 30-40K tokens
5. **Token-based compaction trigger** (`crates/openfang-kernel/src/kernel.rs`)
- In `send_message_streaming()`: replace message-count-only check with token-aware check
- After compaction, verify token count actually decreased
6. **Compact system prompt injections** (`crates/openfang-kernel/src/kernel.rs`)
- Cap canonical context to 500 chars
- Cap memory context to 3 items / 200 chars each
- Cap skill knowledge to 2000 chars total
- Skip MCP summary if tool count < 3
**Done when:**
- `cargo test --workspace` passes
- Start an agent, send 10+ messages no "Token quota exceeded" error
- First-message token count drops from ~45K to ~15-20K
---
### 1.2 Branding & Icon Assets
**Problem:** Desktop app may show Tauri default icons. Branding assets exist at `~/Downloads/openfang/output/` but aren't installed.
**What to do:**
1. Generate all required icon sizes from source PNG (`openfang-logo-transparent.png`, 2000x2000)
2. Place into `crates/openfang-desktop/icons/`:
- `icon.png` (1024x1024)
- `icon.ico` (multi-size: 256, 128, 64, 48, 32, 16)
- `32x32.png`
- `128x128.png`
- `128x128@2x.png` (256x256)
3. Replace web UI logo at `crates/openfang-api/static/logo.png`
4. Update favicon if one exists
**Assets available:**
- `openfang-logo-transparent.png` (328KB, 2000x2000) primary source
- `openfang-logo-black-bg.png` (312KB) for dark contexts
- `openfang-vector-transparent.svg` (293KB) scalable vector
- `openfang-animated.svg` (310KB) for loading screens
**Done when:**
- Desktop app shows OpenFang logo in taskbar, title bar, and installer
- Web UI shows correct logo in sidebar and favicon
---
### 1.3 Tauri Signing Keypair -- DONE
**Status: COMPLETE** Generated Ed25519 signing keypair via `cargo tauri signer generate --ci`. Public key installed in `tauri.conf.json`. Private key at `~/.tauri/openfang.key`. Set `TAURI_SIGNING_PRIVATE_KEY_PATH` in CI secrets.
**Problem (was):** `tauri.conf.json` has `"pubkey": "PLACEHOLDER_REPLACE_WITH_GENERATED_PUBKEY"`. Auto-updater is completely dead without this.
---
### 1.4 First-Run Experience Audit -- DONE
**Status: COMPLETE** Full code audit verified: all 8 wizard API endpoints exist and are implemented (providers list/set/test, templates list, agent spawn, channel configure). 6-step wizard (Welcome Provider Agent Try It Channel Done) fully wired. 13 provider help links connected. Auto-detection of existing API keys via auth_status field working. Config editor fix added (POST /api/config/set).
**Problem (was):** New users need a smooth setup wizard. The web UI has a setup checklist + wizard but it's untested end-to-end.
---
## Sprint 2 — Competitive Parity (4-5 days)
These close the gaps that would make users pick OpenClaw over OpenFang.
### 2.1 Browser Screenshot Rendering in Chat -- DONE
**Status: COMPLETE** browser.rs saves screenshots to uploads temp dir and returns JSON with `image_urls`. chat.js detects `browser_screenshot` tool results and populates `_imageUrls` for inline display.
**Problem (was):** The `browser_screenshot` tool returns base64 image data, but the UI renders it as raw text in a `<pre>` tag.
**What to do:**
1. In `chat.js` `tool_result` handler: detect `browser_screenshot` tool results
2. Parse the base64 data, create `/api/uploads/` entry (like image_generate)
3. Store `_imageUrls` on the tool card
4. UI already renders `tool._imageUrls` just need to populate it
**Files:** `crates/openfang-api/static/js/pages/chat.js`, `crates/openfang-runtime/src/tool_runner.rs`
**Done when:**
- Browser screenshots appear as inline images in tool cards
- Clicking opens full-size in new tab
---
### 2.2 Chat Message Search -- DONE
**Status: COMPLETE** Search bar with Ctrl+F shortcut, real-time filtering via `filteredMessages` getter, text highlighting via `highlightSearch()`, match count display.
**Problem (was):** No way to search through chat history. OpenClaw has full-text search.
**What to do:**
1. Add search input to chat header (icon toggle, expands to input)
2. Client-side filter: `messages.filter(m => m.text.includes(query))`
3. Highlight matches in message bubbles
4. Jump-to-message on click
**Files:** `index_body.html` (search UI), `chat.js` (search logic), `components.css` (search styles)
**Done when:**
- Ctrl+F or search icon opens search bar
- Typing filters messages in real-time
- Matching text is highlighted
---
### 2.3 Skill Marketplace Polish -- DONE
**Status: COMPLETE** Already polished with 4 tabs (Installed, ClawHub, MCP Servers, Quick Start), live search with debounce, sort pills, categories, install/uninstall, skill detail modal, runtime badges, source badges, enable/disable toggles, security warnings.
**Problem (was):** Skills page exists but needs polish for browsing/installing skills.
**What to do:**
1. Verify `/api/skills/search` endpoint works
2. Verify `/api/skills/install` endpoint works
3. Polish UI: skill cards with descriptions, install buttons, installed badge
4. Add FangHub registry URL if not configured
**Files:** `crates/openfang-api/static/js/pages/skills.js`, `crates/openfang-api/src/routes.rs`
**Done when:**
- Users can browse, search, and install skills from the web UI
- Installed skills show "Installed" badge
- Error states handled gracefully
---
### 2.4 Install Script Deployment
**Problem:** `openfang.sh` domain isn't set up. Users can't do `curl -sSf https://openfang.sh | sh`.
**What to do:**
1. Set up GitHub Pages or Cloudflare Worker for openfang.sh
2. Serve `scripts/install.sh` at root
3. Serve `scripts/install.ps1` at `/install.ps1`
4. Test on fresh Linux, macOS, and Windows machines
**Done when:**
- `curl -sSf https://openfang.sh | sh` installs the latest release
- `irm https://openfang.sh/install.ps1 | iex` works on Windows PowerShell
---
### 2.5 First-Run Wizard End-to-End -- DONE
**Status: COMPLETE** 6-step wizard (Welcome Provider Agent Try It Channel Done) with provider auto-detection, API key help links (12 providers), 10 agent templates with category filtering, mini chat for testing, channel setup (Telegram/Discord/Slack), setup checklist on overview page.
**Problem (was):** Setup wizard needs to actually work for zero-config users.
**What to do:**
1. Test wizard steps: welcome, API key entry, provider selection, model pick, first agent spawn
2. Fix any broken flows
3. Add provider-specific help text (where to get API keys)
4. Auto-detect existing `.env` API keys and pre-fill
**Files:** `index_body.html` (wizard template), `routes.rs` (config save endpoint)
**Done when:**
- New user completes wizard in < 2 minutes
- Wizard detects existing API keys from environment
- Clear error messages for invalid keys
---
## Sprint 3 — Differentiation (5-7 days)
These are features where OpenFang can leapfrog OpenClaw.
### 3.1 Voice Input/Output in Web UI -- DONE
**Status: COMPLETE** Mic button with hold-to-record, MediaRecorder with webm/opus codec, auto-upload and transcription, TTS audio player in tool cards, recording timer display, CSP updated for media-src blob:.
**Problem (was):** `media_transcribe` and `text_to_speech` tools exist but there's no mic button or audio playback in the UI.
**What to do:**
1. Add mic button next to attach button in input area
2. Use Web Audio API / MediaRecorder for recording
3. Upload audio as attachment, auto-invoke `media_transcribe`
4. For TTS responses: detect audio URLs in tool results, add `<audio>` player
5. Add audio playback controls (play/pause, seek)
**Files:** `index_body.html`, `chat.js`, `components.css`
**Done when:**
- Users can hold mic button to record voice transcribed to text sent as message
- TTS responses play inline with audio controls
---
### 3.2 Canvas Rendering Verification -- DONE
**Status: COMPLETE** Fixed CSP to allow `frame-src 'self' blob:` and `media-src 'self' blob:` in both API middleware and Tauri config. Added `isHtml` flag bypass to skip markdown processing for canvas messages. Added canvas-panel CSS with vertical resize handle.
**Problem (was):** Canvas WebSocket event exists (`case 'canvas':`) but rendering may not work in practice.
**What to do:**
1. Test: send a message that triggers canvas output
2. Verify iframe sandbox renders correctly
3. Fix CSP if blocking iframe content
4. Add resize handles for canvas iframe
5. Test on desktop app (Tauri webview CSP)
**Files:** `chat.js` (canvas handler), `middleware.rs` (CSP), `index_body.html`
**Done when:**
- Canvas events render interactive iframes in chat
- Works in both web browser and desktop app
---
### 3.3 JavaScript/Python SDK -- DONE
**Status: COMPLETE** Created `sdk/javascript/` (@openfang/sdk) with full REST client: agent CRUD, streaming via SSE, sessions, workflows, skills, channels, memory KV, triggers, schedules + TypeScript declarations. Created `sdk/python/openfang_client.py` (zero-dependency stdlib urllib) with same coverage. Both include basic + streaming examples. Python `setup.py` for pip install.
**Problem (was):** No official client libraries. Developers must raw-fetch the API.
**What to do:**
1. Create `sdks/javascript/` thin wrapper around REST API
- Agent CRUD, message send, streaming via EventSource, file upload
- Publish to npm as `@openfang/sdk`
2. Create `sdks/python/` thin wrapper with httpx
- Same operations
- Publish to PyPI as `openfang`
3. Include usage examples in README
**Done when:**
- `npm install @openfang/sdk` works
- `pip install openfang` works
- Basic example: create agent, send message, get response
---
### 3.4 Observability & Metrics Export -- DONE
**Status: COMPLETE** Added `GET /api/metrics` endpoint returning Prometheus text format. Metrics: `openfang_uptime_seconds`, `openfang_agents_active`, `openfang_agents_total`, `openfang_tokens_total{agent,provider,model}`, `openfang_tool_calls_total{agent}`, `openfang_panics_total`, `openfang_restarts_total`, `openfang_info{version}`.
**Problem (was):** No way to monitor OpenFang in production (no Prometheus, no OpenTelemetry).
**What to do:**
1. Add `/api/metrics` endpoint with Prometheus format
- `openfang_agents_active` gauge
- `openfang_messages_total` counter (by agent, by channel)
- `openfang_tokens_total` counter (by provider, by model)
- `openfang_request_duration_seconds` histogram
- `openfang_tool_calls_total` counter (by tool name)
- `openfang_errors_total` counter (by type)
2. Optional: OTLP export for tracing spans
**Files:** `crates/openfang-api/src/routes.rs`, new `metrics.rs` module
**Done when:**
- `/api/metrics` returns valid Prometheus text format
- Grafana can scrape and visualize the metrics
---
### 3.5 Workflow Visual Builder (Leapfrog Opportunity) -- DONE
**Status: COMPLETE** Added `workflow-builder.js` with full SVG canvas-based visual builder. Node palette with 7 types (Agent, Parallel Fan-out, Condition, Loop, Collect, Start, End). Drag-and-drop from palette, node dragging, bezier curve connections between ports, zoom/pan, auto-layout. Node editor panel for configuring agent, condition expression, loop iterations, fan-out count, collect strategy. TOML export, save-to-API, and clipboard copy. CSS styles in components.css. Integrated into workflows page as "Visual Builder" tab.
**Problem (was):** Both OpenFang and OpenClaw define workflows in TOML/config only. No visual builder exists in either. First to ship this wins.
**What to do:**
1. Add drag-and-drop workflow builder to the Workflows page
2. Node types: Agent Step, Parallel Fan-out, Condition, Loop, Collect
3. Visual connections between nodes
4. Generate TOML from the visual graph
5. Run workflow directly from builder
**Files:** New `js/pages/workflow-builder.js`, `index_body.html` (workflows section), `components.css`
**Done when:**
- Users can visually build a workflow by dragging nodes
- Generated TOML matches hand-written format
- Workflows can be saved and run from the builder
---
## Sprint 4 — Polish & Launch (3-4 days)
### 4.1 Multi-Session per Agent -- DONE
**Status: COMPLETE** Added `list_agent_sessions()`, `create_session_with_label()`, `switch_agent_session()` to kernel. API: `GET/POST /api/agents/{id}/sessions`, `POST /api/agents/{id}/sessions/{sid}/switch`. UI: session dropdown in chat header with badge count, new session button, click-to-switch, active session indicator.
**Problem (was):** Each agent has one session. OpenClaw supports session labels for multiple conversations per agent.
**What to do:**
1. Add session label/ID to session creation
2. UI: session switcher tabs in chat header
3. API: `/api/agents/{id}/sessions` list, `/api/agents/{id}/sessions/{label}` CRUD
**Files:** `crates/openfang-kernel/src/kernel.rs`, `routes.rs`, `ws.rs`, `index_body.html`
---
### 4.2 Config Hot-Reload -- DONE
**Status: COMPLETE** Added polling-based config watcher (every 30 seconds) that auto-detects `config.toml` changes via mtime comparison. Calls existing `kernel.reload_config()` which returns a structured plan with hot actions. Logs applied changes and warnings. No new dependencies needed.
**Problem (was):** Changing `config.toml` requires daemon restart. OpenClaw reloads live.
**What to do:**
1. Watch `~/.openfang/config.toml` for changes (notify crate)
2. On change: re-parse, diff, apply only changed sections
3. Log what was reloaded
4. UI notification: "Config reloaded"
**Files:** `crates/openfang-api/src/server.rs`, `crates/openfang-types/src/config.rs`
---
### 4.3 CHANGELOG & README Polish -- DONE
**Status: COMPLETE** Updated CHANGELOG.md with comprehensive v0.1.0 coverage (15 crates, 41 tools, 27 providers, 130+ models, token management, SDKs, web UI features, 1731+ tests). Updated README.md with SDK section (JS + Python examples), updated feature counts, visual workflow builder mention, comparison table with new rows (workflow builder, SDKs, voice, metrics).
**What to do (was):**
1. Write `CHANGELOG.md` for v0.1.0 covering all features
2. Polish `README.md` quick start, screenshots, feature comparison table
3. Add demo GIF/video showing chat in action
---
### 4.4 Performance & Load Testing -- DONE
**Status: COMPLETE** Created `load_test.rs` with 7 load tests: concurrent agent spawns (20 simultaneous, 97 spawns/sec), endpoint latency (8 endpoints, all p99 < 5ms), concurrent reads (50 parallel, 1728 req/sec), session management (10 sessions in 40ms, switch in 2ms), workflow operations (15 concurrent, 9ms), spawn+kill cycles (18ms per cycle), sustained metrics (2792 req/sec). All 1751 tests pass across workspace.
**Results:**
- Health: p99 = 0.8ms
- Agent list: p99 = 0.5ms
- Metrics: 2,792 req/sec
- Concurrent reads: 1,728 req/sec
- Spawns: 97/sec
**What to do (was):**
1. Write load test: 100 concurrent agents, 10 messages each
2. Measure: memory usage, response latency, CPU
3. Profile hotspots with `cargo flamegraph`
4. Fix any bottlenecks found
---
### 4.5 Final Release -- READY
**Status: ALL CODE COMPLETE** All 18 code items done. 1751 tests passing. Production audit completed: 2 critical bugs fixed (API delete alias, config/set route), CSP hardened (Tauri + middleware), Tauri signing key installed. Remaining for release: tag v0.1.0, build release artifacts, set up openfang.sh domain.
1. Complete items from `production-checklist.md` (keygen DONE, secrets, icons DONE, domain pending)
2. Tag `v0.1.0`
3. Verify all release artifacts (desktop installers, CLI binaries, Docker image)
4. Test auto-updater with v0.1.1 bump
---
## Feature Comparison Scoreboard
| Feature | OpenClaw | OpenFang | Winner |
|---------|----------|----------|--------|
| Language/Performance | Node.js (~200MB) | Rust (~30MB single binary) | **OpenFang** |
| Channels | ~15 | **40** | **OpenFang** |
| Built-in Tools | ~19 | **41** | **OpenFang** |
| Security Systems | Token + sandbox | **16 defense systems** | **OpenFang** |
| Agent Templates | Manual config | **30 pre-configured** | **OpenFang** |
| Hands (autonomous) | None | **7 packages** | **OpenFang** |
| Workflow Engine | Cron + webhooks | **Full DAG with parallel/loops** | **OpenFang** |
| Knowledge Graph | Flat vector store | **Entity-relation graph** | **OpenFang** |
| P2P Networking | None | **OFP wire protocol** | **OpenFang** |
| WASM Sandbox | Docker only | **Dual-metered WASM** | **OpenFang** |
| Desktop App | Electron (~200MB) | **Tauri (~30MB)** | **OpenFang** |
| Migration | N/A | **`migrate --from openclaw`** | **OpenFang** |
| Skills | 54 bundled | **60 bundled** | **OpenFang** |
| LLM Providers | ~15 | **27 providers, 130+ models** | **OpenFang** |
| Plugin SDK | TypeScript published | JS + Python SDK | **Tie** |
| Native Mobile | iOS + Android + macOS | Web responsive only | OpenClaw |
| Voice/Talk Mode | Wake word + TTS + overlay | Mic + TTS playback | OpenClaw (slight) |
| Browser Automation | Playwright with inline screenshots | Playwright + inline screenshots | **Tie** |
| Visual Workflow Builder | None | **Drag-and-drop builder** | **OpenFang** |
**OpenFang wins 15/18 categories.** The remaining gaps are: mobile apps (OpenClaw), voice wake word (OpenClaw slight edge).
---
## Quick Reference: Status
```
Sprint 1: COMPLETE
1.1 Token bloat fix .............. DONE
1.2 Branding assets .............. DONE
1.3 Tauri signing key ............ DONE
1.4 First-run audit .............. DONE
Sprint 2: 4/5 COMPLETE
2.1 Browser screenshots .......... DONE
2.2 Chat search .................. DONE
2.3 Skill marketplace ............ DONE
2.4 Install script domain ........ PENDING (infra: set up openfang.sh domain)
2.5 Wizard end-to-end ............ DONE
Sprint 3: COMPLETE
3.1 Voice UI ..................... DONE
3.2 Canvas verification .......... DONE
3.3 JS/Python SDK ................ DONE
3.4 Observability ................ DONE
3.5 Workflow visual builder ...... DONE
Sprint 4: COMPLETE
4.1 Multi-session ................ DONE
4.2 Config hot-reload ............ DONE
4.3 CHANGELOG + README ........... DONE
4.4 Load testing ................. DONE (7 tests, all p99 < 5ms)
4.5 Final release ................ READY (tag + build)
Production audit:
- OpenFangAPI.delete() bug ....... FIXED
- /api/config/set missing ........ FIXED
- Tauri CSP hardened ............. FIXED
- Middleware CSP narrowed ........ FIXED
- All 16 Alpine.js components .... VERIFIED
- All 120+ API routes ........... VERIFIED
- All 15 JS page files .......... VERIFIED
- 1751 tests ..................... ALL PASSING
```

855
docs/mcp-a2a.md Normal file
View File

@@ -0,0 +1,855 @@
# MCP & A2A Integration Guide
OpenFang implements both the **Model Context Protocol (MCP)** and **Agent-to-Agent (A2A)** protocol, enabling deep interoperability with external tools, IDEs, and other agent frameworks.
---
## Table of Contents
- [Part 1: MCP (Model Context Protocol)](#part-1-mcp-model-context-protocol)
- [Overview](#mcp-overview)
- [MCP Client -- Connecting to External Servers](#mcp-client)
- [MCP Server -- Exposing OpenFang via MCP](#mcp-server)
- [Configuration Examples](#mcp-configuration-examples)
- [API Endpoints](#mcp-api-endpoints)
- [Part 2: A2A (Agent-to-Agent Protocol)](#part-2-a2a-agent-to-agent-protocol)
- [Overview](#a2a-overview)
- [Agent Card](#agent-card)
- [A2A Server](#a2a-server)
- [A2A Client](#a2a-client)
- [Task Lifecycle](#task-lifecycle)
- [API Endpoints](#a2a-api-endpoints)
- [Configuration](#a2a-configuration)
- [Security](#security)
---
## Part 1: MCP (Model Context Protocol)
### MCP Overview
The Model Context Protocol (MCP) is a JSON-RPC 2.0 based protocol that standardizes how LLM applications discover and invoke tools. OpenFang supports MCP in both directions:
- **As a client**: OpenFang connects to external MCP servers (GitHub, filesystem, databases, Puppeteer, etc.) and makes their tools available to all agents.
- **As a server**: OpenFang exposes its own agents as MCP tools, so IDEs like Cursor, VS Code, and Claude Desktop can call OpenFang agents directly.
OpenFang implements MCP protocol version `2024-11-05`.
**Source files:**
- Client: `crates/openfang-runtime/src/mcp.rs`
- Server handler: `crates/openfang-runtime/src/mcp_server.rs`
- CLI server: `crates/openfang-cli/src/mcp.rs`
- Config types: `crates/openfang-types/src/config.rs` (`McpServerConfigEntry`, `McpTransportEntry`)
---
### MCP Client
The MCP client (`McpConnection` in `openfang-runtime`) allows OpenFang to connect to any MCP-compatible server and use its tools as if they were built-in.
#### Configuration
MCP servers are configured in `config.toml` using the `[[mcp_servers]]` array:
```toml
[[mcp_servers]]
name = "github"
timeout_secs = 30
env = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
```
Each entry maps to a `McpServerConfigEntry` struct:
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | `String` | required | Display name, used in tool namespacing |
| `transport` | `McpTransportEntry` | required | How to connect (stdio or SSE) |
| `timeout_secs` | `u64` | `30` | JSON-RPC request timeout |
| `env` | `Vec<String>` | `[]` | Env vars to pass through to the subprocess |
#### Transport Types
OpenFang supports two MCP transports, defined by `McpTransport`:
**Stdio** -- Spawns a subprocess and communicates via stdin/stdout with newline-delimited JSON-RPC:
```toml
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
```
**SSE** -- Connects to a remote HTTP endpoint and sends JSON-RPC via POST:
```toml
[mcp_servers.transport]
type = "sse"
url = "https://mcp.example.com/api"
```
#### Tool Namespacing
All tools discovered from MCP servers are namespaced using the pattern `mcp_{server}_{tool}` to prevent collisions with built-in tools or tools from other servers. Names are normalized to lowercase with hyphens replaced by underscores.
Examples:
- Server `github`, tool `create_issue` becomes `mcp_github_create_issue`
- Server `my-server`, tool `do_thing` becomes `mcp_my_server_do_thing`
Helper functions (exported from `openfang_runtime::mcp`):
- `format_mcp_tool_name(server, tool)` -- builds the namespaced name
- `is_mcp_tool(name)` -- checks if a tool name starts with `mcp_`
- `extract_mcp_server(tool_name)` -- extracts the server name from a namespaced tool
#### Auto-Connection on Kernel Boot
When the kernel starts (`start_background_agents()`), it checks `config.mcp_servers`. If any are configured, it spawns a background task that calls `connect_mcp_servers()`. This method:
1. Iterates each `McpServerConfigEntry` in the config
2. Converts the config-level `McpTransportEntry` into a runtime `McpTransport`
3. Calls `McpConnection::connect()` which:
- Spawns the subprocess (stdio) or creates an HTTP client (SSE)
- Sends the `initialize` handshake with client info
- Sends the `notifications/initialized` notification
- Calls `tools/list` to discover all available tools
- Namespaces each tool with `mcp_{server}_{tool}`
4. Caches discovered `ToolDefinition` entries in `kernel.mcp_tools`
5. Stores the live `McpConnection` in `kernel.mcp_connections`
After connection, the kernel logs the total number of MCP tools available.
#### Tool Discovery and Listing
MCP tools are merged into the agent's available tool set via `available_tools()`:
```
built-in tools (23) + skill tools + MCP tools = full tool list
```
When an agent calls an MCP tool during its loop, the tool runner recognizes the `mcp_` prefix, finds the appropriate `McpConnection`, strips the namespace prefix, and forwards the `tools/call` request to the external MCP server.
#### Connection Lifecycle
The `McpConnection` struct manages the lifetime of the connection:
```rust
pub struct McpConnection {
config: McpServerConfig,
tools: Vec<ToolDefinition>,
transport: McpTransportHandle, // Stdio or SSE
next_id: u64, // JSON-RPC request counter
}
```
When the connection is dropped, stdio subprocesses are automatically killed via `Drop`:
```rust
impl Drop for McpConnection {
fn drop(&mut self) {
if let McpTransportHandle::Stdio { ref mut child, .. } = self.transport {
let _ = child.start_kill();
}
}
}
```
---
### MCP Server
OpenFang can also act as an MCP server, exposing its agents as callable tools to external MCP clients.
#### How It Works
Each OpenFang agent becomes an MCP tool named `openfang_agent_{name}` (with hyphens replaced by underscores). The tool accepts a single `message` string parameter and returns the agent's response.
For example, an agent named `code-reviewer` becomes the MCP tool `openfang_agent_code_reviewer`.
#### CLI: `openfang mcp`
The primary way to run the MCP server is the `openfang mcp` command, which starts a stdio-based MCP server:
```bash
openfang mcp
```
This command:
1. Checks if an OpenFang daemon is running (via `find_daemon()`)
2. If found, proxies all tool calls to the daemon via its HTTP API
3. If no daemon is running, boots an in-process kernel as a fallback
4. Reads Content-Length framed JSON-RPC messages from stdin
5. Writes Content-Length framed JSON-RPC responses to stdout
The MCP server uses `McpBackend` which supports two modes:
- `McpBackend::Daemon` -- forwards requests to a running OpenFang daemon via HTTP
- `McpBackend::InProcess` -- boots a full kernel when no daemon is available
#### HTTP MCP Endpoint
OpenFang also exposes an MCP endpoint over HTTP at `POST /mcp`. Unlike the stdio server (which only exposes agents), the HTTP endpoint exposes the full tool set (built-in + skills + MCP tools) and executes tools via the kernel's `execute_tool()` pipeline. This means the HTTP MCP endpoint supports:
- All 23 built-in tools (file_read, web_fetch, etc.)
- All installed skill tools
- All connected MCP server tools
#### Supported JSON-RPC Methods
| Method | Description |
|--------|-------------|
| `initialize` | Handshake; returns server capabilities and info |
| `notifications/initialized` | Client confirmation; no response |
| `tools/list` | Returns all available tools with names, descriptions, and input schemas |
| `tools/call` | Executes a tool and returns the result |
Unknown methods receive a `-32601` (Method not found) error.
#### Protocol Details
**Message Framing** (stdio mode):
```
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
```
Messages are limited to 10 MB (`MAX_MCP_MESSAGE_SIZE`). Oversized messages are drained and rejected.
**Initialize Handshake:**
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "cursor", "version": "1.0" }
}
}
```
Response:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "openfang", "version": "0.1.0" }
}
}
```
**Tool Call:**
```json
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "openfang_agent_code_reviewer",
"arguments": {
"message": "Review this Python function for security issues..."
}
}
}
```
Response:
```json
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{
"type": "text",
"text": "I found 3 potential security issues..."
}]
}
}
```
#### Connecting from IDEs
**Cursor / VS Code (with MCP extension):**
Add to your MCP configuration file (e.g., `.cursor/mcp.json` or VS Code MCP settings):
```json
{
"mcpServers": {
"openfang": {
"command": "openfang",
"args": ["mcp"]
}
}
}
```
**Claude Desktop:**
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"openfang": {
"command": "openfang",
"args": ["mcp"],
"env": {}
}
}
}
```
After configuration, all OpenFang agents appear as tools in the IDE. For example, you can ask Claude Desktop to "use the openfang code-reviewer agent to review this file."
---
### MCP Configuration Examples
#### GitHub Server (file + issue + PR tools)
```toml
[[mcp_servers]]
name = "github"
timeout_secs = 30
env = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
```
#### Filesystem Server
```toml
[[mcp_servers]]
name = "filesystem"
timeout_secs = 10
env = []
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
```
#### PostgreSQL Server
```toml
[[mcp_servers]]
name = "postgres"
timeout_secs = 30
env = ["DATABASE_URL"]
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres"]
```
#### Puppeteer (Browser Automation)
```toml
[[mcp_servers]]
name = "puppeteer"
timeout_secs = 60
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-puppeteer"]
```
#### Remote SSE Server
```toml
[[mcp_servers]]
name = "remote-tools"
timeout_secs = 30
[mcp_servers.transport]
type = "sse"
url = "https://tools.example.com/mcp"
```
#### Multiple Servers
```toml
[[mcp_servers]]
name = "github"
env = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
[[mcp_servers]]
name = "filesystem"
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
[[mcp_servers]]
name = "postgres"
env = ["DATABASE_URL"]
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres"]
```
---
### MCP API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/mcp/servers` | List configured and connected MCP servers with their tools |
| `POST` | `/mcp` | Handle MCP JSON-RPC requests over HTTP (full tool execution) |
**GET /api/mcp/servers** response:
```json
{
"configured": [
{
"name": "github",
"transport": { "type": "stdio", "command": "npx", "args": [...] },
"timeout_secs": 30,
"env": ["GITHUB_PERSONAL_ACCESS_TOKEN"]
}
],
"connected": [
{
"name": "github",
"tools_count": 12,
"tools": [
{ "name": "mcp_github_create_issue", "description": "[MCP:github] Create a GitHub issue" },
{ "name": "mcp_github_search_repos", "description": "[MCP:github] Search repositories" }
],
"connected": true
}
]
}
```
---
## Part 2: A2A (Agent-to-Agent Protocol)
### A2A Overview
The Agent-to-Agent (A2A) protocol, originally specified by Google, enables cross-framework agent interoperability. It allows agents built with different frameworks to discover each other's capabilities and exchange tasks.
OpenFang implements A2A in both directions:
- **As a server**: Publishes Agent Cards describing each agent's capabilities, accepts task submissions, and tracks task lifecycle.
- **As a client**: Discovers external A2A agents at boot time, sends tasks to them, and polls for results.
**Source files:**
- Protocol types and logic: `crates/openfang-runtime/src/a2a.rs`
- API routes: `crates/openfang-api/src/routes.rs`
- Config types: `crates/openfang-types/src/config.rs` (`A2aConfig`, `ExternalAgent`)
---
### Agent Card
An Agent Card is a JSON document that describes an agent's identity, capabilities, and supported interaction modes. It is served at the well-known path `/.well-known/agent.json` per the A2A specification.
The `AgentCard` struct:
```rust
pub struct AgentCard {
pub name: String,
pub description: String,
pub url: String, // endpoint URL (e.g., "http://host/a2a")
pub version: String, // protocol version
pub capabilities: AgentCapabilities,
pub skills: Vec<AgentSkill>, // A2A skill descriptors
pub default_input_modes: Vec<String>, // e.g., ["text"]
pub default_output_modes: Vec<String>, // e.g., ["text"]
}
```
**AgentCapabilities:**
```rust
pub struct AgentCapabilities {
pub streaming: bool, // true -- OpenFang supports streaming
pub push_notifications: bool, // false -- not currently implemented
pub state_transition_history: bool, // true -- task status history available
}
```
**AgentSkill** (not the same as OpenFang skills -- these are A2A capability descriptors):
```rust
pub struct AgentSkill {
pub id: String, // matches the OpenFang tool name
pub name: String, // human-readable (underscores replaced with spaces)
pub description: String,
pub tags: Vec<String>,
pub examples: Vec<String>,
}
```
Agent Cards are built from OpenFang agent manifests via `build_agent_card()`. Each tool in the agent's capability list becomes an A2A skill descriptor. Example card:
```json
{
"name": "code-reviewer",
"description": "Reviews code for bugs, security issues, and style",
"url": "http://127.0.0.1:50051/a2a",
"version": "0.1.0",
"capabilities": {
"streaming": true,
"pushNotifications": false,
"stateTransitionHistory": true
},
"skills": [
{
"id": "file_read",
"name": "file read",
"description": "Can use the file_read tool",
"tags": ["tool"],
"examples": []
}
],
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"]
}
```
---
### A2A Server
OpenFang serves A2A requests through the REST API. The server-side implementation involves:
1. **Agent Card publication** at `/.well-known/agent.json`
2. **Agent listing** at `/a2a/agents`
3. **Task submission and tracking** via the `A2aTaskStore`
#### A2aTaskStore
The `A2aTaskStore` is an in-memory, bounded store for tracking A2A task lifecycle:
```rust
pub struct A2aTaskStore {
tasks: Mutex<HashMap<String, A2aTask>>,
max_tasks: usize, // default: 1000
}
```
Key properties:
- **Bounded**: When the store reaches `max_tasks`, it evicts the oldest completed/failed/cancelled task (FIFO)
- **Thread-safe**: Uses `Mutex<HashMap>` for concurrent access
- **Kernel field**: Stored as `kernel.a2a_task_store`
Methods on `A2aTaskStore`:
- `insert(task)` -- add a new task, evicting old ones if at capacity
- `get(task_id)` -- retrieve a task by ID
- `update_status(task_id, status)` -- change a task's status
- `complete(task_id, response, artifacts)` -- mark as completed with response
- `fail(task_id, error_message)` -- mark as failed with error
- `cancel(task_id)` -- mark as cancelled
#### Task Submission Flow
When `POST /a2a/tasks/send` is called:
1. Extract the message text from the A2A request format (parts with type "text")
2. Find the target agent (currently uses the first registered agent)
3. Create an `A2aTask` with status `Working` and insert into the task store
4. Send the message to the agent via `kernel.send_message()`
5. On success: complete the task with the agent's response
6. On failure: fail the task with the error message
7. Return the final task state
---
### A2A Client
The `A2aClient` struct discovers and interacts with external A2A agents:
```rust
pub struct A2aClient {
client: reqwest::Client, // 30-second timeout
}
```
**Methods:**
- `discover(url)` -- fetches `{url}/.well-known/agent.json` and parses the Agent Card
- `send_task(url, message, session_id)` -- sends a JSON-RPC task submission
- `get_task(url, task_id)` -- polls for task status
#### Auto-Discovery at Boot
When the kernel starts and A2A is enabled with external agents configured, it spawns a background task that calls `discover_external_agents()`. This function:
1. Creates an `A2aClient`
2. Iterates each configured `ExternalAgent`
3. Fetches each agent's card from `{url}/.well-known/agent.json`
4. Logs successful discoveries (name, URL, skill count)
5. Stores discovered `(name, AgentCard)` pairs in `kernel.a2a_external_agents`
Failed discoveries are logged as warnings but do not prevent boot.
#### Sending Tasks to External Agents
```rust
let client = A2aClient::new();
let task = client.send_task(
"https://other-agent.example.com/a2a",
"Analyze this dataset for anomalies",
Some("session-123"),
).await?;
println!("Task {}: {:?}", task.id, task.status);
```
The client sends a JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "Analyze this dataset..." }]
},
"sessionId": "session-123"
}
}
```
---
### Task Lifecycle
An `A2aTask` tracks the full lifecycle of a cross-agent interaction:
```rust
pub struct A2aTask {
pub id: String,
pub session_id: Option<String>,
pub status: A2aTaskStatus,
pub messages: Vec<A2aMessage>,
pub artifacts: Vec<A2aArtifact>,
}
```
#### Task States
| Status | Description |
|--------|-------------|
| `Submitted` | Task received but not yet started |
| `Working` | Task is being actively processed by the agent |
| `InputRequired` | Agent needs more information from the caller |
| `Completed` | Task finished successfully |
| `Cancelled` | Task was cancelled by the caller |
| `Failed` | Task encountered an error |
#### Message Format
Messages use an A2A-specific format with typed content parts:
```rust
pub struct A2aMessage {
pub role: String, // "user" or "agent"
pub parts: Vec<A2aPart>,
}
pub enum A2aPart {
Text { text: String },
File { name: String, mime_type: String, data: String }, // base64
Data { mime_type: String, data: serde_json::Value },
}
```
#### Artifacts
Tasks can produce artifacts (files, structured data) alongside messages:
```rust
pub struct A2aArtifact {
pub name: String,
pub parts: Vec<A2aPart>,
}
```
---
### A2A API Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/.well-known/agent.json` | Public | Agent Card for the primary agent |
| `GET` | `/a2a/agents` | Public | List all agent cards |
| `POST` | `/a2a/tasks/send` | Public | Submit a task to an agent |
| `GET` | `/a2a/tasks/{id}` | Public | Get task status and messages |
| `POST` | `/a2a/tasks/{id}/cancel` | Public | Cancel a running task |
#### GET /.well-known/agent.json
Returns the Agent Card for the first registered agent. If no agents are spawned, returns a placeholder card.
#### GET /a2a/agents
Lists all registered agents as Agent Cards:
```json
{
"agents": [
{
"name": "code-reviewer",
"description": "Reviews code for bugs and security issues",
"url": "http://127.0.0.1:50051/a2a",
"version": "0.1.0",
"capabilities": { "streaming": true, "pushNotifications": false, "stateTransitionHistory": true },
"skills": [...],
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"]
}
],
"total": 1
}
```
#### POST /a2a/tasks/send
Submit a task. Request body follows JSON-RPC 2.0 format:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "Review this code for security issues" }]
},
"sessionId": "optional-session-id"
}
}
```
Response (completed task):
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"sessionId": "optional-session-id",
"status": "completed",
"messages": [
{
"role": "user",
"parts": [{ "type": "text", "text": "Review this code for security issues" }]
},
{
"role": "agent",
"parts": [{ "type": "text", "text": "I found 2 potential issues..." }]
}
],
"artifacts": []
}
```
#### GET /a2a/tasks/{id}
Poll for task status. Returns `404` if the task is not found or has been evicted.
#### POST /a2a/tasks/{id}/cancel
Cancel a running task. Sets its status to `Cancelled`. Returns `404` if the task is not found.
---
### A2A Configuration
A2A is configured in `config.toml` under the `[a2a]` section:
```toml
[a2a]
enabled = true
listen_path = "/a2a"
[[a2a.external_agents]]
name = "research-agent"
url = "https://research.example.com"
[[a2a.external_agents]]
name = "data-analyst"
url = "https://data.example.com"
```
The `A2aConfig` struct:
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | `bool` | `false` | Whether A2A endpoints are active |
| `listen_path` | `String` | `"/a2a"` | Base path for A2A endpoints |
| `external_agents` | `Vec<ExternalAgent>` | `[]` | External agents to discover at boot |
Each `ExternalAgent`:
| Field | Type | Description |
|-------|------|-------------|
| `name` | `String` | Display name for this external agent |
| `url` | `String` | Base URL where the agent's card is published |
If `a2a` is `None` (not present in config), all A2A features are disabled. The A2A endpoints are always registered in the router but the discovery and task store functionality requires `enabled = true`.
---
## Security
### MCP Security
**Subprocess Sandboxing**: Stdio MCP servers run with `env_clear()` -- the subprocess environment is completely cleared. Only explicitly whitelisted environment variables (listed in the `env` field) plus `PATH` are passed through. This prevents leaking secrets to untrusted MCP server processes.
**Path Traversal Prevention**: The command path for stdio MCP servers is validated to reject `..` sequences.
**SSRF Protection**: SSE transport URLs are checked against known metadata endpoints (169.254.169.254, metadata.google) to prevent SSRF attacks.
**Request Timeout**: All MCP requests have a configurable timeout (default 30 seconds) to prevent hung connections.
**Message Size Limit**: The stdio MCP server enforces a 10 MB maximum message size to prevent out-of-memory attacks. Oversized messages are drained and rejected.
### A2A Security
**Rate Limiting**: A2A endpoints go through the same GCRA rate limiter as all other API endpoints.
**API Authentication**: When `api_key` is set in the kernel config, all API endpoints (including A2A) require a `Authorization: Bearer <key>` header. The exception is `/.well-known/agent.json` and the health endpoint which are typically public.
**Task Store Bounds**: The `A2aTaskStore` is bounded (default 1000 tasks) with FIFO eviction of completed/failed/cancelled tasks, preventing memory exhaustion from task accumulation.
**External Agent Discovery**: The `A2aClient` uses a 30-second timeout and sends a `User-Agent: OpenFang/0.1 A2A` header. Failed discoveries are logged but do not block kernel boot.
### Kernel-Level Protection
Both MCP and A2A tool execution flows through the same security pipeline as all other tool calls:
- Capability-based access control (agents only get tools they are authorized for)
- Tool result truncation (50K character hard cap)
- Universal 60-second tool execution timeout
- Loop guard detection (blocks repetitive tool call patterns)
- Taint tracking on data flowing between tools

View File

@@ -0,0 +1,298 @@
# Production Release Checklist
Everything that must be done before tagging `v0.1.0` and shipping to users. Items are ordered by dependency — complete them top to bottom.
---
## 1. Generate Tauri Signing Keypair
**Status:** BLOCKING — without this, auto-updater is dead. No user will ever receive an update.
The Tauri updater requires an Ed25519 keypair. The private key signs every release bundle, and the public key is embedded in the app binary so it can verify updates.
```bash
# Install the Tauri CLI (if not already installed)
cargo install tauri-cli --locked
# Generate the keypair
cargo tauri signer generate -w ~/.tauri/openfang.key
```
The command will output:
```
Your public key was generated successfully:
dW50cnVzdGVkIGNvb... <-- COPY THIS
Your private key was saved to: ~/.tauri/openfang.key
```
Save both values. You need them for steps 2 and 3.
---
## 2. Set the Public Key in `tauri.conf.json`
**Status:** BLOCKING — the placeholder must be replaced before building.
Open `crates/openfang-desktop/tauri.conf.json` and replace:
```json
"pubkey": "PLACEHOLDER_REPLACE_WITH_GENERATED_PUBKEY"
```
with the actual public key string from step 1:
```json
"pubkey": "dW50cnVzdGVkIGNvb..."
```
---
## 3. Add GitHub Repository Secrets
**Status:** BLOCKING — CI/CD release workflow will fail without these.
Go to **GitHub repo → Settings → Secrets and variables → Actions → New repository secret** and add:
| Secret Name | Value | Required |
|---|---|---|
| `TAURI_SIGNING_PRIVATE_KEY` | Contents of `~/.tauri/openfang.key` | Yes |
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password you set during keygen (or empty string) | Yes |
### Optional — macOS Code Signing
Without these, macOS users will see "app from unidentified developer" warnings. Requires an Apple Developer account ($99/year).
| Secret Name | Value |
|---|---|
| `APPLE_CERTIFICATE` | Base64-encoded `.p12` certificate file |
| `APPLE_CERTIFICATE_PASSWORD` | Password for the .p12 file |
| `APPLE_SIGNING_IDENTITY` | e.g. `Developer ID Application: Your Name (TEAMID)` |
| `APPLE_ID` | Your Apple ID email |
| `APPLE_PASSWORD` | App-specific password from appleid.apple.com |
| `APPLE_TEAM_ID` | Your 10-character Team ID |
To generate the base64 certificate:
```bash
base64 -i Certificates.p12 | pbcopy
```
### Optional — Windows Code Signing
Without this, Windows SmartScreen may warn users. Requires an EV code signing certificate.
Set `certificateThumbprint` in `tauri.conf.json` under `bundle.windows` and add the certificate to the Windows runner in CI.
---
## 4. Create Icon Assets
**Status:** VERIFY — icons may be placeholders.
The following icon files must exist in `crates/openfang-desktop/icons/`:
| File | Size | Usage |
|---|---|---|
| `icon.png` | 1024x1024 | Source icon, macOS .icns generation |
| `icon.ico` | multi-size | Windows taskbar, installer |
| `32x32.png` | 32x32 | System tray, small contexts |
| `128x128.png` | 128x128 | Application lists |
| `128x128@2x.png` | 256x256 | HiDPI/Retina displays |
Verify they are real branded icons (not Tauri defaults). Generate from a single source SVG:
```bash
# Using ImageMagick
convert icon.svg -resize 1024x1024 icon.png
convert icon.svg -resize 32x32 32x32.png
convert icon.svg -resize 128x128 128x128.png
convert icon.svg -resize 256x256 128x128@2x.png
convert icon.svg -resize 256x256 -define icon:auto-resize=256,128,64,48,32,16 icon.ico
```
---
## 5. Set Up the `openfang.sh` Domain
**Status:** BLOCKING for install scripts — users run `curl -sSf https://openfang.sh | sh`.
Options:
- **GitHub Pages**: Point `openfang.sh` to a GitHub Pages site that redirects `/` to `scripts/install.sh` and `/install.ps1` to `scripts/install.ps1` from the repo's latest release.
- **Cloudflare Workers / Vercel**: Serve the install scripts with proper `Content-Type: text/plain` headers.
- **Raw GitHub redirect**: Use `openfang.sh` as a CNAME to `raw.githubusercontent.com/RightNow-AI/openfang/main/scripts/install.sh` (less reliable).
The install scripts reference:
- `https://openfang.sh` → serves `scripts/install.sh`
- `https://openfang.sh/install.ps1` → serves `scripts/install.ps1`
Until the domain is set up, users can install via:
```bash
curl -sSf https://raw.githubusercontent.com/RightNow-AI/openfang/main/scripts/install.sh | sh
```
---
## 6. Verify Dockerfile Builds
**Status:** VERIFY — the Dockerfile must produce a working image.
```bash
docker build -t openfang:local .
docker run --rm openfang:local --version
docker run --rm -p 4200:4200 -v openfang-data:/data openfang:local start
```
Confirm:
- Binary runs and prints version
- `start` command boots the kernel and API server
- Port 4200 is accessible
- `/data` volume persists between container restarts
---
## 7. Verify Install Scripts Locally
**Status:** VERIFY before release.
### Linux/macOS
```bash
# Test against a real GitHub release (after first tag)
bash scripts/install.sh
# Or test syntax only
bash -n scripts/install.sh
shellcheck scripts/install.sh
```
### Windows (PowerShell)
```powershell
# Test against a real GitHub release (after first tag)
powershell -ExecutionPolicy Bypass -File scripts/install.ps1
# Or syntax check only
pwsh -NoProfile -Command "Get-Content scripts/install.ps1 | Out-Null"
```
### Docker smoke test
```bash
docker build -f scripts/docker/install-smoke.Dockerfile .
```
---
## 8. Write CHANGELOG.md for v0.1.0
**Status:** VERIFY — confirm it covers all shipped features.
The release workflow includes a link to `CHANGELOG.md` in every GitHub release body. Ensure it exists at the repo root and covers:
- All 14 crates and what they do
- Key features: 40 channels, 60 skills, 20 providers, 51 models
- Security systems (9 SOTA + 7 critical fixes)
- Desktop app with auto-updater
- Migration path from OpenClaw
- Docker and CLI install options
---
## 9. First Release — Tag and Push
Once steps 1-8 are complete:
```bash
# Ensure version matches everywhere
grep '"version"' crates/openfang-desktop/tauri.conf.json
grep '^version' Cargo.toml
# Commit any final changes
git add -A
git commit -m "chore: prepare v0.1.0 release"
# Tag and push
git tag v0.1.0
git push origin main --tags
```
This triggers the release workflow which:
1. Builds desktop installers for 4 targets (Linux, macOS x86, macOS ARM, Windows)
2. Generates signed `latest.json` for the auto-updater
3. Builds CLI binaries for 5 targets
4. Builds and pushes multi-arch Docker image
5. Creates a GitHub Release with all artifacts
---
## 10. Post-Release Verification
After the release workflow completes (~15-30 min):
### GitHub Release Page
- [ ] `.msi` and `.exe` present (Windows desktop)
- [ ] `.dmg` present (macOS desktop)
- [ ] `.AppImage` and `.deb` present (Linux desktop)
- [ ] `latest.json` present (auto-updater manifest)
- [ ] CLI `.tar.gz` archives present (5 targets)
- [ ] CLI `.zip` present (Windows)
- [ ] SHA256 checksum files present for each CLI archive
### Auto-Updater Manifest
Visit: `https://github.com/RightNow-AI/openfang/releases/latest/download/latest.json`
- [ ] JSON is valid
- [ ] Contains `signature` fields (not empty strings)
- [ ] Contains download URLs for all platforms
- [ ] Version matches the tag
### Docker Image
```bash
docker pull ghcr.io/RightNow-AI/openfang:latest
docker pull ghcr.io/RightNow-AI/openfang:0.1.0
# Verify both architectures
docker run --rm ghcr.io/RightNow-AI/openfang:latest --version
```
### Desktop App Auto-Update (test with v0.1.1)
1. Install v0.1.0 from the release
2. Tag v0.1.1 and push
3. Wait for release workflow to complete
4. Open the v0.1.0 app — after 10 seconds it should:
- Show "OpenFang Updating..." notification
- Download and install v0.1.1
- Restart automatically to v0.1.1
5. Right-click tray → "Check for Updates" → should show "Up to Date"
### Install Scripts
```bash
# Linux/macOS
curl -sSf https://openfang.sh | sh
openfang --version # Should print v0.1.0
# Windows PowerShell
irm https://openfang.sh/install.ps1 | iex
openfang --version
```
---
## Quick Reference — What Blocks What
```
Step 1 (keygen) ──┬──> Step 2 (pubkey in config)
└──> Step 3 (secrets in GitHub)
Step 4 (icons) ──────────┤
Step 5 (domain) ─────────┤
Step 6 (Dockerfile) ─────┤
Step 7 (install scripts) ┤
Step 8 (CHANGELOG) ──────┘
v
Step 9 (tag + push)
v
Step 10 (verify)
```
Steps 4-8 can be done in parallel. Steps 1-3 are sequential and must be done first.

1045
docs/providers.md Normal file

File diff suppressed because it is too large Load Diff

1490
docs/security.md Normal file

File diff suppressed because it is too large Load Diff

595
docs/skill-development.md Normal file
View File

@@ -0,0 +1,595 @@
# Skill Development
Skills are pluggable tool bundles that extend agent capabilities in OpenFang. A skill packages one or more tools with their implementation, letting agents do things that built-in tools do not cover. This guide covers skill creation, the manifest format, Python and WASM runtimes, publishing to FangHub, and CLI management.
## Table of Contents
- [Overview](#overview)
- [Skill Format](#skill-format)
- [Python Skills](#python-skills)
- [WASM Skills](#wasm-skills)
- [Skill Requirements](#skill-requirements)
- [Installing Skills](#installing-skills)
- [Publishing to FangHub](#publishing-to-fanghub)
- [CLI Commands](#cli-commands)
- [OpenClaw Compatibility](#openclaw-compatibility)
- [Best Practices](#best-practices)
---
## Overview
A skill consists of:
1. A **manifest** (`skill.toml` or `SKILL.md`) that declares metadata, runtime type, provided tools, and requirements.
2. An **entry point** (Python script, WASM module, Node.js module, or prompt-only Markdown) that implements the tool logic.
Skills are installed to `~/.openfang/skills/` and made available to agents through the skill registry. OpenFang ships with **60 bundled skills** that are compiled into the binary and available immediately.
### Supported Runtimes
| Runtime | Language | Sandboxed | Notes |
|---------|----------|-----------|-------|
| `python` | Python 3.8+ | No (subprocess with `env_clear()`) | Easiest to write. Uses stdin/stdout JSON protocol. |
| `wasm` | Rust, C, Go, etc. | Yes (Wasmtime dual metering) | Fully sandboxed. Best for security-sensitive tools. |
| `node` | JavaScript/TypeScript | No (subprocess) | OpenClaw compatibility. |
| `prompt_only` | Markdown | N/A | Expert knowledge injected into system prompt. No code execution. |
| `builtin` | Rust | N/A | Compiled into the binary. For core tools only. |
### 60 Bundled Skills
OpenFang includes 60 expert knowledge skills compiled into the binary (no installation needed):
| Category | Skills |
|----------|--------|
| DevOps & Infra | `ci-cd`, `ansible`, `prometheus`, `nginx`, `kubernetes`, `terraform`, `helm`, `docker`, `sysadmin`, `shell-scripting`, `linux-networking` |
| Cloud | `aws`, `gcp`, `azure` |
| Languages | `rust-expert`, `python-expert`, `typescript-expert`, `golang-expert` |
| Frontend | `react-expert`, `nextjs-expert`, `css-expert` |
| Databases | `postgres-expert`, `redis-expert`, `sqlite-expert`, `mongodb`, `elasticsearch`, `sql-analyst` |
| APIs & Web | `graphql-expert`, `openapi-expert`, `api-tester`, `oauth-expert` |
| AI/ML | `ml-engineer`, `llm-finetuning`, `vector-db`, `prompt-engineer` |
| Security | `security-audit`, `crypto-expert`, `compliance` |
| Dev Tools | `github`, `git-expert`, `jira`, `linear-tools`, `sentry`, `code-reviewer`, `regex-expert` |
| Writing | `technical-writer`, `writing-coach`, `email-writer`, `presentation` |
| Data | `data-analyst`, `data-pipeline` |
| Collaboration | `slack-tools`, `notion`, `confluence`, `figma-expert` |
| Career | `interview-prep`, `project-manager` |
| Advanced | `wasm-expert`, `pdf-reader`, `web-search` |
These are `prompt_only` skills using the SKILL.md format -- expert knowledge that gets injected into the agent's system prompt.
### SKILL.md Format
The SKILL.md format (also used by OpenClaw) uses YAML frontmatter and a Markdown body:
```markdown
---
name: rust-expert
description: Expert Rust programming knowledge
---
# Rust Expert
## Key Principles
- Ownership and borrowing rules...
- Lifetime annotations...
## Common Patterns
...
```
SKILL.md files are automatically parsed and converted to `prompt_only` skills. All SKILL.md files pass through an automated **prompt injection scanner** that detects override attempts, data exfiltration patterns, and shell references before inclusion.
---
## Skill Format
### Directory Structure
```
my-skill/
skill.toml # Manifest (required)
src/
main.py # Entry point (for Python skills)
README.md # Optional documentation
```
### Manifest (skill.toml)
```toml
[skill]
name = "web-summarizer"
version = "0.1.0"
description = "Summarizes any web page into bullet points"
author = "openfang-community"
license = "MIT"
tags = ["web", "summarizer", "research"]
[runtime]
type = "python"
entry = "src/main.py"
[[tools.provided]]
name = "summarize_url"
description = "Fetch a URL and return a concise bullet-point summary"
input_schema = { type = "object", properties = { url = { type = "string", description = "The URL to summarize" } }, required = ["url"] }
[[tools.provided]]
name = "extract_links"
description = "Extract all links from a web page"
input_schema = { type = "object", properties = { url = { type = "string" } }, required = ["url"] }
[requirements]
tools = ["web_fetch"]
capabilities = ["NetConnect(*)"]
```
### Manifest Sections
#### [skill] -- Metadata
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Unique skill name (used as install directory name) |
| `version` | string | No | Semantic version (default: `"0.1.0"`) |
| `description` | string | No | Human-readable description |
| `author` | string | No | Author name or organization |
| `license` | string | No | License identifier (e.g., `"MIT"`, `"Apache-2.0"`) |
| `tags` | array | No | Tags for discovery on FangHub |
#### [runtime] -- Execution Configuration
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | `"python"`, `"wasm"`, `"node"`, or `"builtin"` |
| `entry` | string | Yes | Relative path to the entry point file |
#### [[tools.provided]] -- Tool Definitions
Each `[[tools.provided]]` entry defines one tool that the skill provides:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Tool name (must be unique across all tools) |
| `description` | string | Yes | Description shown to the LLM |
| `input_schema` | object | Yes | JSON Schema defining the tool's input parameters |
#### [requirements] -- Host Requirements
| Field | Type | Description |
|-------|------|-------------|
| `tools` | array | Built-in tools this skill needs the host to provide |
| `capabilities` | array | Capability strings the agent must have |
---
## Python Skills
Python skills are the simplest to write. They run as subprocesses and communicate via JSON over stdin/stdout.
### Protocol
1. OpenFang sends a JSON payload to the script's stdin:
```json
{
"tool": "summarize_url",
"input": {
"url": "https://example.com"
},
"agent_id": "uuid-...",
"agent_name": "researcher"
}
```
2. The script processes the input and writes a JSON result to stdout:
```json
{
"result": "- Point one\n- Point two\n- Point three"
}
```
If an error occurs, return an error object:
```json
{
"error": "Failed to fetch URL: connection refused"
}
```
### Example: Web Summarizer
`src/main.py`:
```python
#!/usr/bin/env python3
"""OpenFang skill: web-summarizer"""
import json
import sys
import urllib.request
def summarize_url(url: str) -> str:
"""Fetch a URL and return a basic summary."""
req = urllib.request.Request(url, headers={"User-Agent": "OpenFang-Skill/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
content = resp.read().decode("utf-8", errors="replace")
# Simple extraction: first 500 chars as summary
text = content[:500].strip()
return f"Summary of {url}:\n{text}..."
def extract_links(url: str) -> str:
"""Extract all links from a web page."""
import re
req = urllib.request.Request(url, headers={"User-Agent": "OpenFang-Skill/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
content = resp.read().decode("utf-8", errors="replace")
links = re.findall(r'href="(https?://[^"]+)"', content)
unique_links = list(dict.fromkeys(links))
return "\n".join(unique_links[:50])
def main():
payload = json.loads(sys.stdin.read())
tool_name = payload["tool"]
input_data = payload["input"]
try:
if tool_name == "summarize_url":
result = summarize_url(input_data["url"])
elif tool_name == "extract_links":
result = extract_links(input_data["url"])
else:
print(json.dumps({"error": f"Unknown tool: {tool_name}"}))
return
print(json.dumps({"result": result}))
except Exception as e:
print(json.dumps({"error": str(e)}))
if __name__ == "__main__":
main()
```
### Using the OpenFang Python SDK
For more advanced skills, use the Python SDK (`sdk/python/openfang_sdk.py`):
```python
#!/usr/bin/env python3
from openfang_sdk import SkillHandler
handler = SkillHandler()
@handler.tool("summarize_url")
def summarize_url(url: str) -> str:
# Your implementation here
return "Summary..."
@handler.tool("extract_links")
def extract_links(url: str) -> str:
# Your implementation here
return "link1\nlink2"
if __name__ == "__main__":
handler.run()
```
---
## WASM Skills
WASM skills run inside a sandboxed Wasmtime environment. They are ideal for security-sensitive operations because the sandbox enforces resource limits and capability restrictions.
### Building a WASM Skill
1. Write your skill in Rust (or any language that compiles to WASM):
```rust
// src/lib.rs
use std::io::{self, Read};
#[no_mangle]
pub extern "C" fn _start() {
let mut input = String::new();
io::stdin().read_to_string(&mut input).unwrap();
let payload: serde_json::Value = serde_json::from_str(&input).unwrap();
let tool = payload["tool"].as_str().unwrap_or("");
let input_data = &payload["input"];
let result = match tool {
"my_tool" => {
let param = input_data["param"].as_str().unwrap_or("");
format!("Processed: {param}")
}
_ => format!("Unknown tool: {tool}"),
};
println!("{}", serde_json::json!({"result": result}));
}
```
2. Compile to WASM:
```bash
cargo build --target wasm32-wasi --release
```
3. Reference the `.wasm` file in your manifest:
```toml
[runtime]
type = "wasm"
entry = "target/wasm32-wasi/release/my_skill.wasm"
```
### Sandbox Limits
The WASM sandbox enforces:
- **Fuel limit**: Maximum computation steps (prevents infinite loops).
- **Memory limit**: Maximum memory allocation.
- **Capabilities**: Only the capabilities granted to the agent apply.
These are derived from the agent's `[resources]` section in its manifest.
---
## Skill Requirements
Skills can declare requirements in the `[requirements]` section:
### Tool Requirements
If your skill needs to call built-in tools (e.g., `web_fetch` to download a page before processing it):
```toml
[requirements]
tools = ["web_fetch", "file_read"]
```
The skill registry validates that the agent has these tools available before loading the skill.
### Capability Requirements
If your skill needs specific capabilities:
```toml
[requirements]
capabilities = ["NetConnect(*)", "ShellExec(python3)"]
```
---
## Installing Skills
### From a Local Directory
```bash
openfang skill install /path/to/my-skill
```
This reads the `skill.toml`, validates the manifest, and copies the skill to `~/.openfang/skills/my-skill/`.
### From FangHub
```bash
openfang skill install web-summarizer
```
This downloads the skill from the FangHub marketplace registry.
### From a Git Repository
```bash
openfang skill install https://github.com/user/openfang-skill-example.git
```
### Listing Installed Skills
```bash
openfang skill list
```
Output:
```
3 skill(s) installed:
NAME VERSION TOOLS DESCRIPTION
----------------------------------------------------------------------
web-summarizer 0.1.0 2 Summarizes any web page into bullet points
data-analyzer 0.2.1 3 Statistical analysis tools
code-formatter 1.0.0 1 Format code in 20+ languages
```
### Removing Skills
```bash
openfang skill remove web-summarizer
```
---
## Publishing to FangHub
FangHub is the community skill marketplace for OpenFang.
### Preparing Your Skill
1. Ensure your `skill.toml` has complete metadata:
- `name`, `version`, `description`, `author`, `license`, `tags`
2. Include a `README.md` with usage instructions.
3. Test your skill locally:
```bash
openfang skill install /path/to/my-skill
# Spawn an agent with the skill's tools and test them
```
### Searching FangHub
```bash
openfang skill search "web scraping"
```
Output:
```
Skills matching "web scraping":
web-summarizer (42 stars)
Summarizes any web page into bullet points
https://fanghub.dev/skills/web-summarizer
page-scraper (28 stars)
Extract structured data from web pages
https://fanghub.dev/skills/page-scraper
```
### Publishing
Publishing to FangHub will be available via:
```bash
openfang skill publish
```
This validates the manifest, packages the skill, and uploads it to the FangHub registry.
---
## CLI Commands
### Full Skill Command Reference
```bash
# Install a skill (local directory, FangHub name, or git URL)
openfang skill install <source>
# List all installed skills
openfang skill list
# Remove an installed skill
openfang skill remove <name>
# Search FangHub for skills
openfang skill search <query>
# Create a new skill scaffold (interactive)
openfang skill create
```
### Creating a Skill Scaffold
```bash
openfang skill create
```
This interactive command prompts for:
- Skill name
- Description
- Runtime type (python/node/wasm)
It generates:
```
~/.openfang/skills/my-skill/
skill.toml # Pre-filled manifest
src/
main.py # Starter entry point (for Python)
```
The generated entry point includes a working template that reads JSON from stdin and writes JSON to stdout.
### Using Skills in Agent Manifests
Reference skills in the agent manifest's `skills` field:
```toml
name = "my-assistant"
version = "0.1.0"
description = "An assistant with extra skills"
author = "openfang"
module = "builtin:chat"
skills = ["web-summarizer", "data-analyzer"]
[model]
provider = "groq"
model = "llama-3.3-70b-versatile"
[capabilities]
tools = ["file_read", "web_fetch", "summarize_url"]
memory_read = ["*"]
memory_write = ["self.*"]
```
The kernel loads skill tools and prompts at agent spawn time, merging them with the agent's base capabilities.
---
## OpenClaw Compatibility
OpenFang can install and run OpenClaw-format skills. The skill installer auto-detects OpenClaw skills (by looking for `package.json` + `index.ts`/`index.js`) and converts them.
### Automatic Conversion
```bash
openfang skill install /path/to/openclaw-skill
```
If the directory contains an OpenClaw-style skill (Node.js package), OpenFang:
1. Detects the OpenClaw format.
2. Generates a `skill.toml` manifest from `package.json`.
3. Maps tool names to OpenFang conventions.
4. Copies the skill to the OpenFang skills directory.
### Manual Conversion
If automatic conversion does not work, create a `skill.toml` manually:
```toml
[skill]
name = "my-openclaw-skill"
version = "1.0.0"
description = "Converted from OpenClaw"
[runtime]
type = "node"
entry = "index.js"
[[tools.provided]]
name = "my_tool"
description = "Tool description"
input_schema = { type = "object", properties = { input = { type = "string" } }, required = ["input"] }
```
Place this alongside the existing `index.js`/`index.ts` and install:
```bash
openfang skill install /path/to/skill-directory
```
Skills imported via `openfang migrate --from openclaw` are also scanned and reported in the migration report, with instructions for manual reinstallation.
---
## Best Practices
1. **Keep skills focused** -- one skill should do one thing well.
2. **Declare minimal requirements** -- only request the tools and capabilities your skill actually needs.
3. **Use descriptive tool names** -- the LLM reads the tool name and description to decide when to use it.
4. **Provide clear input schemas** -- include descriptions for every parameter so the LLM knows what to pass.
5. **Handle errors gracefully** -- always return a JSON error object rather than crashing.
6. **Version carefully** -- use semantic versioning; breaking changes require a major version bump.
7. **Test with multiple agents** -- verify your skill works with different agent templates and providers.
8. **Include a README** -- document setup steps, dependencies, and example usage.

557
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,557 @@
# Troubleshooting & FAQ
Common issues, diagnostics, and answers to frequently asked questions about OpenFang.
## Table of Contents
- [Quick Diagnostics](#quick-diagnostics)
- [Installation Issues](#installation-issues)
- [Configuration Issues](#configuration-issues)
- [LLM Provider Issues](#llm-provider-issues)
- [Channel Issues](#channel-issues)
- [Agent Issues](#agent-issues)
- [API Issues](#api-issues)
- [Desktop App Issues](#desktop-app-issues)
- [Performance](#performance)
- [FAQ](#faq)
---
## Quick Diagnostics
Run the built-in diagnostic tool:
```bash
openfang doctor
```
This checks:
- Configuration file exists and is valid TOML
- API keys are set in environment
- Database is accessible
- Daemon status (running or not)
- Port availability
- Tool dependencies (Python, signal-cli, etc.)
### Check Daemon Status
```bash
openfang status
```
### Check Health via API
```bash
curl http://127.0.0.1:4200/api/health
curl http://127.0.0.1:4200/api/health/detail # Requires auth
```
### View Logs
OpenFang uses `tracing` for structured logging. Set the log level via environment:
```bash
RUST_LOG=info openfang start # Default
RUST_LOG=debug openfang start # Verbose
RUST_LOG=openfang=debug openfang start # Only OpenFang debug, deps at info
```
---
## Installation Issues
### `cargo install` fails with compilation errors
**Cause**: Rust toolchain too old or missing system dependencies.
**Fix**:
```bash
rustup update stable
rustup default stable
rustc --version # Need 1.75+
```
On Linux, you may also need:
```bash
# Debian/Ubuntu
sudo apt install pkg-config libssl-dev libsqlite3-dev
# Fedora
sudo dnf install openssl-devel sqlite-devel
```
### `openfang` command not found after install
**Fix**: Ensure `~/.cargo/bin` is in your PATH:
```bash
export PATH="$HOME/.cargo/bin:$PATH"
# Add to ~/.bashrc or ~/.zshrc to persist
```
### Docker container won't start
**Common causes**:
- No API key provided: `docker run -e GROQ_API_KEY=... ghcr.io/RightNow-AI/openfang`
- Port already in use: change the port mapping `-p 3001:4200`
- Permission denied on volume mount: check directory permissions
---
## Configuration Issues
### "Config file not found"
**Fix**: Run `openfang init` to create the default config:
```bash
openfang init
```
This creates `~/.openfang/config.toml` with sensible defaults.
### "Missing API key" warnings on start
**Cause**: No LLM provider API key found in environment.
**Fix**: Set at least one provider key:
```bash
export GROQ_API_KEY="gsk_..." # Groq (free tier available)
# OR
export ANTHROPIC_API_KEY="sk-ant-..."
# OR
export OPENAI_API_KEY="sk-..."
```
Add to your shell profile to persist across sessions.
### Config validation errors
Run validation manually:
```bash
openfang config show
```
Common issues:
- Malformed TOML syntax (use a TOML validator)
- Invalid port numbers (must be 1-65535)
- Missing required fields in channel configs
### "Port already in use"
**Fix**: Change the port in config or kill the existing process:
```bash
# Change API port
# In config.toml:
# [api]
# listen_addr = "127.0.0.1:3001"
# Or find and kill the process using the port
# Linux/macOS:
lsof -i :4200
# Windows:
netstat -aon | findstr :4200
```
---
## LLM Provider Issues
### "Authentication failed" / 401 errors
**Causes**:
- API key not set or incorrect
- API key expired or revoked
- Wrong env var name
**Fix**: Verify your key:
```bash
# Check if the env var is set
echo $GROQ_API_KEY
# Test the provider
curl http://127.0.0.1:4200/api/providers/groq/test -X POST
```
### "Rate limited" / 429 errors
**Cause**: Too many requests to the LLM provider.
**Fix**:
- The driver automatically retries with exponential backoff
- Reduce `max_llm_tokens_per_hour` in agent capabilities
- Switch to a provider with higher rate limits
- Use multiple providers with model routing
### Slow responses
**Possible causes**:
- Provider API latency (try Groq for fast inference)
- Large context window (use `/compact` to shrink session)
- Complex tool chains (check iteration count in response)
**Fix**: Use per-agent model overrides to use faster models for simple agents:
```toml
[model]
provider = "groq"
model = "llama-3.1-8b-instant" # Fast, small model
```
### "Model not found"
**Fix**: Check available models:
```bash
curl http://127.0.0.1:4200/api/models
```
Or use an alias:
```toml
[model]
model = "llama" # Alias for llama-3.3-70b-versatile
```
See the full alias list:
```bash
curl http://127.0.0.1:4200/api/models/aliases
```
### Ollama / local models not connecting
**Fix**: Ensure the local server is running:
```bash
# Ollama
ollama serve # Default: http://localhost:11434
# vLLM
python -m vllm.entrypoints.openai.api_server --model ...
# LM Studio
# Start from the LM Studio UI, enable API server
```
---
## Channel Issues
### Telegram bot not responding
**Checklist**:
1. Bot token is correct: `echo $TELEGRAM_BOT_TOKEN`
2. Bot has been started (send `/start` in Telegram)
3. If `allowed_users` is set, your Telegram user ID is in the list
4. Check logs for "Telegram adapter" messages
### Discord bot offline
**Checklist**:
1. Bot token is correct
2. **Message Content Intent** is enabled in Discord Developer Portal
3. Bot has been invited to the server with correct permissions
4. Check Gateway connection in logs
### Slack bot not receiving messages
**Checklist**:
1. Both `SLACK_BOT_TOKEN` (xoxb-) and `SLACK_APP_TOKEN` (xapp-) are set
2. Socket Mode is enabled in the Slack app settings
3. Bot has been added to the channels it should monitor
4. Required scopes: `chat:write`, `app_mentions:read`, `im:history`, `im:read`, `im:write`
### Webhook-based channels (WhatsApp, LINE, Viber, etc.)
**Checklist**:
1. Your server is publicly accessible (or use a tunnel like ngrok)
2. Webhook URL is correctly configured in the platform dashboard
3. Webhook port is open and not blocked by firewall
4. Verify token matches between config and platform dashboard
### "Channel adapter failed to start"
**Common causes**:
- Missing or invalid token
- Port already in use (for webhook-based channels)
- Network connectivity issues
Check logs for the specific error:
```bash
RUST_LOG=openfang_channels=debug openfang start
```
---
## Agent Issues
### Agent stuck in a loop
**Cause**: The agent is repeatedly calling the same tool with the same parameters.
**Automatic protection**: OpenFang has a built-in loop guard:
- **Warn** at 3 identical tool calls
- **Block** at 5 identical tool calls
- **Circuit breaker** at 30 total blocked calls (stops the agent)
**Manual fix**: Cancel the agent's current run:
```bash
curl -X POST http://127.0.0.1:4200/api/agents/{id}/stop
```
Or via chat command: `/stop`
### Agent running out of context
**Cause**: Conversation history is too long for the model's context window.
**Fix**: Compact the session:
```bash
curl -X POST http://127.0.0.1:4200/api/agents/{id}/session/compact
```
Or via chat command: `/compact`
Auto-compaction is enabled by default when the session reaches the threshold (configurable in `[compaction]`).
### Agent not using tools
**Cause**: Tools not granted in the agent's capabilities.
**Fix**: Check the agent's manifest:
```toml
[capabilities]
tools = ["file_read", "web_fetch", "shell_exec"] # Must list each tool
# OR
# tools = ["*"] # Grant all tools (use with caution)
```
### "Permission denied" errors in agent responses
**Cause**: The agent is trying to use a tool or access a resource not in its capabilities.
**Fix**: Add the required capability to the agent manifest. Common ones:
- `tools = [...]` for tool access
- `network = ["*"]` for network access
- `memory_write = ["self.*"]` for memory writes
- `shell = ["*"]` for shell commands (use with caution)
### Agent spawning fails
**Check**:
1. TOML manifest is valid: `openfang agent spawn --dry-run manifest.toml`
2. LLM provider is configured and has a valid key
3. Model specified in manifest exists in the catalog
---
## API Issues
### 401 Unauthorized
**Cause**: API key required but not provided.
**Fix**: Include the Bearer token:
```bash
curl -H "Authorization: Bearer your-api-key" http://127.0.0.1:4200/api/agents
```
### 429 Too Many Requests
**Cause**: GCRA rate limiter triggered.
**Fix**: Wait for the `Retry-After` period, or increase rate limits in config:
```toml
[api]
rate_limit_per_second = 20 # Increase if needed
```
### CORS errors from browser
**Cause**: Trying to access API from a different origin.
**Fix**: Add your origin to CORS config:
```toml
[api]
cors_origins = ["http://localhost:5173", "https://your-app.com"]
```
### WebSocket disconnects
**Possible causes**:
- Idle timeout (send periodic pings)
- Network interruption (reconnect automatically)
- Agent crashed (check logs)
**Client-side fix**: Implement reconnection logic with exponential backoff.
### OpenAI-compatible API not working with my tool
**Checklist**:
1. Use `POST /v1/chat/completions` (not `/api/agents/{id}/message`)
2. Set the model to `openfang:agent-name` (e.g., `openfang:coder`)
3. Streaming: set `"stream": true` for SSE responses
4. Images: use `image_url` with `data:image/png;base64,...` format
---
## Desktop App Issues
### App won't start
**Checklist**:
1. Only one instance can run at a time (single-instance enforcement)
2. Check if the daemon is already running on the same ports
3. Try deleting `~/.openfang/daemon.json` and restarting
### White/blank screen in app
**Cause**: The embedded API server hasn't started yet.
**Fix**: Wait a few seconds. If persistent, check logs for server startup errors.
### System tray icon missing
**Platform-specific**:
- **Linux**: Requires a system tray (e.g., `libappindicator` on GNOME)
- **macOS**: Should work out of the box
- **Windows**: Check notification area settings, may need to show hidden icons
---
## Performance
### High memory usage
**Tips**:
- Reduce the number of concurrent agents
- Use session compaction for long-running agents
- Use smaller models (Llama 8B instead of 70B for simple tasks)
- Clear old sessions: `DELETE /api/sessions/{id}`
### Slow startup
**Normal startup**: <200ms for the kernel, ~1-2s with channel adapters.
If slower:
- Check database size (`~/.openfang/data/openfang.db`)
- Reduce the number of enabled channels
- Check network connectivity (MCP server connections happen at boot)
### High CPU usage
**Possible causes**:
- WASM sandbox execution (fuel-limited, should self-terminate)
- Multiple agents running simultaneously
- Channel adapters reconnecting (exponential backoff)
---
## FAQ
### How do I switch the default LLM provider?
Edit `~/.openfang/config.toml`:
```toml
[default_model]
provider = "groq"
model = "llama-3.3-70b-versatile"
api_key_env = "GROQ_API_KEY"
```
### Can I use multiple providers at the same time?
Yes. Each agent can use a different provider via its manifest `[model]` section. The kernel creates a dedicated driver per unique provider configuration.
### How do I add a new channel?
1. Add the channel config to `~/.openfang/config.toml` under `[channels]`
2. Set the required environment variables (tokens, secrets)
3. Restart the daemon
### How do I update OpenFang?
```bash
# From source
cd openfang && git pull && cargo install --path crates/openfang-cli
# Docker
docker pull ghcr.io/RightNow-AI/openfang:latest
```
### Can agents talk to each other?
Yes. Agents can use the `agent_send`, `agent_spawn`, `agent_find`, and `agent_list` tools to communicate. The orchestrator template is specifically designed for multi-agent delegation.
### Is my data sent to the cloud?
Only LLM API calls go to the provider's servers. All agent data, memory, sessions, and configuration are stored locally in SQLite (`~/.openfang/data/openfang.db`). The OFP wire protocol uses HMAC-SHA256 mutual authentication for P2P communication.
### How do I back up my data?
Back up these files:
- `~/.openfang/config.toml` (configuration)
- `~/.openfang/data/openfang.db` (all agent data, memory, sessions)
- `~/.openfang/skills/` (installed skills)
### How do I reset everything?
```bash
rm -rf ~/.openfang
openfang init # Start fresh
```
### Can I run OpenFang without an internet connection?
Yes, if you use a local LLM provider:
- **Ollama**: `ollama serve` + `ollama pull llama3.2`
- **vLLM**: Self-hosted model server
- **LM Studio**: GUI-based local model runner
Set the provider in config:
```toml
[default_model]
provider = "ollama"
model = "llama3.2"
```
### What's the difference between OpenFang and OpenClaw?
| Aspect | OpenFang | OpenClaw |
|--------|----------|----------|
| Language | Rust | Python |
| Channels | 40 | 38 |
| Skills | 60 | 57 |
| Providers | 20 | 3 |
| Security | 16 systems | Config-based |
| Binary size | ~30 MB | ~200 MB |
| Startup | <200 ms | ~3 s |
OpenFang can import OpenClaw configs: `openfang migrate --from openclaw`
### How do I report a bug or request a feature?
- Bugs: Open an issue on GitHub
- Security: See [SECURITY.md](../SECURITY.md) for responsible disclosure
- Features: Open a GitHub discussion or PR
### What are the system requirements?
| Resource | Minimum | Recommended |
|----------|---------|-------------|
| RAM | 128 MB | 512 MB |
| Disk | 50 MB (binary) | 500 MB (with data) |
| CPU | Any x86_64/ARM64 | 2+ cores |
| OS | Linux, macOS, Windows | Any |
| Rust | 1.75+ (build only) | Latest stable |
### How do I enable debug logging for a specific crate?
```bash
RUST_LOG=openfang_runtime=debug,openfang_channels=info openfang start
```
### Can I use OpenFang as a library?
Yes. Each crate is independently usable:
```toml
[dependencies]
openfang-runtime = { path = "crates/openfang-runtime" }
openfang-memory = { path = "crates/openfang-memory" }
```
The `openfang-kernel` crate assembles everything, but you can use individual crates for custom integrations.

812
docs/workflows.md Normal file
View File

@@ -0,0 +1,812 @@
# Workflow Engine Guide
## Overview
The OpenFang workflow engine enables multi-step agent pipelines -- orchestrated sequences of tasks where each step routes work to a specific agent, and output from one step flows as input to the next. Workflows let you compose complex behaviors from simple, single-purpose agents without writing any Rust code.
Use workflows when you need to:
- Chain multiple agents together in a processing pipeline (e.g., research then write then review).
- Fan work out to several agents in parallel and collect their results.
- Conditionally branch execution based on an earlier step's output.
- Iterate a step in a loop until a quality gate is met.
- Build reproducible, auditable multi-agent processes that can be triggered via API or CLI.
The implementation lives in `openfang-kernel/src/workflow.rs`. The workflow engine is decoupled from the kernel through closures -- it never directly owns or references the kernel, making it testable in isolation.
---
## Core Types
| Rust type | Description |
|---|---|
| `WorkflowId(Uuid)` | Unique identifier for a workflow definition. |
| `WorkflowRunId(Uuid)` | Unique identifier for a running workflow instance. |
| `Workflow` | A named definition containing a list of `WorkflowStep` entries. |
| `WorkflowStep` | A single step: agent reference, prompt template, mode, timeout, error handling. |
| `WorkflowRun` | A running instance: tracks state, step results, final output, timestamps. |
| `WorkflowRunState` | Enum: `Pending`, `Running`, `Completed`, `Failed`. |
| `StepResult` | Result from one step: agent info, output text, token counts, duration. |
| `WorkflowEngine` | The engine itself: stores definitions and runs in `Arc<RwLock<HashMap>>`. |
---
## Workflow Definition
Workflows are registered via the REST API as JSON. The top-level structure is:
```json
{
"name": "my-pipeline",
"description": "Describe what the workflow does",
"steps": [ ... ]
}
```
The corresponding Rust struct is:
```rust
pub struct Workflow {
pub id: WorkflowId, // Auto-assigned on creation
pub name: String, // Human-readable name
pub description: String, // What this workflow does
pub steps: Vec<WorkflowStep>, // Ordered list of steps
pub created_at: DateTime<Utc>, // Auto-assigned on creation
}
```
---
## Step Configuration
Each step in the `steps` array has the following fields:
| JSON field | Rust field | Type | Default | Description |
|---|---|---|---|---|
| `name` | `name` | `String` | `"step"` | Step name for logging and display. |
| `agent_name` | `agent` | `StepAgent::ByName` | -- | Reference an agent by its name (first match). Mutually exclusive with `agent_id`. |
| `agent_id` | `agent` | `StepAgent::ById` | -- | Reference an agent by its UUID. Mutually exclusive with `agent_name`. |
| `prompt` | `prompt_template` | `String` | `"{{input}}"` | Prompt template with variable placeholders. |
| `mode` | `mode` | `StepMode` | `"sequential"` | Execution mode (see below). |
| `timeout_secs` | `timeout_secs` | `u64` | `120` | Maximum time in seconds before the step times out. |
| `error_mode` | `error_mode` | `ErrorMode` | `"fail"` | How to handle errors (see below). |
| `max_retries` | (inside `ErrorMode::Retry`) | `u32` | `3` | Number of retries when `error_mode` is `"retry"`. |
| `output_var` | `output_var` | `Option<String>` | `null` | If set, stores this step's output in a named variable for later reference. |
| `condition` | (inside `StepMode::Conditional`) | `String` | `""` | Substring to match in previous output (case-insensitive). |
| `max_iterations` | (inside `StepMode::Loop`) | `u32` | `5` | Maximum loop iterations before forced termination. |
| `until` | (inside `StepMode::Loop`) | `String` | `""` | Substring to match in output to terminate the loop (case-insensitive). |
### Agent Resolution
Every step must specify exactly one of `agent_name` or `agent_id`. The `StepAgent` enum is:
```rust
pub enum StepAgent {
ById { id: String }, // UUID of an existing agent
ByName { name: String }, // Name match (first agent with this name)
}
```
If the agent cannot be resolved at execution time, the workflow fails with `"Agent not found for step '<name>'"`.
---
## Step Modes
The `mode` field controls how a step executes relative to other steps in the workflow.
### Sequential (default)
```json
{ "mode": "sequential" }
```
The step runs after the previous step completes. The previous step's output becomes `{{input}}` for this step. This is the default mode when `mode` is omitted.
### Fan-Out
```json
{ "mode": "fan_out" }
```
Fan-out steps run **in parallel**. The engine collects all consecutive `fan_out` steps and launches them simultaneously using `futures::future::join_all`. All fan-out steps receive the same `{{input}}` -- the output from the last step that ran before the fan-out group.
If any fan-out step fails or times out, the entire workflow fails immediately.
### Collect
```json
{ "mode": "collect" }
```
The `collect` step gathers all outputs from the preceding fan-out group. It does not execute an agent -- it is a **data-only** step that joins all accumulated outputs with the separator `"\n\n---\n\n"` and sets the result as `{{input}}` for subsequent steps.
A typical fan-out/collect pattern:
```
step 1: fan_out --> runs in parallel
step 2: fan_out --> runs in parallel
step 3: collect --> joins outputs from steps 1 and 2
step 4: sequential --> receives joined output as {{input}}
```
### Conditional
```json
{ "mode": "conditional", "condition": "ERROR" }
```
The step only executes if the previous step's output **contains** the `condition` substring (case-insensitive comparison via `to_lowercase().contains()`). If the condition is not met, the step is skipped entirely and `{{input}}` is not modified.
When the condition is met, the step executes like a sequential step.
### Loop
```json
{ "mode": "loop", "max_iterations": 5, "until": "APPROVED" }
```
The step repeats up to `max_iterations` times. After each iteration, the engine checks whether the output **contains** the `until` substring (case-insensitive). If found, the loop terminates early.
Each iteration feeds its output back as `{{input}}` for the next iteration. Step results are recorded with names like `"refine (iter 1)"`, `"refine (iter 2)"`, etc.
If the `until` condition is never met, the loop runs exactly `max_iterations` times and continues to the next step with the last iteration's output.
---
## Variable Substitution
Prompt templates support two kinds of variable references:
### `{{input}}` -- Previous step output
Always available. Contains the output from the immediately preceding step (or the workflow's initial input for the first step).
### `{{variable_name}}` -- Named variables
When a step has `"output_var": "my_var"`, its output is stored in a variable map under the key `my_var`. Any subsequent step can reference it with `{{my_var}}` in its prompt template.
The expansion logic (from `WorkflowEngine::expand_variables`):
```rust
fn expand_variables(template: &str, input: &str, vars: &HashMap<String, String>) -> String {
let mut result = template.replace("{{input}}", input);
for (key, value) in vars {
result = result.replace(&format!("{{{{{key}}}}}"), value);
}
result
}
```
Variables persist for the entire workflow run. A later step can overwrite a variable by using the same `output_var` name.
**Example**: A three-step workflow where step 3 references outputs from both step 1 and step 2:
```json
{
"steps": [
{ "name": "research", "output_var": "research_output", "prompt": "Research: {{input}}" },
{ "name": "outline", "output_var": "outline_output", "prompt": "Outline based on: {{input}}" },
{ "name": "combine", "prompt": "Write article.\nResearch: {{research_output}}\nOutline: {{outline_output}}" }
]
}
```
---
## Error Handling
Each step has an `error_mode` that controls behavior when the step fails or times out.
### Fail (default)
```json
{ "error_mode": "fail" }
```
The workflow aborts immediately. The run state is set to `Failed`, the error message is recorded, and `completed_at` is set. The error message format is `"Step '<name>' failed: <error>"` or `"Step '<name>' timed out after <N>s"`.
### Skip
```json
{ "error_mode": "skip" }
```
The step is silently skipped on error or timeout. A warning is logged, but the workflow continues. The `{{input}}` for the next step remains unchanged (it keeps the value from before the skipped step). No `StepResult` is recorded for the skipped step.
### Retry
```json
{ "error_mode": "retry", "max_retries": 3 }
```
The step is retried up to `max_retries` times after the initial attempt (so `max_retries: 3` means up to 4 total attempts: 1 initial + 3 retries). Each attempt gets the full `timeout_secs` budget independently. If all attempts fail, the workflow aborts with `"Step '<name>' failed after <N> retries: <last_error>"`.
### Timeout Behavior
Every step execution is wrapped in `tokio::time::timeout(Duration::from_secs(step.timeout_secs), ...)`. The default timeout is 120 seconds. Timeouts are treated as errors and handled according to the step's `error_mode`.
For fan-out steps, each parallel step gets its own timeout individually.
---
## Examples
### Example 1: Code Review Pipeline
A sequential pipeline where code is analyzed, reviewed, and a summary is produced.
```json
{
"name": "code-review-pipeline",
"description": "Analyze code, review for issues, and produce a summary report",
"steps": [
{
"name": "analyze",
"agent_name": "code-reviewer",
"prompt": "Analyze the following code for bugs, style issues, and security vulnerabilities:\n\n{{input}}",
"mode": "sequential",
"timeout_secs": 180,
"error_mode": "fail",
"output_var": "analysis"
},
{
"name": "security-check",
"agent_name": "security-auditor",
"prompt": "Review this code analysis for security issues. Flag anything critical:\n\n{{analysis}}",
"mode": "sequential",
"timeout_secs": 120,
"error_mode": "retry",
"max_retries": 2,
"output_var": "security_review"
},
{
"name": "summary",
"agent_name": "writer",
"prompt": "Write a concise code review summary.\n\nCode Analysis:\n{{analysis}}\n\nSecurity Review:\n{{security_review}}",
"mode": "sequential",
"timeout_secs": 60,
"error_mode": "fail"
}
]
}
```
### Example 2: Research and Write Article
Research a topic, outline it, then write -- with a conditional fact-check step.
```json
{
"name": "research-and-write",
"description": "Research a topic, outline, write, and optionally fact-check",
"steps": [
{
"name": "research",
"agent_name": "researcher",
"prompt": "Research the following topic thoroughly. Cite sources where possible:\n\n{{input}}",
"mode": "sequential",
"timeout_secs": 300,
"error_mode": "retry",
"max_retries": 1,
"output_var": "research"
},
{
"name": "outline",
"agent_name": "planner",
"prompt": "Create a detailed article outline based on this research:\n\n{{research}}",
"mode": "sequential",
"timeout_secs": 60,
"output_var": "outline"
},
{
"name": "write",
"agent_name": "writer",
"prompt": "Write a complete article.\n\nOutline:\n{{outline}}\n\nResearch:\n{{research}}",
"mode": "sequential",
"timeout_secs": 300,
"output_var": "article"
},
{
"name": "fact-check",
"agent_name": "analyst",
"prompt": "Fact-check this article and note any claims that need verification:\n\n{{article}}",
"mode": "conditional",
"condition": "claim",
"timeout_secs": 120,
"error_mode": "skip"
}
]
}
```
The fact-check step only runs if the article contains the word "claim" (case-insensitive). If the fact-check agent fails, the workflow continues with the article as-is.
### Example 3: Multi-Agent Brainstorm (Fan-Out + Collect)
Three agents brainstorm in parallel, then a fourth agent synthesizes their ideas.
```json
{
"name": "brainstorm",
"description": "Parallel brainstorm with 3 agents, then synthesize",
"steps": [
{
"name": "creative-ideas",
"agent_name": "writer",
"prompt": "Brainstorm 5 creative ideas for: {{input}}",
"mode": "fan_out",
"timeout_secs": 60,
"output_var": "creative"
},
{
"name": "technical-ideas",
"agent_name": "architect",
"prompt": "Brainstorm 5 technically feasible ideas for: {{input}}",
"mode": "fan_out",
"timeout_secs": 60,
"output_var": "technical"
},
{
"name": "business-ideas",
"agent_name": "analyst",
"prompt": "Brainstorm 5 ideas with strong business potential for: {{input}}",
"mode": "fan_out",
"timeout_secs": 60,
"output_var": "business"
},
{
"name": "gather",
"agent_name": "planner",
"prompt": "unused",
"mode": "collect"
},
{
"name": "synthesize",
"agent_name": "orchestrator",
"prompt": "You received brainstorm results from three perspectives. Synthesize them into the top 5 actionable ideas, ranked by impact:\n\n{{input}}",
"mode": "sequential",
"timeout_secs": 120
}
]
}
```
The three fan-out steps run in parallel. The `collect` step joins their outputs with `---` separators. The `synthesize` step receives the combined output.
### Example 4: Iterative Refinement (Loop)
An agent refines a draft until it meets a quality bar.
```json
{
"name": "iterative-refinement",
"description": "Refine a document until approved or max iterations reached",
"steps": [
{
"name": "first-draft",
"agent_name": "writer",
"prompt": "Write a first draft about: {{input}}",
"mode": "sequential",
"timeout_secs": 120,
"output_var": "draft"
},
{
"name": "review-and-refine",
"agent_name": "code-reviewer",
"prompt": "Review this draft. If it meets quality standards, respond with APPROVED at the start. Otherwise, provide specific feedback and a revised version:\n\n{{input}}",
"mode": "loop",
"max_iterations": 4,
"until": "APPROVED",
"timeout_secs": 180,
"error_mode": "retry",
"max_retries": 1
}
]
}
```
The loop runs the reviewer up to 4 times. Each iteration receives the previous iteration's output as `{{input}}`. Once the reviewer includes "APPROVED" in its response, the loop terminates early.
---
## Trigger Engine
The trigger engine (`openfang-kernel/src/triggers.rs`) provides event-driven automation. Triggers watch the kernel's event bus and automatically send messages to agents when matching events arrive.
### Core Types
| Rust type | Description |
|---|---|
| `TriggerId(Uuid)` | Unique identifier for a trigger. |
| `Trigger` | A registered trigger: agent, pattern, prompt template, fire count, limits. |
| `TriggerPattern` | Enum defining which events to match. |
| `TriggerEngine` | The engine: `DashMap`-backed concurrent storage with agent-to-trigger index. |
### Trigger Definition
```rust
pub struct Trigger {
pub id: TriggerId,
pub agent_id: AgentId, // Which agent receives the message
pub pattern: TriggerPattern, // What events to match
pub prompt_template: String, // Template with {{event}} placeholder
pub enabled: bool, // Can be toggled on/off
pub created_at: DateTime<Utc>,
pub fire_count: u64, // How many times it has fired
pub max_fires: u64, // 0 = unlimited
}
```
### Event Patterns
The `TriggerPattern` enum supports 9 matching modes:
| Pattern | JSON | Description |
|---|---|---|
| `All` | `"all"` | Matches every event (wildcard). |
| `Lifecycle` | `"lifecycle"` | Matches any lifecycle event (spawned, started, terminated, etc.). |
| `AgentSpawned` | `{"agent_spawned": {"name_pattern": "coder"}}` | Matches when an agent with a name containing `name_pattern` is spawned. Use `"*"` for any agent. |
| `AgentTerminated` | `"agent_terminated"` | Matches when any agent terminates or crashes. |
| `System` | `"system"` | Matches any system event (health checks, quota warnings, etc.). |
| `SystemKeyword` | `{"system_keyword": {"keyword": "quota"}}` | Matches system events whose debug representation contains the keyword (case-insensitive). |
| `MemoryUpdate` | `"memory_update"` | Matches any memory change event. |
| `MemoryKeyPattern` | `{"memory_key_pattern": {"key_pattern": "config"}}` | Matches memory updates where the key contains `key_pattern`. Use `"*"` for any key. |
| `ContentMatch` | `{"content_match": {"substring": "error"}}` | Matches any event whose human-readable description contains the substring (case-insensitive). |
### Pattern Matching Details
The `matches_pattern` function determines how each pattern evaluates:
- **`All`**: Always returns `true`.
- **`Lifecycle`**: Checks `EventPayload::Lifecycle(_)`.
- **`AgentSpawned`**: Checks for `LifecycleEvent::Spawned` where `name.contains(name_pattern)` or `name_pattern == "*"`.
- **`AgentTerminated`**: Checks for `LifecycleEvent::Terminated` or `LifecycleEvent::Crashed`.
- **`System`**: Checks `EventPayload::System(_)`.
- **`SystemKeyword`**: Formats the system event via `Debug` trait, lowercases it, and checks `contains(keyword)`.
- **`MemoryUpdate`**: Checks `EventPayload::MemoryUpdate(_)`.
- **`MemoryKeyPattern`**: Checks `delta.key.contains(key_pattern)` or `key_pattern == "*"`.
- **`ContentMatch`**: Uses the `describe_event()` function to produce a human-readable string, then checks `contains(substring)` (case-insensitive).
### Prompt Template and `{{event}}`
When a trigger fires, the engine replaces `{{event}}` in the `prompt_template` with a human-readable event description. The `describe_event()` function produces strings like:
- `"Agent 'coder' (id: <uuid>) was spawned"`
- `"Agent <uuid> terminated: shutdown requested"`
- `"Agent <uuid> crashed: out of memory"`
- `"Kernel started"`
- `"Quota warning: agent <uuid>, tokens at 85.0%"`
- `"Health check failed: agent <uuid>, unresponsive for 30s"`
- `"Memory Created on key 'config' for agent <uuid>"`
- `"Tool 'web_search' succeeded (450ms): ..."`
### Max Fires and Auto-Disable
When `max_fires` is set to a value greater than 0, the trigger automatically disables itself (sets `enabled = false`) once `fire_count >= max_fires`. Setting `max_fires` to 0 means the trigger fires indefinitely.
### Trigger Use Cases
**Monitor agent health:**
```json
{
"agent_id": "<ops-agent-uuid>",
"pattern": {"content_match": {"substring": "health check failed"}},
"prompt_template": "ALERT: {{event}}. Investigate and report the status of all agents.",
"max_fires": 0
}
```
**React to new agent spawns:**
```json
{
"agent_id": "<orchestrator-uuid>",
"pattern": {"agent_spawned": {"name_pattern": "*"}},
"prompt_template": "A new agent was just created: {{event}}. Update the fleet roster.",
"max_fires": 0
}
```
**One-shot quota alert:**
```json
{
"agent_id": "<admin-agent-uuid>",
"pattern": {"system_keyword": {"keyword": "quota"}},
"prompt_template": "Quota event detected: {{event}}. Recommend corrective action.",
"max_fires": 1
}
```
---
## API Endpoints
### Workflow Endpoints
#### `POST /api/workflows` -- Create a workflow
Register a new workflow definition.
**Request body:**
```json
{
"name": "my-pipeline",
"description": "Description of the workflow",
"steps": [
{
"name": "step-1",
"agent_name": "researcher",
"prompt": "Research: {{input}}",
"mode": "sequential",
"timeout_secs": 120,
"error_mode": "fail",
"output_var": "research"
}
]
}
```
**Response (201 Created):**
```json
{ "workflow_id": "<uuid>" }
```
#### `GET /api/workflows` -- List all workflows
Returns an array of registered workflow summaries.
**Response (200 OK):**
```json
[
{
"id": "<uuid>",
"name": "my-pipeline",
"description": "Description of the workflow",
"steps": 3,
"created_at": "2026-01-15T10:30:00Z"
}
]
```
#### `POST /api/workflows/:id/run` -- Execute a workflow
Start a synchronous workflow execution. The call blocks until the workflow completes or fails.
**Request body:**
```json
{ "input": "The initial input text for the first step" }
```
**Response (200 OK):**
```json
{
"run_id": "<uuid>",
"output": "Final output from the last step",
"status": "completed"
}
```
**Response (500 Internal Server Error):**
```json
{ "error": "Workflow execution failed" }
```
#### `GET /api/workflows/:id/runs` -- List workflow runs
Returns all workflow runs (not filtered by workflow ID in the current implementation).
**Response (200 OK):**
```json
[
{
"id": "<uuid>",
"workflow_name": "my-pipeline",
"state": "completed",
"steps_completed": 3,
"started_at": "2026-01-15T10:30:00Z",
"completed_at": "2026-01-15T10:32:15Z"
}
]
```
### Trigger Endpoints
#### `POST /api/triggers` -- Create a trigger
Register a new event trigger for an agent.
**Request body:**
```json
{
"agent_id": "<agent-uuid>",
"pattern": "lifecycle",
"prompt_template": "A lifecycle event occurred: {{event}}",
"max_fires": 0
}
```
**Response (201 Created):**
```json
{
"trigger_id": "<uuid>",
"agent_id": "<agent-uuid>"
}
```
#### `GET /api/triggers` -- List all triggers
Optionally filter by agent: `GET /api/triggers?agent_id=<uuid>`
**Response (200 OK):**
```json
[
{
"id": "<uuid>",
"agent_id": "<agent-uuid>",
"pattern": "lifecycle",
"prompt_template": "Event: {{event}}",
"enabled": true,
"fire_count": 5,
"max_fires": 0,
"created_at": "2026-01-15T10:00:00Z"
}
]
```
#### `PUT /api/triggers/:id` -- Enable/disable a trigger
Toggle a trigger's enabled state.
**Request body:**
```json
{ "enabled": false }
```
**Response (200 OK):**
```json
{ "status": "updated", "trigger_id": "<uuid>", "enabled": false }
```
#### `DELETE /api/triggers/:id` -- Delete a trigger
**Response (200 OK):**
```json
{ "status": "removed", "trigger_id": "<uuid>" }
```
**Response (404 Not Found):**
```json
{ "error": "Trigger not found" }
```
---
## CLI Commands
All workflow and trigger CLI commands require a running OpenFang daemon.
### Workflow Commands
```
openfang workflow list
```
Lists all registered workflows with their ID, name, step count, and creation date.
```
openfang workflow create <file>
```
Creates a workflow from a JSON file. The file should contain the same JSON structure as the `POST /api/workflows` request body.
```
openfang workflow run <workflow_id> <input>
```
Executes a workflow by its UUID with the given input text. Blocks until completion and prints the output.
### Trigger Commands
```
openfang trigger list [--agent-id <uuid>]
```
Lists all registered triggers. Optionally filter by agent ID.
```
openfang trigger create <agent_id> <pattern_json> [--prompt <template>] [--max-fires <n>]
```
Creates a trigger for the specified agent. The `pattern_json` argument is a JSON string describing the pattern.
Defaults:
- `--prompt`: `"Event: {{event}}"`
- `--max-fires`: `0` (unlimited)
Examples:
```bash
# Watch all lifecycle events
openfang trigger create <agent-id> '"lifecycle"' --prompt "Lifecycle: {{event}}"
# Watch for a specific agent spawn
openfang trigger create <agent-id> '{"agent_spawned":{"name_pattern":"coder"}}' --max-fires 1
# Watch for content containing "error"
openfang trigger create <agent-id> '{"content_match":{"substring":"error"}}'
```
```
openfang trigger delete <trigger_id>
```
Deletes a trigger by its UUID.
---
## Execution Limits
### Run Eviction Cap
The workflow engine retains a maximum of **200** workflow runs (`WorkflowEngine::MAX_RETAINED_RUNS`). When this limit is exceeded after creating a new run, the oldest **completed** or **failed** runs are evicted (sorted by `started_at`). Runs in `Pending` or `Running` state are never evicted.
### Step Timeouts
Each step has a configurable `timeout_secs` (default: 120 seconds). The timeout is enforced via `tokio::time::timeout` and applies per-attempt -- retry mode gives each attempt a fresh timeout budget. Fan-out steps each get their own independent timeout.
### Loop Iteration Cap
Loop steps are bounded by `max_iterations` (default: 5 in the API). The engine will never execute more than this many iterations, even if the `until` condition is never met.
### Hourly Token Quota
The `AgentScheduler` (in `openfang-kernel/src/scheduler.rs`) tracks per-agent token usage with a rolling 1-hour window via `UsageTracker`. If an agent exceeds its `ResourceQuota.max_llm_tokens_per_hour`, the scheduler returns `OpenFangError::QuotaExceeded`. The window resets automatically after 3600 seconds. This quota applies to all agent interactions, including those invoked by workflows.
---
## Workflow Data Flow Diagram
```
input
|
v
+---------------+
| Step 1 | mode: sequential
| agent: A |
+-------+-------+
| output -> {{input}} for step 2
| -> variables["var1"] if output_var set
v
+---------------+
| Step 2 | mode: fan_out
| agent: B |---+
+---------------+ |
+---------------+ | parallel execution
| Step 3 | | (all receive same {{input}})
| agent: C |---+
+---------------+ |
| |
v v
+---------------+
| Step 4 | mode: collect
| (no agent) | joins all outputs with "---"
+-------+-------+
| combined output -> {{input}}
v
+---------------+
| Step 5 | mode: conditional { condition: "issue" }
| agent: D | (skipped if {{input}} does not contain "issue")
+-------+-------+
|
v
+---------------+
| Step 6 | mode: loop { max_iterations: 3, until: "DONE" }
| agent: E | repeats, feeding output back as {{input}}
+-------+-------+
|
v
final output
```
---
## Internal Architecture Notes
- The `WorkflowEngine` is decoupled from `OpenFangKernel`. The `execute_run` method takes two closures: `agent_resolver` (resolves `StepAgent` to `AgentId` + name) and `send_message` (sends a prompt to an agent and returns output + token counts). This design makes the engine testable without a live kernel.
- All state is held in `Arc<RwLock<HashMap>>`, allowing concurrent read access and serialized writes.
- The `TriggerEngine` uses `DashMap` for lock-free concurrent access, with an `agent_triggers` index for efficient per-agent trigger lookups.
- Fan-out parallelism uses `futures::future::join_all` -- all fan-out steps in a consecutive group are launched simultaneously.
- The trigger `evaluate` method uses `iter_mut()` on the `DashMap` to atomically increment fire counts while checking patterns, preventing race conditions.