llm-plan-api
LLM Plan + Dispatch API (Shell/DB)
This describes the natural language → structured plan → policy → Runner dispatch flow. It is designed for file search/edit and DB queries with a strict, safe-by-default schema.
Endpoints
POST /api/agent/llm-planPOST /api/agent/execute-planned-jobPOST /api/agent/llm-plan-and-dispatchGET /api/agent/decision-traces
LLM Provider Config (YAML)
Set LLM_CONFIG_YAML to load provider routing/config from YAML instead of individual LLM_* env vars.
Example:
- LLM_CONFIG_YAML=core/config/llm-providers.example.yml
Validation behavior:
- Invalid provider names in default_chain cause startup error
- Invalid provider names in tenant_overrides cause startup error
- Invalid provider keys in providers cause startup error
api_key supports:
- literal key string
- env:VARNAME (resolved at runtime)
Plan Request
{
"tenant_id": "default",
"env": "dev",
"prompt": "Find references to grpc in the repo.",
"workspace_root": "/workspace",
"db_dsn_ref": null
}Fields:
- tenant_id: tenant scope for provider routing and policy
- env: prod or non-prod value
- prompt: natural language request
- workspace_root: cwd must be under this path for shell jobs
- db_dsn_ref: optional DB reference; if provided, LLM must use it
Dispatch-only Additions
POST /api/agent/llm-plan-and-dispatch accepts the same fields plus:
runner_id: target Runnerapproval: optional approval payload (used when policy requires approval)
Execute Planned Job Request
POST /api/agent/execute-planned-job executes a previously planned planned_job after re-validating it:
{
"tenant_id": "default",
"runner_id": "runner-1",
"env": "dev",
"workspace_root": "/workspace",
"db_dsn_ref": null,
"approval": null,
"planned_job": {
"workflow_id": "llm-ad-hoc",
"kind": {
"tag": "JobShell",
"contents": {
"command": "rg -n \"grpc\" .",
"cwd": "/workspace",
"shell_timeout_seconds": 30,
"allowlist_tag": "read"
}
}
}
}LLM Output Schema (strict)
The LLM must return ONLY JSON:
{
"decision": "no_op|request_approval|execute_plan",
"rationale": "short reason",
"action_kind": "shell|db",
"risk": "read_only|write",
"job": {
"kind": "shell|db",
"command": "rg -n \"grpc\" .",
"cwd": "/workspace",
"timeout_seconds": 30,
"allowlist_tag": "read",
"driver": "postgres|mysql|sqlite",
"dsn_ref": "primary",
"query": "select ... limit 10"
}
}Policy Rules (v0.1)
prod: approval required for any executionshell/db+risk=write: approval required in any envshell/db+risk=read_only: allowed in non-prod
Shell constraints:
- cwd must be under workspace_root
- risk=read_only requires allowlist_tag=read
- risk=write requires allowlist_tag=write
- Read-only shell commands are enforced with heuristics (e.g. no rm, no redirects)
DB constraints:
- risk=read_only requires a read-only query (no INSERT/UPDATE/DELETE/...)
- If db_dsn_ref is provided, it must match the job dsn_ref
Plan Response
POST /api/agent/llm-plan returns the validated plan and policy result without dispatching:
{
"llm_provider": "ProviderInternal",
"llm_decision": "LlmExecute",
"llm_rationale": "Safe read-only grep.",
"final_decision": "FinalExecute",
"planned_job": {
"workflow_id": "llm-ad-hoc",
"kind": {
"tag": "JobShell",
"contents": {
"command": "rg -n \"grpc\" .",
"cwd": "/workspace",
"shell_timeout_seconds": 30,
"allowlist_tag": "read"
}
}
}
}Dispatch Response
POST /api/agent/llm-plan-and-dispatch returns the same plan plus dispatch status:
{
"llm_provider": "ProviderInternal",
"llm_decision": "LlmExecute",
"llm_rationale": "Safe read-only grep.",
"final_decision": "FinalExecute",
"job_id": "job-abc123",
"planned_job": {
"workflow_id": "llm-ad-hoc",
"kind": {
"tag": "JobShell",
"contents": {
"command": "rg -n \"grpc\" .",
"cwd": "/workspace",
"shell_timeout_seconds": 30,
"allowlist_tag": "read"
}
}
}
}Execute Planned Job Response
POST /api/agent/execute-planned-job returns the inferred action/risk plus dispatch status:
{
"action_kind": "ActionShell",
"inferred_risk": "RiskReadOnly",
"final_decision": "FinalExecute",
"job_id": "job-abc123"
}Job Result Query
GET /api/runner/job-results/{jobId}
Returns a normalized JSON shape:
{
"tenant_id": "default",
"runner_id": "runner-1",
"job_id": "job-abc123",
"status": "SUCCEEDED",
"output": {
"stdout": "...",
"stderr": "...",
"exit_code": 0
},
"error": "",
"started_unix_ms": 1710000000000,
"ended_unix_ms": 1710000000500
}Decision Trace Query
GET /api/agent/decision-traces?limit=50&trace_kind=agent.llm_plan
Returns the most recent redacted decision traces for the authenticated tenant.