feat: implement orchestrator core (Rust)
Task 1.1: ✅ Cargo.toml with axum, rusqlite, matrix-sdk, serde, etc. Task 1.2: ✅ Directory structure: src/core, src/adapters, src/integrations, src/api Task 1.5: ✅ config.example.toml with full schema Task 2.1: ✅ Data models: Agent, Task, Receipt, Artifact, TaskEvent Task 2.2: ✅ Event Store: SQLite append-only with task/agent tables Task 2.3: ✅ Task state machine: created→assigned→running→completed/failed Task 2.4: ✅ Global task queue with priority ordering Task 2.5: ✅ Background timeout checker Task 2.6: ✅ Retry policy with configurable max_retries Compiles clean (warnings only, no errors). API handler stubs in place for Phase 2.
This commit is contained in:
parent
e983955036
commit
4e01728a67
15 changed files with 5220 additions and 3 deletions
131
src/main.rs
Normal file
131
src/main.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
mod config;
|
||||
mod core;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "agent-fleet", about = "Agent Fleet Orchestrator")]
|
||||
struct Cli {
|
||||
/// Path to config file
|
||||
#[arg(short, long, default_value = "config.toml")]
|
||||
config: String,
|
||||
|
||||
/// Bind address
|
||||
#[arg(long)]
|
||||
bind: Option<String>,
|
||||
|
||||
/// Port
|
||||
#[arg(short, long)]
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "agent_fleet=info,tower_http=info".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut config = match config::Config::load(&cli.config) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::warn!("could not load config from {}: {e}, using defaults", cli.config);
|
||||
config::Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(bind) = cli.bind {
|
||||
config.server.bind = bind;
|
||||
}
|
||||
if let Some(port) = cli.port {
|
||||
config.server.port = port;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"agent-fleet orchestrator starting on {}:{}",
|
||||
config.server.bind,
|
||||
config.server.port
|
||||
);
|
||||
|
||||
// Initialize event store
|
||||
let event_store = core::event_store::EventStore::open(std::path::Path::new(&config.orchestrator.db_path))
|
||||
.expect("failed to open event store");
|
||||
let store = std::sync::Arc::new(tokio::sync::Mutex::new(event_store));
|
||||
|
||||
// Initialize core components
|
||||
let state_machine = std::sync::Arc::new(core::state_machine::StateMachine::new(store.clone()));
|
||||
let task_queue = std::sync::Arc::new(core::task_queue::TaskQueue::new(state_machine.clone(), store.clone()));
|
||||
|
||||
// Start timeout checker
|
||||
let timeout_checker = std::sync::Arc::new(core::timeout::TimeoutChecker::new(
|
||||
state_machine.clone(),
|
||||
store.clone(),
|
||||
std::time::Duration::from_secs(30),
|
||||
std::time::Duration::from_secs(config.orchestrator.task_timeout_secs),
|
||||
));
|
||||
tokio::spawn(async move { timeout_checker.run().await });
|
||||
|
||||
// Build axum router (API stubs for now)
|
||||
let app = axum::Router::new()
|
||||
.route("/healthz", axum::routing::get(|| async { "ok" }))
|
||||
.route(
|
||||
"/api/v1/agents/register",
|
||||
axum::routing::post(handlers::register_agent),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/agents/heartbeat",
|
||||
axum::routing::post(handlers::heartbeat),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/agents/deregister",
|
||||
axum::routing::post(handlers::deregister),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/agents",
|
||||
axum::routing::get(handlers::list_agents),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/receipts",
|
||||
axum::routing::post(handlers::submit_receipt),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/webhooks/forgejo",
|
||||
axum::routing::post(handlers::forgejo_webhook),
|
||||
)
|
||||
.with_state(store.clone());
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(format!(
|
||||
"{}:{}",
|
||||
config.server.bind, config.server.port
|
||||
))
|
||||
.await
|
||||
.expect("failed to bind");
|
||||
|
||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).await.expect("server error");
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
pub async fn register_agent() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
pub async fn heartbeat() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
pub async fn deregister() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
pub async fn list_agents() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
pub async fn submit_receipt() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
pub async fn forgejo_webhook() -> &'static str {
|
||||
"TODO"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue