DEV Community

Cover image for Two people, one brief: building PactRoom with Switch, Discord and AWS
Pranay
Pranay

Posted on Edited on

Two people, one brief: building PactRoom with Switch, Discord and AWS

Our client wanted every canceled workshop place to go straight to the next person on the waitlist.

Operations wanted staff to approve replacements close to the start, after the attendance list had been prepared.

Both requests were reasonable. Implementing either one without resolving the other would produce software that disappointed someone.

That small disagreement became the most useful test of PactRoom, our human-and-agent collaboration workflow built with Switch.

We connected people in Discord to specialist Codex agents, hosted the execution environment on AWS, and added a Python coordinator that keeps requirements, confirmations, code and review attached to the same version of the task.

The result was a working registration library with two accepted revisions, independent review and eighteen passing application tests. More importantly, the team could see which policy it had agreed to—and the agents waited for that agreement before building it.


What we set out to build

We started with a simple idea: a small team should be able to discuss a project in chat, ask agents to work on it, and inspect what they produce.

At first, even the vocabulary needed unpacking. What is a workflow? Where does it run? Is an agent a model, an application, or a prompt? If several agents work together, do they need several different models?

Building the system gave those terms concrete meanings:

Component What it does in our project
Discord Gives people a familiar place to discuss requirements and request work
Switch Connects conversations, rooms, agent identities and messages
Codex CLI Runs the coding-agent sessions and their development tools
Role instructions Define the responsibilities of each specialist
Relay Runner Coordinates tasks, validates results and enforces the agreement process
Git and unittest Identify the candidate code and execute its tests
AWS Ubuntu Hosts the shared services, agent processes and project files

Relay is our underlying coordinator. PactRoom is its business-workflow specialization: getting stakeholders to agree on a version of the requirements, then carrying that agreement through implementation and review.

The workshop library is the example that PactRoom produced. The collaboration workflow is the project we are demonstrating.

The workflow in one minute

People discuss the requirement in the team room
    ↓
Facilitator helps identify and resolve disagreement
    ↓
Delivery lead starts a task or requests a change
    ↓
Planner produces a versioned brief
    ↓
Client AND Operations confirm that exact version
    ↓
Builder implements → Runner tests → Reviewer checks
    ↓
Steward prepares delivery evidence
    ↓
Delivery lead accepts the exact reviewed result
Enter fullscreen mode Exit fullscreen mode

The coordinator controls those transitions. The agents do not independently decide that the next person has approved something or that a task is finished.

Explore the project

Our first milestone: one agent answering in a room

Before introducing multiple specialists, we needed one dependable connection.

We began on Windows with Switch Console. The sequence was deliberately small: choose the server, prepare a provider, register an agent, connect the intended conversation, and receive an actual reply there.

For a first-time user, these are separate checks:

  1. Install Console. Use the appropriate download from the official installation page. Our pilot used Windows Console 0.40.1; later versions may look different.
  2. Select the server. This is where the agent identities and rooms belong.
  3. Prepare the provider on the execution machine. Verify installation and authentication where the agent will actually run.
  4. Register one agent. Give it a name, a working location and a focused instruction.
  5. Add it to the intended room and test a reply. A result printed in a terminal is not the same as a message delivered to the team.

That last distinction mattered throughout the build. We wanted evidence that the whole route worked, not just that an individual command could run.

Our early local server was a host-run service. Seeing another container in Docker Desktop did not establish that Switch itself was running in Docker. Later, when we moved to AWS, we checked the actual services rather than relying on the presence of a Docker icon.

Which provider did we use?

The qualified PactRoom demonstration used Codex CLI on the AWS host.

We had explored OpenCode and open-weight model hosting because cost and usage limits mattered to us. Those experiments informed the planning, but they are not the model route this demonstration proves.

The recorded working versions included Switch Core 0.31.0 and Codex CLI 0.166.0. We keep that version record because “works on my machine” becomes difficult to diagnose when the Console, server and provider evolve independently.

