Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Keyper Concorde Agent

Prototype. A Concorde deployment that lets Shutter keyper operators talk to one agent over Telegram, with every message recorded per user in the gateway. Runs on one droplet. No operator is connected yet; the only user is the person testing it.

Built from examples/00_minimal of shutter-network/concorde at commit e3f746e, with:

  • telegram-channel/, a Channel for the Telegram Bot API, written against the Messenger's Channel contract and modelled on the framework's Nostr Channel. It replaces the HTTP Channel. Meant to move into Concorde as @shutter-network/concorde/telegram-channel once it has run.
  • main.ts forwards whichever provider keys are set instead of requiring the Anthropic one, mounts models.json into the agent container, and attaches the tester's chat to the seeded user at boot.
  • settings.json and models.json point pi at an OpenAI-compatible endpoint of your choice. Both are per machine, copied from their .example files and never committed.
  • AGENTS.md adds the Grafana dashboard instructions.

The framework dependency

@shutter-network/concorde is not on npm yet (issue 01 on the Concorde review). Until it is, the image installs it from a tarball in vendor/ that is never committed: .gitignore excludes it, and each machine that builds the image produces its own.

cd path/to/concorde && git checkout e3f746e && npm ci && npm run build && npm pack
mv shutter-network-concorde-0.1.0.tgz path/to/this/repo/vendor/

When the package is published, package.json goes back to "^0.1.0" and vendor/ is deleted.

Before the first run

  1. Put the tarball in vendor/ as above.
  2. cp .env.example .env and fill in LOCAL_API_KEY, USER_PASSWORD, TG_TOKEN, TG_CHAT.
  3. cp models.json.example models.json and cp settings.json.example settings.json, then put the endpoint URL and the model id in both. The key stays in .env; models.json refers to it as $LOCAL_API_KEY.
  4. If another process polls the same bot token, stop it first. Telegram hands each update to one poller, and the Channel logs a 409 until the other one is gone.

Run

docker compose up -d --build
docker compose logs -f gateway

Expected at boot: user <id> logs in with the password ..., then telegram chat <TG_CHAT> now belongs to user <id>. Message the bot; the reply comes back in the same chat.

Users

A message from a chat that belongs to no user gets one answer, its own chat id, and nothing is recorded. That is how a new operator learns the id to send the team. admin.ts does the rest, against the database, with or without the gateway running:

docker compose run --rm --no-deps gateway node admin.ts list
docker compose run --rm --no-deps gateway node admin.ts add <name> <chatId>
docker compose run --rm --no-deps gateway node admin.ts detach <userId>

add creates the user, names them and attaches the chat in one transaction. detach removes the chat only: the framework removes no user, and the message log stays. A group chat works as a user too; its id is negative.

Tests

telegram-channel/telegram-channel.test.ts runs the Channel against a real PostgreSQL and a fake Bot API on localhost (fake-bot-api.ts): recording chats, inbound texts and redelivery, unknown chats, outbound replies, refusals, transient failures, splitting, a reply queued while stopped, a 409 from a second poller, stop and start. The helpers in test-support.ts mirror the framework's own.

Against the stack's database, without starting the gateway:

docker compose run --rm test

Or anywhere with Node 24 and a PostgreSQL to create databases on:

DATABASE_URL=postgres://user:password@host:5432/postgres npm test

Operating notes

  • The model is chosen in settings.json and described in models.json. After changing the model, delete state/agent/sessions/*: pi sessions pin the model they started with. ./switch-model.sh <model-id> does both in one step.

  • A run that fails tells the sender "I could not process your last message" through the handler's post phase, and nothing more. The reason is in docker compose logs gateway, as the error on the Run finished line, and in the agent container's own log while it runs: docker logs $(docker ps -q --filter ancestor=keyper-concorde-agent:0.83.0).

  • If the model endpoint is down, every message looks like the agent is offline. Test the endpoint directly with a one-word chat completion before debugging anything here, with BASE_URL, API_KEY and MODEL set to the values from models.json and settings.json:

    curl -sS -m 60 -w '\nHTTP %{http_code} in %{time_total}s\n' \
      "$BASE_URL/chat/completions" \
      -H "authorization: Bearer $API_KEY" \
      -H "content-type: application/json" \
      -d "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply with the single word OK.\"}],\"max_tokens\":50}"

    A healthy endpoint answers within seconds with HTTP 200. A timeout or HTTP 000 means the box behind the proxy is down, and nothing in this repo can fix that.

  • docker compose down keeps the database. down -v deletes it, including the user, its chat and the whole message log.

Files

file role
main.ts the deployment: runtime, components, handler, seeding
telegram-channel/ the Telegram Channel: schema, chats, outbox, Bot API, channel
compose.yml gateway, migrate, postgres, agent image
AGENTS.md the agent's instructions, mounted read-only
settings.json, models.json pi's model configuration, mounted read-only
schema.ts, drizzle.config.ts tables the deployment applies with drizzle-kit

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages