Skip to content

Conversation Agent — Powered by OpenClaw

What It Is

The Conversation Agent is an OpenClaw agent — the same framework that powers our internal agents (iClaw-E, Pi-E, Volt-E). OpenClaw already handles conversation management, memory, persona, and tool execution. We extend it with channel adapters (Slack, Teams, Web) and register the Accounting API as tools.

Why OpenClaw

Need OpenClaw Build from scratch
Conversation loop Built in Weeks to build
Memory/context Built in Build or integrate
Persona/character Built in (config) Build
Tool execution Built in Build
Discord Built in Build
Slack/Teams/Web Needs adapters Build
Production-tested Running our agents now Untested
We know it Yes N/A

Estimated work: - OpenClaw with Accounting API tools: days - New conversation agent from scratch: weeks

Architecture

┌────────────────────────────────────────────────────┐
│                   OpenClaw Agent                    │
│                                                     │
│  ┌─────────────────────────────────────────────┐   │
│  │          Channel Adapters                    │   │
│  │  Discord (built in)                          │   │
│  │  Slack   (new adapter)                       │   │
│  │  Teams   (new adapter)                       │   │
│  │  Web     (new adapter — WebSocket/REST)      │   │
│  │  Email   (new adapter — webhook)             │   │
│  └──────────────────┬──────────────────────────┘   │
│                     │                               │
│  ┌──────────────────▼──────────────────────────┐   │
│  │          OpenClaw Core                       │   │
│  │  • Conversation loop (multi-turn)            │   │
│  │  • Memory (structured facts per user)        │   │
│  │  • Persona ("Regn" — friendly accountant)    │   │
│  │  • LLM (Gemini Flash / Claude)               │   │
│  │  • Tool router (function-calling)            │   │
│  └──────────────────┬──────────────────────────┘   │
│                     │                               │
│  ┌──────────────────▼──────────────────────────┐   │
│  │          Registered Tools                    │   │
│  │                                              │   │
│  │  accounting_solve(task, files)               │   │
│  │  accounting_query(question)                  │   │
│  │  accounting_monitor(check_type)              │   │
│  │  conversation_facts(user_id)                 │   │
│  │  rules_check(entity_type, data)              │   │
│  │                                              │   │
│  │  Each tool = HTTP call to Accounting API      │   │
│  └──────────────────┬──────────────────────────┘   │
└─────────────────────┼──────────────────────────────┘
                      │ HTTPS
┌─────────────────────▼──────────────────────────────┐
│              Accounting API (ASP.NET Core 10)       │
└─────────────────────────────────────────────────────┘

OpenClaw Configuration

{
  "agent": {
    "name": "Regn",
    "persona": "Du er Regn, en vennlig og profesjonell AI-regnskapsfører for norske bedrifter. Du svarer på norsk med mindre brukeren skriver på et annet språk.",
    "model": "gemini-2.5-flash",
    "temperature": 0.3,
    "maxTurns": 10
  },
  "channels": {
    "discord": {
      "enabled": true,
      "guildId": "...",
      "channelIds": ["regnskap", "expenses"]
    },
    "slack": {
      "enabled": true,
      "botToken": "xoxb-...",
      "appToken": "xapp-..."
    },
    "teams": {
      "enabled": false
    },
    "web": {
      "enabled": true,
      "websocketPath": "/ws/chat"
    },
    "email": {
      "enabled": true,
      "webhookPath": "/webhook/email"
    }
  },
  "tools": {
    "accountingApiBase": "https://api.ai-accountant.no",
    "auth": "service-jwt",
    "timeout": 30
  },
  "memory": {
    "type": "structured_facts",
    "maxFactsPerUser": 50,
    "storageVia": "accounting_api"
  }
}

OpenClaw Tools

The Accounting API endpoints are registered as OpenClaw tools. The LLM decides which tool to call based on the conversation. Every tool is an HTTP call to the Accounting API — OpenClaw never accesses the database directly.

Accounting Tools (call Accounting API)

accounting_solve

Execute an accounting task (invoice, payment, expense, etc.).

