newt_acp_worker/lib.rs
1//! Newt-Agent ACP worker.
2//!
3//! Speaks the Agent Client Protocol (agentclientprotocol.com) over stdio so
4//! `drake-foreman` can dispatch coding goals to Newt instances.
5//!
6//! Contract (per memory `feedback_drake_patch_not_prose` and
7//! `feedback_empty_diff_is_a_crash`):
8//! - Worker ONLY edits files; never `git add` / `git commit` / `git push`.
9//! - Empty `git diff` post-turn is a deterministic crash — foreman counts it
10//! against the model's scorecard.
11//! - `TaskReply.model_id` is mandatory.
12
13mod diff;
14mod server;
15
16#[cfg(feature = "pyo3")]
17pub mod pyo3_module;
18
19pub use diff::{capture_diff, is_empty_diff};
20pub use server::{AcpServer, Session, TaskReply};
21
22/// Spawn the default ACP worker over stdio.
23///
24/// Discovers a local Ollama endpoint (per `LocalOllamaBackend::discover`)
25/// using the default model `llama3.1:8b` and runs the server until stdin
26/// closes.
27pub async fn run_stdio() -> anyhow::Result<()> {
28 run_with_io(tokio::io::stdin(), tokio::io::stdout()).await
29}
30
31/// Spawn the default ACP worker against an explicit reader/writer pair.
32///
33/// Used by the CLI binary's `Worker` dispatch arm to feed a private
34/// "real stdout" file handle (obtained from
35/// [`newt_cli::stdio_guard::redirect_stdout_to_stderr`]) into the
36/// server *after* fd 1 has been redirected to stderr. That sequence
37/// is what protects the JSON-RPC wire from rogue `println!` calls in
38/// dependencies — see the module-level doc on `stdio_guard` for the
39/// full rationale.
40///
41/// Picks the initial Ollama model from `NEWT_DEFAULT_MODEL` env, falling
42/// back to `llama3.1:8b`. Lets the bake-off harness iterate models by
43/// spawning fresh worker subprocesses with different model envs while
44/// session-level model swap isn't wired through `ChatRequest` yet.
45pub async fn run_with_io<R, W>(reader: R, writer: W) -> anyhow::Result<()>
46where
47 R: tokio::io::AsyncRead + Unpin,
48 W: tokio::io::AsyncWrite + Unpin,
49{
50 let default_model =
51 std::env::var("NEWT_DEFAULT_MODEL").unwrap_or_else(|_| "llama3.1:8b".to_string());
52 let backend = newt_inference::local::LocalOllamaBackend::discover(&default_model).await?;
53 let server = AcpServer::new(std::sync::Arc::new(backend));
54 server.run(reader, writer).await
55}