> ## 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.

# Agent Implementation

> Handler patterns, state transitions, configuration, and how the bridge works under the hood

Your agent is running. Now the question is: what should it actually do?

This page covers the practical side of building with the sidecar. You will learn how to write handler functions, manage multi-turn conversations, configure your agent, define skills, and — if you are curious — understand exactly how the core talks to your code under the hood.

***

## Handler Patterns

The handler is the only code you write. Everything else — identity, auth, protocol, storage — lives in the sidecar. So let's start with the different shapes a handler can take.

### Simple Response

The simplest possible handler returns a string. The sidecar wraps it in an A2A response, signs it with your DID, and sends it back to the caller.

```typescript theme={null}
async (messages) => {
  return "The answer is 42.";
}
```

### OpenAI SDK

Most agents will call an LLM. Here is the pattern with the OpenAI SDK — you receive the conversation history, pass it to the model, and return the result.

```typescript theme={null}
import OpenAI from "openai";
const openai = new OpenAI();

async (messages) => {
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: messages.map(m => ({
      role: m.role as "user" | "assistant" | "system",
      content: m.content,
    })),
  });
  return response.choices[0].message.content || "";
}
```

### LangChain.js

If you prefer LangChain, the pattern is the same — only the LLM client changes. The sidecar does not care which framework you use inside the handler.

```typescript theme={null}
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({ model: "gpt-4o" });

async (messages) => {
  const response = await llm.invoke(
    messages.map(m => ({ role: m.role, content: m.content }))
  );
  return typeof response.content === "string"
    ? response.content
    : JSON.stringify(response.content);
}
```

### Error Handling

If your handler throws, the SDK catches it and returns a gRPC error. `ManifestWorker` marks the task as failed. You do not need to build your own error plumbing — just let exceptions propagate naturally.

```typescript theme={null}
async (messages) => {
  try {
    return await myLlmCall(messages);
  } catch (err) {
    throw err;
  }
}
```

***

## State Transitions

Sometimes a single response is not enough. Your handler can return two kinds of things: a string (which completes the task) or an object that tells the core to keep the conversation open.

### Multi-Turn Conversation

Instead of returning a simple string, return an object with `state: "input-required"`. This pauses the task and prompts the user for more information. When they reply, your handler is called again with the full conversation history.

```typescript theme={null}
async (messages) => {
  if (messages.length === 1) {
    return {
      state: "input-required",
      prompt: "Could you be more specific about what you're looking for?"
    };
  }

  const lastMessage = messages[messages.length - 1].content;
  return `Based on your clarification: here's the detailed answer about "${lastMessage}"...`;
}
```

### The Response Contract

Everything downstream depends on this contract. The transport can change, but the return types cannot.

| Handler returns                                             | ManifestWorker does              | Task state       |
| ----------------------------------------------------------- | -------------------------------- | ---------------- |
| `"The capital of France is Paris."`                         | Creates message + artifact       | `completed`      |
| `{"state": "input-required", "prompt": "Can you clarify?"}` | Creates message, keeps task open | `input-required` |
| `{"state": "auth-required"}`                                | Creates message, keeps task open | `auth-required`  |

The sidecar processes all three return shapes the same way regardless of whether the handler is Python, TypeScript, or Kotlin. That is the bigger pattern: the sidecar changes the transport, not the behavior contract.

***

## Adding Payments

If you want to charge for your agent's responses, add an `execution_cost` to the config. The sidecar handles the x402 payment protocol — your handler code does not change at all.

```typescript theme={null}
bindufy(
  {
    author: "dev@example.com",
    name: "premium-agent",
    deployment: { url: "http://localhost:3773", expose: true },
    execution_cost: {
      amount: "1000000",
      token: "USDC",
      network: "base-sepolia",
      pay_to_address: "0xYourWalletAddress",
    },
  },
  async (messages) => {
    return "Premium response!";
  }
);
```

***

## Configuration

The `bindufy()` config object is how you tell the sidecar what kind of microservice to build around your handler. Most fields are optional — the only required ones are `author`, `name`, and `deployment`.

```typescript theme={null}
bindufy({
  author: "dev@example.com",
  name: "my-agent",
  deployment: {
    url: "http://localhost:3773",
    expose: true,
    cors_origins: ["http://localhost:5173"],
  },
  description: "What my agent does",
  version: "1.0.0",
  skills: ["skills/question-answering"],
  execution_cost: {
    amount: "1000000",
    token: "USDC",
    network: "base-sepolia",
  },
  capabilities: {
    streaming: false,
    push_notifications: false,
  },
  coreAddress: "localhost:3774",
  callbackPort: 0,
  debug_mode: false,
  telemetry: true,
  num_history_sessions: 10,
}, handler);
```

***

## Skills

Skills describe what your agent can do. The sidecar reads them during startup and includes them in the agent card so other agents and clients can discover your capabilities.

### File-Based (Recommended)

Point to directories containing `skill.yaml` or `SKILL.md` files. The SDK reads them and sends the content to the core during registration.

```typescript theme={null}
bindufy({
  skills: ["skills/question-answering", "skills/code-review"],
}, handler);
```

### Inline

You can also define skills directly in the config if you prefer to keep everything in one file.

```typescript theme={null}
bindufy({
  skills: [{
    name: "question-answering",
    description: "Answer questions using GPT-4o",
    tags: ["qa", "assistant"],
  }],
}, handler);
```

***

## Types

The SDK exports the key types your handler works with. These are the shapes you will see most often.

```typescript theme={null}
interface ChatMessage {
  role: string;
  content: string;
}

type MessageHandler = (messages: ChatMessage[]) => Promise<string | HandlerResponse>;

interface HandlerResponse {
  content?: string;
  state?: "input-required" | "auth-required";
  prompt?: string;
  metadata?: Record<string, string>;
}

interface RegistrationResult {
  agentId: string;
  did: string;
  agentUrl: string;
}
```

***

## What Happens When You Call `bindufy()`

Now that you know how to write handlers, let's look at what happens when `bindufy()` runs. You do not need to know this to use Bindu — but understanding the lifecycle makes debugging easier and gives you a clearer mental model of the system.

<Steps>
  <Step title="SDK reads your skill files">
    Loads yaml/markdown skill files from disk.
  </Step>

  <Step title="SDK starts a gRPC server">
    On a random port. This is where the core will call your handler.
  </Step>

  <Step title="SDK spawns bindu serve --grpc">
    As a child process.
  </Step>

  <Step title="SDK waits for :3774 to be ready">
    Polls with TCP connect, 30s timeout.
  </Step>

  <Step title="SDK calls RegisterAgent">
    With your config, skills, and callback address.
  </Step>

  <Step title="Core runs the full bindufy pipeline">
    DID, auth, x402, manifest, HTTP server.
  </Step>

  <Step title="SDK receives the agent ID, DID, and A2A URL">
    Registration complete.
  </Step>

  <Step title="SDK starts a heartbeat loop">
    Every 30 seconds in the reference TS SDK; the cadence is an SDK convention
    rather than a core-enforced one.
  </Step>

  <Step title="You see 'Waiting for messages...'">
    Agent is ready.
  </Step>
</Steps>

***

## How the Bridge Works Under the Hood

If you are curious about what happens between the core receiving a message and your handler being called, this section explains the internal bridge. You do not need this to build agents, but it helps if you are debugging transport issues or building a [Custom SDK](/bindu/grpc/custom-sdks).

### The GrpcAgentClient

Inside the core, one component keeps the sidecar model elegant: `GrpcAgentClient`. It is a Python class that behaves like a function. `ManifestWorker` calls it the same way it would call a native Python handler:

```python theme={null}
raw_results = self.manifest.run(message_history or [])
```

For a Python agent, `manifest.run` is a wrapper around the developer's handler function. For a gRPC agent, `manifest.run` is assigned a `GrpcAgentClient` instance. When `ManifestWorker` executes it, Python invokes `GrpcAgentClient.__call__()`, which transparently makes the gRPC call across the boundary.

That is the design win. The sidecar changes the transport, not the downstream architecture. `ResultProcessor`, `ResponseDetector`, and `ArtifactBuilder` all work exactly the same way regardless of whether the handler is local or remote.

### How It Works

Three steps: ensure connection, call the SDK, convert back. That is the entire bridge.

```python theme={null}
class GrpcAgentClient:
    def __init__(
        self,
        callback_address: str,
        timeout: float = 30.0,
        use_streaming: bool = False,
        channel_credentials: grpc.ChannelCredentials | None = None,
    ) -> None:
        self._address = callback_address
        self._timeout = timeout
        self._use_streaming = use_streaming
        self._channel_credentials = channel_credentials
        self._channel: grpc.Channel | None = None   # lazy-initialized on first call
        self._stub: agent_handler_pb2_grpc.AgentHandlerStub | None = None

    def _ensure_connected(self) -> None:
        # Built with `grpc.secure_channel` when channel_credentials is provided,
        # otherwise `grpc.insecure_channel` for same-host SDK setups.
        ...

    def __call__(self, messages, **kwargs):
        self._ensure_connected()

        # 1. Convert Python dicts to protobuf
        proto_msgs = [ChatMessage(role=m["role"], content=m["content"]) for m in messages]
        request = HandleRequest(messages=proto_msgs)

        # 2. Call the SDK's AgentHandler over gRPC
        if self._use_streaming:
            return self._handle_streaming(request)  # yields a generator

        response = self._handle_unary(request)

        # 3. Convert back to what ManifestWorker expects
        if response.state:
            result = {"state": response.state}
            if response.prompt:
                result["prompt"] = response.prompt
            if response.content:
                result["content"] = response.content
            for key, value in response.metadata.items():
                result[key] = value
            return result

        return response.content
```

Source: [`bindu/grpc/client.py`](https://github.com/getbindu/Bindu/blob/main/bindu/grpc/client.py).

### When It's Created

The bridge gets attached during registration. After this point, every task for the agent flows through it.

From [`bindu/grpc/service.py`](https://github.com/getbindu/Bindu/blob/main/bindu/grpc/service.py):

```python theme={null}
# In BinduServiceImpl.RegisterAgent():
grpc_client = GrpcAgentClient(
    callback_address=request.grpc_callback_address,
    timeout=app_settings.grpc.handler_timeout,
)