Parameter Type Required Description
task string Yes Natural-language task description
files string[] No File IDs of uploaded documents (receipts, invoices)

Returns: { status: "completed" | "error", summary: string, entities_created: object[], reversible: boolean }

Example:

{
  "task": "Register taxi receipt, employee Lars, dept Salg",
  "files": ["abc123"]
}

API endpoint: POST /solve


accounting_query

Read-only data query. No side effects, no confirmation required.

Parameter Type Required Description
question string Yes Natural-language question about accounting data

Returns: { data: object, summary: string }

Example:

{
  "question": "What is Kari Nordmann's remaining travel budget for 2026?"
}

API endpoint: POST /query


accounting_monitor

Run a compliance or monitoring check.

Parameter Type Required Description
check_type string Yes Type of check: overdue_bills, expense_compliance, month_end_status, payroll_anomalies, accounting_violations

Returns: { violations: object[], summary: string, severity: "info" | "warning" | "critical" }

Example:

{
  "check_type": "overdue_bills"
}

API endpoint: POST /monitor


rules_check

Validate data against company rules before executing an action.

Parameter Type Required Description
entity_type string Yes What is being validated: expense, invoice, payment, payroll
data object Yes The entity data to validate

Returns: { result: "PASS" | "WARN" | "FAIL", reasons: string[], suggested_account: number? }

Example:

{
  "entity_type": "expense",
  "data": {"category": "taxi", "amount": 1200, "has_receipt": false}
}

API endpoint: POST /rules/check


Memory Tools (call Accounting API)

get_user_facts

Get structured facts for a user (department, role, pending files, preferences).

Parameter Type Required Description
user_id string Yes User email address

Returns: { user: string, company: string, facts: object }

Example:

{
  "user_id": "lars@firma.no"
}

API endpoint: GET /conversation/facts?user_id={user_id}


update_user_facts

Update structured facts after a conversation (e.g., clear pending files, update last action).

Parameter Type Required Description
user_id string Yes User email address
facts object Yes Facts to update (merged with existing)

Returns: { updated: true }

Example:

{
  "user_id": "lars@firma.no",
  "facts": {"last_action": "taxi 350kr registered", "pending_files": []}
}

API endpoint: POST /conversation/facts


get_company_context

Get company information — departments, employees, chart of accounts, cached context.

Parameter Type Required Description
company_id string Yes Company identifier

Returns: { company: object, departments: object[], employees: object[], accounts: object[] }

Example:

{
  "company_id": "invotek-as"
}

API endpoint: GET /company/context?company_id={company_id}


Notification Tools

send_message

Send a message to a specific user on a specific channel.

Parameter Type Required Description
channel string Yes Channel type: slack, discord, email, web
user_id string Yes User identifier (email or channel-specific ID)
message string Yes Message content (markdown supported)

Returns: { sent: true, message_id: string }

API endpoint: POST /notifications/send


send_approval_request

Send an approval request to a manager or accountant.

Parameter Type Required Description
approver_id string Yes Email of the person who should approve
task string Yes Description of what needs approval
data object Yes The data being approved (expense, invoice, etc.)

Returns: { request_id: string, sent: true }

API endpoint: POST /notifications/approval


schedule_reminder

Schedule a future reminder for a user.

Parameter Type Required Description
user_id string Yes User email address
message string Yes Reminder message
when datetime Yes ISO 8601 datetime for when to send

Returns: { reminder_id: string, scheduled_for: datetime }

API endpoint: POST /notifications/reminder


File Tools

upload_file

Store a file for processing (receipt photo, PDF invoice, bank statement CSV).

Parameter Type Required Description
content bytes Yes File content (base64-encoded)
filename string Yes Original filename with extension

Returns: { file_id: string, mime_type: string, size_bytes: number }

API endpoint: POST /files/upload


get_file_text

Get OCR/extracted text from a previously uploaded file.

Parameter Type Required Description
file_id string Yes File ID from upload_file

Returns: { text: string, confidence: number, fields: object? }

API endpoint: GET /files/{file_id}/text


