Skip to main content

kernel/event/record/
harness.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What an official harness reported during a run and what it answered
7//! the city, in this city's own words.
8//!
9//! The words are the city's and not ACP's (kernel D9): a spelling
10//! the Ledger wrote cannot change, and ACP's words follow upstream. The
11//! assembly maps each ACP reading onto these types with an exhaustive
12//! `match`, so a report upstream adds stops the build at the mapping.
13//!
14//! Neither payload carries `#[serde(default)]`: both kinds were written
15//! in this shape from their first line, so a line that lacks a key is
16//! refused rather than guessed at.
17
18use serde::{Deserialize, Serialize};
19
20/// `harness_reported`: one thing the harness reported during a run.
21///
22/// The `report` key says which. A fold that answers "what did the city
23/// do" never reads this kind; only a view that answers "what did the
24/// harness say" does.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
27#[serde(tag = "report", rename_all = "snake_case")]
28pub enum HarnessReported {
29    /// A piece of the answer to the city.
30    Said {
31        /// The piece, as the harness sent it.
32        text: String,
33    },
34    /// A piece of the harness's reasoning.
35    Thought {
36        /// The piece, as the harness sent it.
37        text: String,
38    },
39    /// A tool call the harness started, in its own words.
40    ToolCall {
41        /// The harness's id for the call.
42        call: String,
43        /// The title the harness gave the call.
44        title: String,
45        /// The harness's own word for what kind of call it is.
46        kind: String,
47    },
48    /// The status of a tool call the harness started changed.
49    ToolCallStatus {
50        /// The harness's id for the call.
51        call: String,
52        /// The harness's own word for the new status.
53        status: String,
54    },
55    /// A report this city has no reading for; only its name is kept.
56    Other {
57        /// The name of the report as the harness spelled it.
58        variant: String,
59    },
60    /// The harness asked the city for a permission.
61    PermissionAsked {
62        /// What the harness asked to do.
63        title: String,
64        /// The options it offered, in the order it offered them.
65        options: Vec<HarnessPermit>,
66    },
67    /// The city answered a permission the harness asked for.
68    PermissionAnswered {
69        /// The id of the option the city chose; `None` is the answer
70        /// `cancelled`.
71        chosen: Option<String>,
72    },
73}
74
75/// One option a harness offered when it asked for a permission.
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
78pub struct HarnessPermit {
79    /// The harness's id for the option; the city answers with it.
80    pub id: String,
81    /// The name the harness shows for the option.
82    pub name: String,
83    /// What choosing the option grants or refuses.
84    pub kind: HarnessPermitKind,
85}
86
87/// What choosing a permission option grants or refuses.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
90#[serde(rename_all = "snake_case")]
91pub enum HarnessPermitKind {
92    /// Allowed this once.
93    AllowOnce,
94    /// Allowed from now on.
95    AllowAlways,
96    /// Refused this once.
97    RejectOnce,
98    /// Refused from now on.
99    RejectAlways,
100}
101
102/// `harness_answered`: the harness's answer to the city's prompt.
103///
104/// Written when the stop reason arrives, before the run freezes, for
105/// every stop reason. An `end_turn` answer with text is evidence of
106/// `Completion::Done` (`crates/kernel/spec/Completion.lean` ยง8-20).
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
109pub struct HarnessAnswered {
110    /// Why the harness stopped.
111    pub stop: HarnessStop,
112    /// The answer pieces of this turn, joined in order; written even
113    /// when empty.
114    pub text: String,
115}
116
117/// Why a harness stopped answering the city's prompt.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
120#[serde(rename_all = "snake_case")]
121pub enum HarnessStop {
122    /// The harness finished its turn.
123    EndTurn,
124    /// The model ran out of output tokens.
125    MaxTokens,
126    /// The harness reached its limit of model requests for one turn.
127    MaxTurnRequests,
128    /// The model refused.
129    Refusal,
130    /// The city cancelled the turn.
131    Cancelled,
132}
133
134#[cfg(test)]
135#[allow(clippy::unwrap_used, reason = "test code")]
136mod tests;