nexora-runtime
ExecutionEngine, ties the planner and scheduler together into a single execution unit. You do not use this directly; NexoraEngine (in nexora-api) is the public entry point.
Responsibilities
- Accept an
Intentand pass it to theCompositePlannerto produce aPlan. - Create an
ExecutionContextwith a newtraceIdandexecutionId. - Persist an
ExecutionRecordvia theExecutionStore(if configured). - Hand the
Planand context to theDagStepScheduler. - On completion (success or failure), update the persisted record and fire a
PlanCompleted/Failedevent.
Execution flow
Intent
└─ CompositePlanner.plan()
└─ Plan
└─ DagStepScheduler.execute()
├─ Step A (no deps) ──► CapabilityInvoker ──► InterceptorPipeline
├─ Step B (no deps) ──► CapabilityInvoker ──► InterceptorPipeline
└─ Step C (deps: A,B) ──► (waits) ──► CapabilityInvoker
└─ ExecutionResult
Thread model
By default, the engine uses Executors.newVirtualThreadPerTaskExecutor() (Java 21 virtual threads). Each step runs on its own virtual thread, allowing thousands of concurrent steps with minimal OS thread overhead.
Override the executor to use a platform thread pool or a custom thread factory:
NexoraEngine engine = NexoraEngine.builder()
.withExecutor(Executors.newFixedThreadPool(20))
.build();
Async execution
NexoraEngine.execute() returns a CompletableFuture<ExecutionResult>. The future completes when the entire plan finishes (or fails).
engine.execute(intent)
.thenAccept(result -> log.info("status={}", result.status()))
.exceptionally(ex -> { log.error("Execution failed", ex); return null; });
Use .join() for synchronous callers, but be aware this will block the calling thread.
Cron scheduler
nexora-runtime also hosts CronScheduler, an in-process scheduler backed by a daemon ScheduledExecutorService. It is created automatically by NexoraEngine.build() when an ExecutionStore is configured.
On startup it calls store.findActiveSchedules(), applies the configured MissedFirePolicy for any schedule whose next_fire_at is in the past, then enqueues each wakeup at the correct future instant. After each firing it updates last_fired_at / next_fire_at in the store and re-enqueues the next occurrence.
You do not interact with CronScheduler directly, use NexoraEngine.schedule(...) and the returned ScheduledExecution handle. See Cron Scheduling.