Skip to main content

locode_packs/
pack.rs

1//! The harness-pack abstraction (ADR-0012): a named toolset + a base prompt.
2
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use locode_host::Host;
7use locode_protocol::Message;
8use locode_tools::Registry;
9
10/// Dynamic, per-run context a pack's preamble is rendered against.
11///
12/// Deliberately small (like `ToolCtx`, ADR-0003 rejects god-object contexts): the fields
13/// a harness's real prompt needs (Task 13) — cwd/OS/shell/date + the headless identity
14/// branch. Grows only if a ported prompt needs more.
15#[derive(Debug, Clone)]
16pub struct PackContext {
17    /// Absolute working directory shown to the model.
18    pub cwd: PathBuf,
19    /// Target OS label (e.g. `macos`).
20    pub os: String,
21    /// Login shell (e.g. `/bin/zsh`).
22    pub shell: String,
23    /// Current date, preformatted (the preamble stays a pure function of context).
24    pub date: String,
25    /// Headless run → autonomous identity branch (vs interactive). See Task 13.
26    pub headless: bool,
27    /// Strip identity-revealing sentences from the rendered prompt (e.g. grok's
28    /// "You are Grok released by xAI." and the `<user_guide>` block naming the
29    /// Grok Build TUI). **Default `false` = faithful reproduction** (user
30    /// decision, 2026-07-18); `true` is for A/B runs where the harness's
31    /// self-identity would contaminate the comparison. Post-processing on the
32    /// rendered output only — the verbatim template copies are never edited.
33    pub strip_identity: bool,
34}
35
36/// A faithful reproduction of one harness: its real toolset + its base prompt, selected
37/// whole via `--harness` (ADR-0012). One pack is active per run.
38///
39/// The pack — not a per-tool field — is the unit of harness identity (contrast Grok
40/// Build, which tags every tool with a namespace because it co-locates all harnesses'
41/// tools in one registry; we build a fresh registry per pack, so no tag is needed).
42pub trait Pack: Send + Sync {
43    /// The `--harness` selector and the report-envelope `harness` value.
44    fn name(&self) -> &'static str;
45
46    /// Register this pack's tools into `registry`, each under its harness's **real wire
47    /// name** (`Tool` has no name of its own — the name is assigned here). Tools are
48    /// constructed here holding an `Arc<Host>` (the only OS seam; `ToolCtx` is too small
49    /// to carry it). A duplicate name is a wiring bug and panics inside
50    /// `Registry::register`.
51    fn register(&self, host: &Arc<Host>, registry: &mut Registry);
52
53    /// The pack's **base preamble**: the ordered, role-tagged `System`/`Developer`
54    /// messages that seed the conversation (ADR-0013). Each pack maps its harness onto
55    /// our roles faithfully — a single `System` message, or `System` + `Developer`, etc.
56    /// The wire (Task 12) places each role in the right slot. Task 8 ships a scaffold;
57    /// the real content lands in Task 13.
58    fn preamble(&self, ctx: &PackContext) -> Vec<Message>;
59
60    /// Convenience: a fresh [`Registry`] holding exactly this pack's tools, over `host`.
61    ///
62    /// # Panics
63    /// If the pack's [`Pack::register`] assigns the same wire name twice.
64    fn build_registry(&self, host: &Arc<Host>) -> Registry {
65        let mut registry = Registry::new();
66        self.register(host, &mut registry);
67        registry
68    }
69}