<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bruno Xavier</title>
    <description>The latest articles on Forem by Bruno Xavier (@bfxavier).</description>
    <link>https://forem.com/bfxavier</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F597582%2Fbb76d480-b63b-48e9-ab49-c43e5475f82a.jpeg</url>
      <title>Forem: Bruno Xavier</title>
      <link>https://forem.com/bfxavier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bfxavier"/>
    <language>en</language>
    <item>
      <title>I built a relay so my AI agents stop talking through me</title>
      <dc:creator>Bruno Xavier</dc:creator>
      <pubDate>Sat, 28 Mar 2026 20:34:27 +0000</pubDate>
      <link>https://forem.com/bfxavier/i-built-a-relay-so-my-ai-agents-stop-talking-through-me-1okl</link>
      <guid>https://forem.com/bfxavier/i-built-a-relay-so-my-ai-agents-stop-talking-through-me-1okl</guid>
      <description>&lt;p&gt;My coworker and I were both using Claude Code on a shared infra project. He was building services, I was setting up Pulumi. Our workflow was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Claude tells me something about the deploy structure&lt;/li&gt;
&lt;li&gt;I copy it into Slack&lt;/li&gt;
&lt;li&gt;My coworker pastes it into his Claude&lt;/li&gt;
&lt;li&gt;His Claude responds&lt;/li&gt;
&lt;li&gt;He screenshots it back to me&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We were the middleware. Two humans acting as a message bus between two AIs.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/bfxavier/handoff" rel="noopener noreferrer"&gt;Handoff&lt;/a&gt; — an open-source relay that lets agents talk to each other directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Give agents a shared communication layer with the same primitives they'd need if they were humans on a team: channels, threads, mentions, read receipts, and shared status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Claude:     "ArgoCD expects deploy/{service}/kustomization.yaml"
Their Claude:    "Structured deploy/ to match. checkout-api, inventory-service ready."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No human in the loop. No copy-paste. No screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Create a team (one curl)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://handoff.xaviair.dev/api/signup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"team_name":"my-team","sender_name":"my-name"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back an API key. Share additional keys with teammates via the &lt;code&gt;create_key&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Everyone adds the MCP server (one command)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add handoff &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;RELAY_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://handoff.xaviair.dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;RELAY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key_here &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; handoff-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Claude now has 17 tools for coordination — it discovers and uses them naturally as part of your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Agents coordinate directly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude gets tools like &lt;code&gt;post_message&lt;/code&gt;, &lt;code&gt;read_unread&lt;/code&gt;, &lt;code&gt;set_status&lt;/code&gt;, &lt;code&gt;ack&lt;/code&gt;. When you tell it "check the build channel for updates" or "let the deployer know we're ready", it knows what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Channels &amp;amp; threads
&lt;/h3&gt;

&lt;p&gt;Agents communicate through named channels (&lt;code&gt;build&lt;/code&gt;, &lt;code&gt;deploy&lt;/code&gt;, &lt;code&gt;review&lt;/code&gt;). Messages support threading — reply to a specific message to keep conversations organized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mentions
&lt;/h3&gt;

&lt;p&gt;When posting a message, agents can set a &lt;code&gt;mention&lt;/code&gt; field to direct it at a specific agent. The receiving agent filters on their name to find messages meant for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read receipts (acks)
&lt;/h3&gt;

&lt;p&gt;After reading messages, agents call &lt;code&gt;ack&lt;/code&gt; with the last message ID. Other agents can check &lt;code&gt;get_acks&lt;/code&gt; to see who's caught up. There's also &lt;code&gt;read_unread&lt;/code&gt; which returns only messages after your last ack — the recommended way to poll for new work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared status
&lt;/h3&gt;

&lt;p&gt;Key-value status entries on channels represent shared state: &lt;code&gt;stage = building&lt;/code&gt;, &lt;code&gt;lock = agent-1&lt;/code&gt;, &lt;code&gt;progress = 4/5&lt;/code&gt;. Every write is logged, so you can query the full status change history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-time streaming
&lt;/h3&gt;

&lt;p&gt;SSE endpoint for real-time push. Agents don't have to poll — they can subscribe to a channel and get messages as they arrive.&lt;/p&gt;

&lt;h3&gt;
  
  
  E2EE
&lt;/h3&gt;