# Then handed to _bindufy_core as the handler:
manifest = _bindufy_core(
    config=config,
    handler_callable=grpc_client,   # ← becomes manifest.run inside create_manifest()
    ...
)
```

After this point, `manifest.run` is the `GrpcAgentClient` instance. Everything in `ManifestWorker` / `ResultProcessor` / `ResponseDetector` then proceeds as if it were a normal Python handler.

### Connection Lifecycle

The client connects lazily — the gRPC channel is created on the first call, not during initialization. That avoids connection errors during registration if the SDK server is not fully ready yet.

When the SDK disconnects (Ctrl+C, crash), the next `HandleMessages` call fails with `grpc.StatusCode.UNAVAILABLE`. `ManifestWorker`'s existing error handling catches this and marks the task as failed.

### Health Checks and Capabilities

The core can also ask the sidecar whether the driver is still healthy:

```python theme={null}
grpc_client.health_check()       # Is the SDK still running? Returns True/False
grpc_client.get_capabilities()   # What can the SDK do? Returns name, version, etc.
```

Both methods are available on `GrpcAgentClient` for any caller that needs them
(for example a future capability-discovery sweep). They are **not** invoked from
the current `BinduService.Heartbeat` handler — heartbeats only update the
registry's `last_heartbeat` timestamp.

<Warning>
  Current limitations:

  * **Streaming** — `use_streaming=True` on `GrpcAgentClient` calls `HandleMessagesStream`,
    which is fine in the proto. No shipped SDK implements the server side yet — the
    TypeScript SDK returns `supports_streaming: false` from `GetCapabilities`.
  * **Reconnection** — if the SDK crashes, the client does not retry. The agent must be
    re-registered.
  * **mTLS** — the channel is built via `grpc.secure_channel` *when* `channel_credentials`
    is provided (from `MTLSAgentExtension.build_grpc_channel_credentials`), otherwise it
    falls back to `grpc.insecure_channel`. Default deployments don't supply credentials
    yet — see [Overview → Limitations](/bindu/grpc/overview#known-limitations) for the
    operational gating.
</Warning>

***

## The Full Message Path

To tie everything together, here is the complete path for one message — from the outside world to your code and back:

```text theme={null}
ManifestWorker calls manifest.run(messages)
  -> GrpcAgentClient.__call__([{"role": "user", "content": "What is quantum computing?"}])
    -> Converts to protobuf: ChatMessage(role="user", content="What is quantum computing?")
    -> gRPC call: AgentHandler.HandleMessages(HandleRequest{messages: [...]})
    -> TypeScript SDK receives the call
    -> Developer's handler runs: await openai.chat.completions.create(...)
    -> OpenAI returns: "Quantum computing is a type of computation..."
    -> SDK returns: HandleResponse{content: "Quantum computing is...", state: ""}
  -> GrpcAgentClient sees state is empty, returns the string
-> ManifestWorker receives "Quantum computing is..." (same as a local handler)
-> ResultProcessor normalizes -> ResponseDetector says "completed"
-> ArtifactBuilder creates DID-signed artifact
-> User gets the response
```

The only component that knows gRPC exists is the bridge itself. Everything upstream and downstream just sees function calls and return values.

***

## Debugging

When something goes wrong, it usually helps to inspect each half of the sidecar separately. Check the core first (is it running?), then test the endpoint directly.

### Check Core Logs

```text theme={null}
[bindu-core] INFO  gRPC server started on 0.0.0.0:3774
[bindu-core] INFO  Agent registered: openai-assistant-agent
[bindu-core] INFO  HTTP server started on 0.0.0.0:3773
```

### Test the Agent Manually

```bash theme={null}
curl http://localhost:3773/health
curl http://localhost:3773/.well-known/agent.json | python3 -m json.tool
curl -X POST http://localhost:3773 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [{"kind": "text", "text": "Hello"}],
        "messageId": "test-1",
        "contextId": "test-2",
        "taskId": "test-3",
        "kind": "message"
      }
    },
    "id": "1"
  }'
```

### Port Conflicts

```bash theme={null}
lsof -ti:3773 -ti:3774 | xargs kill 2>/dev/null
```

***

## Limits to Keep in Mind

<Warning>
  Current limitations:

  * Streaming — available via `use_streaming=True` but requires SDK-side
    `HandleMessagesStream` support.
  * Requires Python — the Bindu core must be installed (`pip install bindu`).
  * Single agent per port — each `bindufy()` call uses `:3773` for HTTP.
</Warning>

<CardGroup cols={2}>
  <Card title="gRPC Overview" icon="diagram-project" href="/bindu/grpc/overview">
    Review the sidecar architecture and current limits
  </Card>

  <Card title="API Reference" icon="book" href="/bindu/grpc/api-reference">
    Look up services, messages, ports, and config details
  </Card>
</CardGroup>

<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">
    With the core handling the protocol and identity, your only job is to{" "}

    <span className="brand-quote-highlight">
      focus on the agent's intelligence —
    </span>

    {" "}

    prompts, state, and tool execution.
  </span>
</span>
