Gateway

The Gateway is the persistent process that keeps OpenVesper running. Hub-and-spoke architecture: every channel routes through one place, with shared sessions, proactive heartbeats, command queue, compaction, routing, and delegation.

Why a persistent process?

  • One session shared across CLI, Telegram, VSCode (channel docking)
  • Heartbeats run on schedule โ€” agents message you proactively
  • Steer in-flight runs by sending follow-up messages
  • Compact long conversations automatically
  • Async API for long-running tasks (runId + wait/abort)
  • WebSocket transport for real-time streaming
  • OAuth flows handled locally with PKCE

Starting the gateway

Foreground (logs to your terminal):

vesper gateway start

Detached background:

vesper gateway start --detach
vesper gateway status
vesper gateway logs --lines 100
vesper gateway stop

Channel commands

Any message starting with / is intercepted before reaching the LLM:

CommandEffect
/newStart a fresh session
/resetClear current session messages
/statusShow session info, running run, queue state
/agent <mode>Switch active agent
/queue <mode>Set queue mode: steer / followup / collect / default
/compact [hint]Summarize old messages to free context
/stopAbort the current run
/helpList available commands

Queue modes (handling mid-run messages)

What happens when you send a new message while the agent is busy? Three modes:

  • steer (default) โ€” inject into the active run after current tool calls, before the next LLM call. Use this for "wait, actually do X instead".
  • followup โ€” queue the message, deliver as a new turn after current ends. Debounce 1s by default.
  • collect โ€” batch multiple queued messages and deliver as one structured prompt.

Async API

For long-running tasks, fire-and-forget then check back:

# Returns immediately with { runId, acceptedAt }
curl -X POST http://127.0.0.1:18789/agent/async \
  -d '{"sessionKey":"u1", "message":"analyze 100 tokens"}'

# Later โ€” block until done (timeout 5 min default)
curl -X POST http://127.0.0.1:18789/agent/run/r_xyz/wait

# Or cancel
curl -X POST http://127.0.0.1:18789/agent/run/r_xyz/abort

Compaction

When the context window fills up, OpenVesper compacts the conversation: older messages are summarized into a single system message, recent messages kept verbatim. Trigger manually with /compact or via API:

# Check token usage
curl http://127.0.0.1:18789/sessions/u1/tokens

# Compact (keep last 10 messages verbatim)
curl -X POST http://127.0.0.1:18789/sessions/u1/compact \
  -d '{"keepRecent": 10}'

Multi-agent routing

The router examines a message and picks the best specialist agent:

curl -X POST http://127.0.0.1:18789/agent/route \
  -d '{"message":"check this Solana token for rugs"}'
# โ†’ { "mode": "bags-hunter", "score": 12, "reason": "matched: solana, token, rug" }

Delegation

One agent can sub-query another without changing your active agent:

curl -X POST http://127.0.0.1:18789/agent/delegate \
  -d '{
    "parentSessionKey": "u1",
    "delegateAgent": "code-reviewer",
    "query": "review this snippet for security issues"
  }'

Or hand off the entire session:

curl -X POST http://127.0.0.1:18789/agent/handoff \
  -d '{"sessionKey":"u1", "newAgent":"defi-strategist"}'

OAuth (local PKCE flow)

Plugins that need Gmail, Calendar, GitHub access can authorize via OAuth. The entire flow runs on your machine โ€” tokens never leave it.

# Authorize Google (Gmail + Calendar)
vesper oauth login google \
  --client-id YOUR_GOOGLE_CLIENT_ID \
  --client-secret YOUR_SECRET

# A URL prints โ€” open it, authorize, browser redirects to localhost:53174,
# tokens save to ~/.openvesper/tokens/google.json (mode 0600).

# List authorized providers
vesper oauth list

# Revoke
vesper oauth logout google

Built-in provider templates: google, github, slack. You bring your own Client ID โ€” register an OAuth app with the provider.

Heartbeat daemon

Scans .agents/ for HEARTBEAT.md files with enabled: true. On schedule, the gateway wakes each agent with its checklist. If the agent has work, it runs tools and returns a reply (delivered via the configured channel). If not, it returns HEARTBEAT_OK โ€” gateway silently suppresses.

Endpoints

Bound to 127.0.0.1:18789 by default. Never exposed publicly.

Agent

MethodPathPurpose
POST/agentSync run, returns full reply
POST/agent/asyncAsync run, returns runId
POST/agent/streamSSE streaming reply
GET/agent/run/:runIdRun status
POST/agent/run/:runId/waitBlock until complete
POST/agent/run/:runId/abortCancel
POST/agent/routeRouting decision
POST/agent/delegateSub-query another agent
POST/agent/handoffTransfer session

Sessions

MethodPath
GET/sessions
GET/sessions/:key
POST/sessions/:key/reset
POST/sessions/:key/agent
POST/sessions/:key/compact
GET/sessions/:key/tokens
GET/sessions/:key/queue
POST/sessions/:key/queue/mode

OAuth & Heartbeat

MethodPath
POST/oauth/start
GET/oauth/tokens
GET/oauth/tokens/:provider
DELETE/oauth/tokens/:provider
GET/heartbeat/status
POST/heartbeat/reload
GET/lanes
GET/health
WS/ws

Security

  • Loopback bind default. Gateway listens on 127.0.0.1 only.
  • No remote access without explicit setup. Use Tailscale, SSH tunnel, or Cloudflare Tunnel.
  • Session files mode 0600. Only your user can read them.
  • OAuth tokens mode 0600. Stored at ~/.openvesper/tokens/.
  • Tool permissions still enforced. Mutating tools audit or prompt.
  • Zero data retention. No OpenVesper servers. Nothing leaves your machine.

Hooks (extensibility)

8 lifecycle events: agent:bootstrap, agent:tool-call, agent:tool-result, agent:complete, agent:error, agent:queued, command:new, command:reset.

import { hooks } from "@openvesper/gateway/hooks";

hooks.register("agent:bootstrap", async (ctx) => {
  ctx.systemPrompt += "\n## Local time\n" + new Date().toISOString();
  return ctx;
});

What's next?