&lt;p&gt;Optional AES-256-GCM client-side encryption. Set an &lt;code&gt;encryptionKey&lt;/code&gt; in the SDK and the server never sees plaintext message content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Channel-scoped permissions
&lt;/h2&gt;

&lt;p&gt;This is the feature I'm most proud of. Each API key gets a permissions map that controls exactly which channels it can access and at what level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"monitoring"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;read&lt;/strong&gt; — view messages and status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;write&lt;/strong&gt; — read + post messages, ack, set status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;admin&lt;/strong&gt; — write + delete channels and messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;"*"&lt;/code&gt; as a wildcard for full access across all channels.&lt;/p&gt;

&lt;p&gt;I tested this with a 7-agent deployment simulation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Permissions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;orchestrator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*: admin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;builder&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;build: write&lt;/code&gt;, &lt;code&gt;deploy: read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reviewer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;review: write&lt;/code&gt;, &lt;code&gt;build: read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deployer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deploy: write&lt;/code&gt;, &lt;code&gt;build: read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;monitor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;monitoring: write&lt;/code&gt;, &lt;code&gt;build+deploy: read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qa&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;review: write&lt;/code&gt;, &lt;code&gt;build+deploy: read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;notifier&lt;/td&gt;
&lt;td&gt;all channels: &lt;code&gt;read&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The simulation ran a full deploy pipeline — orchestrator kicks off, builder compiles and posts results, reviewer approves, QA signs off, deployer rolls out, monitor checks health. Every unauthorized write was blocked. The notifier could read everything but write to nothing.&lt;/p&gt;

&lt;p&gt;This means you can give a junior dev's agent read-only access to &lt;code&gt;production-deploys&lt;/code&gt; while letting senior agents write to it. Or give a monitoring bot read access everywhere without the ability to post.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript SDK
&lt;/h2&gt;

&lt;p&gt;If you're building custom agents outside of Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;handoff-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Handoff&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handoff-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Handoff&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://handoff.xaviair.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relay_...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;infra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EKS cluster ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jordan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;infra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What node instance type?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;infra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;infra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;infra&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// SSE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The server is Go with Redis. Messages use Redis streams for ordered IDs, cursor-based pagination, and blocking reads for SSE. All keys are team-namespaced (&lt;code&gt;t:{teamID}:&lt;/code&gt;) for multi-tenant isolation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server/           Go (net/http + go-redis)
├── store/        Redis data layer (34 tests)
├── handler/      HTTP handlers, middleware, SSE (48 tests)

src/              TypeScript
├── sdk.ts        SDK with E2EE support
├── mcp.ts        MCP server (17 tools)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;82 tests, self-hostable with &lt;code&gt;docker compose up -d&lt;/code&gt;. The Go binary is ~15MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Message schemas/contracts so agents can agree on content format&lt;/li&gt;
&lt;li&gt;TTL/expiry for channels and messages&lt;/li&gt;
&lt;li&gt;Per-key rate limiting&lt;/li&gt;
&lt;li&gt;Dashboard for observing agent conversations in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The hosted relay is free at &lt;code&gt;handoff.xaviair.dev&lt;/code&gt;. Self-host with Docker if you prefer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/bfxavier/handoff" rel="noopener noreferrer"&gt;github.com/bfxavier/handoff&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;code&gt;npm install handoff-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP setup:&lt;/strong&gt; one command, shown above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're running multi-agent workflows and tired of being the message bus, give it a shot. Stars appreciated.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>typescript</category>
      <category>go</category>
    </item>
    <item>
      <title>Every company I've worked at had the same broken interview process</title>
      <dc:creator>Bruno Xavier</dc:creator>
      <pubDate>Fri, 13 Mar 2026 19:06:43 +0000</pubDate>
      <link>https://forem.com/bfxavier/every-company-ive-worked-at-had-the-same-broken-interview-process-4cln</link>
      <guid>https://forem.com/bfxavier/every-company-ive-worked-at-had-the-same-broken-interview-process-4cln</guid>
      <description>&lt;p&gt;There's a dirty secret in tech hiring: the people actually doing the interviews have zero tooling.&lt;/p&gt;

&lt;p&gt;HR has their ATS. Recruiters have their pipelines. And the tech lead who just spent 45 minutes evaluating a senior engineer candidate? They have a Slack message and a fading memory.&lt;/p&gt;

&lt;p&gt;I've been on the interviewer side of this at several companies — as a tech lead, staff engineer, head of development. I've done hundreds of technical interviews across these roles. And every single company had the same problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "system"
&lt;/h2&gt;

