Control Plane Reference
gRPC Service Definition
The control plane is exposed as a gRPC service defined in schemabound-proto.
service ControlPlaneService {
rpc SubmitPlan (SubmitPlanRequest) returns (SubmitPlanResponse);
rpc GetPlanStatus (GetPlanStatusRequest) returns (GetPlanStatusResponse);
rpc ExecuteStep (ExecuteStepRequest) returns (ExecuteStepResponse);
rpc CancelPlan (CancelPlanRequest) returns (CancelPlanResponse);
rpc StreamPlanEvents (StreamPlanEventsRequest) returns (stream PlanEvent);
}
Submit a Plan
message SubmitPlanRequest {
string session_id = 1;
string name = 2;
string description = 3;
repeated PlanStepDef steps = 4;
}
message PlanStepDef {
string step_id = 1;
string name = 2;
string tool_name = 3;
string intent = 4;
string query_template = 5;
repeated string depends_on = 6;
repeated string schema_table_hints = 7;
}
A successful response returns a plan_id. All subsequent calls reference this identifier.
Execute a Step
message ExecuteStepRequest {
string plan_id = 1;
string step_id = 2;
}
message ExecuteStepResponse {
bool success = 1;
StepStatus step_result = 2;
LlmContextUpdate llm_context_update = 3;
string error_message = 4;
}
The llm_context_update field is the value to pass back to the LLM before it chooses the next step.
Stream Plan Events
message StreamPlanEventsRequest {
string plan_id = 1;
}
message PlanEvent {
string plan_id = 1;
string event_type = 2;
string payload_json = 3;
string timestamp = 4;
}
Event types include PlanCreated, PlanStepExecuted, PlanCompleted, and PlanFailed.
REST API Endpoints
Create a Plan
POST /api/plans
Content-Type: application/json
Request body
{
"session_id": "sess-abc123",
"name": "Customer order lookup",
"description": "Find customer then retrieve their orders",
"steps": [
{
"id": "lookup_customer",
"name": "Look up customer by email",
"tool_name": "query_customers",
"intent": "read_select",
"query_template": "SELECT id FROM customers WHERE email = 'alice@example.com'",
"depends_on": [],
"schema_table_hints": ["customers"]
},
{
"id": "get_orders",
"name": "Get orders for customer",
"tool_name": "query_orders",
"intent": "read_select",
"query_template": "SELECT * FROM orders WHERE customer_id = {{step.lookup_customer.output}}",
"depends_on": ["lookup_customer"],
"schema_table_hints": ["orders"]
}
]
}
Response
{
"data": {
"plan_id": "plan-8f1c2b3d",
"status": "pending"
}
}
Get Plan Status
GET /api/plans/:plan_id
Response
{
"data": {
"plan_id": "plan-8f1c2b3d",
"status": "running",
"steps": [
{ "step_id": "lookup_customer", "status": "completed" },
{ "step_id": "get_orders", "status": "pending" }
]
}
}
Execute a Step
POST /api/plans/:plan_id/steps/:step_id/execute
Response
{
"data": {
"step_result": {
"step_id": "lookup_customer",
"status": "completed",
"row_count": 1,
"executed_at": "2026-04-20T14:00:00Z"
},
"llm_context_update": {
"plan_id": "plan-8f1c2b3d",
"step_id": "lookup_customer",
"tool_output_json": "{\"id\": 42}",
"schema_additions": [],
"augmentation_hints": []
}
}
}
Cancel a Plan
DELETE /api/plans/:plan_id
OSS Trait Contract
The WorkflowOrchestrator trait in schemabound-public defines the full public interface. Any implementation — including custom ones — must satisfy:
#![allow(unused)]
fn main() {
#[async_trait]
pub trait WorkflowOrchestrator: Send + Sync {
async fn create_plan(
&self,
session_id: &str,
definition: PlanDefinition,
) -> Result<PlanRecord, String>;
async fn get_plan(
&self,
plan_id: &str,
) -> Result<Option<PlanRecord>, String>;
async fn execute_step(
&self,
plan_id: &str,
step_id: &str,
ctx: &QueryRuntimeContext,
) -> Result<(StepResult, LlmContextUpdate), String>;
async fn cancel_plan(
&self,
plan_id: &str,
) -> Result<(), String>;
}
}
NoOpWorkflowOrchestrator is the default in OSS builds. It satisfies the trait boundary and returns an explicit error on any write operation, making the absence of a backing store visible rather than silent.