harnessgate

agent
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Warn
  • process.env — Environment variable access in examples/demo-telegram/src/main.ts
  • process.env — Environment variable access in examples/demo-web/src/main.ts
  • process.env — Environment variable access in examples/with-supabase/src/main.ts
Permissions Pass
  • Permissions — No dangerous permissions requested
Purpose
This tool acts as a lightweight bridge to connect AI agent runtimes (like Claude Managed Agents) to various messaging platforms (like Telegram or Slack) without running a competing local agent loop.

Security Assessment
The overall risk is Low. The codebase does not request any dangerous system permissions, execute arbitrary shell commands, or contain hardcoded secrets. The automated scan flagged the use of `process.env` in the demonstration files. This is actually a secure best practice, as it means the tool correctly relies on environment variables to handle API tokens and credentials rather than exposing them in the source code. The primary function inherently requires making external network requests to route messages between your messaging platform and the AI provider.

Quality Assessment
The project is released under the standard MIT license and the repository was updated very recently, indicating active development by the creator. However, it currently suffers from extremely low community visibility. With only 5 stars on GitHub, the tool has not been widely peer-reviewed or battle-tested by a larger audience. Furthermore, the README contains several leftover TODOs (such as unconfigured GitHub Actions links and missing banners), suggesting the project is still in its early, rough phases.

Verdict
Use with caution. The code itself appears safe and follows standard practices, but the lack of community adoption and early-stage documentation means it lacks the maturity and peer validation typically expected for production environments.
SUMMARY

Connect Claude Managed Agents or any harness runtime to any messaging platform.

README.md

HarnessGate

Connect any AI agent runtime to any messaging platform.

License CI Node TypeScript


Why HarnessGate?

Existing chatbot frameworks (AstrBot, LangBot, Botpress) run their own agent loop — they call LLM APIs, parse tool calls, execute tools locally, and manage context. When you connect them to a managed agent runtime like Claude Managed Agents, they have to bypass their entire infrastructure just to pipe messages through.

HarnessGate takes a different approach: no local agent loop. It's a pure bridge that delegates all intelligence to the provider runtime. The gateway just routes messages between platforms and the agent.

This means:

  • Claude Managed Agents features work out of the box (tool confirmation, custom tools, multi-agent threads, extended thinking)
  • Any future agent runtime plugs in with 4 methods
  • No competing agent loops, no bypassed infrastructure, no wasted abstractions
[Telegram] [Discord] [Slack] [WhatsApp] [Teams] [Web UI]
     |         |        |       |          |       |
     +----+----+----+---+-------+----------+------+
          |         |
     PlatformAdapter interface (per platform)
          |         |
          +----+----+
               |
          Bridge (orchestrator)
          SessionMap + StreamManager
               |
        Provider interface
               |
    +----------+----------+----------+
    | Claude   | HTTP     | Custom   |
    | Managed  | (any     | (npm pkg |
    | Agents   |  server) |  or file)|
    +----------+----------+----------+

Features

  • Provider-agnostic — Claude Managed Agents, any HTTP server, or bring your own
  • Platform adapters — Telegram, Discord, Slack, WhatsApp, Teams, Web UI
  • Multi-app — run multiple app instances per platform, each mapped to a different agent
  • Session management — automatic session creation, SQLite persistence, multi-turn conversations
  • Buffer-then-send — accumulates agent responses, sends as one message per turn
  • Auto-split — respects per-platform message length limits
  • Event passthrough — provider-specific events forwarded via bridge.onEvent() listeners

Quick Start

npm install @harnessgate/core @harnessgate/provider-claude @harnessgate/platform-web
import { Bridge } from "@harnessgate/core";
import { ClaudeProvider } from "@harnessgate/provider-claude";
import { WebAdapter } from "@harnessgate/platform-web";

const provider = new ClaudeProvider(process.env.ANTHROPIC_API_KEY!);
const bridge = new Bridge(provider, {
  provider: { type: "claude" },
  platforms: { web: { enabled: true, port: 3000 } },
});

// Route users to agents (agentId + environmentId from your DB)
bridge.setUserResolver(async (sender) => ({
  userId: sender.id,
  agentId: "agent_01XXXX",
  environmentId: "env_01XXXX",
}));

bridge.addPlatform(new WebAdapter());
await bridge.start();

See examples/demo-web/ for a minimal starter, examples/demo-telegram/ for a Telegram bot, or examples/with-supabase/ for a production starter with Supabase auth and session persistence.

