harnessgate
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
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.
Connect Claude Managed Agents or any harness runtime to any messaging platform.
HarnessGate
Connect any AI agent runtime to any messaging platform.
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" |
| 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 |
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)
Sign in to leave a review.
Leave a reviewNo results found