Source Code Reading: ActionEngine — Dispatcher Pattern for 10 Executors
ActionEngineService is the core dispatching engine within coomia-dip's Agent Runtime layer (Agent Runtime Layer), responsible for translating decision outcomes into executable operations. Exposed via gRPC with 12 RPC methods including ExecuteAction, BatchExecuteActions, and CancelAction, it supports two execution modes -- Rule-based (RuleBasedExecution) and Function-backed (FunctionExecution) -- plus four side-effect types: Notification, Webhook, Ontology Edit, and Function Call. This article dissects the oneof logic dispatch strategy in the Proto contract, the eight-state ExecutionState machine, batch execution's fail-fast/parallel controls, rollback strategy selection, and the ActionTemplate instantiation and parameter constraint validation pipeline.
Source Code Reading: ActionEngine — Dispatcher Pattern for 10 Executors
“Series: S9 Source Code Reading · Article 13 | Level: Advanced | Reading Time: 25 min
#TL;DR
ActionEngineService is the core dispatching engine within coomia-dip's Agent Runtime layer (Agent Runtime Layer), responsible for translating decision outcomes into executable operations. Exposed via gRPC with 12 RPC methods including ExecuteAction, BatchExecuteActions, and CancelAction, it supports two execution modes -- Rule-based (RuleBasedExecution) and Function-backed (FunctionExecution) -- plus four side-effect types: Notification, Webhook, Ontology Edit, and Function Call. This article dissects the oneof logic dispatch strategy in the Proto contract, the eight-state ExecutionState machine, batch execution's fail-fast/parallel controls, rollback strategy selection, and the ActionTemplate instantiation and parameter constraint validation pipeline.
#Table of Contents
- Overall Architecture: Bridge from Decision to Action
- Proto Contract: ActionDefinition's oneof logic Design
- ExecutionState: Eight-State Machine
- ExecuteAction: Single Execution Flow
- BatchExecuteActions: Batch Scheduling and Parallelism Control
- RuleBasedExecution: Declarative Ontology Edits
- FunctionExecution: Function-Backed Actions
- SideEffect: Four Side-Effect Types and Trigger Mechanism
- ActionTemplate: Template Instantiation and Parameter Constraints
- Rollback Strategies: Four RollbackConfig Modes
- Key Takeaways
#1. Overall Architecture: Bridge from Decision to Action
ActionEngine sits in Agent Runtime Layer (Agent Runtime) of coomia-dip's Layered architecture. Its core responsibility is receiving decision outcomes from Reasoning & Decision Layer's Decision Engine and converting them into concrete system operations. The code is structured as:
intelligence-Layer/src/agent_runtime_plane/
├── action/
│ ├── audit_emitter.py # Audit event emitter
│ └── ...
├── api/generated/plane_e/
│ ├── action_engine_pb2.py # Proto generated code
│ └── action_engine_pb2_grpc.py # gRPC Stub
proto/plane_e/
└── action_engine.proto # 867-line core contract
python-sdk/ontology_sdk/
└── grpc_client/grpc_action_client.py # SDK client wrapper
Design philosophy: ActionEngine does not implement concrete operation logic. Instead, it acts as a Dispatcher that routes execution to different executors based on the oneof type in ActionDefinition.logic. This Strategy pattern means adding a new executor type requires only extending the proto definition and implementing the corresponding executor -- zero changes to the core dispatch logic.
#2. Proto Contract: ActionDefinition's oneof logic Design
The most critical design decision in ActionEngine is the oneof logic field in ActionDefinition:
message ActionDefinition {
string action_id = 1;
string action_name = 2;
// Two execution modes, mutually exclusive
oneof logic {
RuleBasedExecution rule_based = 3;
FunctionExecution function = 4;
}
// Side effects
repeated SideEffectExecution side_effects = 5;
RetryPolicy retry_policy = 6;
TimeoutConfig timeout = 7;
RollbackConfig rollback_config = 8;
string description = 9;
repeated string tags = 10;
bool enabled = 11;
// Backward compatibility
ActionType legacy_type = 50;
google.protobuf.Struct legacy_config = 51;
}
Several design decisions deserve attention:
oneof instead of enum + config: The original version used an ActionType enum with a generic Struct config. In v1.5, this was refactored to oneof logic where each execution mode has its own strongly-typed message. This provides compile-time type safety -- you cannot pass a Webhook config to a function executor.
Backward compatibility preservation: Notice legacy_type and legacy_config field numbers start at 50, maintaining distance from normal fields to avoid numbering conflicts during future expansion. The ActionType enum, though marked DEPRECATED, retains its definition to ensure old clients don't experience deserialization failures.
Side effects separated from main logic: side_effects is repeated rather than part of oneof, meaning an Action can have zero to many side effects that run independently of the main logic.
#3. ExecutionState: Eight-State Machine
ActionEngine defines eight execution states forming a clear state machine:
enum ExecutionState {
EXECUTION_STATE_UNSPECIFIED = 0;
EXECUTION_STATE_PENDING = 1;
EXECUTION_STATE_RUNNING = 2;
EXECUTION_STATE_COMPLETED = 3;
EXECUTION_STATE_FAILED = 4;
EXECUTION_STATE_CANCELLED = 5;
EXECUTION_STATE_ROLLBACK_IN_PROGRESS = 6;
EXECUTION_STATE_ROLLBACK_COMPLETED = 7;
EXECUTION_STATE_WAITING_APPROVAL = 8;
}
Valid state transitions:
PENDING -> RUNNING -> COMPLETED
-> FAILED -> ROLLBACK_IN_PROGRESS -> ROLLBACK_COMPLETED
PENDING -> WAITING_APPROVAL -> RUNNING
RUNNING -> CANCELLED
ROLLBACK_IN_PROGRESS -> FAILED (rollback failure)
WAITING_APPROVAL is central to the Human-in-the-Loop pattern -- when an Action requires manual approval, the execution engine pauses at this state, waiting for an approval signal from Reasoning & Decision Layer's ApprovalWorkflowService before proceeding.
The SDK client's state modeling is also worth noting:
class ExecutionState(str, Enum):
UNSPECIFIED = "UNSPECIFIED"
PENDING = "PENDING"
RUNNING = "RUNNING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
CANCELLED = "CANCELLED"
ROLLBACK_IN_PROGRESS = "ROLLBACK_IN_PROGRESS"
ROLLBACK_COMPLETED = "ROLLBACK_COMPLETED"
WAITING_APPROVAL = "WAITING_APPROVAL"
The str, Enum dual inheritance allows state values to be used both as enum comparisons and directly serialized as JSON strings.
#4. ExecuteAction: Single Execution Flow
A single Action execution request contains five core fields:
message ExecuteActionRequest {
com.onto.common.v1.RequestContext context = 1;
string decision_id = 2; // Associated decision ID
ActionDefinition action = 3;
google.protobuf.Struct input = 4;
ExecuteOptions options = 5;
string template_id = 6; // Load ActionDefinition from template
}
The ExecuteOptions provides fine-grained execution control:
message ExecuteOptions {
bool async_execution = 1;
bool dry_run = 2;
string idempotency_key = 3;
int32 priority = 4; // 0-100
}
Idempotency design: The idempotency_key allows clients to avoid duplicate execution during network retries. The execution engine checks whether this key already has a corresponding execution record and returns the existing result if found.
Priority queue: The priority field (0-100) takes effect in async execution mode, where higher-priority Actions are scheduled first.
The response's ActionStatus contains rich execution information including a progress field (0.0 to 1.0) that provides real-time progress feedback for frontend UIs, particularly useful during bulk ontology edit operations where progress advances as each rule completes.
#5. BatchExecuteActions: Batch Scheduling and Parallelism Control
Batch execution is a high-frequency use case for ActionEngine, with carefully designed control options:
message BatchExecuteOptions {
bool fail_fast = 1; // Stop on first failure
int32 max_parallel = 2; // Max parallel executions (0 = sequential)
bool async_execution = 3;
}
fail_fast semantics: When fail_fast = true, the first Action failure immediately cancels all pending Actions. Already-running Actions are not forcibly cancelled (that requires explicit CancelAction calls), but no new Actions are started.
max_parallel control: max_parallel = 0 means strictly sequential execution, guaranteeing operation ordering. Positive values cap the concurrency to prevent overwhelming downstream services during large batch operations.
Each batch item has its own item_id for correlation, and the response includes a comprehensive BatchExecutionSummary with total/succeeded/failed/cancelled counts and total duration.
#6. RuleBasedExecution: Declarative Ontology Edits
RuleBasedExecution aligns with Palantir Foundry's Rules-based Actions, allowing users to describe ontology edits through declarative rules:
message RuleBasedExecution {
repeated OntologyEditExecution rules = 1;
}
message OntologyEditExecution {
string rule_id = 1;
string operation_type = 2; // create_object, modify_object, delete_object,
// create_link, delete_link, create_or_modify
google.protobuf.Struct parameters = 3;
string condition = 4; // Execution condition (optional)
int32 order = 5; // Execution order
}
Six operation types cover the full CRUD spectrum for ontology instances, including the idempotent create_or_modify (upsert). The condition field enables conditional execution based on runtime context, while order ensures proper sequencing for dependent operations like creating a main object before establishing its relationships.
#7. FunctionExecution: Function-Backed Actions
FunctionExecution delegates Action execution logic to Reasoning & Decision Layer's FunctionRuntime:
message FunctionExecution {
string function_id = 1; // References function registered in Reasoning & Decision Layer
string function_version = 2; // Optional version pinning
google.protobuf.Struct parameters = 3;
}
This design achieves complete decoupling between Action definition and execution logic. The Action only specifies "which function to call and with what parameters" -- the actual function implementation is managed by Reasoning & Decision Layer. The function_version field allows pinning a specific function version, preventing behavior changes from function upgrades.
#8. SideEffect: Four Side-Effect Types and Trigger Mechanism
SideEffects, introduced in v1.5, extract notifications and webhooks from the former ActionType into an independent side-effect system:
enum SideEffectType {
SIDE_EFFECT_TYPE_NOTIFICATION = 1;
SIDE_EFFECT_TYPE_WEBHOOK = 2;
SIDE_EFFECT_TYPE_ONTOLOGY_EDIT = 3;
SIDE_EFFECT_TYPE_FUNCTION_CALL = 4;
}
enum SideEffectTrigger {
SIDE_EFFECT_TRIGGER_ON_SUCCESS = 1;
SIDE_EFFECT_TRIGGER_ON_FAILURE = 2;
SIDE_EFFECT_TRIGGER_ALWAYS = 3;
}
The trigger timing control is the essence of SideEffect design: ON_SUCCESS for notification emails after success, ON_FAILURE for alert webhooks on failure, and ALWAYS for audit logging regardless of outcome.
The Webhook execution mode (FEAT-009) further distinguishes synchronous and asynchronous semantics:
enum WebhookExecutionMode {
WEBHOOK_EXECUTION_MODE_WRITEBACK = 1; // Pre-commit: sync, failure rolls back
WEBHOOK_EXECUTION_MODE_SIDE_EFFECT = 2; // Post-commit: async, failure logs only
}
In WRITEBACK mode, webhook return values can be written back to the operation context via WebhookOutputMapping, enabling external system data writeback capabilities.
#9. ActionTemplate: Template Instantiation and Parameter Constraints
ActionTemplate allows saving commonly-used Action configurations as reusable templates. The parameter constraint system (Sprint 4) supports five constraint types: RANGE, LENGTH, PATTERN, ENUM, and CUSTOM (delegating to an external validation function).
SubmissionCriteria combines constraints and rules:
message SubmissionCriteria {
string validation_function_id = 1; // External validation function
repeated ParameterConstraint constraints = 2;
repeated SubmissionRule rules = 3;
}
SubmissionRule supports cross-parameter boolean expression validation and distinguishes ERROR and WARNING severity levels -- WARNING-level validation failures alert but don't block submission.
The template lifecycle is managed through TemplateStatus: DRAFT to ENABLED to DISABLED to DEPRECATED to ARCHIVED.
#10. Rollback Strategies: Four RollbackConfig Modes
When an Action fails, RollbackConfig determines recovery:
enum RollbackStrategy {
ROLLBACK_STRATEGY_NONE = 1; // No rollback
ROLLBACK_STRATEGY_COMPENSATE = 2; // Execute compensating Action
ROLLBACK_STRATEGY_RESTORE_SNAPSHOT = 3; // Restore snapshot
ROLLBACK_STRATEGY_MANUAL = 4; // Manual intervention
}
COMPENSATE mode is the most common strategy -- compensating_action_id points to a dedicated compensating Action, implementing the Saga pattern for eventual consistency. RESTORE_SNAPSHOT leverages Data Layer's Nessie branching to create data snapshots before execution and rollback on failure. auto_rollback_on_failure controls whether rollback triggers automatically.
#11. Key Takeaways
- oneof replaces enum+config:
ActionDefinition.logic'soneofdesign provides compile-time type safety -- an underutilized Proto3 feature. - Main logic and side effects separated: SideEffect independence fully decouples notifications, webhooks, and audit concerns.
- Eight-state machine: From PENDING to ROLLBACK_COMPLETED, the complete state machine covers all enterprise scenario paths.
- Fine-grained batch control: The fail_fast + max_parallel combination provides flexible trade-offs between safety and performance.
- Template constraint validation: ConstraintType + SubmissionRule dual-layer validation ensures complete parameter validation before submission.
- Four rollback strategies: From simple no-rollback to Saga compensation patterns, covering different consistency requirements.
#Next Article
S9-14: FunctionRuntime -- Unified Multi-Language Sandbox Interface, where we dive into Reasoning & Decision Layer's function runtime to understand the sandbox isolation mechanisms for Python, TypeScript, and Groovy.
Tags: #coomia-dip #source-code-reading #action-engine #dispatcher-pattern #grpc #Layer-e #agent-runtime