Skip to content

loewenthal-corp/gary

Repository files navigation

Gary

Gary is an open-source reference implementation of a generic, read-only Slack investigator backed by a long-running OpenCode web server. It defaults to cloning and inspecting this repository, making the example useful without access to private systems.

Slack --Socket Mode--> Go dispatcher --HTTP--> OpenCode server + web UI
                                               |-- shell/tools
                                               `-- /workspace/repos/gary

Each Slack thread maps to one OpenCode session. Session titles encode the Slack channel and thread timestamp, so the dispatcher recovers mappings after restart. Turns within a thread run one at a time, and duplicate Socket Mode deliveries (reconnects, overlapping event subscriptions) are dropped. Replies use Slack's native Markdown block, turns have a 30-minute timeout, and an optional public OpenCode URL lets Gary post a session link before work begins.

Architecture and scope

Kubernetes runs two containers in one pod from one image: OpenCode owns durable sessions and the self-checkout; the small Go bot connects over pod-local localhost. Docker Compose uses two services because containers do not share localhost, so the bot uses http://opencode:4096. Both topologies persist OpenCode data/config and repository checkouts.

Gary is instructed to investigate, not mutate. OpenCode's edit permission is denied and the shipped GitHub/Polylane skills are read-only. This is not a complete sandbox: the model still has shell access, which is powerful. Use isolated infrastructure and narrowly scoped, preferably read-only credentials.

Repository layout

  • main.go, internal/slackbot: health wait, Socket Mode dispatcher, thread/session recovery, and Slack rendering
  • internal/opencode: focused OpenCode HTTP client and opt-in live smoke test
  • opencode.json, AGENTS.md, bundled-skills: model configuration, persona, and investigation guidance
  • Dockerfile, entrypoint.sh, docker-compose.yml: reproducible image and local two-service topology
  • kustomize/gary: one-pod/two-container base with dev and prod examples
  • Tiltfile, Taskfile.yml, .github/workflows: development and CI

Slack app setup

  1. Create a Slack app and enable Socket Mode.
  2. Add bot scopes app_mentions:read, chat:write, channels:history, groups:history, im:history, and reactions:write.
  3. Subscribe to app_mention, message.channels, message.groups, and message.im.
  4. Create an app-level token with connections:write, install the app, and invite it to desired channels.
  5. Put the bot token (xoxb-...) and app token (xapp-...) in .env.

Gary responds to DMs, mentions, and follow-ups in threads it already owns (including replies sent with "also send to channel"). It does not listen indiscriminately to every channel message. Mentions are handled from either the app_mention or message event, so overlapping subscriptions are safe and mention-only setups still work.

Provider setup

Copy .env.example to .env. Set OPENCODE_MODEL to an OpenCode model identifier such as openai/gpt-5, then supply the provider's native environment variable (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.). opencode.json uses official {env:OPENCODE_MODEL} substitution and does not define a custom provider.

Optional settings include OPENCODE_PUBLIC_URL, a scoped GH_TOKEN for private forks or GitHub queries, and Polylane credentials. The public self-checkout does not require a GitHub token.

Docker Compose quickstart

cp .env.example .env
# replace the dummy Slack/provider values
docker compose up --build

The opencode service runs the entrypoint and opencode web; the bot service bypasses that entrypoint and runs gary-bot. The image build requires no secret.

Development

Prerequisites: Go 1.26, Task, kustomize, and optionally yamlfmt, Docker, and Tilt.

go mod tidy
task do                 # format, build/vet, test, manifest rendering
task container:validate # optional Docker build

Unit tests are hermetic — they run the real client and dispatcher against in-process fake Slack and OpenCode servers, with the race detector on. The OpenCode smoke test is skipped unless OPENCODE_URL points to a live server. For Tilt, create an uncommitted .env; the Tiltfile generates gary-secrets locally and applies the dev overlay.

Skills and tools

The runtime ships Bash/core Unix tools, git, ripgrep, jq, curl, Python/uv, GitHub CLI, and Polylane CLI. Bundled skills cover the single local self-checkout, generic read-only GitHub inspection, and generic read-only Polylane investigation. Override GARY_GITHUB_REPOSITORY, branch, destination/root, or refresh interval to inspect a fork.

Deployment and publishing

The kustomize base uses one replica, Recreate, a PVC, a ClusterIP Service for the web UI, and an optional gary-secrets Secret. The pod does not mount a serviceaccount token and containers drop all capabilities, since the model has shell access inside them. Set the image and provide Slack/provider environment values through your secret manager. Keep one replica because Socket Mode ownership and in-memory thread mappings are not distributed.

CI checks formatting, build, vet, tests, and both overlays. The container workflow builds pull requests without pushing; pushes to main and v* tags publish to ghcr.io/${owner}/gary using GitHub's built-in token.

Limitations

  • Read-only behavior depends on instructions, permissions, credential scope, and runtime isolation; shell access remains a security boundary.
  • Session recovery depends on OpenCode's session title/list APIs and the pinned OpenCode version.
  • Gary has no private SaaS, database, cluster, or company-specific integrations in this reference.
  • The default checkout is shallow and periodically refreshed from one configured branch.

See CONTRIBUTING.md, SECURITY.md, and the MIT license.

About

OpenCode-powered Slack bot with a Golang dispatcher.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages