初始化提交
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
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:
105
docs/README.md
Normal file
105
docs/README.md
Normal 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
976
docs/agent-templates.md
Normal 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
2234
docs/api-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
927
docs/architecture.md
Normal file
927
docs/architecture.md
Normal 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 |
|
||||
+------------------+
|
||||
```
|
||||
164
docs/benchmarks/architecture-overview.svg
Normal file
164
docs/benchmarks/architecture-overview.svg
Normal 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 |
116
docs/benchmarks/feature-coverage.svg
Normal file
116
docs/benchmarks/feature-coverage.svg
Normal 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 |
58
docs/benchmarks/performance.svg
Normal file
58
docs/benchmarks/performance.svg
Normal 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 & 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"><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 |
55
docs/benchmarks/security-layers.svg
Normal file
55
docs/benchmarks/security-layers.svg
Normal 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
725
docs/channel-adapters.md
Normal 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
1354
docs/cli-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
1480
docs/configuration.md
Normal file
1480
docs/configuration.md
Normal file
File diff suppressed because it is too large
Load Diff
412
docs/desktop.md
Normal file
412
docs/desktop.md
Normal 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
376
docs/getting-started.md
Normal 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
462
docs/launch-roadmap.md
Normal 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
855
docs/mcp-a2a.md
Normal 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
|
||||
298
docs/production-checklist.md
Normal file
298
docs/production-checklist.md
Normal 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
1045
docs/providers.md
Normal file
File diff suppressed because it is too large
Load Diff
1490
docs/security.md
Normal file
1490
docs/security.md
Normal file
File diff suppressed because it is too large
Load Diff
595
docs/skill-development.md
Normal file
595
docs/skill-development.md
Normal 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
557
docs/troubleshooting.md
Normal 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
812
docs/workflows.md
Normal 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.
|
||||
Reference in New Issue
Block a user