stream: soul pass s2 fixes (thinking window, capped per repo, no proxy env, identical lines, forge read at gap marks)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
70de8cfec2
commit
5defa18afa
3 changed files with 1614 additions and 1499 deletions
|
|
@ -1,110 +1,109 @@
|
||||||
//! `mind-stream --instance <slug> [--once] [--dry-run]`: her stream of thought.
|
//! `mind-stream --instance <slug> [--once] [--dry-run]`: her stream of thought.
|
||||||
//!
|
//!
|
||||||
//! Without `--once` it runs forever: one `mind_body::stream::tick` per `POLL_S`, or per
|
//! Without `--once` it runs forever: one `mind_body::stream::tick` per `STREAM_POLL_S`, or per
|
||||||
//! `STREAM_MIN_GAP_S` after a blank or a fault. `--dry-run` runs one tick that admits nothing and
|
//! `STREAM_MIN_GAP_S` after a blank or a fault. `--dry-run` runs one tick that admits nothing and
|
||||||
//! prints the thought it would admit as JSON. A model or mind fault ends the tick, never the
|
//! prints the thought it would admit as JSON. A model or mind fault ends the tick, never the
|
||||||
//! process: it is one line on stderr. This reads the process's arguments and environment, which
|
//! process: it is one line on stderr. This reads the process's arguments and environment, which
|
||||||
//! the library never does.
|
//! the library never does.
|
||||||
//!
|
//!
|
||||||
//! Environment: `HUGINN_ENDPOINT` (`rudp://<ip>:<port>`, her own daemon), `EUREKA_INSTANCE`
|
//! Environment: `HUGINN_ENDPOINT` (`rudp://<ip>:<port>`, her own daemon), `EUREKA_INSTANCE`
|
||||||
//! (when `--instance` is not given), `MIND_STREAM_ENDPOINT` (`http://<loopback>:<port>`, the
|
//! (when `--instance` is not given), `MIND_STREAM_ENDPOINT` (`http://<loopback>:<port>`, the
|
||||||
//! model host), `MIND_STREAM_PROJECTOR_MODEL` and `MIND_STREAM_BASE_MODEL`.
|
//! model host), `MIND_STREAM_PROJECTOR_MODEL` and `MIND_STREAM_BASE_MODEL`.
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use eureka_pipeline::Slug;
|
use eureka_pipeline::Slug;
|
||||||
use eureka_state::HuginnClient;
|
use eureka_state::HuginnClient;
|
||||||
use mind_body::control::read_effective;
|
use mind_body::control::read_effective;
|
||||||
use mind_body::queue::HuginnMind;
|
use mind_body::queue::HuginnMind;
|
||||||
use mind_body::stream::{HuginnPersonaSource, Mode, OllamaStream, Organs, Outcome, STREAM_MIN_GAP_S, tick};
|
use mind_body::stream::{HuginnPersonaSource, Mode, OllamaStream, Organs, Outcome, STREAM_MIN_GAP_S, STREAM_POLL_S, tick};
|
||||||
|
|
||||||
const TIMEOUT: Duration = Duration::from_secs(30);
|
const TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
const POLL_S: u64 = 60;
|
|
||||||
|
#[allow(clippy::disallowed_methods, reason = "the binary reads its own environment, and hands it to the library")]
|
||||||
#[allow(clippy::disallowed_methods, reason = "the binary reads its own environment, and hands it to the library")]
|
fn setting(name: &str) -> Option<String> {
|
||||||
fn setting(name: &str) -> Option<String> {
|
std::env::var(name).ok().filter(|value| !value.is_empty())
|
||||||
std::env::var(name).ok().filter(|value| !value.is_empty())
|
}
|
||||||
}
|
|
||||||
|
fn required(name: &str) -> Result<String> {
|
||||||
fn required(name: &str) -> Result<String> {
|
setting(name).ok_or_else(|| anyhow!("{name} is not set"))
|
||||||
setting(name).ok_or_else(|| anyhow!("{name} is not set"))
|
}
|
||||||
}
|
|
||||||
|
struct Args {
|
||||||
struct Args {
|
instance: Option<String>,
|
||||||
instance: Option<String>,
|
once: bool,
|
||||||
once: bool,
|
dry_run: bool,
|
||||||
dry_run: bool,
|
}
|
||||||
}
|
|
||||||
|
fn arguments() -> Result<Args> {
|
||||||
fn arguments() -> Result<Args> {
|
let mut args = Args { instance: None, once: false, dry_run: false };
|
||||||
let mut args = Args { instance: None, once: false, dry_run: false };
|
let mut given = std::env::args().skip(1);
|
||||||
let mut given = std::env::args().skip(1);
|
while let Some(argument) = given.next() {
|
||||||
while let Some(argument) = given.next() {
|
match argument.as_str() {
|
||||||
match argument.as_str() {
|
"--instance" => args.instance = Some(given.next().ok_or_else(|| anyhow!("--instance needs a slug"))?),
|
||||||
"--instance" => args.instance = Some(given.next().ok_or_else(|| anyhow!("--instance needs a slug"))?),
|
"--once" => args.once = true,
|
||||||
"--once" => args.once = true,
|
"--dry-run" => args.dry_run = true,
|
||||||
"--dry-run" => args.dry_run = true,
|
other => bail!("unknown argument {other}; usage: mind-stream --instance <slug> [--once] [--dry-run]"),
|
||||||
other => bail!("unknown argument {other}; usage: mind-stream --instance <slug> [--once] [--dry-run]"),
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(args)
|
||||||
Ok(args)
|
}
|
||||||
}
|
|
||||||
|
fn run() -> Result<()> {
|
||||||
fn run() -> Result<()> {
|
let args = arguments()?;
|
||||||
let args = arguments()?;
|
let instance = Slug(args.instance.map_or_else(|| required("EUREKA_INSTANCE"), Ok)?);
|
||||||
let instance = Slug(args.instance.map_or_else(|| required("EUREKA_INSTANCE"), Ok)?);
|
let address: SocketAddr = required("HUGINN_ENDPOINT")?
|
||||||
let address: SocketAddr = required("HUGINN_ENDPOINT")?
|
.strip_prefix("rudp://")
|
||||||
.strip_prefix("rudp://")
|
.and_then(|address| address.parse().ok())
|
||||||
.and_then(|address| address.parse().ok())
|
.ok_or_else(|| anyhow!("HUGINN_ENDPOINT is not rudp://<ip>:<port>"))?;
|
||||||
.ok_or_else(|| anyhow!("HUGINN_ENDPOINT is not rudp://<ip>:<port>"))?;
|
let projector_model = required("MIND_STREAM_PROJECTOR_MODEL")?;
|
||||||
let projector_model = required("MIND_STREAM_PROJECTOR_MODEL")?;
|
let base_model = required("MIND_STREAM_BASE_MODEL")?;
|
||||||
let base_model = required("MIND_STREAM_BASE_MODEL")?;
|
let model = OllamaStream::open(&required("MIND_STREAM_ENDPOINT")?, &projector_model, &base_model)?;
|
||||||
let model = OllamaStream::open(&required("MIND_STREAM_ENDPOINT")?, &projector_model, &base_model)?;
|
let mind = HuginnMind::new(HuginnClient::new(address, instance.clone(), TIMEOUT));
|
||||||
let mind = HuginnMind::new(HuginnClient::new(address, instance.clone(), TIMEOUT));
|
let persona = HuginnPersonaSource::new(HuginnClient::new(address, instance.clone(), TIMEOUT));
|
||||||
let persona = HuginnPersonaSource::new(HuginnClient::new(address, instance.clone(), TIMEOUT));
|
let organs = Organs { mind: &mind, persona: &persona, model: &model, projector_model: &projector_model, base_model: &base_model };
|
||||||
let organs = Organs { mind: &mind, persona: &persona, model: &model, projector_model: &projector_model, base_model: &base_model };
|
let mode = if args.dry_run { Mode::DryRun } else { Mode::Live };
|
||||||
let mode = if args.dry_run { Mode::DryRun } else { Mode::Live };
|
let once = args.once || args.dry_run;
|
||||||
let once = args.once || args.dry_run;
|
loop {
|
||||||
loop {
|
let mut faulted = false;
|
||||||
let mut faulted = false;
|
let waits = match tick(&organs, &|| read_effective(&instance), mode, Utc::now()) {
|
||||||
let waits = match tick(&organs, &|| read_effective(&instance), mode, Utc::now()) {
|
Ok(Outcome::Thought { thought, admitted }) => {
|
||||||
Ok(Outcome::Thought { thought, admitted }) => {
|
if admitted {
|
||||||
if admitted {
|
println!("admitted {}:thought:{}", instance.0, thought.label.0);
|
||||||
println!("admitted {}:thought:{}", instance.0, thought.label.0);
|
} else {
|
||||||
} else {
|
println!("{}", serde_json::to_string_pretty(&*thought)?);
|
||||||
println!("{}", serde_json::to_string_pretty(&*thought)?);
|
}
|
||||||
}
|
false
|
||||||
false
|
}
|
||||||
}
|
Ok(Outcome::Blank) => true,
|
||||||
Ok(Outcome::Blank) => true,
|
Ok(Outcome::Held(reason)) => {
|
||||||
Ok(Outcome::Held(reason)) => {
|
eprintln!("mind-stream: held ({reason:?}); nothing ticked");
|
||||||
eprintln!("mind-stream: held ({reason:?}); nothing ticked");
|
false
|
||||||
false
|
}
|
||||||
}
|
Ok(Outcome::NotDue) => false,
|
||||||
Ok(Outcome::NotDue) => false,
|
Err(fault) => {
|
||||||
Err(fault) => {
|
eprintln!("mind-stream: {}", format!("{fault:#}").replace('\n', " "));
|
||||||
eprintln!("mind-stream: {}", format!("{fault:#}").replace('\n', " "));
|
faulted = true;
|
||||||
faulted = true;
|
true
|
||||||
true
|
}
|
||||||
}
|
};
|
||||||
};
|
if once {
|
||||||
if once {
|
return if faulted { Err(anyhow!("the tick faulted")) } else { Ok(()) };
|
||||||
return if faulted { Err(anyhow!("the tick faulted")) } else { Ok(()) };
|
}
|
||||||
}
|
std::thread::sleep(Duration::from_secs(if waits { STREAM_MIN_GAP_S as u64 } else { STREAM_POLL_S as u64 }));
|
||||||
std::thread::sleep(Duration::from_secs(if waits { STREAM_MIN_GAP_S as u64 } else { POLL_S }));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
fn main() -> ExitCode {
|
match run() {
|
||||||
match run() {
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Err(fault) => {
|
||||||
Err(fault) => {
|
eprintln!("mind-stream: {}", format!("{fault:#}").replace('\n', " "));
|
||||||
eprintln!("mind-stream: {}", format!("{fault:#}").replace('\n', " "));
|
ExitCode::FAILURE
|
||||||
ExitCode::FAILURE
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue