Files · Ollama Agent Mesh for Small Business Workflow Automation
87 (1 binary, 808.2 kB total)attempt 2
README.md·9361 B·markdown
markdown
# Ollama Agent Mesh for Small Business Workflow Automation
> Orchestrate a fleet of local LLM specialists — email triage, CRM updates,
> report generation — without sending data to the cloud.
A privacy-preserving automation layer for SMBs. An Express server exposes
`POST /execute-task` and routes each request through a mesh of specialist agents
powered by local [Ollama](https://ollama.com) models. The mesh uses
`@reaatech/*` packages for routing, confidence scoring, session continuity,
agent handoff, and model selection.
---
## Overview
Every SMB handles the same repetitive workflows: sorting incoming email,
keeping CRM records current, and generating weekly reports. Off-the-shelf
solutions either cost too much or require shipping customer data to a third
party.
This project runs **entirely offline** using Ollama on your own hardware. An
Express server accepts task requests, classifies the intent, routes to the
right specialist agent via a confidence-scored mesh, and returns results — no
data ever leaves your network.
### What you can do with it
- **Email triage** — Classify inbound messages as support-request,
sales-inquiry, billing-issue, or spam; extract action items; draft replies.
- **CRM updates** — Extract structured contact and deal records from
conversation or email text.
- **Report generation** — Produce structured row data ready for spreadsheet
export (`.xlsx` via the `xlsx` package).
---
## Architecture
```
┌──────────────┐
│ Express │ POST /execute-task { input, employee_id? }
│ Server │ GET /health
└──────┬───────┘
│
▼
┌──────────────────┐
│ Intent │ Classifies raw input → agent route
│ Classifier │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Confidence │ Routes above threshold → specialist
│ Router │ Falls back below threshold → handoff
│ (@reaatech/ │
│ confidence- │
│ router) │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Specialist │ email-triage │ crm-update │ report
│ Agents │ (Mastra Agent + @ai-sdk/openai-compatible)
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Ollama │ llama3.1, qwen3:1.8b (or any local model)
│ (localhost: │
│ 11434) │
└──────────────────┘
```
### REAA packages powering the mesh
| Package | Role |
|---|---|
| `@reaatech/agent-mesh-router` | Dispatch requests to the correct agent in the mesh |
| `@reaatech/confidence-router` | Score routing confidence; fallback below threshold |
| `@reaatech/session-continuity` | Maintain conversation state across turns |
| `@reaatech/agent-handoff` | Graceful handoff when confidence is low |
| `@reaatech/llm-router-core` | Cost-optimised model selection |
---
## Prerequisites
- **Ollama** running locally with at least one pulled model:
```bash
ollama pull llama3.1
ollama pull qwen3:1.8b # lighter model for analysis tasks
```
- **Node.js** >= 22
- **pnpm** 10
```bash
corepack enable && corepack prepare pnpm@10 --activate
```
---
## Quick Start
```bash
# 1. Install dependencies
pnpm install
# 2. Copy environment file and customise
cp .env.example .env
# Edit .env — at minimum set OLLAMA_HOST if your instance isn't at
# http://127.0.0.1:11434
# 3. Confirm Ollama is reachable
curl http://localhost:11434/api/tags
# 4. Start the mesh server
pnpm exec tsx src/server.ts
# → Mesh server listening on port 3001
```
Verify the server is alive:
```bash
curl http://localhost:3001/health
# → {"status":"ok","uptime_ms":1234}
```
Try a task:
```bash
curl -X POST http://localhost:3001/execute-task \
-H 'Content-Type: application/json' \
-d '{"input":"Can you sort these emails for me? We got a complaint from Acme Corp"}'
```
---
## API Reference
### `POST /execute-task`
The primary endpoint. Accepts a natural-language task description and optional
employee identifier, routes through the agent mesh, and returns the result.
**Request body**
| Field | Type | Required | Description |
|---|---|---|---|
| `input` | `string` | yes | Natural-language task or message |
| `employee_id` | `string` | no | Employee identifier for context/tracking |
**Response**
| Field | Type | Description |
|---|---|---|
| `content` | `string` | The agent's output (reply, extracted data, report) |
| `workflow_complete` | `boolean` | Whether the workflow reached a terminal state |
| `workflow_state` | `object` | Session state for follow-up turns |
**Example**
```bash
curl -X POST http://localhost:3001/execute-task \
-H 'Content-Type: application/json' \
-d '{"input":"Create a weekly report for Q1 deals","employee_id":"emp_42"}'
```
```json
{
"content": "[{\"quarter\":\"Q1\",\"deals\":5,\"value\":125000,\"stage\":\"closed-won\"},...]",
"workflow_complete": true,
"workflow_state": { "turn": 2, "agent": "report", "summary": "..." }
}
```
### `GET /health`
Returns server health information.
```json
{
"status": "ok",
"uptime_ms": 12345
}
```
---
## Configuration
The mesh is configured entirely through `mesh-config.yaml` in the project root.
```yaml
confidence:
routeThreshold: 0.8 # minimum score to route directly
fallbackThreshold: 0.3 # below this → handoff to human
llm:
defaultStrategy: cost-optimized
models:
- id: llama3.1
capabilities: [general, code, reasoning]
costPerMillionInput: 0
costPerMillionOutput: 0
maxTokens: 8192
- id: qwen3:1.8b
capabilities: [general, analysis]
costPerMillionInput: 0
costPerMillionOutput: 0
maxTokens: 32768
agents:
- id: email-triage
displayName: Email Triage Specialist
model: qwen3:1.8b
instructions: >
You are an email triage specialist for a small business. Categorize each
email as: support-request, sales-inquiry, billing-issue, or spam. Extract
sender name, email, and key action items. Draft a reply when possible.
- id: crm-update
displayName: CRM Update Specialist
model: llama3.1
instructions: >
You extract structured CRM records from conversation. Output a JSON array
of records with fields: company, contact_name, email, phone, deal_stage,
notes, next_step. Only include verified facts, mark uncertain data with
confidence.
- id: report
displayName: Report Generation Specialist
model: llama3.1
instructions: >
You generate structured report data. For each report request, produce a
JSON array of row objects with consistent keys. Include summary statistics
when relevant. Output should be ready for spreadsheet conversion.
session:
maxTokens: 4096
reserveTokens: 500
overflowStrategy: compress
```
---
## Environment Variables
| Variable | Default | Required | Description |
|---|---|---|---|
| `NODE_ENV` | `development` | — | Runtime environment |
| `PORT` | `3001` | — | Express server port |
| `OLLAMA_HOST` | `http://127.0.0.1:11434` | — | Ollama API base URL |
| `OLLAMA_DEFAULT_MODEL` | `llama3.1` | — | Fallback model for unclassified tasks |
| `FIRECRAWL_API_KEY` | — | no | API key for web research (Firecrawl) |
| `GOOGLE_APPLICATION_CREDENTIALS` | — | no | Path to GCP service-account JSON for Sheets/Gmail |
| `LANGFUSE_PUBLIC_KEY` | — | no | Langfuse trace observability public key |
| `LANGFUSE_SECRET_KEY` | — | no | Langfuse trace observability secret key |
| `LANGFUSE_BASE_URL` | — | no | Langfuse instance base URL |
---
## Adding a Specialist Agent
Add a new agent in three steps:
### 1. Register in `mesh-config.yaml`
Add a new entry under the `agents` list:
```yaml
agents:
- id: compliance-check
displayName: Compliance Check Specialist
model: llama3.1
instructions: >
You review text for regulatory compliance. Flag any language that violates
GDPR, HIPAA, or SOC2 requirements. Output a JSON array of findings with
fields: clause, regulation, risk_level, recommendation.
```
### 2. (Optional) Create a dedicated agent file
If the agent needs custom logic beyond the generic `specialist-agent.ts`,
create `src/agents/compliance-check-agent.ts`:
```ts
import { Agent } from '@mastra/core/agent';
import { createOllamaProvider } from './specialist-agent.js';
const provider = createOllamaProvider();
export const complianceCheckAgent = new Agent({
id: 'compliance-check',
name: 'Compliance Check Specialist',
instructions: `You review text for regulatory compliance...`,
model: provider.chatModel('llama3.1'),
});
```
### 3. Register in the loader
If you created a custom file, wire it in `src/config/config-loader.ts` or your
registry. For agents that only need a config entry (no custom logic), the
generic `AgentRegistry.initFromConfig()` already picks them up from
`mesh-config.yaml`.
---
## License
MIT — see [LICENSE](./LICENSE).