&lt;p&gt;Here's what interview feedback looks like at most engineering teams I've been part of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone writes a few lines in Slack right after the interview&lt;/li&gt;
&lt;li&gt;Someone else waits until the debrief meeting and goes from memory&lt;/li&gt;
&lt;li&gt;A third person opens a Google Doc, writes half a page, then never shares the link&lt;/li&gt;
&lt;li&gt;Everyone shows up to the hiring debrief with completely different formats and criteria&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You end up in a meeting where one person says "she was really strong technically" and another says "I didn't love the communication" and there's no way to actually compare because everyone evaluated different things in different ways.&lt;/p&gt;

&lt;p&gt;The candidate who interviewed on Monday gets a different process than the one who interviewed on Friday. It's not fair to them and it leads to bad decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nobody builds for this
&lt;/h2&gt;

&lt;p&gt;There's a whole industry of hiring tools, but almost all of them are built for HR and recruiters. They're ATS platforms — Greenhouse, Lever, Ashby — with scorecards bolted on as a feature.&lt;/p&gt;

&lt;p&gt;The problem is that the tech lead never logs into the ATS. They get a calendar invite, do the interview, and need to put their feedback somewhere fast. They're not going to learn a new platform for this. They're not going to ask their manager for a Greenhouse license.&lt;/p&gt;

&lt;p&gt;So the feedback goes into Slack. Or Obsidian. Or nowhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I got tired of this and built &lt;a href="https://techscore.dev" rel="noopener noreferrer"&gt;techscore.dev&lt;/a&gt; — a simple scoring app for technical interviews. No backend, no signup, no data leaves your browser. Just open it during or after an interview and score.&lt;/p&gt;

&lt;p&gt;It evaluates candidates across 6 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart&lt;/strong&gt; — problem decomposition, adaptability, structured reasoning, learning agility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get Things Done&lt;/strong&gt; — delivery track record, live coding execution, pragmatism, ownership&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drive&lt;/strong&gt; — initiative, self-motivation, ambition, persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Culture Fit&lt;/strong&gt; — collaboration, communication, humility, values alignment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical Experience&lt;/strong&gt; — architecture, testing, frameworks, code quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Experience&lt;/strong&gt; — domain knowledge, data &amp;amp; analytics, tooling, industry awareness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each sub-competency gets a 1-4 score. You can add notes per item. The sidebar shows a running total. When you're done, you export a markdown scorecard and drop it wherever your team communicates — Slack, Notion, a PR comment, whatever.&lt;/p&gt;

&lt;p&gt;That markdown file is the whole point. It's structured, it's consistent, and when three interviewers all export one, you can actually compare them side by side in the debrief.&lt;/p&gt;

&lt;h2&gt;
  
  
  The categories are opinionated
&lt;/h2&gt;

&lt;p&gt;I know. Six categories with four items each is a specific framework, not a universal truth. It's based on what I've found useful over years of interviewing engineers — loosely inspired by the "Smart and Gets Things Done" philosophy but extended to cover the things I kept wishing I had tracked.&lt;/p&gt;

&lt;p&gt;You might disagree with some of them. That's fine. I'd rather give you an opinionated starting point than a blank template where you have to invent your own rubric every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened when I shared it
&lt;/h2&gt;

&lt;p&gt;I originally built this just for myself. Then I shared it with the other engineers doing interviews at my company and something unusual happened — they actually used it. Repeatedly. Without me nagging them.&lt;/p&gt;

&lt;p&gt;That almost never happens with internal tools. It only works when the tool is faster than whatever someone was already doing. In this case, the bar was "write a Slack message," so the tool had to be really low friction to beat that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://techscore.dev" rel="noopener noreferrer"&gt;techscore.dev&lt;/a&gt; — open it, score a candidate, export the scorecard. Takes about 2 minutes.&lt;/p&gt;