One lesson from onboarding was that installation, authentication and successful execution through the controller are different observations. A CLI version appearing in a terminal does not by itself prove that an agent can start through Console and answer in a room.

The provider setup documentation explains the platform side of that process. In our project, the practical completion check was still a real request and a real delivered response.

We did not fine-tune five models. Our specialists use different roles and sessions with the coding runtime. Provider account limits still apply.

Bringing the agents into Discord

Discord gave the team a conversation space it already understood. Switch supplied the bridge to agents and rooms.

There were several pieces to connect: the Discord application and bot authorization, the connector, the linked channel, the Switch room, and the agent's membership in that room.

A linked channel without an agent could show that the bridge existed, but it could not produce a useful AI answer. Likewise, registering an agent did not automatically make it a participant in every conversation.

We therefore checked the visible chain: the intended channel was linked, the intended agent was present, and an explicit mention produced a response in the right place.

We also wanted normal team conversation to remain normal conversation. Merely writing the word “agent” should not start development work. The facilitator responds when intentionally addressed; engineering assignments come through Runner.

For someone repeating this setup, use the invitation controls or syntax shown by your installed connector. The agent onboarding guide is the reference for registration. Keep bot tokens and provider credentials out of screenshots and chat.




Explore PactRoom: Join our Discord demo community.

The project workflow runs in restricted team channels. Joining the server does not automatically grant agent, repository or cloud access.

Five AI roles, three human responsibilities

Adding more agents only helped once their responsibilities were clear.

We created two rooms:

  • The team room contains the people, facilitator and Runner. It is where requirements and decisions should remain readable.
  • The engine room contains Runner and the four engineering specialists. It carries assignments, structured results and execution detail.
Profile Responsibility
pact-facilitator Identify conflicting requests, attribute them and help people formulate a precise policy
pact-planner Produce requirements with stable identifiers, owners and acceptance examples
pact-builder Implement the confirmed assignment, test it and return a real candidate commit
pact-reviewer Independently check that candidate against the agreed brief
pact-steward Prepare delivery information and the evidence for the next human decision

relay-runner is a separate identity backed by deterministic Python software. It is not a sixth AI model.

The human roles were equally important. I acted as delivery lead: starting and changing tasks, then accepting reviewed results. Rachit acted as the client, responsible for the desired behavior. Rishabh acted as operations, responsible for constraints around how that behavior would be used.

These were real teammates taking stakeholder roles in our demonstration, not a claim that we had deployed the system for a paying customer.

A shared project does not mean one moving review folder

Each profile has a launch directory. Runner then supplies the actual task workspace in its assignment.

The builder works in the room-owned Git project. The reviewer receives a separate archive of the exact candidate commit. Reports belong outside the candidate checkout.

That arrangement lets everyone discuss the same project while keeping the reviewed code stable. If the working branch changes later, the reviewer is not silently reviewing different files.

Separate folders and prompts are useful coordination boundaries. They are not operating-system security isolation; our pilot still uses a trusted shared host account.

How handoffs became automatic

Our earlier flow required a person to mention the next specialist after the previous one finished. That made the human the scheduler.

Runner gave the workflow durable state and explicit completion contracts.

An assignment identifies the task, role, room, workspace, brief revision and handoff. A result must come from the expected identity in the expected context. The structured completion carries the fields that stage needs: a brief, candidate commit, review findings or delivery details.

A friendly paragraph saying “done” does not contain enough information to validate that transition. Neither does a report saved to disk if the room never receives the required completion.

The same principle applies to human agreement. This simplified pseudocode illustrates the gate:

required_people = {client_id, operations_id}

if brief_has_open_decisions:
    wait_for_decision()
elif not required_people <= confirmations_for_this_revision_and_hash:
    wait_for_confirmation()
else:
    dispatch_builder()
Enter fullscreen mode Exit fullscreen mode

The real implementation also checks identities, rooms, duplicate events and persistent state. The important behavior is easy to explain: the first confirmation records one person's agreement; the second completes the gate for that version.

