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 identity;
33#[cfg(feature = "client")]
34pub mod worker;
35
36pub use merge::Merger;
37
38use time::OffsetDateTime;
39
40/// A timestamp in the format every Recall API answer uses: JavaScript's
41/// `Date.toISOString()`, millisecond precision with a `Z`, such as
42/// `2026-09-03T21:49:55.191Z`. The same function as `recall_server::now`,
43/// kept here so the merge does not depend on the server.
44pub fn now() -> String {
45    let fmt = time::macros::format_description!(
46        "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
47    );
48    OffsetDateTime::now_utc()
49        .format(&fmt)
50        .expect("the timestamp format is a compile-time constant")
51}
52
53/// What `recall-worker` sends as its `User-Agent`, and so what `/health`
54/// shows as the worker's agent: `recall-worker/0.4.2 (linux-x86_64)`.
55pub fn user_agent() -> String {
56    format!(
57        "recall-worker/{} ({}-{})",
58        recall_wire::discovery::version(),
59        std::env::consts::OS,
60        std::env::consts::ARCH
61    )
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn timestamps_match_javascripts_toisostring() {
70        let t = now();
71        assert_eq!(t.len(), 24, "got {t}");
72        assert!(t.ends_with('Z') && &t[10..11] == "T" && &t[19..20] == ".");
73    }
74
75    #[test]
76    fn the_agent_names_the_worker() {
77        assert!(
78            user_agent().starts_with("recall-worker/"),
79            "{}",
80            user_agent()
81        );
82    }
83}