&lt;p&gt;If you do technical interviews and have opinions about the scoring categories, I'd love to hear them. The framework is the part I'm least sure about and most interested in iterating on.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>software</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Quick Raspberry Pi rTorrent and RuTorrent Install</title>
      <dc:creator>Bruno Xavier</dc:creator>
      <pubDate>Tue, 16 Mar 2021 18:30:56 +0000</pubDate>
      <link>https://forem.com/bfxavier/quick-raspberry-pi-rtorrent-and-rutorrent-install-nje</link>
      <guid>https://forem.com/bfxavier/quick-raspberry-pi-rtorrent-and-rutorrent-install-nje</guid>
      <description>&lt;p&gt;Do you want to transform your Raspberry Pi into a lightweight, always on, seedbox? For this project, we are going to do just that using rTorrent and RuTorrent, using a fresh Ubuntu 20.04 install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is rTorrent and RuTorrent?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/rakshasa/rtorrent" rel="noopener noreferrer"&gt;rTorrent&lt;/a&gt;  is a text-based &lt;a href="https://en.wikipedia.org/wiki/BitTorrent" rel="noopener noreferrer"&gt;BitTorrent&lt;/a&gt; client, based on ncurses, that relies on the libTorrent libraries for Unix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Novik/ruTorrent" rel="noopener noreferrer"&gt;RuTorrent&lt;/a&gt; is a web UI interface for a torrent application, rTorrent. It helps you to connect, manage,  remove torrents to your slot, monitor rTorrent settings, and do a lot of other things through its plugins.It is a highly powerful torrent application and has rich features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Make sure your machine is up-to-date by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to also write down your LAN ip&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostname -I
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing rtinst
&lt;/h2&gt;

&lt;p&gt;We'll be using &lt;a href="https://github.com/arakasi72/rtinst" rel="noopener noreferrer"&gt;rtinst&lt;/a&gt;, a bash script setup, that will take care of most of the installation.&lt;/p&gt;

&lt;p&gt;Just run the following command to install it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo bash -c "$(wget --no-check-certificate -qO - https://raw.githubusercontent.com/arakasi72/rtinst/master/rtsetup)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing rTorrent and RuTorrent
&lt;/h2&gt;

&lt;p&gt;Now that we have rtinst installed, we just need to run it and watch the magic happen&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo rtinst -l -d -t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are passing the following parameters&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-l enables log output to ~/rtinst.log
-d enables http downloads
-t keeps the default ssh port
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a few prompts will show, asking you which IP Address to use, which you should point to the one found above and asking you to confirm it. It will also ask you to enter a password for the WebUI.&lt;/p&gt;

&lt;p&gt;You'll see the following in your prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set Password for RuTorrent web client
Enter a password (6+ chars)
or leave blank to generate a random one
Please enter the new password:
Enter the new password again:
No additional users to add

No more user input required, you can complete unattended
It will take approx 10 minutes for the script to complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go grab a cup of coffee and wait for the installation to finish.&lt;/p&gt;

&lt;p&gt;If everything goes well, you should see all the info necessary to access your fresh RuTorrent Web UI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary of Installation (Important Information, please read

SSH Configured
   SSH port set to 22
   root login directly from SSH disabled
   login with ubuntu and switch to root using: sudo su

FTP Server
   vsftpd 3.0.3-12 installed
   ftp port set to 46628
   ftp client should be set to explicit ftp over tls using port 46628

rtorrent torrent client
   rtorrent 0.9.8 installed
   crontab entries made. rtorrent and irssi will start on boot for ubuntu

RuTorrent Web GUI
   RuTorrent 3.10 installed
   rutorrent can be accessed at https://10.10.10.1/rutorrent
   rutorrent password as set by user
   to change rutorrent password enter: rtpass

   If enabled, access https downloads at https://10.10.10.1/download/ubuntu

IMPORTANT: SSH Port set to 22
IMPORTANT: SSH Port set to 22
IMPORTANT: SSH Port set to 22
Please ensure you can login BEFORE closing this session

The above information is stored in rtinst.info in your home directory.
To see contents enter: cat /home/ubuntu/rtinst.info

To install webmin enter: sudo rtwebmin

SCROLL UP IF NEEDED TO READ ALL THE SUMMARY INFO
PLEASE REBOOT YOUR SYSTEM ONCE YOU HAVE NOTED THE ABOVE INFORMATION

Thank You for choosing rtinst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you follow the URL you should be greeted with the RuTorrent WebUI!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xexicjelk1jajbkktlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xexicjelk1jajbkktlp.png" alt="RuTorrent WebUI" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thanks to rtinst, installing rTorrent with ruTorrent is a breeze.&lt;/p&gt;

&lt;p&gt;Hope you all enjoyed it!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>raspberrypi</category>
    </item>
  </channel>
</rss>
