Use this file to discover all available pages before exploring further.
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.
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.
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.
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.
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.
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.
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}"...`;}
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.
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.
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.
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.
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.
1
SDK reads your skill files
Loads yaml/markdown skill files from disk.
2
SDK starts a gRPC server
On a random port. This is where the core will call your handler.
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.
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:
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.
Three steps: ensure connection, call the SDK, convert back. That is the entire bridge.
class GrpcAgentClient: def __init__(self, callback_address: str, timeout: float = 30.0, use_streaming: bool = False): self._address = callback_address self._timeout = timeout self._use_streaming = use_streaming self._channel = None # lazy-initialized on first call self._stub = None def __call__(self, messages, **kwargs): self._ensure_connected() # creates channel + stub on first use # 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
The bridge gets attached during registration. After this point, every task for the agent flows through it.
# In BinduServiceImpl.RegisterAgent():grpc_client = GrpcAgentClient(request.grpc_callback_address)# In create_manifest():manifest.run = grpc_client # GrpcAgentClient IS the handler now
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.
The core can also ask the sidecar whether the driver is still healthy:
grpc_client.health_check() # Is the SDK still running? Returns True/Falsegrpc_client.get_capabilities() # What can the SDK do? Returns name, version, etc.
These are used during heartbeat processing and capability discovery.
Current limitations:
Streaming — supported via use_streaming=True on GrpcAgentClient. The SDK’s
AgentHandler must implement HandleMessagesStream to use it. The TypeScript SDK
currently returns supports_streaming: false.
Reconnection — if the SDK crashes, the client does not retry. The agent must be
re-registered.
TLS — uses insecure channels. Only safe on localhost or trusted networks.
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.
[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