protoc \
	--go_out=. \
	--go_opt=module=iop \
	--proto_path=. \
	proto/iop/runtime.proto \
	proto/iop/node.proto \
	proto/iop/control.proto \
	proto/iop/job.proto
diff --git a/proto/gen/iop/runtime.pb.go b/proto/gen/iop/runtime.pb.go
index 64c8a01c..0bbc5cb4 100644
--- a/proto/gen/iop/runtime.pb.go
+++ b/proto/gen/iop/runtime.pb.go
@@ -314,6 +314,107 @@ func (WorkspaceErrorCode) EnumDescriptor() ([]byte, []int) {
 	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{4}
 }
 
+// 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.
+type WorkspaceArtifactKind int32
+
+const (
+	WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED WorkspaceArtifactKind = 0
+	WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN        WorkspaceArtifactKind = 1
+	WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW      WorkspaceArtifactKind = 2
+)
+
+// Enum value maps for WorkspaceArtifactKind.
+var (
+	WorkspaceArtifactKind_name = map[int32]string{
+		0: "WORKSPACE_ARTIFACT_KIND_UNSPECIFIED",
+		1: "WORKSPACE_ARTIFACT_KIND_PLAN",
+		2: "WORKSPACE_ARTIFACT_KIND_REVIEW",
+	}
+	WorkspaceArtifactKind_value = map[string]int32{
+		"WORKSPACE_ARTIFACT_KIND_UNSPECIFIED": 0,
+		"WORKSPACE_ARTIFACT_KIND_PLAN":        1,
+		"WORKSPACE_ARTIFACT_KIND_REVIEW":      2,
+	}
+)
+
+func (x WorkspaceArtifactKind) Enum() *WorkspaceArtifactKind {
+	p := new(WorkspaceArtifactKind)
+	*p = x
+	return p
+}
+
+func (x WorkspaceArtifactKind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (WorkspaceArtifactKind) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto_iop_runtime_proto_enumTypes[5].Descriptor()
+}
+
+func (WorkspaceArtifactKind) Type() protoreflect.EnumType {
+	return &file_proto_iop_runtime_proto_enumTypes[5]
+}
+
+func (x WorkspaceArtifactKind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use WorkspaceArtifactKind.Descriptor instead.
+func (WorkspaceArtifactKind) EnumDescriptor() ([]byte, []int) {
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{5}
+}
+
+type WorkspaceArtifactOperation int32
+
+const (
+	WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED WorkspaceArtifactOperation = 0
+	WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ        WorkspaceArtifactOperation = 1
+	WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE       WorkspaceArtifactOperation = 2
+)
+
+// Enum value maps for WorkspaceArtifactOperation.
+var (
+	WorkspaceArtifactOperation_name = map[int32]string{
+		0: "WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED",
+		1: "WORKSPACE_ARTIFACT_OPERATION_READ",
+		2: "WORKSPACE_ARTIFACT_OPERATION_WRITE",
+	}
+	WorkspaceArtifactOperation_value = map[string]int32{
+		"WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED": 0,
+		"WORKSPACE_ARTIFACT_OPERATION_READ":        1,
+		"WORKSPACE_ARTIFACT_OPERATION_WRITE":       2,
+	}
+)
+
+func (x WorkspaceArtifactOperation) Enum() *WorkspaceArtifactOperation {
+	p := new(WorkspaceArtifactOperation)
+	*p = x
+	return p
+}
+
+func (x WorkspaceArtifactOperation) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (WorkspaceArtifactOperation) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto_iop_runtime_proto_enumTypes[6].Descriptor()
+}
+
+func (WorkspaceArtifactOperation) Type() protoreflect.EnumType {
+	return &file_proto_iop_runtime_proto_enumTypes[6]
+}
+
+func (x WorkspaceArtifactOperation) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use WorkspaceArtifactOperation.Descriptor instead.
+func (WorkspaceArtifactOperation) EnumDescriptor() ([]byte, []int) {
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{6}
+}
+
 type NodeConfigRefreshStatus int32
 
 const (
@@ -353,11 +454,11 @@ func (x NodeConfigRefreshStatus) String() string {
 }
 
 func (NodeConfigRefreshStatus) Descriptor() protoreflect.EnumDescriptor {
-	return file_proto_iop_runtime_proto_enumTypes[5].Descriptor()
+	return file_proto_iop_runtime_proto_enumTypes[7].Descriptor()
 }
 
 func (NodeConfigRefreshStatus) Type() protoreflect.EnumType {
-	return &file_proto_iop_runtime_proto_enumTypes[5]
+	return &file_proto_iop_runtime_proto_enumTypes[7]
 }
 
 func (x NodeConfigRefreshStatus) Number() protoreflect.EnumNumber {
@@ -366,7 +467,7 @@ func (x NodeConfigRefreshStatus) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use NodeConfigRefreshStatus.Descriptor instead.
 func (NodeConfigRefreshStatus) EnumDescriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{5}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{7}
 }
 
 // RunRequest initiates an adapter execution on a node.
@@ -3228,6 +3329,166 @@ func (x *WorkspaceToolResponse) GetDurationMs() int64 {
 	return 0
 }
 
+type WorkspaceArtifactRequest struct {
+	state         protoimpl.MessageState     `protogen:"open.v1"`
+	RequestId     string                     `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+	Kind          WorkspaceArtifactKind      `protobuf:"varint,2,opt,name=kind,proto3,enum=iop.WorkspaceArtifactKind" json:"kind,omitempty"`
+	Operation     WorkspaceArtifactOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=iop.WorkspaceArtifactOperation" json:"operation,omitempty"`
+	Content       []byte                     `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *WorkspaceArtifactRequest) Reset() {
+	*x = WorkspaceArtifactRequest{}
+	mi := &file_proto_iop_runtime_proto_msgTypes[30]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *WorkspaceArtifactRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WorkspaceArtifactRequest) ProtoMessage() {}
+
+func (x *WorkspaceArtifactRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_iop_runtime_proto_msgTypes[30]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WorkspaceArtifactRequest.ProtoReflect.Descriptor instead.
+func (*WorkspaceArtifactRequest) Descriptor() ([]byte, []int) {
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{30}
+}
+
+func (x *WorkspaceArtifactRequest) GetRequestId() string {
+	if x != nil {
+		return x.RequestId
+	}
+	return ""
+}
+
+func (x *WorkspaceArtifactRequest) GetKind() WorkspaceArtifactKind {
+	if x != nil {
+		return x.Kind
+	}
+	return WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactRequest) GetOperation() WorkspaceArtifactOperation {
+	if x != nil {
+		return x.Operation
+	}
+	return WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactRequest) GetContent() []byte {
+	if x != nil {
+		return x.Content
+	}
+	return nil
+}
+
+type WorkspaceArtifactResponse struct {
+	state         protoimpl.MessageState     `protogen:"open.v1"`
+	RequestId     string                     `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+	Kind          WorkspaceArtifactKind      `protobuf:"varint,2,opt,name=kind,proto3,enum=iop.WorkspaceArtifactKind" json:"kind,omitempty"`
+	Operation     WorkspaceArtifactOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=iop.WorkspaceArtifactOperation" json:"operation,omitempty"`
+	Status        WorkspaceStatus            `protobuf:"varint,4,opt,name=status,proto3,enum=iop.WorkspaceStatus" json:"status,omitempty"`
+	ErrorCode     WorkspaceErrorCode         `protobuf:"varint,5,opt,name=error_code,json=errorCode,proto3,enum=iop.WorkspaceErrorCode" json:"error_code,omitempty"`
+	Error         string                     `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"`
+	Content       []byte                     `protobuf:"bytes,7,opt,name=content,proto3" json:"content,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *WorkspaceArtifactResponse) Reset() {
+	*x = WorkspaceArtifactResponse{}
+	mi := &file_proto_iop_runtime_proto_msgTypes[31]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *WorkspaceArtifactResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WorkspaceArtifactResponse) ProtoMessage() {}
+
+func (x *WorkspaceArtifactResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_iop_runtime_proto_msgTypes[31]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WorkspaceArtifactResponse.ProtoReflect.Descriptor instead.
+func (*WorkspaceArtifactResponse) Descriptor() ([]byte, []int) {
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{31}
+}
+
+func (x *WorkspaceArtifactResponse) GetRequestId() string {
+	if x != nil {
+		return x.RequestId
+	}
+	return ""
+}
+
+func (x *WorkspaceArtifactResponse) GetKind() WorkspaceArtifactKind {
+	if x != nil {
+		return x.Kind
+	}
+	return WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactResponse) GetOperation() WorkspaceArtifactOperation {
+	if x != nil {
+		return x.Operation
+	}
+	return WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactResponse) GetStatus() WorkspaceStatus {
+	if x != nil {
+		return x.Status
+	}
+	return WorkspaceStatus_WORKSPACE_STATUS_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactResponse) GetErrorCode() WorkspaceErrorCode {
+	if x != nil {
+		return x.ErrorCode
+	}
+	return WorkspaceErrorCode_WORKSPACE_ERROR_CODE_UNSPECIFIED
+}
+
+func (x *WorkspaceArtifactResponse) GetError() string {
+	if x != nil {
+		return x.Error
+	}
+	return ""
+}
+
+func (x *WorkspaceArtifactResponse) GetContent() []byte {
+	if x != nil {
+		return x.Content
+	}
+	return nil
+}
+
 type WorkspaceCancelRequest struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	RequestId     string                 `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
@@ -3239,7 +3500,7 @@ type WorkspaceCancelRequest struct {
 
 func (x *WorkspaceCancelRequest) Reset() {
 	*x = WorkspaceCancelRequest{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[30]
+	mi := &file_proto_iop_runtime_proto_msgTypes[32]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3251,7 +3512,7 @@ func (x *WorkspaceCancelRequest) String() string {
 func (*WorkspaceCancelRequest) ProtoMessage() {}
 
 func (x *WorkspaceCancelRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[30]
+	mi := &file_proto_iop_runtime_proto_msgTypes[32]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3264,7 +3525,7 @@ func (x *WorkspaceCancelRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use WorkspaceCancelRequest.ProtoReflect.Descriptor instead.
 func (*WorkspaceCancelRequest) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{30}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{32}
 }
 
 func (x *WorkspaceCancelRequest) GetRequestId() string {
@@ -3302,7 +3563,7 @@ type WorkspaceCancelResponse struct {
 
 func (x *WorkspaceCancelResponse) Reset() {
 	*x = WorkspaceCancelResponse{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[31]
+	mi := &file_proto_iop_runtime_proto_msgTypes[33]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3314,7 +3575,7 @@ func (x *WorkspaceCancelResponse) String() string {
 func (*WorkspaceCancelResponse) ProtoMessage() {}
 
 func (x *WorkspaceCancelResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[31]
+	mi := &file_proto_iop_runtime_proto_msgTypes[33]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3327,7 +3588,7 @@ func (x *WorkspaceCancelResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use WorkspaceCancelResponse.ProtoReflect.Descriptor instead.
 func (*WorkspaceCancelResponse) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{31}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{33}
 }
 
 func (x *WorkspaceCancelResponse) GetRequestId() string {
@@ -3383,7 +3644,7 @@ type WorkspaceCleanupRequest struct {
 
 func (x *WorkspaceCleanupRequest) Reset() {
 	*x = WorkspaceCleanupRequest{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[32]
+	mi := &file_proto_iop_runtime_proto_msgTypes[34]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3395,7 +3656,7 @@ func (x *WorkspaceCleanupRequest) String() string {
 func (*WorkspaceCleanupRequest) ProtoMessage() {}
 
 func (x *WorkspaceCleanupRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[32]
+	mi := &file_proto_iop_runtime_proto_msgTypes[34]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3408,7 +3669,7 @@ func (x *WorkspaceCleanupRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use WorkspaceCleanupRequest.ProtoReflect.Descriptor instead.
 func (*WorkspaceCleanupRequest) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{32}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{34}
 }
 
 func (x *WorkspaceCleanupRequest) GetRequestId() string {
@@ -3432,7 +3693,7 @@ type WorkspaceCleanupResponse struct {
 
 func (x *WorkspaceCleanupResponse) Reset() {
 	*x = WorkspaceCleanupResponse{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[33]
+	mi := &file_proto_iop_runtime_proto_msgTypes[35]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3444,7 +3705,7 @@ func (x *WorkspaceCleanupResponse) String() string {
 func (*WorkspaceCleanupResponse) ProtoMessage() {}
 
 func (x *WorkspaceCleanupResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[33]
+	mi := &file_proto_iop_runtime_proto_msgTypes[35]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3457,7 +3718,7 @@ func (x *WorkspaceCleanupResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use WorkspaceCleanupResponse.ProtoReflect.Descriptor instead.
 func (*WorkspaceCleanupResponse) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{33}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{35}
 }
 
 func (x *WorkspaceCleanupResponse) GetRequestId() string {
@@ -3526,7 +3787,7 @@ type AdapterConfig struct {
 
 func (x *AdapterConfig) Reset() {
 	*x = AdapterConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[34]
+	mi := &file_proto_iop_runtime_proto_msgTypes[36]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3538,7 +3799,7 @@ func (x *AdapterConfig) String() string {
 func (*AdapterConfig) ProtoMessage() {}
 
 func (x *AdapterConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[34]
+	mi := &file_proto_iop_runtime_proto_msgTypes[36]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3551,7 +3812,7 @@ func (x *AdapterConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use AdapterConfig.ProtoReflect.Descriptor instead.
 func (*AdapterConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{34}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{36}
 }
 
 func (x *AdapterConfig) GetType() string {
@@ -3668,7 +3929,7 @@ type MockAdapterConfig struct {
 
 func (x *MockAdapterConfig) Reset() {
 	*x = MockAdapterConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[35]
+	mi := &file_proto_iop_runtime_proto_msgTypes[37]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3680,7 +3941,7 @@ func (x *MockAdapterConfig) String() string {
 func (*MockAdapterConfig) ProtoMessage() {}
 
 func (x *MockAdapterConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[35]
+	mi := &file_proto_iop_runtime_proto_msgTypes[37]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3693,7 +3954,7 @@ func (x *MockAdapterConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use MockAdapterConfig.ProtoReflect.Descriptor instead.
 func (*MockAdapterConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{35}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{37}
 }
 
 type OllamaAdapterConfig struct {
@@ -3710,7 +3971,7 @@ type OllamaAdapterConfig struct {
 
 func (x *OllamaAdapterConfig) Reset() {
 	*x = OllamaAdapterConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[36]
+	mi := &file_proto_iop_runtime_proto_msgTypes[38]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3722,7 +3983,7 @@ func (x *OllamaAdapterConfig) String() string {
 func (*OllamaAdapterConfig) ProtoMessage() {}
 
 func (x *OllamaAdapterConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[36]
+	mi := &file_proto_iop_runtime_proto_msgTypes[38]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3735,7 +3996,7 @@ func (x *OllamaAdapterConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use OllamaAdapterConfig.ProtoReflect.Descriptor instead.
 func (*OllamaAdapterConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{36}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{38}
 }
 
 func (x *OllamaAdapterConfig) GetBaseUrl() string {
@@ -3793,7 +4054,7 @@ type VllmAdapterConfig struct {
 
 func (x *VllmAdapterConfig) Reset() {
 	*x = VllmAdapterConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[37]
+	mi := &file_proto_iop_runtime_proto_msgTypes[39]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3805,7 +4066,7 @@ func (x *VllmAdapterConfig) String() string {
 func (*VllmAdapterConfig) ProtoMessage() {}
 
 func (x *VllmAdapterConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[37]
+	mi := &file_proto_iop_runtime_proto_msgTypes[39]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3818,7 +4079,7 @@ func (x *VllmAdapterConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use VllmAdapterConfig.ProtoReflect.Descriptor instead.
 func (*VllmAdapterConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{37}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{39}
 }
 
 func (x *VllmAdapterConfig) GetEndpoint() string {
@@ -3875,7 +4136,7 @@ type OpenAICompatAdapterConfig struct {
 
 func (x *OpenAICompatAdapterConfig) Reset() {
 	*x = OpenAICompatAdapterConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[38]
+	mi := &file_proto_iop_runtime_proto_msgTypes[40]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3887,7 +4148,7 @@ func (x *OpenAICompatAdapterConfig) String() string {
 func (*OpenAICompatAdapterConfig) ProtoMessage() {}
 
 func (x *OpenAICompatAdapterConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[38]
+	mi := &file_proto_iop_runtime_proto_msgTypes[40]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3900,7 +4161,7 @@ func (x *OpenAICompatAdapterConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use OpenAICompatAdapterConfig.ProtoReflect.Descriptor instead.
 func (*OpenAICompatAdapterConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{38}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{40}
 }
 
 func (x *OpenAICompatAdapterConfig) GetProvider() string {
@@ -3971,7 +4232,7 @@ type ProtocolAuth struct {
 
 func (x *ProtocolAuth) Reset() {
 	*x = ProtocolAuth{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[39]
+	mi := &file_proto_iop_runtime_proto_msgTypes[41]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -3983,7 +4244,7 @@ func (x *ProtocolAuth) String() string {
 func (*ProtocolAuth) ProtoMessage() {}
 
 func (x *ProtocolAuth) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[39]
+	mi := &file_proto_iop_runtime_proto_msgTypes[41]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3996,7 +4257,7 @@ func (x *ProtocolAuth) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ProtocolAuth.ProtoReflect.Descriptor instead.
 func (*ProtocolAuth) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{39}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{41}
 }
 
 func (x *ProtocolAuth) GetHeader() string {
@@ -4031,7 +4292,7 @@ type ConcreteProtocolProfile struct {
 
 func (x *ConcreteProtocolProfile) Reset() {
 	*x = ConcreteProtocolProfile{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[40]
+	mi := &file_proto_iop_runtime_proto_msgTypes[42]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -4043,7 +4304,7 @@ func (x *ConcreteProtocolProfile) String() string {
 func (*ConcreteProtocolProfile) ProtoMessage() {}
 
 func (x *ConcreteProtocolProfile) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[40]
+	mi := &file_proto_iop_runtime_proto_msgTypes[42]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4056,7 +4317,7 @@ func (x *ConcreteProtocolProfile) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ConcreteProtocolProfile.ProtoReflect.Descriptor instead.
 func (*ConcreteProtocolProfile) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{40}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{42}
 }
 
 func (x *ConcreteProtocolProfile) GetId() string {
@@ -4127,7 +4388,7 @@ type NodeRuntimeConfig struct {
 
 func (x *NodeRuntimeConfig) Reset() {
 	*x = NodeRuntimeConfig{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[41]
+	mi := &file_proto_iop_runtime_proto_msgTypes[43]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -4139,7 +4400,7 @@ func (x *NodeRuntimeConfig) String() string {
 func (*NodeRuntimeConfig) ProtoMessage() {}
 
 func (x *NodeRuntimeConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[41]
+	mi := &file_proto_iop_runtime_proto_msgTypes[43]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4152,7 +4413,7 @@ func (x *NodeRuntimeConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NodeRuntimeConfig.ProtoReflect.Descriptor instead.
 func (*NodeRuntimeConfig) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{41}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{43}
 }
 
 func (x *NodeRuntimeConfig) GetConcurrency() int32 {
@@ -4174,7 +4435,7 @@ type NodeConfigRefreshRequest struct {
 
 func (x *NodeConfigRefreshRequest) Reset() {
 	*x = NodeConfigRefreshRequest{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[42]
+	mi := &file_proto_iop_runtime_proto_msgTypes[44]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -4186,7 +4447,7 @@ func (x *NodeConfigRefreshRequest) String() string {
 func (*NodeConfigRefreshRequest) ProtoMessage() {}
 
 func (x *NodeConfigRefreshRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[42]
+	mi := &file_proto_iop_runtime_proto_msgTypes[44]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4199,7 +4460,7 @@ func (x *NodeConfigRefreshRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NodeConfigRefreshRequest.ProtoReflect.Descriptor instead.
 func (*NodeConfigRefreshRequest) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{42}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{44}
 }
 
 func (x *NodeConfigRefreshRequest) GetRequestId() string {
@@ -4236,7 +4497,7 @@ type NodeConfigRefreshResponse struct {
 
 func (x *NodeConfigRefreshResponse) Reset() {
 	*x = NodeConfigRefreshResponse{}
-	mi := &file_proto_iop_runtime_proto_msgTypes[43]
+	mi := &file_proto_iop_runtime_proto_msgTypes[45]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -4248,7 +4509,7 @@ func (x *NodeConfigRefreshResponse) String() string {
 func (*NodeConfigRefreshResponse) ProtoMessage() {}
 
 func (x *NodeConfigRefreshResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_iop_runtime_proto_msgTypes[43]
+	mi := &file_proto_iop_runtime_proto_msgTypes[45]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4261,7 +4522,7 @@ func (x *NodeConfigRefreshResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NodeConfigRefreshResponse.ProtoReflect.Descriptor instead.
 func (*NodeConfigRefreshResponse) Descriptor() ([]byte, []int) {
-	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{43}
+	return file_proto_iop_runtime_proto_rawDescGZIP(), []int{45}
 }
 
 func (x *NodeConfigRefreshResponse) GetRequestId() string {
@@ -4626,7 +4887,23 @@ const file_proto_iop_runtime_proto_rawDesc = "" +
 	"\texit_code\x18\v \x01(\x05R\bexitCode\x12\x1c\n" +
 	"\ttruncated\x18\f \x01(\bR\ttruncated\x12\x1f\n" +
 	"\vduration_ms\x18\r \x01(\x03R\n" +
-	"durationMs\"t\n" +
+	"durationMs\"\xc2\x01\n" +
+	"\x18WorkspaceArtifactRequest\x12\x1d\n" +
+	"\n" +
+	"request_id\x18\x01 \x01(\tR\trequestId\x12.\n" +
+	"\x04kind\x18\x02 \x01(\x0e2\x1a.iop.WorkspaceArtifactKindR\x04kind\x12=\n" +
+	"\toperation\x18\x03 \x01(\x0e2\x1f.iop.WorkspaceArtifactOperationR\toperation\x12\x18\n" +
+	"\acontent\x18\x04 \x01(\fR\acontent\"\xbf\x02\n" +
+	"\x19WorkspaceArtifactResponse\x12\x1d\n" +
+	"\n" +
+	"request_id\x18\x01 \x01(\tR\trequestId\x12.\n" +
+	"\x04kind\x18\x02 \x01(\x0e2\x1a.iop.WorkspaceArtifactKindR\x04kind\x12=\n" +
+	"\toperation\x18\x03 \x01(\x0e2\x1f.iop.WorkspaceArtifactOperationR\toperation\x12,\n" +
+	"\x06status\x18\x04 \x01(\x0e2\x14.iop.WorkspaceStatusR\x06status\x126\n" +
+	"\n" +
+	"error_code\x18\x05 \x01(\x0e2\x17.iop.WorkspaceErrorCodeR\terrorCode\x12\x14\n" +
+	"\x05error\x18\x06 \x01(\tR\x05error\x12\x18\n" +
+	"\acontent\x18\a \x01(\fR\acontent\"t\n" +
 	"\x16WorkspaceCancelRequest\x12\x1d\n" +
 	"\n" +
 	"request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" +
@@ -4762,7 +5039,15 @@ const file_proto_iop_runtime_proto_rawDesc = "" +
 	"\x1eWORKSPACE_ERROR_CODE_NOT_FOUND\x10\x04\x12 \n" +
 	"\x1cWORKSPACE_ERROR_CODE_TIMEOUT\x10\x05\x12\"\n" +
 	"\x1eWORKSPACE_ERROR_CODE_CANCELLED\x10\x06\x12!\n" +
-	"\x1dWORKSPACE_ERROR_CODE_INTERNAL\x10\a*\xed\x01\n" +
+	"\x1dWORKSPACE_ERROR_CODE_INTERNAL\x10\a*\x86\x01\n" +
+	"\x15WorkspaceArtifactKind\x12'\n" +
+	"#WORKSPACE_ARTIFACT_KIND_UNSPECIFIED\x10\x00\x12 \n" +
+	"\x1cWORKSPACE_ARTIFACT_KIND_PLAN\x10\x01\x12\"\n" +
+	"\x1eWORKSPACE_ARTIFACT_KIND_REVIEW\x10\x02*\x99\x01\n" +
+	"\x1aWorkspaceArtifactOperation\x12,\n" +
+	"(WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED\x10\x00\x12%\n" +
+	"!WORKSPACE_ARTIFACT_OPERATION_READ\x10\x01\x12&\n" +
+	"\"WORKSPACE_ARTIFACT_OPERATION_WRITE\x10\x02*\xed\x01\n" +
 	"\x17NodeConfigRefreshStatus\x12*\n" +
 	"&NODE_CONFIG_REFRESH_STATUS_UNSPECIFIED\x10\x00\x12&\n" +
 	"\"NODE_CONFIG_REFRESH_STATUS_APPLIED\x10\x01\x12/\n" +
@@ -4782,137 +5067,147 @@ func file_proto_iop_runtime_proto_rawDescGZIP() []byte {
 	return file_proto_iop_runtime_proto_rawDescData
 }
 
-var file_proto_iop_runtime_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
-var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 58)
+var file_proto_iop_runtime_proto_enumTypes = make([]protoimpl.EnumInfo, 8)
+var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 60)
 var file_proto_iop_runtime_proto_goTypes = []any{
 	(ProviderTunnelFrameKind)(0),      // 0: iop.ProviderTunnelFrameKind
 	(NodeCommandType)(0),              // 1: iop.NodeCommandType
 	(WorkspaceOperation)(0),           // 2: iop.WorkspaceOperation
 	(WorkspaceStatus)(0),              // 3: iop.WorkspaceStatus
 	(WorkspaceErrorCode)(0),           // 4: iop.WorkspaceErrorCode
-	(NodeConfigRefreshStatus)(0),      // 5: iop.NodeConfigRefreshStatus
-	(*RunRequest)(nil),                // 6: iop.RunRequest
-	(*RunEvent)(nil),                  // 7: iop.RunEvent
-	(*ProviderTunnelRequest)(nil),     // 8: iop.ProviderTunnelRequest
-	(*CredentialLeaseScope)(nil),      // 9: iop.CredentialLeaseScope
-	(*SignedCredentialLease)(nil),     // 10: iop.SignedCredentialLease
-	(*CredentialLeaseBinding)(nil),    // 11: iop.CredentialLeaseBinding
-	(*AcquireLeaseRequest)(nil),       // 12: iop.AcquireLeaseRequest
-	(*AcquireLeaseResponse)(nil),      // 13: iop.AcquireLeaseResponse
-	(*ProviderTunnelFrame)(nil),       // 14: iop.ProviderTunnelFrame
-	(*EdgeNodeEvent)(nil),             // 15: iop.EdgeNodeEvent
-	(*ExecutionFailure)(nil),          // 16: iop.ExecutionFailure
-	(*Usage)(nil),                     // 17: iop.Usage
-	(*Heartbeat)(nil),                 // 18: iop.Heartbeat
-	(*CancelRequest)(nil),             // 19: iop.CancelRequest
-	(*NodeCommandRequest)(nil),        // 20: iop.NodeCommandRequest
-	(*NodeCommandResponse)(nil),       // 21: iop.NodeCommandResponse
-	(*ProviderSnapshot)(nil),          // 22: iop.ProviderSnapshot
-	(*Error)(nil),                     // 23: iop.Error
-	(*RegisterRequest)(nil),           // 24: iop.RegisterRequest
-	(*RegisterResponse)(nil),          // 25: iop.RegisterResponse
-	(*NodeReadyRequest)(nil),          // 26: iop.NodeReadyRequest
-	(*NodeReadyResponse)(nil),         // 27: iop.NodeReadyResponse
-	(*NodeConfigPayload)(nil),         // 28: iop.NodeConfigPayload
-	(*WorkspaceCommandConfig)(nil),    // 29: iop.WorkspaceCommandConfig
-	(*WorkspaceConfig)(nil),           // 30: iop.WorkspaceConfig
-	(*WorkspaceOpenRequest)(nil),      // 31: iop.WorkspaceOpenRequest
-	(*WorkspaceOpenResponse)(nil),     // 32: iop.WorkspaceOpenResponse
-	(*WorkspaceWriteInput)(nil),       // 33: iop.WorkspaceWriteInput
-	(*WorkspaceToolRequest)(nil),      // 34: iop.WorkspaceToolRequest
-	(*WorkspaceToolResponse)(nil),     // 35: iop.WorkspaceToolResponse
-	(*WorkspaceCancelRequest)(nil),    // 36: iop.WorkspaceCancelRequest
-	(*WorkspaceCancelResponse)(nil),   // 37: iop.WorkspaceCancelResponse
-	(*WorkspaceCleanupRequest)(nil),   // 38: iop.WorkspaceCleanupRequest
-	(*WorkspaceCleanupResponse)(nil),  // 39: iop.WorkspaceCleanupResponse
-	(*AdapterConfig)(nil),             // 40: iop.AdapterConfig
-	(*MockAdapterConfig)(nil),         // 41: iop.MockAdapterConfig
-	(*OllamaAdapterConfig)(nil),       // 42: iop.OllamaAdapterConfig
-	(*VllmAdapterConfig)(nil),         // 43: iop.VllmAdapterConfig
-	(*OpenAICompatAdapterConfig)(nil), // 44: iop.OpenAICompatAdapterConfig
-	(*ProtocolAuth)(nil),              // 45: iop.ProtocolAuth
-	(*ConcreteProtocolProfile)(nil),   // 46: iop.ConcreteProtocolProfile
-	(*NodeRuntimeConfig)(nil),         // 47: iop.NodeRuntimeConfig
-	(*NodeConfigRefreshRequest)(nil),  // 48: iop.NodeConfigRefreshRequest
-	(*NodeConfigRefreshResponse)(nil), // 49: iop.NodeConfigRefreshResponse
-	nil,                               // 50: iop.RunRequest.MetadataEntry
-	nil,                               // 51: iop.RunEvent.MetadataEntry
-	nil,                               // 52: iop.ProviderTunnelRequest.HeadersEntry
-	nil,                               // 53: iop.ProviderTunnelRequest.MetadataEntry
-	nil,                               // 54: iop.ProviderTunnelFrame.HeadersEntry
-	nil,                               // 55: iop.ProviderTunnelFrame.MetadataEntry
-	nil,                               // 56: iop.EdgeNodeEvent.MetadataEntry
-	nil,                               // 57: iop.ExecutionFailure.MetadataEntry
-	nil,                               // 58: iop.NodeCommandRequest.MetadataEntry
-	nil,                               // 59: iop.NodeCommandResponse.ResultEntry
-	nil,                               // 60: iop.WorkspaceToolRequest.EnvironmentEntry
-	nil,                               // 61: iop.OpenAICompatAdapterConfig.HeadersEntry
-	nil,                               // 62: iop.ConcreteProtocolProfile.OperationsEntry
-	nil,                               // 63: iop.ConcreteProtocolProfile.ModelMappingEntry
-	(*structpb.Struct)(nil),           // 64: google.protobuf.Struct
+	(WorkspaceArtifactKind)(0),        // 5: iop.WorkspaceArtifactKind
+	(WorkspaceArtifactOperation)(0),   // 6: iop.WorkspaceArtifactOperation
+	(NodeConfigRefreshStatus)(0),      // 7: iop.NodeConfigRefreshStatus
+	(*RunRequest)(nil),                // 8: iop.RunRequest
+	(*RunEvent)(nil),                  // 9: iop.RunEvent
+	(*ProviderTunnelRequest)(nil),     // 10: iop.ProviderTunnelRequest
+	(*CredentialLeaseScope)(nil),      // 11: iop.CredentialLeaseScope
+	(*SignedCredentialLease)(nil),     // 12: iop.SignedCredentialLease
+	(*CredentialLeaseBinding)(nil),    // 13: iop.CredentialLeaseBinding
+	(*AcquireLeaseRequest)(nil),       // 14: iop.AcquireLeaseRequest
+	(*AcquireLeaseResponse)(nil),      // 15: iop.AcquireLeaseResponse
+	(*ProviderTunnelFrame)(nil),       // 16: iop.ProviderTunnelFrame
+	(*EdgeNodeEvent)(nil),             // 17: iop.EdgeNodeEvent
+	(*ExecutionFailure)(nil),          // 18: iop.ExecutionFailure
+	(*Usage)(nil),                     // 19: iop.Usage
+	(*Heartbeat)(nil),                 // 20: iop.Heartbeat
+	(*CancelRequest)(nil),             // 21: iop.CancelRequest
+	(*NodeCommandRequest)(nil),        // 22: iop.NodeCommandRequest
+	(*NodeCommandResponse)(nil),       // 23: iop.NodeCommandResponse
+	(*ProviderSnapshot)(nil),          // 24: iop.ProviderSnapshot
+	(*Error)(nil),                     // 25: iop.Error
+	(*RegisterRequest)(nil),           // 26: iop.RegisterRequest
+	(*RegisterResponse)(nil),          // 27: iop.RegisterResponse
+	(*NodeReadyRequest)(nil),          // 28: iop.NodeReadyRequest
+	(*NodeReadyResponse)(nil),         // 29: iop.NodeReadyResponse
+	(*NodeConfigPayload)(nil),         // 30: iop.NodeConfigPayload
+	(*WorkspaceCommandConfig)(nil),    // 31: iop.WorkspaceCommandConfig
+	(*WorkspaceConfig)(nil),           // 32: iop.WorkspaceConfig
+	(*WorkspaceOpenRequest)(nil),      // 33: iop.WorkspaceOpenRequest
+	(*WorkspaceOpenResponse)(nil),     // 34: iop.WorkspaceOpenResponse
+	(*WorkspaceWriteInput)(nil),       // 35: iop.WorkspaceWriteInput
+	(*WorkspaceToolRequest)(nil),      // 36: iop.WorkspaceToolRequest
+	(*WorkspaceToolResponse)(nil),     // 37: iop.WorkspaceToolResponse
+	(*WorkspaceArtifactRequest)(nil),  // 38: iop.WorkspaceArtifactRequest
+	(*WorkspaceArtifactResponse)(nil), // 39: iop.WorkspaceArtifactResponse
+	(*WorkspaceCancelRequest)(nil),    // 40: iop.WorkspaceCancelRequest
+	(*WorkspaceCancelResponse)(nil),   // 41: iop.WorkspaceCancelResponse
+	(*WorkspaceCleanupRequest)(nil),   // 42: iop.WorkspaceCleanupRequest
+	(*WorkspaceCleanupResponse)(nil),  // 43: iop.WorkspaceCleanupResponse
+	(*AdapterConfig)(nil),             // 44: iop.AdapterConfig
+	(*MockAdapterConfig)(nil),         // 45: iop.MockAdapterConfig
+	(*OllamaAdapterConfig)(nil),       // 46: iop.OllamaAdapterConfig
+	(*VllmAdapterConfig)(nil),         // 47: iop.VllmAdapterConfig
+	(*OpenAICompatAdapterConfig)(nil), // 48: iop.OpenAICompatAdapterConfig
+	(*ProtocolAuth)(nil),              // 49: iop.ProtocolAuth
+	(*ConcreteProtocolProfile)(nil),   // 50: iop.ConcreteProtocolProfile
+	(*NodeRuntimeConfig)(nil),         // 51: iop.NodeRuntimeConfig
+	(*NodeConfigRefreshRequest)(nil),  // 52: iop.NodeConfigRefreshRequest
+	(*NodeConfigRefreshResponse)(nil), // 53: iop.NodeConfigRefreshResponse
+	nil,                               // 54: iop.RunRequest.MetadataEntry
+	nil,                               // 55: iop.RunEvent.MetadataEntry
+	nil,                               // 56: iop.ProviderTunnelRequest.HeadersEntry
+	nil,                               // 57: iop.ProviderTunnelRequest.MetadataEntry
+	nil,                               // 58: iop.ProviderTunnelFrame.HeadersEntry
+	nil,                               // 59: iop.ProviderTunnelFrame.MetadataEntry
+	nil,                               // 60: iop.EdgeNodeEvent.MetadataEntry
+	nil,                               // 61: iop.ExecutionFailure.MetadataEntry
+	nil,                               // 62: iop.NodeCommandRequest.MetadataEntry
+	nil,                               // 63: iop.NodeCommandResponse.ResultEntry
+	nil,                               // 64: iop.WorkspaceToolRequest.EnvironmentEntry
+	nil,                               // 65: iop.OpenAICompatAdapterConfig.HeadersEntry
+	nil,                               // 66: iop.ConcreteProtocolProfile.OperationsEntry
+	nil,                               // 67: iop.ConcreteProtocolProfile.ModelMappingEntry
+	(*structpb.Struct)(nil),           // 68: google.protobuf.Struct
 }
 var file_proto_iop_runtime_proto_depIdxs = []int32{
-	64, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct
-	64, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct
-	50, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry
-	17, // 3: iop.RunEvent.usage:type_name -> iop.Usage
-	51, // 4: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry
-	16, // 5: iop.RunEvent.failure:type_name -> iop.ExecutionFailure
-	52, // 6: iop.ProviderTunnelRequest.headers:type_name -> iop.ProviderTunnelRequest.HeadersEntry
-	53, // 7: iop.ProviderTunnelRequest.metadata:type_name -> iop.ProviderTunnelRequest.MetadataEntry
-	10, // 8: iop.ProviderTunnelRequest.credential_lease:type_name -> iop.SignedCredentialLease
-	11, // 9: iop.ProviderTunnelRequest.credential_binding:type_name -> iop.CredentialLeaseBinding
-	9,  // 10: iop.SignedCredentialLease.scope:type_name -> iop.CredentialLeaseScope
-	11, // 11: iop.AcquireLeaseRequest.binding:type_name -> iop.CredentialLeaseBinding
-	10, // 12: iop.AcquireLeaseResponse.lease:type_name -> iop.SignedCredentialLease
+	68, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct
+	68, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct
+	54, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry
+	19, // 3: iop.RunEvent.usage:type_name -> iop.Usage
+	55, // 4: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry
+	18, // 5: iop.RunEvent.failure:type_name -> iop.ExecutionFailure
+	56, // 6: iop.ProviderTunnelRequest.headers:type_name -> iop.ProviderTunnelRequest.HeadersEntry
+	57, // 7: iop.ProviderTunnelRequest.metadata:type_name -> iop.ProviderTunnelRequest.MetadataEntry
+	12, // 8: iop.ProviderTunnelRequest.credential_lease:type_name -> iop.SignedCredentialLease
+	13, // 9: iop.ProviderTunnelRequest.credential_binding:type_name -> iop.CredentialLeaseBinding
+	11, // 10: iop.SignedCredentialLease.scope:type_name -> iop.CredentialLeaseScope
+	13, // 11: iop.AcquireLeaseRequest.binding:type_name -> iop.CredentialLeaseBinding
+	12, // 12: iop.AcquireLeaseResponse.lease:type_name -> iop.SignedCredentialLease
 	0,  // 13: iop.ProviderTunnelFrame.kind:type_name -> iop.ProviderTunnelFrameKind
-	54, // 14: iop.ProviderTunnelFrame.headers:type_name -> iop.ProviderTunnelFrame.HeadersEntry
-	17, // 15: iop.ProviderTunnelFrame.usage:type_name -> iop.Usage
-	55, // 16: iop.ProviderTunnelFrame.metadata:type_name -> iop.ProviderTunnelFrame.MetadataEntry
-	16, // 17: iop.ProviderTunnelFrame.failure:type_name -> iop.ExecutionFailure
-	56, // 18: iop.EdgeNodeEvent.metadata:type_name -> iop.EdgeNodeEvent.MetadataEntry
-	57, // 19: iop.ExecutionFailure.metadata:type_name -> iop.ExecutionFailure.MetadataEntry
+	58, // 14: iop.ProviderTunnelFrame.headers:type_name -> iop.ProviderTunnelFrame.HeadersEntry
+	19, // 15: iop.ProviderTunnelFrame.usage:type_name -> iop.Usage
+	59, // 16: iop.ProviderTunnelFrame.metadata:type_name -> iop.ProviderTunnelFrame.MetadataEntry
+	18, // 17: iop.ProviderTunnelFrame.failure:type_name -> iop.ExecutionFailure
+	60, // 18: iop.EdgeNodeEvent.metadata:type_name -> iop.EdgeNodeEvent.MetadataEntry
+	61, // 19: iop.ExecutionFailure.metadata:type_name -> iop.ExecutionFailure.MetadataEntry
 	1,  // 20: iop.NodeCommandRequest.type:type_name -> iop.NodeCommandType
-	58, // 21: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry
+	62, // 21: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry
 	1,  // 22: iop.NodeCommandResponse.type:type_name -> iop.NodeCommandType
-	59, // 23: iop.NodeCommandResponse.result:type_name -> iop.NodeCommandResponse.ResultEntry
-	22, // 24: iop.NodeCommandResponse.provider_snapshots:type_name -> iop.ProviderSnapshot
-	28, // 25: iop.RegisterResponse.config:type_name -> iop.NodeConfigPayload
-	40, // 26: iop.NodeConfigPayload.adapters:type_name -> iop.AdapterConfig
-	47, // 27: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig
-	30, // 28: iop.NodeConfigPayload.workspaces:type_name -> iop.WorkspaceConfig
+	63, // 23: iop.NodeCommandResponse.result:type_name -> iop.NodeCommandResponse.ResultEntry
+	24, // 24: iop.NodeCommandResponse.provider_snapshots:type_name -> iop.ProviderSnapshot
+	30, // 25: iop.RegisterResponse.config:type_name -> iop.NodeConfigPayload
+	44, // 26: iop.NodeConfigPayload.adapters:type_name -> iop.AdapterConfig
+	51, // 27: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig
+	32, // 28: iop.NodeConfigPayload.workspaces:type_name -> iop.WorkspaceConfig
 	2,  // 29: iop.WorkspaceConfig.operations:type_name -> iop.WorkspaceOperation
-	29, // 30: iop.WorkspaceConfig.commands:type_name -> iop.WorkspaceCommandConfig
+	31, // 30: iop.WorkspaceConfig.commands:type_name -> iop.WorkspaceCommandConfig
 	2,  // 31: iop.WorkspaceOpenRequest.operations:type_name -> iop.WorkspaceOperation
 	3,  // 32: iop.WorkspaceOpenResponse.status:type_name -> iop.WorkspaceStatus
 	4,  // 33: iop.WorkspaceOpenResponse.error_code:type_name -> iop.WorkspaceErrorCode
 	2,  // 34: iop.WorkspaceToolRequest.operation:type_name -> iop.WorkspaceOperation
-	33, // 35: iop.WorkspaceToolRequest.write:type_name -> iop.WorkspaceWriteInput
-	60, // 36: iop.WorkspaceToolRequest.environment:type_name -> iop.WorkspaceToolRequest.EnvironmentEntry
+	35, // 35: iop.WorkspaceToolRequest.write:type_name -> iop.WorkspaceWriteInput
+	64, // 36: iop.WorkspaceToolRequest.environment:type_name -> iop.WorkspaceToolRequest.EnvironmentEntry
 	3,  // 37: iop.WorkspaceToolResponse.status:type_name -> iop.WorkspaceStatus
 	4,  // 38: iop.WorkspaceToolResponse.error_code:type_name -> iop.WorkspaceErrorCode
-	3,  // 39: iop.WorkspaceCancelResponse.status:type_name -> iop.WorkspaceStatus
-	4,  // 40: iop.WorkspaceCancelResponse.error_code:type_name -> iop.WorkspaceErrorCode
-	3,  // 41: iop.WorkspaceCleanupResponse.status:type_name -> iop.WorkspaceStatus
-	4,  // 42: iop.WorkspaceCleanupResponse.error_code:type_name -> iop.WorkspaceErrorCode
-	64, // 43: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct
-	42, // 44: iop.AdapterConfig.ollama:type_name -> iop.OllamaAdapterConfig
-	43, // 45: iop.AdapterConfig.vllm:type_name -> iop.VllmAdapterConfig
-	41, // 46: iop.AdapterConfig.mock:type_name -> iop.MockAdapterConfig
-	44, // 47: iop.AdapterConfig.openai_compat:type_name -> iop.OpenAICompatAdapterConfig
-	61, // 48: iop.OpenAICompatAdapterConfig.headers:type_name -> iop.OpenAICompatAdapterConfig.HeadersEntry
-	46, // 49: iop.OpenAICompatAdapterConfig.protocol_profile:type_name -> iop.ConcreteProtocolProfile
-	62, // 50: iop.ConcreteProtocolProfile.operations:type_name -> iop.ConcreteProtocolProfile.OperationsEntry
-	45, // 51: iop.ConcreteProtocolProfile.auth:type_name -> iop.ProtocolAuth
-	63, // 52: iop.ConcreteProtocolProfile.model_mapping:type_name -> iop.ConcreteProtocolProfile.ModelMappingEntry
-	64, // 53: iop.ConcreteProtocolProfile.extensions:type_name -> google.protobuf.Struct
-	28, // 54: iop.NodeConfigRefreshRequest.config:type_name -> iop.NodeConfigPayload
-	5,  // 55: iop.NodeConfigRefreshResponse.status:type_name -> iop.NodeConfigRefreshStatus
-	56, // [56:56] is the sub-list for method output_type
-	56, // [56:56] is the sub-list for method input_type
-	56, // [56:56] is the sub-list for extension type_name
-	56, // [56:56] is the sub-list for extension extendee
-	0,  // [0:56] is the sub-list for field type_name
+	5,  // 39: iop.WorkspaceArtifactRequest.kind:type_name -> iop.WorkspaceArtifactKind
+	6,  // 40: iop.WorkspaceArtifactRequest.operation:type_name -> iop.WorkspaceArtifactOperation
+	5,  // 41: iop.WorkspaceArtifactResponse.kind:type_name -> iop.WorkspaceArtifactKind
+	6,  // 42: iop.WorkspaceArtifactResponse.operation:type_name -> iop.WorkspaceArtifactOperation
+	3,  // 43: iop.WorkspaceArtifactResponse.status:type_name -> iop.WorkspaceStatus
+	4,  // 44: iop.WorkspaceArtifactResponse.error_code:type_name -> iop.WorkspaceErrorCode
+	3,  // 45: iop.WorkspaceCancelResponse.status:type_name -> iop.WorkspaceStatus
+	4,  // 46: iop.WorkspaceCancelResponse.error_code:type_name -> iop.WorkspaceErrorCode
+	3,  // 47: iop.WorkspaceCleanupResponse.status:type_name -> iop.WorkspaceStatus
+	4,  // 48: iop.WorkspaceCleanupResponse.error_code:type_name -> iop.WorkspaceErrorCode
+	68, // 49: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct
+	46, // 50: iop.AdapterConfig.ollama:type_name -> iop.OllamaAdapterConfig
+	47, // 51: iop.AdapterConfig.vllm:type_name -> iop.VllmAdapterConfig
+	45, // 52: iop.AdapterConfig.mock:type_name -> iop.MockAdapterConfig
+	48, // 53: iop.AdapterConfig.openai_compat:type_name -> iop.OpenAICompatAdapterConfig
+	65, // 54: iop.OpenAICompatAdapterConfig.headers:type_name -> iop.OpenAICompatAdapterConfig.HeadersEntry
+	50, // 55: iop.OpenAICompatAdapterConfig.protocol_profile:type_name -> iop.ConcreteProtocolProfile
+	66, // 56: iop.ConcreteProtocolProfile.operations:type_name -> iop.ConcreteProtocolProfile.OperationsEntry
+	49, // 57: iop.ConcreteProtocolProfile.auth:type_name -> iop.ProtocolAuth
+	67, // 58: iop.ConcreteProtocolProfile.model_mapping:type_name -> iop.ConcreteProtocolProfile.ModelMappingEntry
+	68, // 59: iop.ConcreteProtocolProfile.extensions:type_name -> google.protobuf.Struct
+	30, // 60: iop.NodeConfigRefreshRequest.config:type_name -> iop.NodeConfigPayload
+	7,  // 61: iop.NodeConfigRefreshResponse.status:type_name -> iop.NodeConfigRefreshStatus
+	62, // [62:62] is the sub-list for method output_type
+	62, // [62:62] is the sub-list for method input_type
+	62, // [62:62] is the sub-list for extension type_name
+	62, // [62:62] is the sub-list for extension extendee
+	0,  // [0:62] is the sub-list for field type_name
 }
 
 func init() { file_proto_iop_runtime_proto_init() }
@@ -4926,7 +5221,7 @@ func file_proto_iop_runtime_proto_init() {
 		(*WorkspaceToolRequest_CommandId)(nil),
 		(*WorkspaceToolRequest_Write)(nil),
 	}
-	file_proto_iop_runtime_proto_msgTypes[34].OneofWrappers = []any{
+	file_proto_iop_runtime_proto_msgTypes[36].OneofWrappers = []any{
 		(*AdapterConfig_Ollama)(nil),
 		(*AdapterConfig_Vllm)(nil),
 		(*AdapterConfig_Mock)(nil),
@@ -4937,8 +5232,8 @@ func file_proto_iop_runtime_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_runtime_proto_rawDesc), len(file_proto_iop_runtime_proto_rawDesc)),
-			NumEnums:      6,
-			NumMessages:   58,
+			NumEnums:      8,
+			NumMessages:   60,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
exit=1
