Support reports from inside the product are the highest-signal bug reports there are, and they are also written by people who are not engineers and arrive faster than a three-person team can triage. This subsystem turns them into worked tickets. Members report from any screen with attachments, operators get a queue with severity triage, and an investigating agent can claim a report, post a task-by-task plan that the operator watches live, and link the pull request it opened. The design work that mattered was deciding what the agent is not allowed to hold.
What it's built with.
How it works.
What the agent is not allowed to hold
The instinct when wiring an automated investigator into a product is to hand it database access, because that is the shortest path to letting it read what it needs. The reason not to is that this particular process consumes untrusted input by definition, member-written report text, and executes code in a shell. Those two properties together mean the credential it holds is the blast radius.
So it holds none. Every read and write is an ordinary authenticated HTTP request against an endpoint that exposes exactly the actions triage requires, which has the useful side effect that everything the agent does is loggable through the normal request path and revocable by rotating one secret, with no database change required.
One writer, one set of rules
Three different callers change support reports: the member who filed one, the operator working the queue, and the agent. The temptation is to let each write its own UPDATE, and the failure that follows is subtle rather than loud. The agent resolves a report a human had already closed, or reopens one nobody is watching, and nothing errors because each caller was individually correct about its own rules.
Consolidating the status machine into a single shared module makes the transitions the same object for everyone. It is the same argument as the capability system elsewhere in the platform: put the rule in one place, and make every path go through it rather than reimplement it.
The things I'm proudest of.
- ▹Built the agent's interface as an HTTP machine API rather than a database connection, on the explicit reasoning that a process which reads untrusted member-written text and runs a shell should not also carry a credential to the production database.
- ▹Kept one status machine for three callers. The member's own pages, the operator panel, and the agent API all route their writes through a single module, because a state machine enforced in one place and bypassed in another is not enforced, and a second writer with its own SQL is a second set of triage rules that will drift from the first.
- ▹Separated authorization from operations by design: each caller proves identity in its own way, a session for the panel and a bearer token for the API, then calls into shared logic. Mixing them would mean the API's auth had to be understood in order to review the UI's.
- ▹Made the agent endpoint fail closed. With no shared secret configured the route refuses everything rather than exposing an unauthenticated write surface over every club's support threads, and the comparison uses a timing-safe check.
- ▹Modelled the agent's work as first-class data (runs, tasks, outcomes, attached pull requests) so an operator sees a live plan and activity feed rather than an opaque pause, and a stopped run says so explicitly.
- ▹Gated whether the agent may speak to members at all behind a platform setting, so an operator stays between an automated system and a customer until that is earned.