momo

module
v0.0.0-...-f6022ad Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT

README ΒΆ

momo

momo connects your channels to AI agents. Contacts message your business on WhatsApp, Telegram or Facebook Messenger, or a program speaks to momo directly over ACP; momo receives every message, in both directions, and acts on it.

You run momo the way you run nginx or Caddy: one binary, one configuration file, logs on stdout, restart to apply a change.

Install

momo ships as a single static binary. Pick one:

  • Prebuilt binary (WIP): download the binary for your platform from GitHub Releases and place it on your path.

  • Build from source (needs Go 1.26 or newer):

    git clone https://github.com/8monkey-ai/momo && cd momo
    go build -o momo ./cmd/momo
    

    Copy the binary somewhere on your path, for example /usr/local/bin/momo.

Start

momo -config /etc/momo/momo.yaml

-config defaults to /etc/momo/momo.yaml, so with the file in that location momo alone is enough.

momo writes its log to stdout. At startup it reports every channel it brought up and the HTTP paths that channel serves:

level=INFO msg="channel ready" channel=acp paths="[/v1/acp]"
level=INFO msg="channel ready" channel=respondio paths="[/respondio/received /respondio/sent]"
level=INFO msg="πŸ’ momo listening" address=:8080 health=/healthz max_connections=1024

If the configuration file is missing, unreadable or incomplete, momo reports the reason and exits without serving.

To stop or restart momo, send it SIGTERM (or Ctrl-C). It stops accepting new requests, finishes the ones already in progress, and then exits, so a restart or redeploy does not cut off a webhook delivery already under way.

(WIP) Handling that outlives the response is drained as well, so no message momo has already acknowledged is lost to a restart. It lands with the agent harness.

Health

GET /healthz answers 200 ok while momo is running. Point your uptime monitor at it.

Configure

Everything momo does is set in the configuration file. No change to momo requires a rebuild.

# Address momo listens on. Default: ":8080"
listen: ":8080"
# Connections momo serves at once. Past this number a new connection waits until
# a slot opens, so an ACP client holding a stream open for hours counts against
# it. Default: 1024
max_connections: 1024
# How long a request may take to send its headers. Default: "10s"
read_header_timeout: "10s"
# How long a request may take to send its body. An ACP stream is not affected,
# because the deadline is cleared once momo starts answering. Default: "30s"
read_timeout: "30s"
# How long an idle keep-alive connection is kept. Default: "2m"
idle_timeout: "2m"
# How long a shutdown waits for in-flight requests before giving up. Default: "20s"
shutdown_timeout: "20s"

channels:
  respondio:
    # Signing key of the webhook that fires on incoming messages. Required.
    received_secret: "paste the message.received signing key"
    # Signing key of the webhook that fires on outgoing messages. Required.
    sent_secret: "paste the message.sent signing key"
    # API token momo sends replies with. Required.
    api_token: "paste the respond.io API access token"
    # Base URL of the respond.io API. Default: "https://api.respond.io/v2"
    api_url: "https://api.respond.io/v2"
    # Paths momo serves the two webhooks on.
    # Defaults: "/respondio/received" and "/respondio/sent"
    received_path: "/respondio/received"
    sent_path: "/respondio/sent"

  acp:
    # Bearer token every ACP request must present. Required.
    token: "a long random string you generate"
    # Path momo serves the ACP endpoint on. Default: "/v1/acp"
    path: "/v1/acp"
    # How long a connection nobody is listening to is kept before momo drops it.
    # Must be positive. Default: "5m"
    connection_grace: "5m"

Each channel requires only its credentials: the two signing keys and the API token for respond.io, the token for ACP. Every other setting has a default, so the shortest working file for respond.io alone is:

channels:
  respondio:
    received_secret: "paste the message.received signing key"
    sent_secret: "paste the message.sent signing key"
    api_token: "paste the respond.io API access token"

Leave out both channel blocks and momo starts with no channel, serving only /healthz.

The keys and the ACP token are secrets. Keep the file readable only by the user momo runs as:

chmod 600 /etc/momo/momo.yaml

Restart momo to apply any change to the file.

Connect respond.io

momo must be reachable from the internet. Put it behind a reverse proxy that terminates TLS and forwards to momo's listen address: the webhook payloads carry contact messages, so the connection respond.io makes should be HTTPS.

In respond.io, go to Workspace Settings β†’ Integrations β†’ Webhooks and click Connect twice, once for each webhook.

