callwire 2.2.0

High-performance bidirectional RPC over TCP with MessagePack framing — Go, Python, Rust interop
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# Callwire

**Callwire v2.2.0: High-performance, bidirectional RPC across 9 languages (Go, Python, Rust, TypeScript, Java, C, C++, Swift, COBOL) — over raw TCP with MessagePack framing.**

No schemas. No `.proto` files. No codegen. Export a function, call it from anywhere. **All 4 gRPC streaming patterns (unary, server-streaming, client-streaming, bidirectional), zero config.**

---

## Features

- **Zero-schema RPC** — export any function, call it from any language
- **All 4 gRPC patterns** — unary, server-streaming, client-streaming, bidirectional-streaming (full parity with gRPC, no `.proto` codegen)
- **Bidirectional** — clients and servers call each other over the same socket
- **9 languages shipped, 8 at full parity** — Go, Python, Rust, TypeScript, Java, C, C++, Swift all support all 4 patterns, both client and server. COBOL ships client + server for unary calls (its typical legacy-integration role). Roadmap: C#, Kotlin, Ruby
- **v3 Orchestration** — one `callwire.toml` spawns and connects workers automatically
- **Dynamic routing** — connect to registry, call any function without knowing worker addresses
- **TLS & mTLS** — secure transport with optional client certificate auth
- **Batch API** — fire multiple calls concurrently over a single connection
- **Auto-reconnect** — exponential backoff on connection drops

---

## Quick Start

### Go

```go
import "github.com/emaad/callwire"

// Export a function
callwire.Export("add", func(a, b int) int { return a + b })

// Call a remote function
client, _ := callwire.Connect("localhost:9090")
result, _ := callwire.Ref[int](client, "add")(10, 20) // 30
```

### Python

```python
import callwire

# 1. Export a local function (makes it server-ready)
@callwire.export
def add(a, b):
    return a + b

# 2. Dynamic module import (connects & invokes dynamically)
from callwire import add

result = add(10, 20)  # 30
```

### Rust

```rust
use callwire::{Client, register_unary};

register_unary("add", |(a, b): (i64, i64)| Ok(a + b));

let client = Client::connect("127.0.0.1:9090").await?;
let result: i64 = client.import("add", &(10i64, 20i64)).await?; // 30
```

### TypeScript

```typescript
import { Server, remote } from 'callwire';

// 1. Export local function
const server = new Server();
server.export('add', ([a, b]) => (a as number) + (b as number));
await server.serve('0.0.0.0', 9090);

// 2. Call dynamically using the remote Proxy
const result = await remote.add(10, 20); // 30
```

### Java

```java
import dev.callwire.core.*;

// 1. Export a function
Server server = new Server();
server.export("add", args -> {
    long a = ((Number) args.get(0)).longValue();
    long b = ((Number) args.get(1)).longValue();
    return a + b;
});
server.serve("localhost", 9090);

// 2. Call a remote function
Client client = new Client();
client.connect("localhost", 9090);
long result = client.callLong("add", 10L, 20L); // 30
```

### C

```c
#include "callwire.h"

CALLWIRE_EXPORT_INT2(add, a, b) { return a + b; }

callwire_server_t *server = callwire_server_new("0.0.0.0", 9090);
callwire_server_export(server, "add", add);
callwire_server_serve(server);

// Client
callwire_client_t *client = callwire_client_connect("localhost", 9090);
int64_t result;
callwire_call_ints(client, "add", (int64_t[]){10, 20}, 2, &result); // 30
```

### C++

```cpp
#include "callwire.hpp"

callwire::Server server("0.0.0.0", 9090);
server.exportFunc("add", [](int64_t a, int64_t b) { return a + b; });
server.serve();

// Client
callwire::Client client("localhost", 9090);
int64_t result = client.call<int64_t>("add", 10, 20); // 30
```

### Swift

```swift
import Callwire

let server = try Server(host: "0.0.0.0", port: 9090)
try server.exportTyped("add") { (a: Int64, b: Int64) in a + b }
try server.serve()

// Client
let client = try Client(host: "localhost", port: 9090)
let result = try client.add(10, 20) // 30 — dynamic call, no .call("add", ...)
```

### COBOL

