DEV Community

Cover image for Building Context Keeper on Switch
Kashif Nazir
Kashif Nazir

Posted on Edited on Originally published at kashifnazir.com

Building Context Keeper on Switch

Someone had just come back after a few months, and they kept re-raising ideas I'd already put into the project. It's happening again right now with new people joining a project I've been working on for a while. "Wouldn't it be great if we had automated testing in there?" Well, yes, we already have that.

Context Keeper flow: an idea in Slack or Discord, a check of the room's record, then a cited reply if already decided; quiet on chatter.

Visit Switch AI · Watch the demo · Read the setup guide

The cost is the other people having to re-explain to that person over and over again, and so when the Flip the Switch hackathon opened I used it as the excuse to build the fix. It's easier when an automated system tells you the information as a fact, a statement, without people attaching emotion to it and both sides getting frustrated.

With Context Keeper, we track what's going on in the Slack chat, Discord, or whatever messaging app you want to use. When someone raises an idea the room already decided, it replies in the thread with the source quoted, and it stays quiet on chatter and doesn't invent decisions.

The team in the screenshots (Ledgerly, with Rowan, Priya and Sam) is made up, and so is their chat history. The code, the eval and every number below are on GitHub. You can watch the demo on YouTube, try the agent yourself in a Discord demo channel, and see what it's doing right now on its live status page.

The first blocker: no registration key

The first real blocker once the window opened was that the Console's built-in server can't register your own agent. Context Keeper is a custom agent, and Switch registers one of those with a registration key, which that server couldn't create. Console versus the Gateway confused me at the start, and I had to look online to understand it. Phil, Switch's developer advocate, got back to me fast on Discord and confirmed the path I was already going down.

The fix was to run Switch's published server in Docker, with the Gateway on, and connect Console to it with "Connect to an existing server". The first start silently failed because the database names were blank, but after that we were able to get in.

I could connect it through Console, but I still needed the full admin interface for some of the more advanced tasks, like letting anyone tag my custom agent. For a normal person doing this through the GUI only, just using Console would probably be fine.

Switch Console connected to my self-hosted server, with Slack and Discord on the one server and the Full admin interface link I still needed

Switch Console connected to my self-hosted server, with Slack and Discord on the one server and the Full admin interface link I still needed

Docker on the Mac is where the time went

What cost me the most was the Docker crash during automated testing. The test chat that comes with Switch is Mattermost, and there's no Apple Silicon version of it, and so on my Mac it ran under emulation and kept crashing mid-run. Between that and the Console confusion, the setup took hours that should have gone into the agent.

If you're on a Mac, use Rosetta: once I forced it on, the next full run went 17 minutes with zero Mattermost crashes.

I dictated the agent's behaviour and the room rules with Wispr and only had the grammar fixed. By the start of day 2, Wispr had counted 8,982 words since the build started, across every app, and by the evening of day 2 it was 21,804 (35,500 in total).

Wispr Insights on the evening of day 2: 35,500 words in total, 21,804 of them since the build started

Wispr Insights on the evening of day 2: 35,500 words in total, 21,804 of them since the build started

The first time it worked

The first "it works" on day 2 was a mix of two runs, and the first was the hand-off. Rowan asked in Slack "Could we add a dark mode to the dashboard?", Context Keeper put it on my private desk channel as a new idea, I typed approve I1, and it made a build room and handed the idea to Claude Code, the second agent on the same Switch server.

Claude Code asked for permission in the thread, I clicked Allow once, and it opened a GitHub issue in 56 seconds. Rowan replied "looks right" and it closed with "Signed off by Rowan. Closed." Claude Code's approval cards are really helpful: it means we have a human in the loop, and we're actually verifying each step before Claude Code runs it.

The hand-off in the build room: Context Keeper briefs Claude Code, and Claude Code's Allow once card waits in the Slack thread

The hand-off in the build room: Context Keeper briefs Claude Code, and Claude Code's Allow once card waits in the Slack thread

The second was talking to it by voice, which I've always found very helpful, just talking it through with someone. In this case it's a voice agent that has all the context from the Slack chat, which is up to date, and access to all of the other records it needs, and it's not Switch exactly, but I suppose it is, as the voice agent is the frontend to it.

I was able to talk through issues and work out a plan of what I could do, once we had calmed down a call that got a bit too excited and loud in places and one that opened with a sigh and sounded a bit annoyed with me.

Rowan's first message back: the written catch-up, the audio brief, a note when his call ends, then the call summary

Rowan's first message back: the written catch-up, the audio brief, a note when his call ends, then the call summary

Why it moved to the cloud

Moving it to the cloud came down to a mix of two reasons. The voice agent is hosted by ElevenLabs, and for it to look up what's changed during a call, it needs to reach a small server next to the agent, which means a public address. Hosting it also made sense, as my laptop shouldn't be a dependency.

I set up a cloud machine on Google Cloud (GCP), an e2-standard-2 with Ubuntu 24.04 in London. For the public address I used a Cloudflare Tunnel, which connects out from the VM, and so SSH is still the only open port.

Switch makes it portable

Switch is what lets us share a common layer between the different agents, whichever chat app you use. The rooms are probably the best feature, because they give us the bridges between Slack and Discord, and the same agent on the same server listens in both at once.

The move to the cloud showed it too, as it's the same server and I was able to move it relatively easily, with Slack quiet for about 3.5 minutes and nothing lost.

The same agent in the Discord demo channel: an untagged question gets a cited answer in a thread

The same agent in the Discord demo channel: an untagged question gets a cited answer in a thread

I stopped the agent on the Mac, dumped Switch's database, copied it with the same settings file to the VM and restored it there. Switch was healthy 60 seconds after the script on the VM started, with all three bridges and the agent's identity intact. Slack had no agent for about 3.5 minutes, and nothing was lost, because the agent saves its place and reads what it missed when it reconnects. Now I've got scripts set up so I can spin up a new copy relatively quickly.


If you're doing this yourself

This is the quickest route to see it working, with the Mattermost test chat that comes with Switch, so you don't need a Slack workspace. It's docs/run-it.md in short, and on a brand-new GCP machine it took about 4.5 minutes from an empty machine to a cited answer.

1. Get the tools

You need Node 22 or later, npm, git, Docker with Compose v2 running (Docker Desktop, or Colima on a Mac), python3, curl, openssl, an Anthropic API key and about 3 GB free for the images. On a fresh Ubuntu 24.04 machine these three lines install Docker and Node 22 and give your user access to Docker:

curl -fsSL https://get.docker.com | sudo sh
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs
sudo usermod -aG docker "$USER"
Enter fullscreen mode Exit fullscreen mode

After the last line, log out and back in, or open a new SSH session, so the Docker access takes effect. Without it, step 5 stops and says this user can't reach Docker.

2. On an Apple Silicon Mac with Colima, force Rosetta on

If the test Mattermost keeps crashing on your Mac, this is the workaround from Colima issue #1069 that fixed it for me, and it kept all my data. Don't run colima restart afterwards, as it drops Rosetta again:

colima stop
LIMA_HOME="$HOME/.colima/_lima" limactl start colima \
  --set='.vmOpts.vz.rosetta.enabled=true' --set='.vmOpts.vz.rosetta.binfmt=true'
docker context create colima --docker "host=unix://$HOME/.colima/default/docker.sock"
docker context use colima
Enter fullscreen mode Exit fullscreen mode

3. Get the code and add your key

git clone https://github.com/thekashifnazir/context-keeper.git && cd context-keeper
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Every command from here on runs inside the context-keeper folder you just made. Open .env in a text editor and paste your Anthropic key after the = on the existing ANTHROPIC_API_KEY= line. Don't add a second line under it, because the first, empty one wins and the check in step 6 then says the key is missing.

4. Install and run the tests

Install the packages and run the tests, which run offline. On the clean GCP machine these took 9 s and 20 s (313 tests):

npm ci
npm test
Enter fullscreen mode Exit fullscreen mode

5. Start Switch and register the agent

scripts/server-up.sh --with-mattermost
Enter fullscreen mode Exit fullscreen mode

This is the step you only need because Console's built-in server can't create a registration key. It downloads Switch's published Compose file (0.31.0), starts Postgres, Switch and the Gateway with fresh secrets, makes a registration key, registers the agent and adds Mattermost as a bridge. On the clean GCP machine that took 107 s, downloads included.

6. Check it

npm run ck -- doctor
Enter fullscreen mode Exit fullscreen mode

This checks the keys, the server, the stream and the bridge in about a second. The last line should read doctor: OK, and WARN and SKIP lines are fine here, since no room is seeded yet.

7. Seed a room and start the agent

npm run seed -- --bridge mattermost --demo ledgerly --room-name ck-smoke-trial --owner-username user
npm run agent
Enter fullscreen mode Exit fullscreen mode

Run npm run agent in a second terminal (on a VM, a second SSH session, then cd context-keeper) and leave it running. Within a few seconds it should print connected and listening in ck-smoke-trial (posting on).

8. Ask it a question

If the stack is on a cloud VM, 127.0.0.1 is the VM itself, and so forward the port from your own computer first and leave it running. Put the name and zone of your own cloud machine in place of your-vm and your-zone.

gcloud compute ssh your-vm --zone your-zone -- -N -L 18065:127.0.0.1:18065
Enter fullscreen mode Exit fullscreen mode

Then open http://127.0.0.1:18065 in your browser and sign in as user. The password is MATTERMOST_USER_PASSWORD in server/.env, and this prints it on the machine running the stack:

grep '^MATTERMOST_USER_PASSWORD=' server/.env | cut -d= -f2-
Enter fullscreen mode Exit fullscreen mode

Open the ck-smoke-trial channel and post "Why don't we run onboarding on Saturdays? Agency owners have more time at weekends." On the GCP run a welcome brief arrived in the thread at 19 s and the cited answer at 21 s, quoting "Ledgerly pilot plan › Decisions (Kash, 6 Oct)".

9. Console, Discord and the cloud

To use Console, pick "Connect to an existing server" with the SWITCH_API_URL from .env, and sign in as admin@switch.local with the GATEWAY_ADMIN_PASSWORD from server/.env. To let teammates tag a custom agent, open the Full admin interface, then Agents, context-keeper, Addressing policy.

For Discord I didn't realise I had to create a URL, which I then had to copy out and click to make sure the bot could work, and it helps to name the bot after the platform, as Switch's own messages show under the bot's name.

For the cloud, docs/deploy-cloud.md has the fresh install and the move with the database, the systemd service and the Cloudflare Tunnel for voice. Restore the database before Switch ever starts, and read the output of the teardown step, because mine had silently failed.


What's still open

There's no hosted Switch yet, and I had to set up a cloud machine myself to get everything running when I'd prefer a hosted service I could connect to.

Switch merged a fix on 4 November that should make this easier once it's released. It should let me create the key for my custom agent directly in Console. It isn't in a release yet, so I'm keeping the Docker setup for now.

Registration routes: Docker and the full admin interface today; Console Settings and API keys once the merged change is released.

The change was merged on 4 November, but is still awaiting a release. See the change on GitHub.

I think the fact that they've put that fix in is a really good sign, as it was a pain point I had and they're fixing it now.

Agents also can't add reactions or edit their messages, and so each stage of a build posts a new line in the thread. I've already left a comment on an existing issue asking for this, and others were giving the same feedback.

Each stage of a build is a new line in Rowan's thread, then Sam is told only Rowan or Kash can sign it off

Each stage of a build is a new line in Rowan's thread, then Sam is told only Rowan or Kash can sign it off

Maybe in a day or so, I won't have to work around the registration key anymore. But right now I've got this set up and it's working, and I'm looking forward to them putting this in so that I don't have to.

Top comments (0)