8.6 KiB
Jobs Quickstart: Create and Send a Simple Job to the Supervisor
This guide shows how a new (simple) job looks, how to construct it, and how to submit it to the Supervisor. It covers:
- The minimal fields a job needs
- Picking an actor via script type
- Submitting a job using the Rust API
- Submitting a job via the OpenRPC server over Unix IPC (and WS)
Key references:
- rust.ScriptType determines the target actor queue
- rust.Job is the canonical job payload stored in Redis
- rust.JobBuilder::new(), rust.JobBuilder::caller_id(), rust.JobBuilder::context_id(), rust.JobBuilder::script_type(), rust.JobBuilder::script(), rust.JobBuilder::timeout(), rust.JobBuilder::build()
- rust.SupervisorBuilder::new(), rust.SupervisorBuilder::build()
- rust.Supervisor::create_job(), rust.Supervisor::start_job(), rust.Supervisor::run_job_and_await_result(), rust.Supervisor::get_job_output()
- Redis key namespace: rust.NAMESPACE_PREFIX
1) What is a “simple job”?
A simple job is the minimal unit of work that an actor can execute. At minimum, you must provide:
- caller_id: String (identifier of the requester; often a public key)
- context_id: String (the “circle” or execution context)
- script: String (the code to run; Rhai for OSIS/SAL; HeroScript for V/Python)
- script_type: ScriptType (OSIS | SAL | V | Python)
- timeout: Duration (optional; default used if not set)
The job’s script_type selects the actor and thus the queue. See rust.ScriptType::actor_queue_suffix() for mapping.
2) Choosing the actor by ScriptType
- OSIS: Rhai script, sequential non-blocking
- SAL: Rhai script, blocking async, concurrent
- V: HeroScript via V engine
- Python: HeroScript via Python engine
Pick the script_type that matches your script/runtime requirements. See design summary in core/docs/architecture.md.
3) Build and submit a job using the Rust API
This is the most direct, strongly-typed integration. You will:
- Build a Supervisor
- Construct a Job (using the “core” job builder for explicit caller_id/context_id)
- Submit it with either:
- create_job + start_job (two-step)
- run_job_and_await_result (one-shot request-reply)
Note: We deliberately use the core job builder (hero_job) so we can set caller_id explicitly via rust.JobBuilder::caller_id().
Example Rhai script (returns 42):
40 + 2
Rust example (two-step create + start + poll output):
use hero_supervisor::{SupervisorBuilder, ScriptType};
use hero_job::JobBuilder as CoreJobBuilder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1) Build a Supervisor
let supervisor = SupervisorBuilder::new()
.redis_url("redis://127.0.0.1/")
.build()
.await?;
// 2) Build a Job (using core job builder to set caller_id, context_id)
let job = CoreJobBuilder::new()
.caller_id("02abc...caller") // required
.context_id("02def...context") // required
.script_type(ScriptType::SAL) // select the SAL actor
.script("40 + 2") // simple Rhai script
.timeout(std::time::Duration::from_secs(10))
.build()?; // returns hero_job::Job
let job_id = job.id.clone();
// 3a) Store the job in Redis
supervisor.create_job(&job).await?;
// 3b) Start the job (pushes ID to the actor’s Redis queue)
supervisor.start_job(&job_id).await?;
// 3c) Fetch output when finished (or poll status via get_job_status)
if let Some(output) = supervisor.get_job_output(&job_id).await? {
println!("Job {} output: {}", job_id, output);
} else {
println!("Job {} has no output yet", job_id);
}
Ok(())
}
Rust example (one-shot request-reply):
use hero_supervisor::{SupervisorBuilder, ScriptType};
use hero_job::JobBuilder as CoreJobBuilder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let supervisor = SupervisorBuilder::new()
.redis_url("redis://127.0.0.1/")
.build()
.await?;
let job = CoreJobBuilder::new()
.caller_id("02abc...caller")
.context_id("02def...context")
.script_type(ScriptType::SAL)
.script("40 + 2")
.timeout(std::time::Duration::from_secs(10))
.build()?;
// Creates the job, dispatches it to the correct actor queue,
// and waits for a reply on the dedicated reply queue.
let output = supervisor.run_job_and_await_result(&job).await?;
println!("Synchronous output: {}", output);
Ok(())
}
References used in this flow:
- rust.SupervisorBuilder::new(), rust.SupervisorBuilder::build()
- rust.JobBuilder::caller_id(), rust.JobBuilder::context_id(), rust.JobBuilder::script_type(), rust.JobBuilder::script(), rust.JobBuilder::timeout(), rust.JobBuilder::build()
- rust.Supervisor::create_job(), rust.Supervisor::start_job(), rust.Supervisor::get_job_output()
- rust.Supervisor::run_job_and_await_result()
4) Submit a job via the OpenRPC server (Unix IPC or WebSocket)
The OpenRPC server exposes JSON-RPC 2.0 methods which proxy to the Supervisor:
- Types: rust.JobParams
- Methods registered in interfaces/openrpc/server/src/lib.rs
Unix IPC launcher and client:
- Server: interfaces/unix/server/src/main.rs
- Client: interfaces/unix/client/src/main.rs
Start the IPC server:
cargo run -p hero-unix-server -- \
--socket /tmp/baobab.ipc \
--db-path ./db
Create a job (JSON-RPC, IPC):
cargo run -p hero-unix-client -- \
--socket /tmp/baobab.ipc \
--method create_job \
--params '{
"script": "40 + 2",
"script_type": "SAL",
"caller_id": "02abc...caller",
"context_id": "02def...context",
"timeout": 10
}'
This returns the job_id. Then start the job:
cargo run -p hero-unix-client -- \
--socket /tmp/baobab.ipc \
--method start_job \
--params '["<job_id_from_create>"]'
Fetch output (optional):
cargo run -p hero-unix-client -- \
--socket /tmp/baobab.ipc \
--method get_job_output \
--params '["<job_id_from_create>"]'
Notes:
- The “run_job” JSON-RPC method is present but not fully wired to the full request-reply flow; prefer create_job + start_job + get_job_output for now.
- JobParams fields are defined in rust.JobParams.
5) What happens under the hood
- The job is serialized to Redis under the namespace rust.NAMESPACE_PREFIX
- The Supervisor picks the actor queue from rust.ScriptType::actor_queue_suffix() and LPUSHes your job ID
- The actor BLPOPs its queue, loads the job, executes your script, and stores the result back into the Redis job hash
- For synchronous flows, Supervisor waits on a dedicated reply queue until the result arrives via rust.Supervisor::run_job_and_await_result()
6) Minimal scripts by actor type
-
OSIS/SAL (Rhai):
- "40 + 2"
- "let x = 21; x * 2"
- You can access injected context variables such as CALLER_ID, CONTEXT_ID (see architecture doc in core/docs/architecture.md).
-
V/Python (HeroScript):
- Provide a valid HeroScript snippet appropriate for the selected engine and your deployment.
7) Troubleshooting
- Ensure Redis is running and reachable at the configured URL
- SAL vs OSIS: pick SAL if your script is blocking/IO-heavy and needs concurrency; otherwise OSIS is fine for sequential non-blocking tasks
- If using OpenRPC IPC, ensure the socket path matches between server and client
- For lifecycle of actors (starting/restarting/health checks), see core/supervisor/README.md