iop/proto/iop/runtime.proto
toki d7a150c7fe feat(agent): 단일 요청 실행 경로를 완성한다
Claude의 단일 Anthropic 요청 안에서 IOP가 Plan, Work, Review와 workspace 도구 실행을 끝내고 실제 dev smoke로 계약을 검증할 수 있어야 한다.\n\n완료 task evidence와 마일스톤 검토 상태도 같은 변경에 고정한다.
2026-08-08 23:35:13 +09:00

607 lines
20 KiB
Protocol Buffer

syntax = "proto3";
package iop;
import "google/protobuf/struct.proto";
option go_package = "iop/proto/gen/iop";
// RunRequest initiates an adapter execution on a node.
message RunRequest {
reserved 4, 10;
reserved "workspace", "session_mode";
string run_id = 1;
string adapter = 2;
string target = 3;
google.protobuf.Struct policy = 5;
google.protobuf.Struct input = 6;
int32 timeout_sec = 7;
map<string, string> metadata = 8;
string session_id = 9;
bool background = 11;
// response_stall_timeout_ms is the selected provider's response-stall
// timeout in milliseconds. Zero means the Node applies the documented
// default (300000). Negative or overflow values are rejected at the Node
// boundary before router/provider invocation.
int64 response_stall_timeout_ms = 12;
}
// RunEvent is a streaming execution event.
message RunEvent {
string run_id = 1;
string type = 2; // start | delta | complete | error | cancelled
string delta = 3;
string message = 4;
string error = 5;
Usage usage = 6;
map<string, string> metadata = 7;
int64 timestamp = 8; // unix nano
string session_id = 9;
bool background = 10;
string node_id = 11;
string node_alias = 12;
ExecutionFailure failure = 13;
}
enum ProviderTunnelFrameKind {
PROVIDER_TUNNEL_FRAME_KIND_UNSPECIFIED = 0;
PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START = 1;
PROVIDER_TUNNEL_FRAME_KIND_BODY = 2;
PROVIDER_TUNNEL_FRAME_KIND_END = 3;
PROVIDER_TUNNEL_FRAME_KIND_ERROR = 4;
PROVIDER_TUNNEL_FRAME_KIND_USAGE = 5;
}
// ProviderTunnelRequest asks a node to open a provider HTTP request and relay
// the raw provider response over ProviderTunnelFrame messages on the existing
// Edge-Node socket. It is separate from RunRequest, which remains the
// normalized adapter execution path.
message ProviderTunnelRequest {
string run_id = 1;
string tunnel_id = 2;
string adapter = 3;
string target = 4;
string method = 5;
string path = 6;
map<string, string> headers = 7;
bytes body = 8;
bool stream = 9;
int32 timeout_sec = 10;
map<string, string> metadata = 11;
string session_id = 12;
// operation is the protocol operation id (e.g. "chat_completions",
// "messages", "models"). When set, the Node adapter resolves the request URL
// from the concrete profile's operation path. When empty, the legacy Path
// field is used as a mixed-version fallback.
string operation = 13;
// credential_lease is a signed, recipient-sealed credential envelope. It is
// deliberately separate from headers/metadata so protobuf debug output and
// generic forwarding paths cannot expose plaintext provider credentials.
SignedCredentialLease credential_lease = 14;
// credential_binding is the independently resolved Edge dispatch binding
// the Node compares byte-for-byte with the signed lease before consumption.
CredentialLeaseBinding credential_binding = 15;
// response_stall_timeout_ms is the selected provider's response-stall
// timeout in milliseconds. Zero means the Node applies the documented
// default (300000). Negative or overflow values are rejected at the Node
// boundary before router/provider invocation.
int64 response_stall_timeout_ms = 16;
}
message CredentialLeaseScope {
string lease_id = 1;
string principal_ref = 2;
string credential_slot_ref = 3;
string route_id = 4;
string profile_id = 5;
string upstream_target = 6;
string node_id = 7;
string recipient_key_id = 8;
string header_name = 9;
string scheme = 10;
uint64 credential_revision = 11;
uint64 route_revision = 12;
uint64 projection_generation = 13;
int64 issued_at_unix_nano = 14;
int64 expires_at_unix_nano = 15;
}
message SignedCredentialLease {
uint32 version = 1;
string issuer_key_id = 2;
CredentialLeaseScope scope = 3;
bytes ephemeral_public_key = 4;
bytes nonce = 5;
bytes ciphertext = 6;
bytes signature = 7;
}
message CredentialLeaseBinding {
string principal_ref = 1;
string credential_slot_ref = 2;
string route_id = 3;
string profile_id = 4;
string upstream_target = 5;
string node_id = 6;
string recipient_key_id = 7;
uint64 credential_revision = 8;
uint64 route_revision = 9;
uint64 projection_generation = 10;
}
// AcquireLeaseRequest is sent only over the authenticated Control Plane-Edge
// channel after an exact Node candidate is chosen. It is a runtime delivery
// operation, not a credential-management or bootstrap API.
message AcquireLeaseRequest {
string edge_id = 1;
CredentialLeaseBinding binding = 2;
bytes recipient_public_key = 3;
}
message AcquireLeaseResponse {
SignedCredentialLease lease = 1;
string error = 2;
}
// ProviderTunnelFrame carries ordered raw provider response data back to Edge.
// Body bytes are the passthrough source of truth and must not be routed through
// RunEvent.delta or the lossy event bus fanout.
message ProviderTunnelFrame {
string run_id = 1;
string tunnel_id = 2;
int64 sequence = 3;
ProviderTunnelFrameKind kind = 4;
int32 status_code = 5;
map<string, string> headers = 6;
bytes body = 7;
bool end = 8;
string error = 9;
Usage usage = 10;
map<string, string> metadata = 11;
int64 timestamp = 12; // unix nano
string node_id = 13;
string node_alias = 14;
ExecutionFailure failure = 15;
}
// EdgeNodeEvent is a general edge-node lifecycle/control event envelope.
// It is separate from RunEvent, which is reserved for adapter execution streams.
message EdgeNodeEvent {
string event_id = 1;
string type = 2; // node.connected | node.disconnected | edge.disconnected | ...
string source = 3; // edge | node
string node_id = 4;
string alias = 5;
string reason = 6;
map<string, string> metadata = 7;
int64 timestamp = 8; // unix nano
}
// ExecutionFailure is the typed failure payload carried by execution envelopes.
message ExecutionFailure {
string code = 1;
string message = 2;
bool retryable = 3;
map<string, string> metadata = 4;
}
message Usage {
int32 input_tokens = 1;
int32 output_tokens = 2;
// reasoning_tokens and cached_input_tokens are populated only when the
// provider reports them. A zero value means "not reported"; downstream metric
// emit never estimates reasoning tokens from observed reasoning text.
int32 reasoning_tokens = 3;
int32 cached_input_tokens = 4;
}
// Heartbeat is sent by both sides to keep the connection alive.
message Heartbeat {
int64 timestamp = 1;
}
// CancelRequest asks the node to cancel a running execution.
message CancelRequest {
reserved 2, 3, 4, 5;
reserved "adapter", "target", "session_id", "action";
string run_id = 1;
}
enum NodeCommandType {
reserved 1, 3;
reserved "NODE_COMMAND_TYPE_USAGE_STATUS", "NODE_COMMAND_TYPE_SESSION_LIST";
NODE_COMMAND_TYPE_UNSPECIFIED = 0;
NODE_COMMAND_TYPE_CAPABILITIES = 2;
NODE_COMMAND_TYPE_TRANSPORT_STATUS = 4;
NODE_COMMAND_TYPE_OLLAMA_API = 5;
}
message NodeCommandRequest {
string request_id = 1;
NodeCommandType type = 2;
string adapter = 3;
string target = 4;
string session_id = 5;
int32 timeout_sec = 6;
map<string, string> metadata = 7;
}
message NodeCommandResponse {
reserved 6;
reserved "usage_status";
string request_id = 1;
NodeCommandType type = 2;
string adapter = 3;
string target = 4;
string session_id = 5;
string error = 7;
// result carries free-form key/value data for provider-only commands.
map<string, string> result = 8;
repeated ProviderSnapshot provider_snapshots = 9;
}
// ProviderSnapshot is the stable wire name for a node provider status snapshot.
message ProviderSnapshot {
string adapter = 1;
string status = 2; // unknown|available|unavailable
int32 capacity = 3;
int32 in_flight = 4;
int32 queued = 5;
// Provider catalog fields (MVP).
// id uniquely identifies this provider within its node.
string id = 6;
// type is the runtime type (e.g. "ollama", "vllm", "lemonade", "sglang", "openai_api").
string type = 7;
// category classifies the provider; values: api, local_inference.
string category = 8;
// served_models lists the model names this provider can actually serve.
repeated string served_models = 9;
// health is the observed provider health state (e.g. "healthy", "degraded", "unhealthy").
string health = 10;
// load_ratio is the computed in_flight / capacity value. When capacity is 0
// or unknown the provider is treated as unavailable for selection.
float load_ratio = 11;
// lifecycle_capabilities lists coarse lifecycle capability flags (e.g.
// "list_models", "load_model", "unload_model", "pull_model", "delete_model").
repeated string lifecycle_capabilities = 12;
// Long-context fields.
int32 long_context_capacity = 13;
int32 long_in_flight = 14;
int32 long_queued = 15;
}
// Error is returned when a request fails at the transport layer.
message Error {
string code = 1;
string message = 2;
}
// RegisterRequest is sent by node to edge immediately on connect.
message RegisterRequest {
string token = 1;
string credential_recipient_key_id = 2;
bytes credential_recipient_public_key = 3;
}
// RegisterResponse is sent by edge to node in response to RegisterRequest.
message RegisterResponse {
bool accepted = 1;
string node_id = 2;
string alias = 3;
string reason = 4; // rejection reason
NodeConfigPayload config = 5;
}
// NodeReadyRequest is sent by node to edge after it has applied the config from
// RegisterResponse and installed its message handler, signalling that it can now
// receive run/tunnel/command dispatch. Accepted registration only claims
// ownership and delivers config; edge opens dispatch eligibility and pumps the
// node's stranded waiters only on this ready handshake, never before. node_id
// carries the identity edge assigned in RegisterResponse so the ready transition
// binds to the exact accepted connection.
message NodeReadyRequest {
string node_id = 1;
}
// NodeReadyResponse acknowledges a NodeReadyRequest. ready is true once edge has
// marked the connection dispatch-ready (idempotent for the current owner); it is
// false when the connection is stale — superseded by a reconnect or already gone
// — in which case the node closes and reconnects. reason describes a rejection.
message NodeReadyResponse {
bool ready = 1;
string reason = 2;
}
// NodeConfigPayload carries all configuration edge pushes to the node.
message NodeConfigPayload {
repeated AdapterConfig adapters = 1;
NodeRuntimeConfig runtime = 2;
// workspaces is the Node-private, operator-approved workspace capability
// catalog. It is deliberately separate from RunRequest metadata and from
// the closed NodeCommand surface.
repeated WorkspaceConfig workspaces = 3;
}
// WorkspaceOperation is the closed set of workspace operations admitted by
// Edge and implemented by the Node-private executor.
enum WorkspaceOperation {
WORKSPACE_OPERATION_UNSPECIFIED = 0;
WORKSPACE_OPERATION_READ = 1;
WORKSPACE_OPERATION_LIST = 2;
WORKSPACE_OPERATION_WRITE = 3;
WORKSPACE_OPERATION_DELETE = 4;
WORKSPACE_OPERATION_COMMAND = 5;
}
message WorkspaceCommandConfig {
string id = 1;
string executable = 2;
repeated string args = 3;
}
// WorkspaceConfig is delivered only inside the Edge-owned Node config payload.
// Roots, command templates, and environment names never appear in public API
// responses or in a caller-selected request field.
message WorkspaceConfig {
string ref = 1;
string platform = 2;
string root = 3;
repeated WorkspaceOperation operations = 4;
repeated WorkspaceCommandConfig commands = 5;
repeated string environment_allowlist = 6;
int64 max_read_bytes = 7;
int64 max_write_bytes = 8;
int64 max_output_bytes = 9;
int64 max_command_timeout_ms = 10;
}
enum WorkspaceStatus {
WORKSPACE_STATUS_UNSPECIFIED = 0;
WORKSPACE_STATUS_SUCCESS = 1;
WORKSPACE_STATUS_ERROR = 2;
WORKSPACE_STATUS_TIMEOUT = 3;
WORKSPACE_STATUS_CANCELLED = 4;
WORKSPACE_STATUS_UNSUPPORTED = 5;
}
enum WorkspaceErrorCode {
WORKSPACE_ERROR_CODE_UNSPECIFIED = 0;
WORKSPACE_ERROR_CODE_NOT_READY = 1;
WORKSPACE_ERROR_CODE_UNSUPPORTED = 2;
WORKSPACE_ERROR_CODE_INVALID_REQUEST = 3;
WORKSPACE_ERROR_CODE_NOT_FOUND = 4;
WORKSPACE_ERROR_CODE_TIMEOUT = 5;
WORKSPACE_ERROR_CODE_CANCELLED = 6;
WORKSPACE_ERROR_CODE_INTERNAL = 7;
}
// WorkspaceOpenRequest begins one request-owned workspace lifecycle. request_id
// is the immutable coordinator identity and later names .iop/job/<request_id>.
message WorkspaceOpenRequest {
string request_id = 1;
string workspace_ref = 2;
int64 timeout_ms = 3;
repeated WorkspaceOperation operations = 4;
repeated string command_ids = 5;
int64 max_read_bytes = 6;
int64 max_write_bytes = 7;
int64 max_output_bytes = 8;
int64 max_command_timeout_ms = 9;
}
message WorkspaceOpenResponse {
string request_id = 1;
string workspace_ref = 2;
WorkspaceStatus status = 3;
WorkspaceErrorCode error_code = 4;
string error = 5;
}
message WorkspaceWriteInput {
string relative_path = 1;
bytes content = 2;
}
// WorkspaceToolRequest carries only closed operation input. A caller cannot
// select a Node, root, executable, argv, or arbitrary environment.
message WorkspaceToolRequest {
string request_id = 1;
string stage_id = 2;
string tool_call_id = 3;
WorkspaceOperation operation = 4;
int64 timeout_ms = 5;
oneof input {
string relative_path = 6;
// Legacy source/wire-compatible field. WRITE requires the structured
// write input because this field cannot carry a destination path.
bytes write_content = 7;
string command_id = 8;
WorkspaceWriteInput write = 10;
}
map<string, string> environment = 9;
}
message WorkspaceToolResponse {
string request_id = 1;
string stage_id = 2;
string tool_call_id = 3;
WorkspaceStatus status = 4;
WorkspaceErrorCode error_code = 5;
string error = 6;
bytes content = 7;
repeated string entries = 8;
bytes stdout = 9;
bytes stderr = 10;
int32 exit_code = 11;
bool truncated = 12;
int64 duration_ms = 13;
}
// WorkspaceArtifactKind is a closed coordinator-only artifact selector. Node
// maps these values to fixed names inside .iop/job/<request_id>; no path crosses
// the wire or becomes available to public workspace tools.
enum WorkspaceArtifactKind {
WORKSPACE_ARTIFACT_KIND_UNSPECIFIED = 0;
WORKSPACE_ARTIFACT_KIND_PLAN = 1;
WORKSPACE_ARTIFACT_KIND_REVIEW = 2;
}
enum WorkspaceArtifactOperation {
WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED = 0;
WORKSPACE_ARTIFACT_OPERATION_READ = 1;
WORKSPACE_ARTIFACT_OPERATION_WRITE = 2;
}
message WorkspaceArtifactRequest {
string request_id = 1;
WorkspaceArtifactKind kind = 2;
WorkspaceArtifactOperation operation = 3;
bytes content = 4;
}
message WorkspaceArtifactResponse {
string request_id = 1;
WorkspaceArtifactKind kind = 2;
WorkspaceArtifactOperation operation = 3;
WorkspaceStatus status = 4;
WorkspaceErrorCode error_code = 5;
string error = 6;
bytes content = 7;
}
message WorkspaceCancelRequest {
string request_id = 1;
string stage_id = 2;
string tool_call_id = 3;
}
message WorkspaceCancelResponse {
string request_id = 1;
string stage_id = 2;
string tool_call_id = 3;
WorkspaceStatus status = 4;
WorkspaceErrorCode error_code = 5;
string error = 6;
}
// WorkspaceCleanupRequest is explicit and request-owned. It removes only
// request artifacts/processes; user workspace results remain outside cleanup.
message WorkspaceCleanupRequest {
string request_id = 1;
}
message WorkspaceCleanupResponse {
string request_id = 1;
WorkspaceStatus status = 2;
WorkspaceErrorCode error_code = 3;
string error = 4;
int32 cleaned_processes = 5;
int32 cleaned_artifacts = 6;
}
// AdapterConfig describes one adapter to enable on the node.
// name is the stable instance identity within a node; for single-instance
// adapters it may be empty (equivalent to the type name). When a node carries
// multiple instances of the same adapter type each must have a unique name.
message AdapterConfig {
reserved 4;
reserved "cli";
string type = 1; // "mock" | "ollama" | "vllm" | "openai_compat"
bool enabled = 2;
google.protobuf.Struct settings = 3; // legacy/compat path; new adapters use oneof
oneof config {
OllamaAdapterConfig ollama = 5;
VllmAdapterConfig vllm = 6;
MockAdapterConfig mock = 7;
OpenAICompatAdapterConfig openai_compat = 10;
}
string name = 8; // stable instance identity; empty = legacy single-instance
string target = 9; // optional default target/route hint for this instance
}
message MockAdapterConfig {}
message OllamaAdapterConfig {
string base_url = 1;
int32 context_size = 2;
int32 capacity = 3;
int32 max_queue = 4;
int32 queue_timeout_ms = 5;
int32 request_timeout_ms = 6;
}
message VllmAdapterConfig {
string endpoint = 1;
int32 capacity = 2;
int32 max_queue = 3;
int32 queue_timeout_ms = 4;
int32 request_timeout_ms = 5;
}
message OpenAICompatAdapterConfig {
string provider = 1;
string endpoint = 2;
map<string, string> headers = 3;
int32 capacity = 4;
int32 max_queue = 5;
int32 queue_timeout_ms = 6;
int32 request_timeout_ms = 7;
// protocol_profile is the resolved concrete protocol profile snapshot.
// When set, the adapter uses the profile's operation paths for URL
// resolution instead of the legacy endpoint + /v1 suffix heuristic.
ConcreteProtocolProfile protocol_profile = 8;
}
// ProtocolAuth declares how a concrete protocol profile authenticates to its
// upstream. It is the wire representation of config.ProtocolAuthConf.
message ProtocolAuth {
string header = 1; // request header name (e.g. "Authorization", "x-api-key")
string scheme = 2; // auth scheme prefix (e.g. "Bearer"); empty means raw header value
}
// ConcreteProtocolProfile is the immutable, resolved snapshot of a protocol
// profile carried over the wire. It never contains a Base reference.
message ConcreteProtocolProfile {
string id = 1;
string driver = 2;
string base_url = 3;
map<string, string> operations = 4;
ProtocolAuth auth = 5;
repeated string capabilities = 6;
map<string, string> model_mapping = 7;
google.protobuf.Struct extensions = 8;
}
// NodeRuntimeConfig carries legacy node runtime metadata. Execution admission
// must not use this as a node-wide global gate; provider/resource capacity owns
// concurrency.
message NodeRuntimeConfig {
int32 concurrency = 1; // legacy compatibility, 0/unset means no node-wide limit
reserved 2;
}
enum NodeConfigRefreshStatus {
NODE_CONFIG_REFRESH_STATUS_UNSPECIFIED = 0;
NODE_CONFIG_REFRESH_STATUS_APPLIED = 1;
NODE_CONFIG_REFRESH_STATUS_RESTART_REQUIRED = 2;
NODE_CONFIG_REFRESH_STATUS_FAILED = 3;
NODE_CONFIG_REFRESH_STATUS_SKIPPED = 4;
}
// NodeConfigRefreshRequest is sent by edge to a connected node to push a new config payload.
message NodeConfigRefreshRequest {
string request_id = 1;
NodeConfigPayload config = 2;
repeated string changed_paths = 3;
}
// NodeConfigRefreshResponse is returned by the node to acknowledge or report failure.
message NodeConfigRefreshResponse {
string request_id = 1;
NodeConfigRefreshStatus status = 2;
repeated string restart_required_paths = 3;
string error = 4;
}