1. Incoming messages β€” respond.io labels this event New Incoming Message, and sends it as message.received

  • URL: https://your-domain.example/respondio/received

2. Outgoing messages β€” labelled New Outgoing Message and sent as message.sent; these are replies from the workspace, whether by a human operator or, later, by an agent

  • URL: https://your-domain.example/respondio/sent

Each webhook gets its own signing key, shown by respond.io on the webhook's page when you create it. Copy the key from the incoming-message webhook into received_secret and the key from the outgoing-message webhook into sent_secret, then restart momo.

momo verifies the X-Webhook-Signature header on every delivery and rejects a request whose signature does not match that webhook's key with 401. If your log shows 401 for deliveries respond.io says it sent, the two keys are swapped or one was copied incompletely.

Both webhooks may also deliver event types momo does not act on, such as contact updates. momo accepts and ignores them, so respond.io does not retry them and new event types respond.io adds later need no upgrade on your side.

momo answers an incoming message with one call to respond.io's send-a-message API, POST {api_url}/contact/id:{contact}/message, authenticated with api_token. Outgoing messages (message.sent) are recorded only: they include momo's own replies, and answering them would make momo talk to itself.

Connect an ACP client

The acp block turns on a second channel: an ACP endpoint momo answers as the agent side. Any program that speaks ACP over HTTP can send momo prompts, and each prompt is a message from a contact.

Add the block with a token, restart momo, and hand whoever runs the client two things:

  • the URL, https://your-domain.example followed by the configured path (default /v1/acp)
  • the token, which the client sends as Authorization: Bearer <token> on every request

Requests without the token, or with the wrong one, are answered 401 and get no further. Put momo behind a reverse proxy that terminates TLS: prompts carry contact messages, and the token travels with every request.

The client connects like this:

  1. POST an initialize request. The response carries a connection id, in the body and in the Acp-Connection-Id header, which every later request must present.
  2. GET the endpoint with Accept: text/event-stream and that connection id, to open the connection-scoped stream. Answers arrive here, not in the response to the POST that asked for them.
  3. POST session/new. The POST is answered 202 with an empty body, and the session id arrives on the connection-scoped stream.
  4. GET the endpoint again with both the connection id and Acp-Session-Id, to open that session's stream.
  5. POST session/prompt with both headers. The prompt reaches momo, and the reply and the response arrive on that session's stream.

momo answers a prompt on the session's own stream: one session/update notification per content block, each carrying a single agent_message_chunk, and then the session/prompt response with stopReason: "end_turn", always after the content it is ending.

DELETE the endpoint with the connection id to finish: momo releases the connection's sessions and closes its streams. A connection nobody is listening to is dropped on its own after connection_grace.

A prompt may carry any ACP content block β€” text, images, audio, resource links, embedded resources β€” and momo carries the blocks to the core exactly as they arrived. A prompt with no blocks, or a block with no type, is answered invalid params. A method momo does not implement is answered method not found and the connection stays usable.

Sessions live in memory only. Restarting momo loses every connection and session, and clients have to initialize again.

Connect an agent

(WIP) The agent harness β€” running an ACP agent per contact β€” lands in a follow-up release. Until then, momo echoes every message it receives back on the channel it came from.

Directories ΒΆ

Path Synopsis
cmd
momo command
Command momo runs the momo server.
Command momo runs the momo server.
internal
channel
Package channel is the contract a messaging channel plugs into: it declares itself at startup, decodes its own settings, and contributes the HTTP routes it needs, if any.
Package channel is the contract a messaging channel plugs into: it declares itself at startup, decodes its own settings, and contributes the HTTP routes it needs, if any.
channel/acp
Package acp serves the Agent Client Protocol over streamable HTTP, with momo as the agent: the peer that connects is the client, and a prompt it sends is a message from a contact.
Package acp serves the Agent Client Protocol over streamable HTTP, with momo as the agent: the peer that connects is the client, and a prompt it sends is a message from a contact.
channel/respondio
Package respondio receives the webhook events respond.io pushes to momo and answers them over respond.io's REST API.
Package respondio receives the webhook events respond.io pushes to momo and answers them over respond.io's REST API.
config
Package config reads momo's configuration file.
Package config reads momo's configuration file.
core
Package core holds momo's view of a conversation: the messages channels deliver and the action each direction leads to.
Package core holds momo's view of a conversation: the messages channels deliver and the action each direction leads to.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL