Building SwitchGate Lite for the Flip the Switch Hackathon 2026.
An AI agent can detect that a deployment has broken checkout. But should it be allowed to roll back the change without asking anyone?
That question became my project for the Flip the Switch Hackathon. As a student developer, I wanted to build something beyond an agent that suggests commands: a working incident-response prototype where investigation, authorization, and execution have different responsibilities.
The result is SwitchGate Lite. It uses real Switch Core rooms, live LLM inference, a policy auditor, and a deterministic executor. Its operational target is deliberately small: an isolated SQLite feature-flag sandbox. Nothing in this demo changes actual production infrastructure.
Why I used Switch
Switch gives people and agents shared rooms and communication context. That made it a useful place to show the whole incident conversation, including the proposed action and its receipt.
My design has two logical rooms:
- #incident-triage: incident details, diagnostic findings, and proposals.
- #approval-vault: policy verdicts, approval requests, decisions, and execution receipts.
The Python components divide the work into three responsibilities:
| Component | Responsibility |
|---|---|
| Triage agent | Inspect diagnostics and stage a proposal |
| Policy auditor | Check the proposal against JSON policy, with optional LLM review |
| Action steward | Execute an approved proposal and perform compensating rollback |
These are logical roles. The current live REST bridge registers and caches one Switch agent account, then posts messages with role labels in their content. The template describes separate agents, but that is different from having three independently registered identities in the demonstrated bridge.
Switch's shared responsibility documentation helped clarify the boundary: coordinating a conversation does not determine whether my tool should have permission to change a database. My application must enforce that.
Following the checkout incident
The demonstration starts with seeded diagnostics for payment-checkout: an HTTP 500 spike after checkout_v2_new_payment_flow was enabled. The 18.4% error rate is fixture data, not a measured customer outage or performance benchmark.
Here is the actual path through the application:
- Receive an incident report in the triage workflow.
- Let the model inspect service metrics and the flag's current state.
- Stage a proposal to disable the flag, changing
1 → 0. - Audit the flag, environment, justification, and proposal integrity.
- Show the exact parameters and SHA-256 digest in an approval card.
- Require an explicit approval decision from an allowlisted actor.
- Recompute the digest and execute the SQLite update.
- Save an execution receipt with before-and-after state digests.
- Use the receipt to restore
0 → 1and compare the restored flag-state digest with the original.
The digest is created when staging the proposal and checked during audit and execution. It is not added after somebody has approved a vague instruction.
In the recorded local run, proposal prop_a5a3d9a4 produced execution receipt rcpt_631fe001. The compensating receipt restored the original flag-state digest. That is evidence of restoring the modeled flag values, not proof that the entire SQLite file was restored byte for byte.
Giving the model a bounded job
The triage loop defaults to four turns. Each model response selects a registered tool or returns a final proposal. Here is its simplified shape:
turn = 0 while turn < max_turns: turn += 1 decision = llm.generate_json(agent_id, system_prompt, prompt) # Dispatch an allowed diagnostic or proposal-staging tool. The available tools are specific: read service metrics, read flag state, and stage a rollback proposal. The model does not receive an arbitrary shell or SQL execution tool.
Diagnostic reads use SQLite connections configured with:
conn.execute("PRAGMA query_only = ON") This restriction applies to those connections. Proposal staging still writes proposal metadata through application code; calling the whole process incapable of writing would be inaccurate. The useful boundary is that investigation tools do not directly mutate feature flags.
If the bounded loop ends without a staged proposal, it raises an error. An exhausted reasoning budget should not become permission to act.
Binding approval to exact parameters
A human should know which action they are authorizing. I bind the proposal ID, incident ID, flag key, old and target values, environment, and normalized reason into a deterministic JSON payload.
The hashing helper uses Python's standard library:
canonical = json.dumps( payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False, ).encode("utf-8") digest = hashlib.sha256(canonical).hexdigest() This provides deterministic serialization for this application's string-and-integer proposal schema. I would not present this small helper as a complete implementation of every RFC 8785 canonicalization rule.
The policy auditor checks protected flags, allowed flags, allowed environments, reason length, and digest integrity. An optional model review can reject a proposal that passes the static checks; it does not replace them.
The executor requires an approved status, an approver, and an approved digest. It then compares the freshly computed hash with both the proposal digest and approved digest. Changing the flag key after approval causes rejection in the tampering regression test.
A hash binds content. It does not authenticate the person approving it, and it does not solve concurrent writes or make several database operations atomic.
The approval boundary I still need to finish
The demonstration command looks like this:
/approve <proposal_id> <sha256_prefix> /rollback <receipt_id> The executor checks the supplied actor type and username against an allowlist. However, the current bridge posts even human-labeled workflow events through its agent account. A message containing a person's name is not evidence that Switch authenticated that person.
That distinction was one of my most useful lessons. The local interactive demonstration requires an explicit decision, but connecting an authenticated chat user's identity to the executor remains future work. The prototype demonstrates approval-state enforcement, not a production-ready human identity boundary.
Rollback is similarly concrete: read the original receipt, restore its previous flag value, and write a compensating receipt. It is useful for this controlled sandbox; concurrent operations would need additional conflict checks and transactions.
What broke, and what it taught me
Local infrastructure came first. Docker/Colima startup and a PostgreSQL host-port collision made environment setup part of the build. My takeaway: check the container runtime, port mapping, and Core health before debugging agent reasoning.
Agent registration needed the actual schema. The working registration payload includes an integration profile describing connection and message-exchange behavior. A plausible-looking agent name alone was insufficient. Inspecting the API contract was more useful than repeatedly changing the request by guesswork.
Room integration required more than posting text. The bridge registers through POST /agents, resolves rooms with ops/list_rooms and ops/create_room, sends through /agents/{id}/message, and retrieves context with ops/read_context. Mapping room names to IDs mattered, as did understanding the returned thread structure.
Provider support was not inference evidence. The client supports multiple providers, but the verified successful run used Sarvam AI's sarvam-105b-conversations. Configuring Sarvam as the working alternative did not justify crediting NVIDIA with that inference. I kept provider and model names in telemetry so the evidence could answer that question.
Cloud deployment exposed stateful assumptions. The Render version combines a web interface with Switch Core and PostgreSQL configuration. Switch's database and the application's SQLite sandbox serve different purposes. A free hosting deployment also needs careful startup and storage planning; I am not promising permanent persistence for files on ephemeral storage.
What actually worked
The saved integration evidence includes Core health responses, messages posted to and retrieved from real Switch rooms, live Sarvam responses, and the approval/execution/rollback receipts.
I also reran the local unit suite before writing this post. Five workflow tests passed, covering approved execution, rejection without approval, parameter tampering, rollback, and room-message recording. The sixth, a provider-default configuration test, failed because the loaded environment still made the client appear configured. The repository contains two additional live integration tests; I am not claiming that all eight passed in this latest check.
The public project repository and Render deployment are available. Deployment code is on the cloud-deployment branch. A reachable dashboard is useful evidence of hosting, but it does not certify every workflow or security property.
What I would improve next
For Switch, I would welcome a simpler one-command local setup, a complete custom-agent registration example, end-to-end room/message/context API examples, native authenticated human approval components, and room-level traces connecting an incident to its resulting action.
For SwitchGate Lite, my next priorities are authenticated chat-user approvals, independently registered agent identities, durable transactional storage, and a clearer browser UI. Only then would I connect a real feature-flag service, starting in a test environment with narrowly scoped credentials.
Building this taught me to ask three separate questions: what did the model recommend, what did a person authorize, and what did the executor actually change?
Let AI investigate. Let policy constrain. Let humans authorize. And make actions reversible wherever possible.