Client + server, unary calls (COBOL's typical legacy-integration role — connecting to/from modern services with simple numeric/string payloads). Handlers are separate compiled subprograms registered by name:

```cobol
*> Server: register a handler subprogram
CALL "callwire_cobol_export_int2" USING
    BY VALUE WS-SERVER-PTR BY REFERENCE WS-FUNC-ADD
    BY REFERENCE WS-PROG-ADD RETURNING WS-RC END-CALL.

*> Client: one CALL statement
CALL "callwire_cobol_call_ints" USING
    BY VALUE WS-CLIENT-PTR BY REFERENCE WS-FUNC-ADD
    BY REFERENCE WS-ARGS BY VALUE WS-ARGC
    BY REFERENCE WS-INT-RESULT RETURNING WS-RC END-CALL.
```

Full setup → [cobol/README.md](cobol/README.md)

---

## Installation

### Published packages

- **npm**: `npm install @emaad-ansari/callwire` (v2.2.0)
- **PyPI**: `pip install callwire==2.2.0`
- **Cargo**: `cargo add callwire --version 2.2.0`
- **Maven Central**: `dev.callwire:callwire:2.2.0`

These auto-publish via CI on version bump.

### Build from source (C, C++, Swift, COBOL)

These SDKs aren't on a package registry yet — build against this repo directly.

**C** — CMake, no external dependencies:
```sh
cd c && mkdir build && cd build
cmake -DCALLWIRE_WITH_TLS=OFF .. && cmake --build . && ctest
```
Produces `libcallwire_core.{a,dylib}` + the `callwire` CLI. `#include "callwire.h"`, link against the static or shared lib.

**C++** — header-only (`cpp/include/callwire/callwire.hpp`), links directly against the C core sources:
```sh
cd cpp && mkdir build && cd build
cmake .. && cmake --build . && ctest
```

**Swift** — Swift Package Manager manifest exists (`swift/Package.swift`), but if `swift build` fails with a `PackageDescription`/`Foundation` SDK-mismatch error in your toolchain, use the bypass build script instead (see [swift/README.md](swift/README.md) for why):
```sh
cd swift && ./build.sh
```

**COBOL** — requires GnuCOBOL (`brew install gnucobol` / `apt install gnucobol`):
```sh
cd cobol && ./build.sh
```
Builds and runs both the import-side and export-side (COBOL-hosted server) round-trip tests automatically.

## Orchestration (v2)

Workers are auto-discovered by the `callwire init` CLI and declared in `callwire.toml`:

```toml
[project]
name = "my-project"
version = "1.0.0"

[services.go-worker]
dev_cmd  = "cd go/callwire && go run examples/server.go"
prod_cmd = "./bin/go-worker"

[services.rust-worker]
dev_cmd  = "cd rust && cargo run --quiet --example my-worker"
prod_cmd = "./bin/rust-worker"
```

Generate it with any of the native CLIs — they all produce the same output:

```bash
# Python
PYTHONPATH=python python3 -m callwire init

# Go
cd go/callwire && go run ./cmd/callwire/ init

# Rust
cargo run --manifest-path rust/Cargo.toml --bin callwire -- init

# TypeScript
npx tsx ts/src/cli.ts init

# Java
cd java && mvn -q compile exec:java -Dexec.args="init"

# C (also used by C++/Swift — they share the C core's CLI, no separate one)
cd c && mkdir -p build && cd build && cmake -DCALLWIRE_WITH_TLS=OFF .. && cmake --build . --target callwire
./callwire init
```

COBOL doesn't ship a `callwire init` — it's a client/server library (`cobol/src/cobol_shim.c`), not a standalone CLI tool, matching its scope as a legacy-integration SDK rather than an orchestrated worker.

Then call `init()` — Callwire starts a registry, spawns workers, and routes everything automatically:

```python
import callwire

callwire.init()  # reads callwire.toml, spawns workers

# Import functions dynamically as if they were local!
from callwire import add, predict

res1 = add(15, 27)      # → routed to Go worker
res2 = predict("data")  # → routed to Rust worker

callwire.shutdown()
```

See the full demo → [examples/2_orchestrated/demo.py](examples/2_orchestrated/demo.py)

### FastAPI integration

```python
from contextlib import asynccontextmanager
from fastapi import FastAPI
import callwire

@asynccontextmanager
async def lifespan(app: FastAPI):
    await callwire.async_init()
    yield
    await callwire.async_shutdown()

app = FastAPI(lifespan=lifespan)
```

---

## Service Discovery & Dynamic Routing

Workers self-register with the registry. Clients connect once and call anything dynamically — no worker addresses needed.

```python
# Python — dynamic module import
from callwire import add
result = add(10, 20)  # routed transparently via registry
```

```rust
// Rust — connect to registry, route calls transparently
let client = callwire::Client::connect_registry("127.0.0.1:29000").await?;
let sum: i32 = client.import("add", &(10, 20)).await?;
```

```typescript
// TypeScript — connect to registry, route calls transparently
const client = new Client();
await client.connectRegistry('127.0.0.1', 29000);
const sum = await client.call<number>('add', [10, 20]);
```

For load-balancing across multiple workers of the same type, use `DiscoverPool`:

```go
pool, _ := callwire.NewDiscoverPool("127.0.0.1:29090", "my-service")
result, _ := callwire.DiscoverRef[string](pool, "say_hello")("World")
```

---

## TLS & mTLS

```go
// Go — TLS server
callwire.ServeWithTLS("0.0.0.0:9090", callwire.TLSConfig{
    CertPem: cert,
    KeyPem:  key,
})

// Go — TLS client (with optional mTLS)
client, _ := callwire.ConnectWithReconnectTLS("localhost:9090", callwire.TLSConfig{
    CAPem: caCert,
})
```

```python
# Python — TLS client
client.connect("localhost", 9090, tls={
    "cafile":   "ca.pem",
    "certfile": "client.pem",  # mTLS
    "keyfile":  "client.key",  # mTLS
})
```

```rust
// Rust — TLS client
let client = callwire::TlsConfig { ca_pem: Some(ca_pem), ..Default::default() }
    .connect("127.0.0.1:9090").await?;
```

```typescript
// TypeScript — TLS server
const server = new Server();
await server.serve('0.0.0.0', 9090, {
  cert: fs.readFileSync('server.pem', 'utf8'),
  key:  fs.readFileSync('server.key', 'utf8'),
});

// TypeScript — TLS client (skip verify for self-signed)
const client = new Client({ tls: { rejectUnauthorized: false } });
await client.connect('127.0.0.1', 9090);

// TypeScript — TLS client with CA verification + mTLS
const clientMTLS = new Client({ tls: {
  ca:   fs.readFileSync('ca.pem', 'utf8'),
  cert: fs.readFileSync('client.pem', 'utf8'),
  key:  fs.readFileSync('client.key', 'utf8'),
}});
await clientMTLS.connect('127.0.0.1', 9090);
```

---

## Streaming

```typescript
// TypeScript — server-side streaming
server.export('count_up', async function* ([n]) {
  for (let i = 1; i <= (n as number); i++) yield i;
});

for await (const chunk of client.callStream<number>('count_up', [5])) {
  console.log(chunk); // 1, 2, 3, 4, 5
}
```

---

## Examples

```
examples/
├── 1_standalone/   — One Go server, one client (Python / Rust / TypeScript)
└── 2_orchestrated/ — One command spawns Go + Rust workers automatically
```

→ [examples/README.md](examples/README.md)

---

## Configuration

| Env Var | Default | Description |
|---|---|---|
| `CALLWIRE_HOST` | `localhost` | Default hostname for auto-serving & clients |
| `CALLWIRE_PORT` | `9090` | Default port |
| `CALLWIRE_AUTO` | `1` | Set to `0` to disable auto-server on Export |
| `CALLWIRE_REGISTRY` | *(set by orchestrator)* | Registry address for worker mode |
| `CALLWIRE_SPAWNED` | *(set by orchestrator)* | `1` when running as a managed worker |

---

## Running Tests

```bash
# Go
cd go/callwire && go test -v ./...

# Python
cd python && .venv/bin/python3 -m unittest discover -s . -p "test_*.py"

# Rust
cd rust && cargo test -- --nocapture

# TypeScript
cd ts && npm test

# Java
cd java && mvn test

# C
cd c && mkdir -p build && cd build && cmake -DCALLWIRE_WITH_TLS=OFF .. && cmake --build . && ctest

# C++
cd cpp && mkdir -p build && cd build && cmake .. && cmake --build . && ctest

# Swift
cd swift && ./build.sh

# COBOL
cd cobol && ./build.sh
```

---

## Wire Protocol

Callwire uses a simple, fully-specified binary protocol — implement it in any language.  
→ [SPEC.md](SPEC.md)

---

## Performance

`~33 µs` per round-trip · `~81K calls/sec` on a single connection · **1.3–1.7× faster than gRPC** for unary workloads on Apple M4.

| Metric | Callwire | gRPC | Δ |
|--------|----------|------|---|
| Latency — noop | **32.7 µs** | 57.7 µs | 1.76× faster |
| Latency — add(a, b) | **34.6 µs** | 58.8 µs | 1.70× faster |
| Throughput (10 workers) | **80K calls/sec** | 49K calls/sec | 1.65× faster |
| Throughput (100 workers) | **81K calls/sec** | 62K calls/sec | 1.30× faster |

Full breakdown → [benchmarks/compare_grpc.md](benchmarks/compare_grpc.md)

---

## How It Compares

### vs gRPC

| Dimension | Callwire | gRPC |
|-----------|----------|------|
| **Schema** | None — export any function | Required `.proto` files + codegen |
| **Latency (noop)** | **32.7 µs** | 57.7 µs |
| **Throughput** | **81K calls/sec** | 62K calls/sec |
| **Transport** | Raw TCP (4-byte length + msgpack) | HTTP/2 + HPACK |
| **Bidirectional** | Same socket, any order | HTTP/2 streams (half-duplex per stream) |
| **Orchestration** | Built-in `callwire.toml` + `init()` | External (Kubernetes, Consul, etc.) |
| **Languages** | **9 shipped (Go, Python, Rust, TS, Java, C, C++, Swift, COBOL), roadmap C#/Kotlin/Ruby** | 11+ languages |
| **Streaming** | **All 4 (unary, server, client, bidi)** | All 4 |
| **Browser** | No | Yes (gRPC-Web) |
| **Ecosystem** | Minimal | Envoy, gRPC-Gateway, health probes, reflection |

**When to pick Callwire:** polyglot services, developer velocity over formal schemas, teams that want zero-config orchestration and legacy-system bridging (COBOL↔Go/Python/Rust in one wire protocol, no middleware).

**When to pick gRPC:** cross-org APIs, browser clients, extensive tooling ecosystem (reflection, health checks, gRPC-Gateway), mature production observability.

### vs protosocket (Momento)

Rust-only TCP RPC framework (v1: 100KHz, sub-ms p99.9). Callwire has protosocket beat on language coverage (4 runtimes vs 1) and built-in orchestration. protosocket is faster per-core for pure Rust workloads and has production battle-testing at Momento scale.

### vs ZeroRPC / Zero (zeroapi)

Python MessagePack-over-ZeroMQ RPC. Zero hits ~100K req/s on TCP but is Python-only and has a hard `gevent` dependency. Callwire matches that throughput **in every language** and adds TLS, streaming, orchestration, and cross-language interop.

### vs MagicOnion (C#)

MessagePack-over-gRPC for .NET/Unity. Shares Callwire's zero-schema philosophy (C# interfaces instead of `.proto`) but is C#-only and inherits gRPC's HTTP/2 overhead. Callwire is 1.3–1.7× faster on wire latency and spans 9 runtimes today, with a C# SDK on the roadmap.

### vs Cap'n Proto RPC

Zero-copy RPC with time-travel (promise pipelining). Extremely fast deserialization, but requires `.capnp` schemas and supports only 6 languages. Callwire has no schema, wider language coverage, and built-in orchestration.

### vs Apache Thrift

Mature, 20+ language RPC with multiple transports. Requires `.thrift` schemas + codegen, no streaming. Callwire is simpler to set up and faster for the languages it supports.

### vs NPRPC

Feature-rich multi-transport RPC (TCP/WS/HTTP3/QUIC/SharedMemory) for C++/TS/Swift with FlatBuffers. Strong where Callwire doesn't go (browsers, QUIC). But Callwire has C++/TS/Swift support (via C core ABI), no schema/codegen, and built-in orchestration. NPRPC's multi-transport is valuable where protocols vary; Callwire focuses on raw-TCP performance and simplicity.

---

## Moat

Callwire's defensible advantages:

1. **Zero-schema across 9 shipped languages** — no other library lets you export a function in Go/Python/Rust/TS/Java/C/C++/Swift/COBOL and call it from any of the others without a schema definition or codegen step. Same zero-schema wire format everywhere. C#/Kotlin/Ruby on the roadmap.

2. **All 4 gRPC patterns, zero-config** — unary, server-streaming, client-streaming, bidi-streaming all supported. No `.proto` files, no codegen. Export a function that streams; it works from any language.

3. **Legacy-to-modern bridge** — the only RPC framework connecting COBOL mainframes directly to Go/Rust/TS/Python/Java microservices over the same zero-schema wire protocol. No gateway layer, no middleware required.

4. **Built-in orchestration**`callwire init` auto-detects workers across all languages from a single config file. Competitors require external process managers (supervisord), Kubernetes, or shell scripts.

5. **Bidirectional symmetry** — the same socket serves both client and server roles. Only protosocket offers this; gRPC, Thrift, Cap'n Proto enforce client/server roles.

6. **Protocol simplicity** — 4-byte length prefix + MessagePack. Full spec fits on one page ([SPEC.md]SPEC.md). Implementing from scratch takes hours, not weeks.

7. **C core ABI** — languages without hand-crafted SDKs can wrap the stable C ABI (`c/include/callwire.h`). Swift, COBOL, and others depend on this frozen interface. Lowers barrier for adding new runtimes.

8. **Per-language CLI** — each SDK ships its own `callwire init` with zero cross-language build dependencies.