After implementation, Runner executes the controlled unittest profile. Review can request changes and return work to Builder, with a maximum of two repair cycles before escalation.

That repair path is supported behavior. We do not present an invented review rejection as part of a run that passed.

Moving the execution environment to AWS

We wanted teammates to work without depending on the operator's laptop staying awake.

Moving only the Switch server would not satisfy that requirement. A provider session still running on a laptop would remain dependent on that laptop.

Our hosted arrangement therefore included the execution components as well:

AWS Ubuntu
├── Docker
│   ├── Switch Core
│   ├── Gateway
│   └── PostgreSQL
├── Relay Runner service
├── Agent controller service
├── Codex CLI sessions
└── Git project files, tests and review artifacts

External model provider
└── Inference used by the CLI sessions
Enter fullscreen mode Exit fullscreen mode

The model itself was not running as downloaded weights on our EC2 CPU. The host ran the agent processes and tools; those sessions used the configured provider service.

The migration sequence

We used an Ubuntu EC2 host, established SSH access and deployed the Switch infrastructure through Docker. We then connected Console to the shared server, prepared the provider on the remote host, and selected that host as the agents' run location.

We made Runner and the agent controller persistent services, checked database readiness, and gave authorized teammates their own access.

Selecting a cloud server in Console and choosing an agent's run location are separate decisions. That is worth checking explicitly: an agent can otherwise remain local even though its rooms belong to the shared server.

The team-hosting documentation and remote-host guide describe the platform setup. Our repository documents the Runner configuration and service assumptions. Readers should use version-matched upstream deployment instructions rather than copy private infrastructure settings from a screenshot.

For our existing Ubuntu host, the basic read-only checks included:

systemctl is-active relay-runner
systemctl --user is-active switch-agent-controller
loginctl show-user ubuntu -p Linger
sudo docker ps --format 'table {{.Names}}\t{{.Status}}'
Enter fullscreen mode Exit fullscreen mode

We checked PostgreSQL's listener separately with pg_isready inside its container. A container being present is not the same as its application being ready.

What hosting changed for the team

Code and evidence now had an authoritative home on the server. Authorized teammates could inspect the same environment, and the architecture no longer required an operator's laptop-hosted agent session to remain open.

Those benefits have boundaries. The EC2 instance, services, network and provider access must still be available. Private network access and individual Switch accounts remain necessary. Cloud files are not automatically synchronized into everyone's local folder, and Discord does not become a collaborative file browser.

For our pilot, that was a useful tradeoff: shared execution and explicit access, with ordinary infrastructure responsibilities still visible.

Deployment access: PactRoom runs on AWS in a private team environment.
Access requires an authorized network connection and an individual Switch
account. The recorded demo shows the working deployment; the repository
provides the source and reproduction instructions.

Version 1: a small result we could inspect

We deliberately began with a small Python library rather than a large application.

It had two confirmed places. Participant identifiers had to be nonempty, unique and case-sensitive. Registrations filled confirmed places first, then joined a FIFO waitlist. Invalid operations had to preserve existing state.

The example was easy to inspect:

Ada registers  → confirmed
Ben registers  → confirmed
Chen registers → waiting
Enter fullscreen mode Exit fullscreen mode

Both stakeholders confirmed the version-1 brief. The implementation passed six application tests, went through review and delivery preparation, and was accepted by the lead.

That gave us a real baseline to change.

Version 2: the team changes the requirement

Rachit then asked for cancellation and automatic promotion so places would not be wasted. Rishabh raised the attendance-list constraint: near the start, replacements needed explicit staff control.

The facilitator helped make the boundary precise. The agreed policy distinguished more than 120 minutes before start from exactly 120 minutes and closer.

The lead's change request also specified waitlist cancellation, repeated cancellation, new arrivals while a place was pending, and rejection of mutations at or after the start. That request became a revised brief.

The old acceptance remained a historical record. It did not authorize version 2.

Both stakeholders had to confirm the new revision and hash from their own accounts. In the recorded run, operations confirmed first. Coding still waited for the client's valid standalone confirmation.