Tool Summary

Tool API Endpoint Purpose
accounting_solve POST /solve Execute accounting task
accounting_query POST /query Read-only data query
accounting_monitor POST /monitor Run compliance check
rules_check POST /rules/check Validate against company rules
get_user_facts GET /conversation/facts Get user's structured facts
update_user_facts POST /conversation/facts Update user's facts
get_company_context GET /company/context Get company info and cached data
send_message POST /notifications/send Send message to channel
send_approval_request POST /notifications/approval Request approval
schedule_reminder POST /notifications/reminder Schedule future reminder
upload_file POST /files/upload Store file for processing
get_file_text GET /files/{file_id}/text Get OCR/extracted text

The LLM sees these as functions. OpenClaw handles the HTTP serialization.

Conversation Flow — Step by Step

Example: "Kan du registrere taxikvitteringen jeg sendte i går?"

Turn 1: Message arrives via Slack adapter

  OpenClaw loads user facts:
  → tool_call: conversation_facts(user_id="lars@firma.no")
  → returns: {pending_files: [{yesterday, "taxi_receipt.jpg", id: "abc123"}]}

Turn 2: OpenClaw has context

  LLM thinks: "Found yesterday's receipt. Send to accounting."
  → tool_call: accounting_solve(
      task="Register taxi receipt, employee Lars, dept Salg",
      files=["abc123"]
    )
  → returns: {status: "completed", summary: "Taxi 350 kr, konto 7140"}

Turn 3: Update facts and reply

  → tool_call: conversation_facts(user_id="lars@firma.no",
      update={remove_pending: "abc123", last_action: "taxi 350kr registered"})
  → Slack reply: "Registrert! Taxi 350 kr på konto 7140, avd. Salg."

Example: Clarification needed

Turn 1: "Lag faktura til Nordvik"

  LLM thinks: "Need more info. Let me check if there are multiple Nordviks."
  → tool_call: accounting_query(question="Search customers named Nordvik")
  → returns: [{name:"Nordvik AS"}, {name:"Nordvik Shipping AS"}]

Turn 2: Ask user

  Slack reply: "Hvilken Nordvik?
   1. Nordvik AS (912345678)
   2. Nordvik Shipping AS (987654321)
   Hva gjelder fakturaen og hvor mye?"

Turn 3: "Nr 1, konsulentbistand 25000"

  → tool_call: accounting_solve(
      task="Invoice Nordvik AS (912345678), 25000kr, Konsulentbistand")
  → Slack reply: "Faktura #1234 opprettet. Skal jeg sende den?"

Turn 4: "Ja"

  → tool_call: accounting_solve(task="Send invoice #1234")
  → Slack reply: "Sendt!"

Memory: Structured Facts

OpenClaw stores structured facts per user, not raw chat logs:

{
  "user": "lars@firma.no",
  "company": "invotek-as",
  "facts": {
    "department": "Salg",
    "role": "Konsulent",
    "language": "no",
    "pending_files": [],
    "last_action": "taxi 350kr registered 2026-03-22",
    "waiting_for": null,
    "travel_budget_used": 12450,
    "travel_budget_total": 30000,
    "preferences": {
      "tone": "informal",
      "auto_send_invoices": false
    }
  }
}

Facts are updated after each conversation via the conversation_facts tool. They're stored in the Accounting API's database — OpenClaw never touches the DB directly.

Channel Adapters to Build

Channel Status Work
Discord Built in None
Slack New Slack Bolt SDK, event subscriptions, file upload
Teams New Bot Framework SDK, Teams manifest
Web New WebSocket endpoint, simple React chat widget
Email New Webhook receiver, MIME parser, reply via SMTP

MVP: Discord (free) + Slack (most companies use it). Teams and email in Phase 2.

Scaling

OpenClaw instances are stateless (memory stored via Accounting API). Run N instances behind a load balancer:

Slack  ─┐
Teams  ─┤
Discord─┼→ Load Balancer → OpenClaw (N instances) → Accounting API
Web    ─┤                                              ↓
Email  ─┘                                           Database