How I brought a 1988 chat protocol back to life for Kiroween 2026, and what happened when I gave one spec task to Kiro's agent.
By Zhichao Duan (solo entrant)
This post is written as part of the Kiroween 2026 hackathon (Resurrection category: "Bring your favorite dead technology back to life").
The dead technology: IRC
Internet Relay Chat was written by Jarkko Oikarinen at the University of Oulu, Finland, in August 1988. It was documented as RFC 1459 in 1993 and updated by RFC 2810–2813 in 2000. For decades it was where open-source projects and hobbyist communities lived, and most of them later moved to closed, account-based chat apps.
The protocol is still very simple. It is a line of text over a TCP socket, and you can type it by hand in telnet. That made it a good fit for the Resurrection category. My goal was to keep the protocol real, so that old clients still work, and add two modern things around it: a browser client and an AI participant.
What I built: Seance Relay Chat
Seance Relay Chat is a small, self-hostable chat server that speaks real IRC.
-
A real IRC server on TCP port 6667. Classic clients such as Irssi, HexChat and WeeChat connect with
NICK,USERandJOIN. It covers the RFC 1459 / RFC 2812 basics:PRIVMSG,NOTICE,PING/PONG,TOPIC,NAMES,QUIT, plusLIST,WHO, read-onlyMODE,MOTD, and an emptyCAP LSso modern clients register cleanly. -
A browser client over WebSocket that joins the same channels using only a nickname. A message typed in the browser arrives at an Irssi user as a real
PRIVMSGline, and the other way around. -
Ghost, the AI bot. Ghost is just another IRC user in the channel.
!askanswers a question, and!summaryand!tldrread back recent history. The live demo runs Ghost on a built-in mock provider, so every reply starts with[mock]. Anyone self-hosting can plug in their own key for any OpenAI-compatible API. -
SQLite history, using Node's built-in
node:sqlite, so there is no native addon.
One hub for every client
The whole thing is one Node.js + TypeScript process. Its only runtime dependency is ws. TCP clients, WebSocket clients and Ghost all implement the same Client interface and exchange parsed IRC messages through one ChatHub (src/core/hub.ts). The hub is pure logic with no sockets, so most tests run against a fake client. Because of this design, a browser user and an Irssi user see exactly the same channel.
Making both worlds safe was the hardest part: a 512-byte IRC line limit, stripping CR/LF/NUL from anything written into a line, flood control for both TCP and WebSocket clients, and an Origin check on the WebSocket endpoint. The app is deployed with Docker on a VPS.
How Kiro fit in
The repository has a .kiro/ folder at its root, committed on purpose. It has three parts:
-
Steering:
product.md,tech.mdandstructure.md(always included), plussecurity.md, included byfileMatchfor source, web and deploy files. They tell Kiro what the product is, the stack (Node ≥ 22.13, strict TypeScript, ESM,wsas the only runtime dependency), where modules live, and the security rules. -
Specs:
irc-core,web-clientandai-ghost, each withrequirements.md("WHEN … THE SYSTEM SHALL …" acceptance criteria),design.mdandtasks.md. -
Hooks: type-check on save (
PostFileSaveon*.ts), runnpm testwhen the agent stops (Stop), and an optional protocol-review agent hook that I left disabled because agent hooks use credits.
The honest part
I want to be clear about the order of events. The steering files, the three specs and the hooks were drafted outside Kiro first, following Kiro's documented formats. Each file says so in its first line. Tasks 1–4 of irc-core are checked because that code already existed when the task list was written. Kiro did not write the core server.
Next I opened the project in the Kiro IDE 1.4.62, signed in on the free plan. Kiro starts the folder in Restricted Mode and does not run tasks or hooks there. Hooks that tried to run before I trusted the folder were blocked (executionDisabledUntrustedWorkspace). Once I trusted the folder, Kiro showed each spec in its Requirements / Design / Task list view.
Running spec task 5.2 in Kiro
I left one follow-up open on purpose: task 5.2, "Channel operators and KICK" in irc-core. I clicked Start task in tasks.md, and Kiro's spec-task agent worked on it for 2 minutes 51 seconds. It changed three files: src/core/hub.ts, src/irc/numerics.ts and test/hub.test.ts. What it added:
- the first user to join an empty channel becomes an operator;
-
NAMESshows operators with@, and ISUPPORT advertisesPREFIX=(o)@; -
MODE #chan +o/-o nickgrants or removes operator status; -
KICKreturns the correct error numerics: 403, 442, 482 and 441.
It added 10 new tests and updated one existing test whose expected NAMES output had changed. Then it ran npm test itself (after I approved the command): 51 tests, 51 pass, 0 fail. Kiro marked 5.2 as done and wrote tasks.meta.json with the execution history. Kiro's chat showed an estimate of about 3.8 credits for the run. I committed and pushed the result as b727fc8.
What I did not see
Kiro's log showed all three hooks loaded. I saw no sign that run-tests-on-stop fired when the agent stopped, so I'm not claiming it did. The tests passed because the agent ran them itself, not because of the hook.
One more caveat: the live demo still runs the earlier build, without operators or KICK. That code is in the repo but not deployed yet.
What I learned / what I'd do differently
Spec tasks are a good unit of work for an agent. Task 5.2 was a single line in a task list, but the requirements and design around it gave the agent enough context. It changed only the hub, the numerics and the hub tests, and its work came with tests. Small, well-scoped tasks that you can check against a requirements file made reviewing easy.
Write the spec first, in Kiro. I drafted the spec after the code and outside Kiro, which is why tasks 1–4 were already checked. If I did this again, I would start in Kiro with requirements, then design, then tasks, and let the agent build more of the project task by task instead of only the final follow-up.
Keep steering and tasks consistent. My product.md steering file listed "channel operators and bans" as out of scope, while tasks.md listed operators and KICK as a follow-up. The agent built the task anyway. Next time I'd update the steering when the plan changes so the two don't contradict each other.
Verify hooks, don't assume them. My hook files loaded, but I never saw the stop hook fire. Next time I'd test each hook on its own first (save a .ts file, finish a short agent turn) and check the log before relying on it. And trust the workspace early, because Restricted Mode blocks hooks and tasks.
Be honest in the repo. I put a "What actually happened" log in the README. It was easy to write and makes clear which parts Kiro did and which parts it didn't.
Try it
- Live demo (mock AI, earlier build): http://192.74.249.13:8666. Classic clients can connect to
192.74.249.13:6667, then/join #seanceand type!ask is IRC dead? - Code: https://github.com/dyc19991225/seance-relay-chat (see "How Kiro was used" in the README)
- Video: https://youtu.be/InMUVHeS2YI
- Devpost: https://devpost.com/software/seance-relay-chat
IRC isn't dead. It was just waiting for someone to hold a séance.



Top comments (0)