Deploying an AI Assistant Your Whole Team Can Use

Most teams interact with AI through individual ChatGPT or Claude subscriptions. Everyone’s in their own silo — no shared context, no connection to internal data, no way to automate anything. I wanted something better: a single AI assistant the whole team could talk to through Slack, with access to our actual data.

OpenClaw is an open-source personal AI assistant you can self-host. It supports Slack as a channel, runs as a systemd service, and has a plugin system for tools and skills. I deployed it on a single EC2 instance and it’s been surprisingly useful.

Here’s what I did and what it unlocked.

The setup

The infrastructure is intentionally simple. One t3.xlarge EC2 instance running Ubuntu, Node.js, and OpenClaw. That’s it.

Slack (Socket Mode — outbound WebSocket, no inbound ports)
      |
      v
+-----------------------------+
|   EC2: Ubuntu 24.04 LTS    |
|                             |
|   OpenClaw Gateway          |
|   (loopback only)           |
|                             |
|   Tailscale for SSH access  |
+-----------------------------+
      |
      v
  Model API (Claude, GPT, etc.)

OpenClaw uses Slack’s Socket Mode, which means the connection is outbound only — the instance opens a WebSocket to Slack. No inbound ports, no webhook URLs, no load balancer. The security group only needs SSH as a fallback.

Installation is one command:

sudo npm install -g openclaw@latest
openclaw onboard

The onboard wizard walks you through model provider selection (I use Anthropic’s API), Slack app creation, and systemd daemon setup. After that it just runs.

Connecting to Slack

The Slack app setup is the most tedious part, but it’s a one-time thing. You create a Slack app, enable Socket Mode, add bot token scopes (chat:write, im:history, im:read, im:write, app_mentions:read), subscribe to message.im and app_mention events, and install it to your workspace.

OpenClaw has a pairing system — when someone first DMs the bot, they get a pairing code. An admin approves it on the server:

openclaw pairing approve slack <CODE>

This is a good security model. Unknown users are blocked by default. You explicitly approve each person.

Making it actually secure

Running an AI assistant that your team talks to through Slack requires thinking about security. The default OpenClaw config is permissive — it can run shell commands, access the filesystem, and more. For a shared team deployment, you want to lock it down.

Here’s what I configured:

# No elevated/admin commands
openclaw config set tools.elevated.enabled false

# File access restricted to workspace only
openclaw config set tools.fs.workspaceOnly true

# Block dangerous control-plane tools
openclaw config set tools.deny '["gateway","sessions_spawn"]'

# Isolate each user's DM session
openclaw config set session.dmScope per-channel-peer

That last one is important — per-channel-peer means each user’s conversation is isolated. Person A can’t see Person B’s chat history or context. Without this, sessions could leak across users.

Run openclaw security audit --deep after configuring. It flags anything that looks risky.

Remote access with Tailscale

Instead of managing SSH keys and security group IP allowlists, I set up Tailscale. It creates a private mesh network — team members install Tailscale, join the organization’s tailnet, and can SSH into the instance from anywhere:

# On the server
sudo tailscale up
sudo tailscale set --ssh

# From any team member's laptop (no key file needed)
ssh ubuntu@100.x.x.x

Tailscale SSH authenticates by identity, not keys. New team members install Tailscale, sign in with their work account, and they can SSH in. No .pem files, no authorized_keys management.

The free tier covers up to 3 users and 100 devices, which is enough for most small teams.

The interesting part: connecting it to your data

A chatbot that can only answer general questions isn’t that useful. What makes this setup valuable is giving it readonly access to internal data sources.

We have Elasticsearch indexes that ingest real-time data — sensor readings, pipeline outputs, processed datasets. I gave OpenClaw readonly access to query these indexes. Now anyone on the team can ask questions in natural language:

“What’s the latest data from the northeast pipeline?” “Show me all records where anomaly_score > 0.8 in the last 24 hours” “Compare this week’s throughput to last week”

The bot translates these into Elasticsearch queries, runs them, and returns formatted results — all in a Slack DM. No one needs to know the query DSL or have direct cluster access.

The key here is readonly. The Elasticsearch credentials I gave OpenClaw only have read permissions on specific indexes. It can’t modify data, delete indexes, or access anything outside the designated indexes. Defense in depth — even if someone crafts a clever prompt, the credentials physically can’t do damage.

The Elasticsearch skill

The pattern is: create a readonly credential, write a skill that uses it, register it with OpenClaw. For Elasticsearch, I started with an API key scoped to the indexes we wanted to expose:

{
  "name": "openclaw-readonly",
  "role_descriptors": {
    "readonly_role": {
      "indices": [
        {
          "names": ["your-index-*"],
          "privileges": ["read", "view_index_metadata"]
        }
      ]
    }
  }
}

Then I built a custom OpenClaw skill — two files in the skills directory:

skills/elasticsearch/
├── SKILL.md          # documents capabilities and usage for the model
└── elasticsearch.js  # the implementation

SKILL.md tells the model what the skill can do: search one or multiple indexes, full-text queries, term/range filters, aggregations, scroll for large result sets. Actions are read-only — search, count, get, msearch, and indices. No write operations.

Credentials come from environment variables on the OpenClaw service:

ES_HOST=https://your-cluster.es.amazonaws.com:443
ES_API_KEY=your_readonly_api_key_here

The implementation script handles auth and query execution. The core of it:

const ES_URL = process.env.ES_HOST || process.env.ELASTICSEARCH_URL || '';
const ES_API_KEY = process.env.ES_API_KEY || process.env.ELASTICSEARCH_API_KEY || '';

function makeRequest(path, method = 'GET', body = null) {
  const url = new URL(path, ES_URL);
  const headers = { 'Content-Type': 'application/json' };

  if (ES_API_KEY) {
    headers['Authorization'] = `ApiKey ${ES_API_KEY}`;
  }

  // ... HTTP request to ES cluster ...
}

async function search(args) {
  const index = args.index || '_all';
  const body = {
    query: args.query || { match_all: {} },
    size: args.size || 10,
    aggs: args.aggs,
  };

  const result = await makeRequest(`/${index}/_search`, 'POST', body);
  // format and return hits + aggregations
  return result;
}

When someone asks “Show me all records where anomaly_score > 0.8 in the last 24 hours”, the model translates that into a skill call:

tool: elasticsearch
action: search
index: pipeline-readings-*
query:
  bool:
    filter:
      - range:
          anomaly_score:
            gt: 0.8
      - range:
          "@timestamp":
            gte: now-24h
size: 50

The script runs the query, formats the results, and the bot returns them in Slack. No one on the team needs Kibana access or query DSL knowledge.

This works with any data source — Postgres (read-only user), BigQuery (viewer role), internal APIs (read-only API key). The pattern is always: minimal permissions, specific scope, read-only actions only in the skill implementation.

Cron jobs and alerting

This is where it gets genuinely useful beyond chat. OpenClaw supports cron-style scheduling, so you can set up automated checks that alert through Slack.

The idea: define conditions on your data, check them on a schedule, and notify specific people or channels when something triggers.

Examples:

Each user can set up their own alerts through the Slack chat interface. The bot remembers the cron schedule and runs checks accordingly. It’s like a lightweight alerting system that anyone can configure without touching code or infrastructure.

This is the kind of thing that would normally require setting up something like ElastAlert or writing custom Lambda functions. Having it accessible through a chat interface means non-engineers on the team can set up their own alerts.

What this actually costs

A t3.xlarge (4 vCPU, 16 GB RAM) runs about $120/month on-demand, less with reserved pricing. The main variable cost is the model API — depends on how chatty your team is. For a team of 10-15 with moderate usage, we’re looking at maybe $50-100/month in API calls.

Total: roughly $150-200/month for an AI assistant your entire team can use, connected to your actual data, with no per-seat licensing.

Compare that to $20-30/person/month for individual ChatGPT or Claude subscriptions that can’t access your internal data and don’t talk to each other. The math works out fast.

What I’d do differently

A few things I learned:

The bigger picture

The pattern here isn’t specific to OpenClaw or Elasticsearch. It’s: deploy an AI assistant your team can reach through tools they already use, give it scoped access to your data, and let people ask questions in natural language.

The assistant becomes a shared interface to your company’s data. Instead of “let me write a query” or “can someone pull this data for me,” people just ask the bot. The data team spends less time fielding ad-hoc requests. Non-technical team members get self-service access to information they’d otherwise need to ask for.

And the cron/alerting layer means it’s not just reactive — it’s actively watching your data and telling people when something needs attention.

The infrastructure is one EC2 instance. The security model is straightforward. The whole thing took a weekend to set up. If your team is paying for individual AI subscriptions and still can’t ask questions about your own data, this is worth trying.


References

← Back to blog