Skip to content
Back to skills

Twirp Rpc Patterns

ASecurity

Twirp lightweight RPC over HTTP/1.1 with Protobuf or JSON transport. Service routing, error codes, middleware hooks, and no-proxy deployment for simple agent-to-agent calls. Sources: twitchtv/twirp (Apache-2.0).

  • 10 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 9, 2026
ai-agentstypescriptbashnode

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add yanacuti1121/Yana-AI --skill twirp-rpc-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Twirp Rpc Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Twirp Rpc Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yanacuti1121-twirp-rpc-patterns/badge)](https://www.skillsdirectory.com/skills/yanacuti1121-twirp-rpc-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: twirp-rpc-patterns
description: Twirp lightweight RPC over HTTP/1.1 with Protobuf or JSON transport. Service routing, error codes, middleware hooks, and no-proxy deployment for simple agent-to-agent calls. Sources: twitchtv/twirp (Apache-2.0).
origin: yana-ai — synthesized from twitchtv/twirp (Apache-2.0)
license: Apache-2.0
version: 1.0.0
compatibility: yana-ai >= 1.3.50
---

# /twirp-rpc-patterns

## When to Use

- gRPC is overkill: no HTTP/2 infrastructure, no streaming required
- Simple request-response RPC with Protobuf encoding over plain HTTP
- Behind standard reverse proxies (nginx, Caddy) without grpc_pass config
- JSON fallback: same service works with both Protobuf and JSON clients

## Do NOT use for

- Bidirectional streaming (use [[grpc-streaming-rpc]])
- Browser clients without CORS concerns (Twirp JSON works, but REST is simpler)

---

## Twirp URL convention

```
POST /twirp/<package>.<Service>/<Method>
Content-Type: application/protobuf   (binary)
           or application/json       (human-readable fallback)
```

---

## Node.js Twirp server (twirp-ts)

```typescript
import { createServer }   from 'http'
import { TwirpServer, chainHooks } from 'twirp-ts'
import { AgentBus }       from './generated/agent_bus_twirp'  // generated from proto

const server = new TwirpServer({
  service: AgentBus,
  implementation: {
    async DispatchTask(ctx, req) {
      const taskId = `task-${Date.now()}`
      return { taskId, accepted: true }
    },

    async GetTaskStatus(ctx, req) {
      const status = await taskRegistry.get(req.taskId)
      if (!status) throw new TwirpError(TwirpErrorCode.NotFound, `task ${req.taskId} not found`)
      return { taskId: req.taskId, status }
    },
  },
  hooks: chainHooks(
    {
      requestReceived: async (ctx) => {
        ctx.agentId = ctx.req.headers['x-agent-id'] as string
        if (!ctx.agentId) throw new TwirpError(TwirpErrorCode.Unauthenticated, 'missing x-agent-id')
      },
    }
  ),
})

createServer((req, res) => server.httpHandler(req, res)).listen(8080)
```

---

## Twirp client (JSON transport)

```typescript
import fetch from 'node-fetch'

async function twirpCall<Req, Res>(
  baseUrl:  string,
  service:  string,
  method:   string,
  body:     Req,
  agentId:  string
): Promise<Res> {
  const url = `${baseUrl}/twirp/${service}/${method}`
  const res = await fetch(url, {
    method:  'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Agent-Id':   agentId,
    },
    body: JSON.stringify(body),
  })

  if (!res.ok) {
    const err = await res.json()
    throw new Error(`[twirp] ${err.code}: ${err.msg}`)
  }
  return res.json()
}

const ack = await twirpCall(
  'http://localhost:8080',
  'yamtam.AgentBus',
  'DispatchTask',
  { tool: 'bash', paramsJson: '{"cmd":"ls"}' },
  'did:yamtam:0xabc123'
)
```

---

## Twirp error codes

```typescript
import { TwirpError, TwirpErrorCode } from 'twirp-ts'

// Standard error codes (map to HTTP status):
// Canceled           → 408
// Unknown            → 500
// InvalidArgument    → 400
// NotFound           → 404
// AlreadyExists      → 409
// PermissionDenied   → 403
// Unauthenticated    → 401
// ResourceExhausted  → 429  ← rate limit
// Internal           → 500

throw new TwirpError(TwirpErrorCode.ResourceExhausted, 'rate limit exceeded')
```

---

## Anti-Fake-Pass Checklist

```
❌ Missing Content-Type: application/json → Twirp returns 415 Unsupported Media Type
❌ URL path case mismatch — Twirp routing is case-sensitive: /DispatchTask ≠ /dispatchtask
❌ Throwing plain Error in handler → becomes TwirpErrorCode.Internal with no details
❌ No authentication hook → any caller can invoke any method
❌ Protobuf and JSON clients mixed without content-type check → binary parsed as JSON
❌ TwirpErrorCode.ResourceExhausted not mapped to retry logic on client → no backoff
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.