One practical usability detail emerged here: a participant initially pasted an instruction block that included the command. The later standalone command was the valid confirmation. Our instructions now emphasize sending exact commands alone.

What should happen after one confirmation?

Before reading the answer, picture the planner's complete brief and one stakeholder's green check. Should the builder start?

Reveal the workflow decision

The builder waits. A complete plan and one person's agreement are not the two-person confirmation required by this workflow. Both confirmations must refer to the current revision and hash. A subsequent change requires fresh agreement.

This small delay is intentional. It makes disagreement visible before implementation creates momentum around one interpretation.

The two-hour boundary, in actual code

The accepted library uses UTC-aware time and an injectable clock, so the boundary can be tested without sleeping or relying on the wall clock.

Scenario Expected result
Ada cancels 121 minutes before start Chen is promoted automatically
Ada cancels exactly 120 minutes before start The vacancy remains pending explicit promotion
Dia registers while that place is pending Dia joins the queue and cannot bypass Chen
Staff explicitly promotes the next participant Chen takes the available place
An attempted mutation occurs at or after start It is rejected without changing state

Here is the exact-cutoff example using the implemented API:

from datetime import datetime, timedelta, timezone
from workshop_registration import WorkshopRegistration

start = datetime(2030, 1, 1, 15, tzinfo=timezone.utc)
now = start - timedelta(minutes=120)
workshop = WorkshopRegistration(start, clock=lambda: now)

workshop.register("Ada")
workshop.register("Ben")
workshop.register("Chen")

workshop.cancel("Ada")
assert workshop.confirmed == ("Ben",)
assert workshop.waitlist == ("Chen",)
assert workshop.pending_places == 1

workshop.register("Dia")
assert workshop.waitlist == ("Chen", "Dia")

assert workshop.promote() == "Chen"
assert workshop.confirmed == ("Ben", "Chen")
Enter fullscreen mode Exit fullscreen mode

“Staff promotion” here means an explicit library operation. We did not build a staff authentication system or workshop website around it.

Why not let a new registration take the empty place?

The empty place is reserved for the agreed promotion process. Letting a new arrival take it would bypass both the waitlist and the near-start approval rule. A vacancy is therefore not always an ordinary available place; the implementation tracks pending places explicitly.

The final application passed eighteen tests covering the cutoff, start-time boundaries, FIFO behavior, pending places and invalid operations.

Review has to follow the version

A passing test suite is valuable evidence, but it cannot by itself establish that the team asked for the right behavior or that every requirement was checked.

PactRoom connects several records:

Current brief revision and hash
          +
Exact candidate commit
          +
Actual test result
          +
Independent review findings
          ↓
Delivery eligible for the lead's acceptance
Enter fullscreen mode Exit fullscreen mode

The reviewer checks a fixed-commit copy. The lead's acceptance is attached to the current revision, hash and candidate. If those inputs change, previous evidence cannot silently stand in for the new result.

Hashes help identify inputs. They do not make the code correct, make the records tamper-proof or replace the identity of the person approving the delivery.

The corrections that made the system usable

The build became more useful when we treated confusing behavior as a design problem and verified the fixes against real interactions.

Requirement owners had to mean people

An early planner result assigned some requirements to Builder. Our schema intended ownership to mean the human decision owner: client, operations or both.

The result was rejected. We clarified the contract and accepted the planner's actual corrected response. The distinction matters because implementing a requirement does not make an agent the stakeholder authorized to decide it.

An autonomous assignment needed a task branch

Builder reported a blocker when its assignment did not designate a branch. We added a deterministic relay/<task_id> branch to the handoff and guidance for creating it safely on an empty repository.

Recovery preserved the agreed brief and confirmations. It did not reset the task to make the dashboard look healthy or manufacture a successful result.

An honest limitation should not be rejected as missing evidence

One requirement concerned scope and nonpublication. Builder supplied no behavioral test for it and explained why it needed inspection.

