From 9d72b398e2965bd2dcf89b928eb90247bab80913 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 8 Nov 2026 18:08:12 +0000 Subject: [PATCH] feat(gateway): bench workers that win and don't deliver; images get 6 minutes; bump v0.2.68 Live: an outside worker (bench 2802) kept winning diffusion auctions over seed2 and never sent a receipt, so every caller waited the full 15-min receipt timeout and failed. Now a winner that fails, goes silent, or can't be fetched from is benched from this gateway's auctions (30 min, doubling to 6 h; cleared on a delivered job) and the job is re-auctioned. Image jobs get a 6-min receipt timeout (a healthy CPU worker needs ~2). Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 32 +++---- Cargo.toml | 2 +- apps/tui/package.json | 2 +- apps/web/package.json | 2 +- .../web/src/app/releases/latest.json/route.ts | 2 +- node/crates/c0mpute-core/src/lib.rs | 1 + node/crates/c0mpute-core/src/penalty_box.rs | 89 +++++++++++++++++++ .../crates/c0mpute-core/src/speech_gateway.rs | 49 ++++++++-- package.json | 2 +- packages/shared/package.json | 2 +- plugins/ad-generator-agent/module.toml | 2 +- plugins/ads-manager/module.toml | 2 +- plugins/coinpay/module.toml | 2 +- plugins/crawl/module.toml | 2 +- plugins/ctv-ads/module.toml | 2 +- plugins/diffusion/module.toml | 2 +- plugins/embed/module.toml | 2 +- plugins/gameserver/module.toml | 2 +- plugins/hosting/module.toml | 2 +- plugins/infernet/module.toml | 2 +- plugins/live-stream/module.toml | 2 +- plugins/ocr/module.toml | 2 +- plugins/podcast-ads/module.toml | 2 +- plugins/podcasting/module.toml | 2 +- plugins/podcasts/module.toml | 2 +- plugins/render/module.toml | 2 +- plugins/secure-chat/module.toml | 2 +- plugins/storage/module.toml | 2 +- plugins/train/module.toml | 2 +- plugins/transcode/module.toml | 2 +- plugins/tts/module.toml | 2 +- plugins/video/module.toml | 2 +- plugins/webhook/module.toml | 2 +- plugins/whisper/module.toml | 2 +- plugins/workflow-step/module.toml | 2 +- 35 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 node/crates/c0mpute-core/src/penalty_box.rs diff --git a/Cargo.lock b/Cargo.lock index 84cdfc4a..73df895c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "c0mpute-api" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "c0mpute-proto", @@ -493,7 +493,7 @@ dependencies = [ [[package]] name = "c0mpute-bench" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "blake3", @@ -504,7 +504,7 @@ dependencies = [ [[package]] name = "c0mpute-cli" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "axum", @@ -534,7 +534,7 @@ dependencies = [ [[package]] name = "c0mpute-core" -version = "0.2.67" +version = "0.2.68" dependencies = [ "aes-gcm", "anyhow", @@ -578,7 +578,7 @@ dependencies = [ [[package]] name = "c0mpute-doctor" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "serde", @@ -590,7 +590,7 @@ dependencies = [ [[package]] name = "c0mpute-envelope" -version = "0.2.67" +version = "0.2.68" dependencies = [ "blake3", "bs58", @@ -603,7 +603,7 @@ dependencies = [ [[package]] name = "c0mpute-gateway" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "axum", @@ -625,7 +625,7 @@ dependencies = [ [[package]] name = "c0mpute-market" -version = "0.2.67" +version = "0.2.68" dependencies = [ "c0mpute-envelope", "serde", @@ -635,7 +635,7 @@ dependencies = [ [[package]] name = "c0mpute-net" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "async-trait", @@ -652,7 +652,7 @@ dependencies = [ [[package]] name = "c0mpute-placement" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "async-trait", @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "c0mpute-proto" -version = "0.2.67" +version = "0.2.68" dependencies = [ "blake3", "hex", @@ -685,7 +685,7 @@ dependencies = [ [[package]] name = "c0mpute-secure-chat" -version = "0.2.67" +version = "0.2.68" dependencies = [ "aes-gcm", "anyhow", @@ -708,7 +708,7 @@ dependencies = [ [[package]] name = "c0mpute-store" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "blake3", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "c0mpute-transcode" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "c0mpute-proto", @@ -739,7 +739,7 @@ dependencies = [ [[package]] name = "c0mpute-update" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "hex", @@ -753,7 +753,7 @@ dependencies = [ [[package]] name = "c0mpute-verify" -version = "0.2.67" +version = "0.2.68" dependencies = [ "anyhow", "blake3", diff --git a/Cargo.toml b/Cargo.toml index 6e39d486..b6f6508d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.2.67" +version = "0.2.68" edition = "2024" license = "MIT" repository = "https://github.com/profullstack/c0mpute" diff --git a/apps/tui/package.json b/apps/tui/package.json index 267c28d3..61928f77 100644 --- a/apps/tui/package.json +++ b/apps/tui/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/tui", - "version": "0.2.67", + "version": "0.2.68", "private": true, "type": "module", "bin": { diff --git a/apps/web/package.json b/apps/web/package.json index 357ebc88..e79f0881 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute", - "version": "0.2.67", + "version": "0.2.68", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app/releases/latest.json/route.ts b/apps/web/src/app/releases/latest.json/route.ts index 67b35208..80188eba 100644 --- a/apps/web/src/app/releases/latest.json/route.ts +++ b/apps/web/src/app/releases/latest.json/route.ts @@ -28,7 +28,7 @@ export const dynamic = "force-dynamic"; * this in sync with the workspace version in `Cargo.toml` on each release so a * freshly built CLI reports "already latest" rather than a phantom upgrade. */ -const FALLBACK_VERSION = "0.2.67"; +const FALLBACK_VERSION = "0.2.68"; const FALLBACK_MIN_REQUIRED = "0.0.1"; type Channel = "stable" | "beta" | "nightly"; diff --git a/node/crates/c0mpute-core/src/lib.rs b/node/crates/c0mpute-core/src/lib.rs index f2378b16..da336141 100644 --- a/node/crates/c0mpute-core/src/lib.rs +++ b/node/crates/c0mpute-core/src/lib.rs @@ -14,6 +14,7 @@ pub(crate) mod gateway_util; pub mod jobs_gateway; pub mod net_guard; pub mod opendisk; +pub mod penalty_box; pub mod pricing; pub mod reachability; pub mod private; diff --git a/node/crates/c0mpute-core/src/penalty_box.rs b/node/crates/c0mpute-core/src/penalty_box.rs new file mode 100644 index 00000000..e0b25254 --- /dev/null +++ b/node/crates/c0mpute-core/src/penalty_box.rs @@ -0,0 +1,89 @@ +//! Buyer-side memory of workers that won and did not deliver. +//! +//! A worker that wins an auction and then fails the job, never sends a +//! receipt, or cannot be fetched from is benched for this gateway's +//! auctions: 30 minutes for the first strike, doubling per strike up to +//! 6 hours. A delivered job clears the record. Without this, a fast-bidding +//! worker that cannot deliver (behind NAT, too slow, broken runtime) wins +//! every auction and every caller waits out the full receipt timeout. + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +pub const FIRST_BENCH: Duration = Duration::from_secs(30 * 60); +pub const MAX_BENCH: Duration = Duration::from_secs(6 * 60 * 60); + +#[derive(Clone, Default)] +pub struct PenaltyBox { + inner: Arc>>, +} + +/// How long the `strikes`-th strike benches a worker. +pub fn bench_for(strikes: u32) -> Duration { + let factor = 1u32.checked_shl(strikes.saturating_sub(1)).unwrap_or(u32::MAX); + FIRST_BENCH.saturating_mul(factor).min(MAX_BENCH) +} + +impl PenaltyBox { + pub fn new() -> Self { + Self::default() + } + + /// Peers benched at `now`. + pub fn benched_at(&self, now: Instant) -> Vec { + let map = self.inner.lock().unwrap_or_else(|e| e.into_inner()); + map.iter() + .filter(|(_, (until, _))| *until > now) + .map(|(peer, _)| peer.clone()) + .collect() + } + + pub fn benched(&self) -> Vec { + self.benched_at(Instant::now()) + } + + /// Record a non-delivery; returns how long the peer is benched. + pub fn strike_at(&self, peer: &str, now: Instant) -> Duration { + let mut map = self.inner.lock().unwrap_or_else(|e| e.into_inner()); + let strikes = map.get(peer).map(|(_, s)| s + 1).unwrap_or(1); + let bench = bench_for(strikes); + map.insert(peer.to_string(), (now + bench, strikes)); + bench + } + + pub fn strike(&self, peer: &str) -> Duration { + self.strike_at(peer, Instant::now()) + } + + /// A delivered job wipes the record. + pub fn clear(&self, peer: &str) { + self.inner.lock().unwrap_or_else(|e| e.into_inner()).remove(peer); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bench_doubles_and_caps() { + assert_eq!(bench_for(1), Duration::from_secs(30 * 60)); + assert_eq!(bench_for(2), Duration::from_secs(60 * 60)); + assert_eq!(bench_for(3), Duration::from_secs(120 * 60)); + assert_eq!(bench_for(10), MAX_BENCH); + assert_eq!(bench_for(100), MAX_BENCH); + } + + #[test] + fn struck_peers_are_benched_until_expiry_and_cleared_on_delivery() { + let pb = PenaltyBox::new(); + let t0 = Instant::now(); + pb.strike_at("a", t0); + assert_eq!(pb.benched_at(t0 + Duration::from_secs(60)), vec!["a".to_string()]); + assert!(pb.benched_at(t0 + FIRST_BENCH + Duration::from_secs(1)).is_empty()); + assert_eq!(pb.strike_at("a", t0), Duration::from_secs(60 * 60), "second strike doubles"); + pb.clear("a"); + assert!(pb.benched_at(t0).is_empty()); + } +} diff --git a/node/crates/c0mpute-core/src/speech_gateway.rs b/node/crates/c0mpute-core/src/speech_gateway.rs index 63f09ef5..7395a364 100644 --- a/node/crates/c0mpute-core/src/speech_gateway.rs +++ b/node/crates/c0mpute-core/src/speech_gateway.rs @@ -27,6 +27,9 @@ const MAX_AUDIO_BYTES: usize = 25 * 1024 * 1024; const MAX_WAV_BYTES: usize = 20 * 1024 * 1024; const MAX_SPEECH_WORKER_ATTEMPTS: usize = 3; const MAX_CONCURRENT_IMAGE_JOBS: usize = 4; +/// A healthy CPU worker draws an image in ~2 minutes. One that has not +/// answered in 6 is benched and the job goes to the next worker. +const IMAGE_RECEIPT_TIMEOUT: Duration = Duration::from_secs(6 * 60); #[derive(Clone)] struct GatewayState { @@ -40,6 +43,8 @@ struct GatewayState { image_slots: Arc, /// Warm, pinned sessions this node is holding (see `speech_session`). sessions: crate::speech_session::SessionStore, + /// Workers that won and did not deliver, benched for a while. + penalties: crate::penalty_box::PenaltyBox, } #[derive(Debug)] @@ -130,6 +135,7 @@ pub async fn serve(bind: std::net::SocketAddr, token: String, max_price_usd: f64 stt_slots: Arc::new(Semaphore::new(2)), image_slots: Arc::new(Semaphore::new(MAX_CONCURRENT_IMAGE_JOBS)), sessions: crate::speech_session::SessionStore::new(), + penalties: crate::penalty_box::PenaltyBox::new(), }; let app = Router::new() .route("/healthz", get(healthz)) @@ -440,29 +446,41 @@ async fn run_speech_auction( spec: serde_json::Value, required: Vec, excluded_bidders: &[String], -) -> ApiResult { +) -> Result)> { + // Learn the winner as soon as it is chosen, so a winner that then goes + // silent can be benched and the job re-auctioned. + let (winner_tx, mut winner_rx) = tokio::sync::mpsc::unbounded_channel(); let auction = JobAuction::new(workload, spec) + .with_accept_notify(winner_tx) .with_required_capabilities(required) .with_excluded_bidders(excluded_bidders.to_vec()) .with_max_price_usd(state.max_price_usd) .with_bid_window(Duration::from_secs(4)) .with_deadline(if is_video(workload) { crate::video::JOB_TIMEOUT + Duration::from_secs(5 * 60) + } else if workload == crate::diffusion::WORKLOAD_TXT2IMG { + IMAGE_RECEIPT_TIMEOUT + Duration::from_secs(60) } else { Duration::from_secs(20 * 60) }) .with_receipt_timeout(if is_video(workload) { crate::video::JOB_TIMEOUT + } else if workload == crate::diffusion::WORKLOAD_TXT2IMG { + IMAGE_RECEIPT_TIMEOUT } else { Duration::from_secs(15 * 60) }); run_auction(state.net.clone(), auction) .await .map_err(|error| { - tracing::warn!(workload, err = %error, "speech auction failed"); - ApiError( - StatusCode::SERVICE_UNAVAILABLE, - "no c0mpute worker completed the speech job".into(), + let winner = winner_rx.try_recv().ok().map(|(_, bid)| bid.bidder_peer_id); + tracing::warn!(workload, err = %error, winner = ?winner, "speech auction failed"); + ( + ApiError( + StatusCode::SERVICE_UNAVAILABLE, + "no c0mpute worker completed the speech job".into(), + ), + winner, ) }) } @@ -478,7 +496,8 @@ async fn run_speech_job( required: Vec, max_bytes: usize, ) -> ApiResult<(crate::AuctionOutcome, Vec)> { - let mut excluded_bidders = Vec::new(); + // Workers benched for not delivering earlier stay out of the auction. + let mut excluded_bidders = state.penalties.benched(); let mut last_error = None; for attempt in 1..=MAX_SPEECH_WORKER_ATTEMPTS { @@ -492,7 +511,17 @@ async fn run_speech_job( .await { Ok(outcome) => outcome, - Err(error) => return Err(last_error.unwrap_or(error)), + // A winner was chosen and then never answered: bench it and try + // the next worker instead of failing the caller. + Err((error, Some(winner))) => { + let bench = state.penalties.strike(&winner); + tracing::warn!(workload, attempt, worker = %winner, bench_secs = bench.as_secs(), + "winner never delivered; benched, trying another worker"); + excluded_bidders.push(winner); + last_error = Some(error); + continue; + } + Err((error, None)) => return Err(last_error.unwrap_or(error)), }; let worker = outcome.accepted_bid.bidder_peer_id.clone(); let result = if outcome.receipt.status != c0mpute_net::topics::JobStatus::Completed { @@ -517,8 +546,12 @@ async fn run_speech_job( }; match result { - Ok(bytes) => return Ok((outcome, bytes)), + Ok(bytes) => { + state.penalties.clear(&worker); + return Ok((outcome, bytes)); + } Err(error) => { + state.penalties.strike(&worker); tracing::warn!( workload, attempt, diff --git a/package.json b/package.json index fb1d3cf6..d106ceac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute-monorepo", - "version": "0.2.67", + "version": "0.2.68", "private": true, "workspaces": [ "apps/*", diff --git a/packages/shared/package.json b/packages/shared/package.json index b53736f2..1302a19c 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/shared", - "version": "0.2.67", + "version": "0.2.68", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/plugins/ad-generator-agent/module.toml b/plugins/ad-generator-agent/module.toml index 3dcb088b..69f1b92e 100644 --- a/plugins/ad-generator-agent/module.toml +++ b/plugins/ad-generator-agent/module.toml @@ -21,7 +21,7 @@ [module] id = "ad-generator-agent" name = "Ad Generator Agent — every ad type from one URL" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "available" diff --git a/plugins/ads-manager/module.toml b/plugins/ads-manager/module.toml index 090da2a5..1c2ca503 100644 --- a/plugins/ads-manager/module.toml +++ b/plugins/ads-manager/module.toml @@ -18,7 +18,7 @@ [module] id = "ads-manager" name = "Ads Manager — shared ad operations layer" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "coming-soon" diff --git a/plugins/coinpay/module.toml b/plugins/coinpay/module.toml index a53b0d6d..99611e46 100644 --- a/plugins/coinpay/module.toml +++ b/plugins/coinpay/module.toml @@ -7,7 +7,7 @@ [module] id = "coinpay" name = "CoinPay — DID, payments, escrow, reputation" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "available" diff --git a/plugins/crawl/module.toml b/plugins/crawl/module.toml index 924189a2..fd78651e 100644 --- a/plugins/crawl/module.toml +++ b/plugins/crawl/module.toml @@ -8,7 +8,7 @@ [module] id = "crawl" name = "Crawl — distributed web fetching" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "data" status = "available" diff --git a/plugins/ctv-ads/module.toml b/plugins/ctv-ads/module.toml index 9a37616a..6a3dfade 100644 --- a/plugins/ctv-ads/module.toml +++ b/plugins/ctv-ads/module.toml @@ -20,7 +20,7 @@ [module] id = "ctv-ads" name = "CTV Ads — Connected TV ad marketplace" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "coming-soon" diff --git a/plugins/diffusion/module.toml b/plugins/diffusion/module.toml index 19855a24..7b0c05de 100644 --- a/plugins/diffusion/module.toml +++ b/plugins/diffusion/module.toml @@ -11,7 +11,7 @@ [module] id = "diffusion" name = "Diffusion — image generation" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "available" diff --git a/plugins/embed/module.toml b/plugins/embed/module.toml index 37aca4ea..efd41cdc 100644 --- a/plugins/embed/module.toml +++ b/plugins/embed/module.toml @@ -13,7 +13,7 @@ [module] id = "embed" name = "Embeddings — vector generation" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "coming-soon" diff --git a/plugins/gameserver/module.toml b/plugins/gameserver/module.toml index 6dbe41be..a5710a20 100644 --- a/plugins/gameserver/module.toml +++ b/plugins/gameserver/module.toml @@ -14,7 +14,7 @@ [module] id = "gameserver" name = "Game server — Minecraft / Factorio / Valheim" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "infrastructure" status = "coming-soon" diff --git a/plugins/hosting/module.toml b/plugins/hosting/module.toml index fc4af38e..4308543f 100644 --- a/plugins/hosting/module.toml +++ b/plugins/hosting/module.toml @@ -12,7 +12,7 @@ [module] id = "hosting" name = "Hosting — censorship-resistant static sites" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "data" status = "coming-soon" diff --git a/plugins/infernet/module.toml b/plugins/infernet/module.toml index ddf2b1e7..1262bc1c 100644 --- a/plugins/infernet/module.toml +++ b/plugins/infernet/module.toml @@ -10,7 +10,7 @@ [module] id = "infernet" name = "Infernet Protocol — AI inference" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "partial" diff --git a/plugins/live-stream/module.toml b/plugins/live-stream/module.toml index d8cea775..57fe0e47 100644 --- a/plugins/live-stream/module.toml +++ b/plugins/live-stream/module.toml @@ -23,7 +23,7 @@ [module] id = "live-stream" name = "Live Stream — p2p HLS live streaming" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "media" status = "coming-soon" diff --git a/plugins/ocr/module.toml b/plugins/ocr/module.toml index 5cd7b82d..698720c7 100644 --- a/plugins/ocr/module.toml +++ b/plugins/ocr/module.toml @@ -12,7 +12,7 @@ [module] id = "ocr" name = "OCR — document text extraction" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "coming-soon" diff --git a/plugins/podcast-ads/module.toml b/plugins/podcast-ads/module.toml index 284ab51b..8b12189f 100644 --- a/plugins/podcast-ads/module.toml +++ b/plugins/podcast-ads/module.toml @@ -22,7 +22,7 @@ [module] id = "podcast-ads" name = "Podcast Ads — audio ad marketplace" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "coming-soon" diff --git a/plugins/podcasting/module.toml b/plugins/podcasting/module.toml index 96904dd6..da57ae3d 100644 --- a/plugins/podcasting/module.toml +++ b/plugins/podcasting/module.toml @@ -20,7 +20,7 @@ [module] id = "podcasting" name = "Podcasting — p2p podcast publishing" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "media" status = "coming-soon" diff --git a/plugins/podcasts/module.toml b/plugins/podcasts/module.toml index f8bb37fb..8511440a 100644 --- a/plugins/podcasts/module.toml +++ b/plugins/podcasts/module.toml @@ -19,7 +19,7 @@ [module] id = "podcasts" name = "Podcasts — p2p podcast listener" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "media" status = "coming-soon" diff --git a/plugins/render/module.toml b/plugins/render/module.toml index c8916390..f7dfc7b7 100644 --- a/plugins/render/module.toml +++ b/plugins/render/module.toml @@ -13,7 +13,7 @@ [module] id = "render" name = "Render — Blender 3D rendering" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "media" status = "coming-soon" diff --git a/plugins/secure-chat/module.toml b/plugins/secure-chat/module.toml index 2cc74ec3..5b3bb25d 100644 --- a/plugins/secure-chat/module.toml +++ b/plugins/secure-chat/module.toml @@ -18,7 +18,7 @@ [module] id = "secure-chat" name = "Secure Chat — E2E encrypted p2p messaging" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "applications" status = "partial" diff --git a/plugins/storage/module.toml b/plugins/storage/module.toml index 94966e3b..d47b9552 100644 --- a/plugins/storage/module.toml +++ b/plugins/storage/module.toml @@ -20,7 +20,7 @@ [module] id = "storage" name = "Storage — Reed-Solomon shard service" -version = "0.2.67" +version = "0.2.68" kind = "service" category = "data" status = "available" diff --git a/plugins/train/module.toml b/plugins/train/module.toml index c8adabf1..0193ab32 100644 --- a/plugins/train/module.toml +++ b/plugins/train/module.toml @@ -13,7 +13,7 @@ [module] id = "train" name = "Fine-tune — LoRA/QLoRA training" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "coming-soon" diff --git a/plugins/transcode/module.toml b/plugins/transcode/module.toml index f1a3f8db..f050ef8c 100644 --- a/plugins/transcode/module.toml +++ b/plugins/transcode/module.toml @@ -7,7 +7,7 @@ [module] id = "transcode" name = "FFmpeg Transcoding" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "media" status = "available" diff --git a/plugins/tts/module.toml b/plugins/tts/module.toml index dece4abc..12e46b6d 100644 --- a/plugins/tts/module.toml +++ b/plugins/tts/module.toml @@ -15,7 +15,7 @@ [module] id = "tts" name = "TTS — text-to-speech" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "available" diff --git a/plugins/video/module.toml b/plugins/video/module.toml index bf786195..f8fbe4d2 100644 --- a/plugins/video/module.toml +++ b/plugins/video/module.toml @@ -44,7 +44,7 @@ [module] id = "video" name = "Video — generation (Wan 2.2, LTX-2.3 with audio)" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "partial" diff --git a/plugins/webhook/module.toml b/plugins/webhook/module.toml index 1d874b24..7214f259 100644 --- a/plugins/webhook/module.toml +++ b/plugins/webhook/module.toml @@ -7,7 +7,7 @@ [module] id = "webhook" name = "Webhooks — paid, end-to-end encrypted delivery" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/whisper/module.toml b/plugins/whisper/module.toml index e6980735..d38553ac 100644 --- a/plugins/whisper/module.toml +++ b/plugins/whisper/module.toml @@ -7,7 +7,7 @@ [module] id = "whisper" name = "Whisper — speech-to-text" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "ai" status = "available" diff --git a/plugins/workflow-step/module.toml b/plugins/workflow-step/module.toml index 40c1130d..13bb7dfc 100644 --- a/plugins/workflow-step/module.toml +++ b/plugins/workflow-step/module.toml @@ -14,7 +14,7 @@ [module] id = "workflow-step" name = "Workflow step — webhook workflow steps (meshhook)" -version = "0.2.67" +version = "0.2.68" kind = "workload" category = "infrastructure" status = "available"