> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbindu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom SDKs

> How to build Bindu SDKs for Rust, Go, Swift, or any language with gRPC

You have seen how the TypeScript SDK works. Now imagine doing the same thing in Rust, Go, Swift, or any language that has gRPC support. That is what this page is for.

The key insight is that the goal is not to rewrite the core. The goal is to build a new driver that can plug into the same engine. Because the sidecar keeps infrastructure centralized, each new language only needs a thin adapter — usually 200–400 lines of actual logic.

<Info>
  The architectural rule is simple: new languages get new drivers, not new engines.
</Info>

***

## What an SDK Does

Before we dive into the steps, it helps to understand the four responsibilities every SDK shares. Under the hood, the SDK does four things:

1. **Implements `AgentHandler`** — a gRPC server that receives `HandleMessages` calls from the core and invokes the developer's handler
2. **Calls `BinduService.RegisterAgent`** — a gRPC client that registers the agent with the core
3. **Launches the Python core** — spawns `bindu serve --grpc` as a child process
4. **Exposes `bindufy(config, handler)`** — the developer-facing API that orchestrates all of the above

The proto contract at `proto/agent_handler.proto` is the single source of truth. As long as your SDK speaks that contract, it works with the core.

***

## Step 1: Generate gRPC Stubs

Before your SDK can talk to the core, it needs to speak the same language. The first job is to generate typed client and server stubs from the protobuf definition. Every gRPC ecosystem has its own tooling for this:

| Language | Tool                 | Command                                                              |
| -------- | -------------------- | -------------------------------------------------------------------- |
| Rust     | `tonic-build`        | Add `tonic-build` to `build.rs`, it compiles the proto at build time |
| Go       | `protoc-gen-go-grpc` | `protoc --go_out=. --go-grpc_out=. proto/agent_handler.proto`        |
| Swift    | `grpc-swift`         | `protoc --swift_out=. --grpc-swift_out=. proto/agent_handler.proto`  |
| C#       | `Grpc.Tools`         | NuGet package auto-generates from `.proto` in the project            |

The generated code gives you typed messages and service interfaces. Everything else in the SDK is built on top of those stubs.

***

## Step 2: Implement AgentHandler (Server)

This is the server that the core will call when work arrives. It is the language-specific half of the sidecar — how the engine talks back to the driver. Of the three RPCs, `HandleMessages` is the critical one.

### HandleMessages / HandleMessagesStream

This RPC receives conversation history, calls the developer's handler, and returns the response. Here is the shape of the request and response:

```text theme={null}
Input:  HandleRequest { messages: [ChatMessage{role, content}, ...] }
Output: HandleResponse {
  content: string,
  state: string,
  prompt: string,
  is_final: bool,
  metadata: map<string, string>
}
```

The rules for converting your developer's handler return value into a `HandleResponse` are straightforward:

* If the handler returns a plain string, set `content` to the string and leave `state` empty.
* If the handler returns a state transition, set `state` to `"input-required"` or `"auth-required"` and `prompt` to the follow-up question.
* If the handler throws, return a gRPC `INTERNAL` error with the error message.
* Unary path (`HandleMessages`): Set `is_final` to `true` (there is only one response).
* Streaming path (`HandleMessagesStream`): Yield multiple `HandleResponse` chunks with `is_final: false`, and set `is_final: true` on the last chunk.
* Both paths use the exact same `HandleResponse` message shape.

### GetCapabilities

Return static info about the SDK. This is how the core discovers what your agent supports.

```text theme={null}
Output: GetCapabilitiesResponse { name, description, version, supports_streaming }
```

### HealthCheck

Return `{healthy: true, message: "OK"}`. `GrpcAgentClient.health_check()` can call
this on demand, but the core's `BinduService.Heartbeat` handler does **not**
invoke it — heartbeats only update the registry timestamp. Implement the RPC
anyway so future eviction sweeps and external watchdogs work.

***

## Step 3: Implement BinduService Client

Now build the other direction — the client that talks from your SDK into the core. This is how your SDK registers the agent and keeps it alive.

### RegisterAgent

This is the main entry point. It sends config, skills, and the SDK's callback address:

```text theme={null}
Input: RegisterAgentRequest {
  config_json: string,            // Full config as JSON
  skills: [SkillDefinition],      // Skills with raw file content
  grpc_callback_address: string   // e.g., "localhost:50052"
}
Output: RegisterAgentResponse { success, agent_id, did, agent_url, error }
```

The `config_json` is a JSON string matching the Python `bindufy()` config format. That is intentional. The schema lives in one place, and SDKs serialize to it.

### Heartbeat

Call this on a regular interval — the reference TypeScript SDK uses 30 seconds — to
signal liveness. The cadence is an SDK convention, not a core-enforced contract;
the Python core records `last_heartbeat` but does not currently evict stale
agents, so the value mainly matters when an external watchdog or future eviction
sweep is wired in.

```text theme={null}
Input: HeartbeatRequest { agent_id, timestamp }
```

### UnregisterAgent

Call this when your SDK process is shutting down cleanly so the core can release
the agent slot immediately. Today this is the **only** way an agent leaves the
registry — the Python core does not currently evict on heartbeat timeout, so
without `UnregisterAgent` a crashed SDK leaks its registry entry until the core
restarts.

```text theme={null}
Input: UnregisterAgentRequest { agent_id }
```

<Note>
  The TypeScript reference SDK does **not** currently call `UnregisterAgent` — its `SIGINT`
  handler only clears the heartbeat interval and force-shuts the gRPC server. New SDKs
  should improve on this and invoke `UnregisterAgent` from their shutdown path.
</Note>

Together, `RegisterAgent`, `Heartbeat`, and `UnregisterAgent` are the control plane for the sidecar.

***

## Step 4: Implement Core Launcher

The SDK must be able to start the engine automatically. This is what makes the developer experience feel local even though the architecture is split. Without it, the developer would need to manually start the Python core in a separate terminal — and that would break the illusion of a single-process experience.

<Note>
  Pass the executable and arguments as a properly split array to your language's process
  spawner — not as a single shell string.
</Note>

The detection logic tries three approaches in order:

<Note>
  Pass the executable and arguments as a properly split array to your language's process
  spawner — not as a single shell string.
</Note>

<Steps>
  <Step title="Check if bindu CLI is available">
    Run `bindu` with args `["serve", "--grpc", "--grpc-port", "3774"]`
  </Step>

  <Step title="If not, check if uv is available">
    Run `uv` with args `["run", "bindu", "serve", "--grpc", "--grpc-port", "3774"]`
  </Step>

  <Step title="If not, fall back to Python module">
    Run `python3` with args `["-m", "bindu.cli", "serve", "--grpc", "--grpc-port", "3774"]`
  </Step>

  <Step title="Wait for :3774 to accept TCP connections">
    Poll every 500ms, timeout 30s.
  </Step>

  <Step title="On parent exit (Ctrl+C)">
    Kill the child process cleanly.
  </Step>
</Steps>

***

## Step 5: Implement `bindufy()`

At this point, you have all the pieces. The last step is to wire them behind one developer-facing function. This is the moment where your SDK goes from "a collection of gRPC plumbing" to "a one-line developer experience."

```text theme={null}
function bindufy(config, handler):
    skills = read_skill_files(config.skills)
    callback_port = start_agent_handler_server(handler)
    launch_python_core(grpc_port=3774)
    wait_for_port(3774)
    result = register_agent(config, skills, callback_address="localhost:{callback_port}")
    start_heartbeat_loop(result.agent_id)
    print("Agent registered! A2A URL: {result.agent_url}")
```

That is the whole idea. The SDK should feel small because the core already does the heavy lifting.

***

## Skill Loading

Skills live in the developer's project, not in the core. Your SDK is responsible for reading them and shipping their contents to the core during registration. This way the core does not need filesystem access to the SDK's project directory.

<Steps>
  <Step title="For each skill path in config.skills">
    Look for `skill.yaml` first, then fall back to `SKILL.md`.
  </Step>

  <Step title="Read the file content">
    Load the raw file data.
  </Step>

  <Step title="Extract metadata">
    For YAML: parse the body and pull out `name` and `description` (the reference TypeScript SDK
    extracts only these two fields client-side — the rest is left to the core to parse from `raw_content`).
    For Markdown: send `raw_content` as-is with `format: "markdown"`; the reference TS SDK does **not**
    parse YAML frontmatter out of `SKILL.md`. If your SDK wants to surface more metadata locally
    (e.g. for logging), parse it yourself — the core treats `raw_content` as the source of truth.
  </Step>

  <Step title="Send as SkillDefinition">
    `{ name, description, tags, input_modes, output_modes, version, author, raw_content, format }`.
    `tags`, `input_modes`, and `output_modes` default to sensible values (`[]`, `["text/plain"]`,
    `["text/plain"]` in the TS SDK). Only `raw_content` and `format` are strictly required for
    the core to fully reconstruct the skill.
  </Step>
</Steps>

***

## Testing Your SDK

The fastest way to keep an SDK honest is to test both the transport and the sidecar lifecycle. Start with unit tests to verify serialization, then work up to integration tests that exercise the full round trip.

<AccordionGroup>
  <Accordion title="Unit test">
    Mock the gRPC channel and verify `HandleMessages` correctly invokes the handler and
    serializes the response.
  </Accordion>

  <Accordion title="Integration test">
    Start a real Bindu core with `bindu serve --grpc`, register an agent from your SDK,
    send an A2A message, and verify the response. The Python E2E tests in
    `tests/integration/grpc/test_grpc_e2e.py` show exactly this pattern.
  </Accordion>

  <Accordion title="Smoke test">
    Run one of the examples end to end and `curl` the agent.
  </Accordion>
</AccordionGroup>

***

## Reference: TypeScript SDK

If you want a concrete model to follow, the TypeScript SDK is the reference implementation. It is intentionally small — most of the complexity lives in the core, not in the SDK.

| File                   | What it does                        | Lines |
| ---------------------- | ----------------------------------- | ----- |
| `src/index.ts`         | `bindufy()` function + skill loader | \~220 |
| `src/server.ts`        | `AgentHandler` gRPC server          | \~130 |
| `src/client.ts`        | `BinduService` gRPC client          | \~105 |
| `src/core-launcher.ts` | Spawns Python core                  | \~170 |
| `src/types.ts`         | TypeScript interfaces               | \~120 |

Total: \~745 lines. Most of that is type definitions and error handling. The core logic is under 300 lines.

<CardGroup cols={2}>
  <Card title="TypeScript SDK" icon="code" href="/bindu/grpc/agent-client">
    See how the reference driver behaves from the developer's point of view
  </Card>

  <Card title="API Reference" icon="book" href="/bindu/grpc/api-reference">
    Review the protobuf messages and service contracts directly
  </Card>
</CardGroup>

***

## Publishing

Once your SDK works, publish it to the language's standard package registry so others can use it:

| Language | Registry              | Package name convention            |
| -------- | --------------------- | ---------------------------------- |
| Rust     | crates.io             | `bindu-sdk`                        |
| Go       | Go modules            | `github.com/getbindu/bindu-sdk-go` |
| Swift    | Swift Package Manager | `bindu-sdk`                        |
| C#       | NuGet                 | `Bindu.Sdk`                        |

<Info>
  Include the proto file in the package so users do not need to download it separately.
</Info>

<span className="brand-quote">
  <img src="https://mintcdn.com/pebbling/x2BFCGEbWywg69kQ/logo/light.svg?fit=max&auto=format&n=x2BFCGEbWywg69kQ&q=85&s=a69e734bb925e661b3c2ca2a20a050a9" alt="Sunflower Logo" width="32" className="clean-icon" data-path="logo/light.svg" />

  <span className="brand-quote-text">
    Because the infrastructure is entirely decoupled, building a new SDK is just a
    matter of{" "}
    <span className="brand-quote-highlight">satisfying the gRPC contract</span>,
    not rewriting the core engine.
  </span>
</span>