Our initial validator was too strict. We changed the contract to require an explicit explanation for an empty test mapping, then passed that limitation to the reviewer and steward.

That is why the final evidence says nine of ten requirements have builder-supplied test references. The remaining scope requirement is inspection-only. It is not a failing application test, and we do not count it as automated coverage.

Two permission layers produced one confusing conversation

At one point an authorized command advanced the workflow, while Switch also displayed a refusal.

Runner's command authorization and Switch's addressing policy were checking different identity representations. In the inspected deployment, the scoped addressing rule needed resolved external-user IDs rather than transport sender strings.

We corrected the narrow team-room rule while preserving the existing owner rule. Fresh status requests from real participants then succeeded without the contradictory refusal.

Evidence needed to be readable

A detailed evidence reply once expanded into many messages of test mappings. That made it harder for people to understand the decision in front of them.

We separated the compact evidence response from the full brief and retained detailed records on the server. The team now sees the current tests, review, mapping limitation, acceptance and commit in a concise summary.

Readable output is part of the control system. People should be able to tell what they are accepting without reconstructing it from protocol traffic.

What we can claim from the result

The live pilot completed two accepted revisions. Two actual stakeholders confirmed the revised brief. Builder, Reviewer and Steward completed their stages, and the lead accepted the resulting candidate.

The verification results are separate:

Check Recorded result
Version-1 workshop application 6 tests passed
Version-2 workshop application 18 tests passed
Coordinator regression on Ubuntu 64 tests passed
Clean-package coordinator regression on Windows 63 passed, 1 symlink test skipped

We observed a workflow with less manual role-by-role prompting, visible stakeholder decisions and a shared place for code and evidence. We have not measured a percentage productivity improvement, and this is not a production-scale reliability study.

The pilot also has deliberate limits: a trusted shared OS account, serialized autonomous work, provider availability and usage limits, and a qualified Python unittest test profile. A controlled test command does not make arbitrary Python code safe to run from untrusted users.

Accepted delivery does not mean published delivery. Our steward prepares delivery information; PactRoom does not automatically push, create PRs, merge or deploy the generated application.

Try the example before setting up the cloud

The source package includes the coordinator, five role instructions, a disabled-by-default configuration example, setup guidance and the accepted workshop library.

With Git and Python 3.11 or later, readers can run the offline checks without model credentials:

git clone https://github.com/Pranay22077/pactroom.git
cd pactroom
python -m unittest discover -s tests -v

cd examples/workshop-registration
python -m unittest discover -s tests -v
Enter fullscreen mode Exit fullscreen mode

Use python3 on Ubuntu if python is unavailable. A short checkout path is helpful on Windows. The offline tests use simulated transport and temporary Git repositories; they do not start live agents. The live worker runs on Linux because it uses a POSIX process lock.

The PactRoom repository contains the setup and verification details. Start with the example, then the architecture, then the live deployment guide. You do not need to rent a server merely to understand the behavior.

Build it yourself

What we would improve next

The next improvements are clearer approval controls, stronger isolation between execution roles, and a separately authorized publication path. Additional model providers would need their own qualification rather than an assumption that one successful provider proves them all.

The most useful result of this project is a visible relationship between a human decision and the code delivered for it.

A new message can change the requirements. PactRoom makes the consequences explicit: agree again, build the right version, review the right commit, and let a person accept the result.

That is the collaboration experience we wanted our agents to support.


Build disclosure: Relay's coordination foundation and local/cloud experimentation began before the hackathon. PactRoom specializes that foundation into the stakeholder-agreement workflow described here. The clean source repository is a packaging of that work; its initial public commit is not a claim that every line was written on that date.

Credits: Built with Switch, Discord, Codex, Python, Git, Docker, PostgreSQL, AWS and Tailscale. Rachit and Rishabh contributed the distinct stakeholder requests and confirmations. AI coding and writing tools assisted the work; the team remains responsible for reviewing the code, claims and final article.

Further reading: Switch source, Switch documentation, and the Flip the Switch event.

Top comments (0)