Skip to main content

recall_worker/
lib.rs

1//! `recall-worker`: the process that merges, so the one facing the internet
2//! does not have to.
3//!
4//! It is an enrolled device with the `worker` scope, running beside
5//! `recall-server` (in the same compose file, as a second service) with no
6//! inbound port. It long-polls the server's merge queue, reconciles each
7//! job's two versions with the local `claude` CLI, and posts the result
8//! back. The `claude` login lives on the worker's own volume, so a
9//! compromise of the API process no longer reaches it. See
10//! `docs/design/part5-plan.md`, "The worker", and the "Jobs" section of
11//! `docs/reference/api.md`.
12//!
13//! No Anthropic API key appears anywhere here, as anywhere in Recall: the
14//! merge rides whatever account the CLI on this machine is logged in to.
15//!
16//! The crate has two halves. [`merge`] is the merge itself, the prompt and
17//! the flags that keep it cheap; `recall-server` depends on it for the
18//! inline merge a deployment without a worker still runs. Everything else
19//! is behind the `client` feature (on by default): enrolling, signing
20//! requests, and the job loop, with the HTTP client they need. The server
21//! turns it off.
22
23#![deny(missing_docs)]
24
25pub mod merge;
26
27#[cfg(feature = "client")]
28pub mod api;
29#[cfg(feature = "client")]
30pub mod config;
31#[cfg(feature = "client")]
32pub mod evaluate;
33#[cfg(feature = "client")]
34pub mod identity;
35#[cfg(feature = "client")]
36pub mod worker;
37
38pub use merge::Merger;
39
40use time::OffsetDateTime;
41
42/// A timestamp in the format every Recall API answer uses: JavaScript's
43/// `Date.toISOString()`, millisecond precision with a `Z`, such as
44/// `2026-09-03T21:49:55.191Z`. The same function as `recall_server::now`,
45/// kept here so the merge does not depend on the server.
46pub fn now() -> String {
47    let fmt = time::macros::format_description!(
48        "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
49    );
50    OffsetDateTime::now_utc()
51        .format(&fmt)
52        .expect("the timestamp format is a compile-time constant")
53}
54
55/// What `recall-worker` sends as its `User-Agent`, and so what `/health`
56/// shows as the worker's agent: `recall-worker/0.4.2 (linux-x86_64)`.
57pub fn user_agent() -> String {
58    format!(
59        "recall-worker/{} ({}-{})",
60        recall_wire::discovery::version(),
61        std::env::consts::OS,
62        std::env::consts::ARCH
63    )
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn timestamps_match_javascripts_toisostring() {
72        let t = now();
73        assert_eq!(t.len(), 24, "got {t}");
74        assert!(t.ends_with('Z') && &t[10..11] == "T" && &t[19..20] == ".");
75    }
76
77    #[test]
78    fn the_agent_names_the_worker() {
79        assert!(
80            user_agent().starts_with("recall-worker/"),
81            "{}",
82            user_agent()
83        );
84    }
85}