Run an example from the repo

git clone https://github.com/your-org/harnessgate.git
cd harnessgate
pnpm install

cd examples/demo-telegram
cp .env.example .env
# Add ANTHROPIC_API_KEY in .env

# Fill in botToken, agentId, environmentId in src/main.ts

pnpm build
node --env-file=.env dist/main.js

Provider Setup

HarnessGate supports three ways to connect an agent runtime:

1. Claude Managed Agents

provider:
  type: claude
  apiKey: ${ANTHROPIC_API_KEY}
  agentId: agent_01XXXX
  environmentId: env_01XXXX

Connects to Claude Managed Agents. Full support for streaming, tool confirmation, custom tools, extended thinking, and multi-agent threads.

2. HTTP — any server

provider:
  type: http
  baseUrl: http://localhost:8080
  headers:
    Authorization: Bearer ${MY_TOKEN}

Connects to any HTTP server that implements these endpoints:

Endpoint Request Response
POST /sessions { systemPrompt? } { id: "session-123" }
POST /sessions/{id}/message { message: "hello", sessionId: "..." } 200 OK
GET /sessions/{id}/stream SSE stream data: {"type": "message", "text": "..."}
DELETE /sessions/{id} 200 OK

Your server can be written in any language. SSE events can use either format:

# HarnessGate-native format (recommended)
data: {"type": "message", "text": "Hello!"}
data: {"type": "status", "status": "idle"}

# Simple format (auto-detected)
data: {"response": "Hello!"}
data: {"text": "Hello!"}

Custom endpoint paths:

provider:
  type: http
  baseUrl: http://localhost:8080
  endpoints:
    createSession: POST /api/conversations
    sendMessage: POST /api/conversations/{sessionId}/chat
    stream: GET /api/conversations/{sessionId}/events
    destroySession: DELETE /api/conversations/{sessionId}

3. Custom provider — npm package or local file

provider:
  type: "@my-org/my-langgraph-provider"    # npm package
  apiKey: xxx

# or

provider:
  type: "./my-provider.js"                  # local file
  apiKey: xxx

The package/file must default-export a class implementing the Provider interface:

import type { Provider } from "@harnessgate/core";

export default class MyProvider implements Provider {
  readonly id = "my-provider";
  readonly capabilities = {
    interrupt: false,
    toolConfirmation: false,
    customTools: false,
    thinking: false,
  };

  constructor(config: Record<string, unknown>) {
    // config contains everything from the provider block in YAML
  }

  async createSession(opts) { /* ... */ }
  async sendMessage(sessionId, message) { /* ... */ }
  async *stream(sessionId, signal) { /* ... */ }
  async destroySession(sessionId) { /* ... */ }
}

User Auth

HarnessGate supports per-user access control and agent routing via a UserResolver:

bridge.setUserResolver(async (sender, platform, message) => {
  const user = await db.findUser(platform, sender.id);
  if (!user?.isActive) return null; // reject

  return {
    userId: user.id,
    agentId: user.agentId,         // which agent template
    environmentId: user.envId,     // which environment
    metadata: { plan: user.plan }, // passed to provider session
  };
});

Return null to reject. When no resolver is set, all users are allowed with their platform ID as the user ID.

Session Management

Each conversation context gets its own Claude session:

Context Session scope Example key
DM Per user telegram:direct:123:u:user99
Group/Channel Shared (all users) slack:group:ch1
Thread Per thread discord:thread:ch1:t:thread99

Sessions are persisted to SQLite by default (survives restarts). Configurable:

session:
  store: sqlite               # "sqlite" (default) or "memory"
  path: ./harnessgate.db      # SQLite file path (only for sqlite)

For custom stores (Supabase, Postgres, Redis), use the library mode:

bridge.setSessionStore({
  async get(key) { /* query your DB */ },
  async set(key, entry) { /* upsert */ },
  async delete(key) { /* delete */ },
  async touch(key) { /* update lastActiveAt */ },
});

See examples/with-supabase/main.ts for a complete example with Supabase for both auth and session persistence.

Multi-Bot / appId

Every platform adapter supports running multiple app instances simultaneously. Each app connects to the platform and receives a platform-assigned appId — an opaque identifier that flows through every InboundMessage and ChannelTarget.

// Add multiple Telegram bots at runtime
const supportBotId = await bridge.connect("telegram", { botToken: process.env.SUPPORT_BOT_TOKEN });
const salesBotId = await bridge.connect("telegram", { botToken: process.env.SALES_BOT_TOKEN });

// Route based on which bot received the message
bridge.setUserResolver(async (sender, platform, message) => {
  const agentId = await db.getAgentForBot(message.appId);
  return { userId: sender.id, agentId, environmentId: "env_01XXXX" };
});

appId per platform

Each platform exposes a different identifier as appId. The adapter reads it from the platform SDK after connecting:

Platform Source Example value
Telegram bot.botInfo.id "123456789"
Discord client.application.id "1098765432101234567"
Slack event.api_app_id "A0123456789"
WhatsApp WABA phone number ID "106540352267890"
Teams activity.recipient.id "28:abc123..."
Web N/A (single instance)

The appId is included in session keys as app:<appId>, so each bot maintains separate conversation sessions even in the same channel.

Platform Configuration

platforms:
  web:
    enabled: true
    port: 3000
  telegram:
    enabled: false
    botToken: ${TELEGRAM_BOT_TOKEN}
  discord:
    enabled: false
    token: ${DISCORD_BOT_TOKEN}
  slack:
    enabled: false
    botToken: ${SLACK_BOT_TOKEN}
    appToken: ${SLACK_APP_TOKEN}
  whatsapp:
    enabled: false
    phoneNumberId: ${WHATSAPP_PHONE_NUMBER_ID}
    accessToken: ${WHATSAPP_ACCESS_TOKEN}
    verifyToken: ${WHATSAPP_VERIFY_TOKEN}
    webhookPort: 8080
  teams:
    enabled: false
    appId: ${TEAMS_APP_ID}
    appPassword: ${TEAMS_APP_PASSWORD}
    webhookPort: 3978

logging:
  level: info

Environment variables are interpolated via ${VAR} syntax.

Platform Setup Guides

Detailed setup instructions for each platform, including SaaS multi-tenant distribution:

Platform Guide Library
Telegram docs/setup-telegram.md grammY
Discord docs/setup-discord.md discord.js
Slack docs/setup-slack.md @slack/bolt
WhatsApp docs/setup-whatsapp.md Cloud API (fetch)
Teams docs/setup-teams.md botbuilder
Web docs/setup-web.md Built-in HTTP

Project Structure

harnessgate/
├── src/
│   ├── index.ts                # Barrel exports
│   ├── bridge.ts               # Orchestrator
│   ├── session-map.ts          # Session persistence
│   ├── stream-manager.ts       # SSE stream lifecycle
│   ├── platforms/
│   │   ├── telegram-adapter.ts # grammY
│   │   ├── discord-adapter.ts  # discord.js
│   │   ├── slack-adapter.ts    # @slack/bolt
│   │   ├── whatsapp-adapter.ts # Cloud API (fetch)
│   │   ├── teams-adapter.ts    # Bot Framework SDK
│   │   └── web-adapter.ts      # Built-in HTTP + SSE
│   └── providers/
│       ├── claude-provider.ts  # Claude Managed Agents
│       └── http-provider.ts    # Generic HTTP
├── docs/                       # Platform setup guides
└── examples/                   # Starter projects

Extending HarnessGate

See CONTRIBUTING.md for step-by-step guides on adding platforms and providers.

Provider interface

interface Provider {
  readonly id: string;
  readonly capabilities: ProviderCapabilities;

  // Required — every provider
  createSession(opts: CreateSessionOpts): Promise<ProviderSession>;
  sendMessage(sessionId: string, message: MessagePayload): Promise<void>;
  stream(sessionId: string, signal: AbortSignal): AsyncIterable<ProviderEvent>;
  destroySession(sessionId: string): Promise<void>;

  // Optional — capability-gated
  interrupt?(sessionId: string): Promise<void>;
  confirmTool?(sessionId: string, toolUseId: string, approved: boolean): Promise<void>;
  submitToolResult?(sessionId: string, toolUseId: string, result: unknown): Promise<void>;
}

Platform interface

interface PlatformAdapter {
  readonly id: string;
  readonly capabilities: PlatformCapabilities;
  start(ctx: PlatformContext): Promise<void>;
  stop(): Promise<void>;
  send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
  sendTyping?(target: ChannelTarget): Promise<void>;
  connect?(credentials: Record<string, unknown>, ctx: PlatformContext): Promise<string>;
  disconnect?(appId: string): Promise<void>;
  activeConnections?(): string[];
}

Requirements

  • Node.js >= 22
  • pnpm

License

MIT

Reviews (0)

No results found