mod common;
use std::{
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use common::RunningWorker;
use serde::{Deserialize, Serialize};
use steda::{Error, Result, RetryStrategy, Step, Task, TaskContext, TaskExecutor};
#[derive(Debug, Deserialize, Serialize)]
struct AgentTurnInput {
session_id: String,
prompt: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct PreparedTurn {
prompt: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct AgentTurnOutput {
session_id: String,
environment_id: u64,
response: String,
}
const AGENT_TURN: Task<AgentTurnInput, AgentTurnOutput> = Task::new("agent-turn");
const PREPARE_TURN: Step<PreparedTurn> = Step::new("prepare-turn");
#[derive(Debug)]
struct SandboxExecutor {
next_environment_id: AtomicU64,
}
impl SandboxExecutor {
const fn new() -> Self {
Self { next_environment_id: AtomicU64::new(1) }
}
}
impl TaskExecutor<AgentTurnInput, AgentTurnOutput> for SandboxExecutor {
fn execute(
&self,
input: AgentTurnInput,
context: TaskContext,
) -> impl Future<Output = Result<AgentTurnOutput>> + Send {
let environment_id = self.next_environment_id.fetch_add(1, Ordering::Relaxed);
let attempt = context.attempt();
async move {
println!("attempt {attempt}: provisioning environment {environment_id}");
let prepared = context
.step(PREPARE_TURN, async || {
println!("preparing durable input for {}", input.session_id);
Ok(PreparedTurn { prompt: input.prompt.clone() })
})
.await?;
if attempt == 1 {
println!("attempt 1: environment {environment_id} exited unexpectedly");
return Err(Error::Other("provisioned runtime exited unexpectedly".to_owned()));
}
println!("attempt 2: reused the durable prepared input");
tokio::time::sleep(Duration::from_millis(50)).await;
let output = AgentTurnOutput {
session_id: input.session_id,
environment_id,
response: format!("processed: {}", prepared.prompt),
};
println!("environment {environment_id} completed successfully");
println!("destroying environment {environment_id}");
Ok(output)
}
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let steda = common::connect().await?;
let queue = steda.queue("example-provisioned")?;
queue.create().await?;
let worker = queue.worker().task_executor(AGENT_TURN, SandboxExecutor::new()).build()?;
let worker = RunningWorker::start(worker);
let task = queue
.spawn(
AGENT_TURN,
AgentTurnInput {
session_id: "SESSION-1001".to_owned(),
prompt: "Summarize the attached repository changes".to_owned(),
},
)
.max_attempts(2)
.retry_strategy(RetryStrategy::fixed(Duration::ZERO))
.await?;
let output = task.result_with_timeout(Duration::from_secs(10)).await?;
println!("session {} completed in environment {}", output.session_id, output.environment_id);
println!("response: {}", output.response);
worker.stop().await?;
Ok(())
}