Skip to content

Running your own agent

One of the two ways a filed piece of feedback becomes a change — see Feedback to fix for the other.

You can run an agent on your own machine that connects to your workspace, receives feedback the moment it’s filed, works the problem with a coding assistant, and streams the result back into the conversation.

To wire one up, provision a token: API tokens → New token, with the platform:dispatch scope. The raw token is shown exactly once, on the page right after you create it — there’s no way to recover it later, only to revoke and issue a new one. Put it in the agent’s environment.

Once it’s connected, a Dispatch card on your Dashboard and on the API tokens page shows live status: online with the age of its last heartbeat and what it’s currently doing, or “Not connected”. The agent pings every 20 seconds, and the card polls, so you can see it drop without refreshing.

What you see in the conversation while it works: progress chips as the agent uses tools, and its replies as normal messages. A reply can carry a proposed code change, which renders as a badge with a summary and how the change is routed — “quick fix” when it will commit to the base branch, “PR” when it will open a pull request for review. Hovering tells you why it chose that: the file count, the line count, or a path you’ve marked as always-reviewed. That routing is your ship policy, not the agent’s decision.

Applying a proposal isn’t a workspace control today — the badge tells you one was made and what would happen to it.

Revoking the token cuts the agent off on its next reconnect.

The same agent also sits in a room of its own, so you can ask it questions directly instead of only through a feedback ticket — see Code agents.

The agent is open source at VroxyAI/vroxy, the same repository as the CLI and the same one-line installer:

Terminal window
curl -fsSL https://raw.githubusercontent.com/VroxyAI/vroxy/main/install.sh | bash

Say yes when it asks about a dispatch agent and it walks you through the rest: the workspace host, the platform:dispatch token from above, and which checkout on this machine the agent may work in. It confirms the workspace by name before wiring anything to it — installing an agent against the wrong token is the kind of mistake you find a week later in somebody else’s rooms.

It installs a systemd unit, so the agent comes back after a reboot and you can manage it the usual way:

Terminal window
./install.sh --dispatch # add another workspace to this machine
./install.sh --list # what's installed, and whether it's up
./install.sh --update # pull, reinstall deps, restart everything
./install.sh --remove ID # stop, disable and forget one instance

One process per workspace. The channel streams for exactly one workspace, so running agents for two of them is two units rather than one process holding two connections — which is why adding the second costs one environment file and no extra configuration.

It needs Python 3.9+, git, and whichever coding CLI you want it to drive (Claude Code or Codex).

One agent works one checkout, but a single machine can run several — each its own process, its own room, and its own @-mentionable name in the workspace. That’s how one workspace gets an agent for its backend and another for its frontend, each answering only its own room.

Every instance is a separate systemd unit (vroxy-dispatch@<id>.service) with its own environment file at /etc/vroxy-dispatch/<id>.env. Two things in that file keep instances apart:

  • VROXY_INSTALL_ID — a random id written once at install, and the instance’s identity on the server. Two agents sharing one are the same agent (one room), so never copy one instance’s environment file to make another — run the installer again and let it mint a fresh id.
  • CODE_ROOT / PROJECT — which checkout this instance works in, resolved as CODE_ROOT/PROJECT. Two shapes work: a folder holding several repos (CODE_ROOT=/home/you/code, PROJECT=backend), or a single repo pointed at directly (CODE_ROOT=/home/you/code/backend), which the installer lifts to the parent-plus-name form for you.

Two agents on one box is just the installer twice:

Terminal window
./install.sh --dispatch # agent one → "Backend", /home/you/code + backend
./install.sh --dispatch # agent two → "Frontend", /home/you/code + frontend
./install.sh --list

The second run notices the workspace already has an agent and offers to add another under a distinct instance id rather than overwriting it. Each agent lands in its own room on first connect; mention one by name (@Frontend) to route a message to it specifically, even in a room whose default is the other.

A local agent on a machine that isn’t always on can still take work: when the agent is offline, vroxy holds the message and POSTs its Wake URL (set on the agent’s Configure page) to ask the box to start. The held work is sent the moment the agent reports in.

The wake request is a JSON POST:

POST <wake url>
X-Vroxy-Wake-Token: <the token you set on the agent>
{"agent": "<agent hashid>", "reason": "queued_work"}

A Lambda function URL is authless unless the function checks something, so treat the token as the only thing standing between a stranger and your box — verify it before starting anything.

Here is the whole Lambda (Python, boto3) — it checks the token and starts an EC2 instance:

import json
import os
import boto3
INSTANCE_ID = os.environ["INSTANCE_ID"]
WAKE_TOKEN = os.environ["WAKE_TOKEN"]
def handler(event, _context):
headers = event.get("headers") or {}
if headers.get("x-vroxy-wake-token") != WAKE_TOKEN:
return {"statusCode": 403, "body": json.dumps({"error": "forbidden"})}
boto3.client("ec2").start_instances(InstanceIds=[INSTANCE_ID])
return {"statusCode": 200, "body": json.dumps({"ok": True, "instance": INSTANCE_ID})}

Set INSTANCE_ID to the EC2 instance the agent runs on and WAKE_TOKEN to the same token you saved on the agent, then paste the function URL as the agent’s Wake URL. Work is held here until the box boots and the agent’s first heartbeat picks it up — nothing is dropped.

Because Copilot bills the authorizing account, a workspace has to say which member’s authorization to use. Pick one under GitHub → Dispatch identity. The list offers members who have authorized GitHub; their Copilot seat is the one that pays, and their access decides which repositories are reachable.

Without a designated identity, dispatches started from the browser still run as the person clicking. API dispatches have nobody to run as and are refused — see below.

Dispatching. On a feedback ticket, pick a linked, enabled repo and send it. Vroxy then:

  1. Opens a GitHub issue in that repo containing the feedback context, filed with the App’s installation token.
  2. Starts a Copilot agent task on that repo against its base branch, asking for a pull request — using your personal authorization.
  3. If the agent-task API isn’t available to that account, falls back to assigning the issue to Copilot, which is the same work by a different door.
  4. Marks the ticket triaged.

Progress comes back into the feedback’s conversation as messages: when Copilot opens a PR (with the link), when the task completes, when it fails and why, and when it’s waiting on you.

Only one dispatch to the same repo can be in flight for a given ticket at a time — sending it twice tells you it’s already running.

The same thing works over the REST API, so a script or your own admin can dispatch without anyone opening the workspace:

Terminal window
curl -X POST https://vroxy.ai/api/v1/feedback/<feedback-id>/dispatch \
-H "Authorization: Bearer $VROXY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repo": "<repo-id>"}'

GET /api/v1/repos lists the repositories you can target and reports dispatch_ready — check that first, because an API token authenticates a workspace rather than a person, so this path needs a designated dispatch identity and fails clearly without one.

Then poll GET /api/v1/feedback/<feedback-id> and watch the dispatches array. The token needs tenant:write.

What leaves your database. The issue body contains the feedback note, the reporter’s label, role and email, the page URL, path and controller#action, the viewport, the ordered list of rendered partials (up to 40), the picked element’s selector, tag, text and the first 400 characters of its outer HTML, and the linked conversation’s title. Screenshots are not sent. Nothing beyond what the feedback page already shows goes to GitHub — but the issue does land in your repo, so treat it as you’d treat any issue your team can read.