Skip to content
338 linesCodeBlameRaw
1//! Runs and recorded sessions. Kept by the work service.
2//!
3//! Every sandbox g1t starts for checks, the merge queue or a merge check is
4//! an [`AgentRun`]: what it is doing, on which pull request, step by step,
5//! and how it ended. Runs of the agents g1t once ran itself stay readable.
6//!
7//! A session is what a coding agent recorded on a pull request
8//! (`append_session`, `read_session`), summed up and listed here.
9//!
10//! Each `*Args` struct is the argument of the method of the same name,
11//! served at `POST /rpc/<method>`.
12
13use serde::{Deserialize, Serialize};
14
15use crate::repos::RepoPath;
16use crate::work::{Pull, PullStatus, SessionEntry};
17use crate::{User, Viewer};
18
19/// The work a run does. Only `Checks`, `Queue` and `Mergecheck` are
20/// started now; the others are runs of the agents g1t once ran itself,
21/// kept so the runs and run credentials already stored still parse. None
22/// of them is started or granted any more.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum RunKind {
26 /// Making the change an issue asks for.
27 Implement,
28 /// Addressing failed checks or a review on its own pull request.
29 Revise,
30 /// Reviewing someone's pull request.
31 Review,
32 /// Answering another agent's question or handoff.
33 Answer,
34 /// Merging in the branch a pull request will land on.
35 Update,
36 /// Turning an outcome into a plan of issues.
37 Plan,
38 /// Running an issue's acceptance checks. Not an agent: a sandbox.
39 Checks,
40 /// Building and checking a state of the merge queue. Not an agent.
41 Queue,
42 /// Finding out whether a pull request merges cleanly. Not an agent.
43 Mergecheck,
44}
45
46impl RunKind {
47 pub const ALL: [RunKind; 9] = [
48 RunKind::Implement,
49 RunKind::Revise,
50 RunKind::Review,
51 RunKind::Answer,
52 RunKind::Update,
53 RunKind::Plan,
54 RunKind::Checks,
55 RunKind::Queue,
56 RunKind::Mergecheck,
57 ];
58
59 pub fn as_str(self) -> &'static str {
60 match self {
61 RunKind::Implement => "implement",
62 RunKind::Revise => "revise",
63 RunKind::Review => "review",
64 RunKind::Answer => "answer",
65 RunKind::Update => "update",
66 RunKind::Plan => "plan",
67 RunKind::Checks => "checks",
68 RunKind::Queue => "queue",
69 RunKind::Mergecheck => "mergecheck",
70 }
71 }
72
73 pub fn parse(value: &str) -> Option<RunKind> {
74 RunKind::ALL.into_iter().find(|kind| kind.as_str() == value)
75 }
76
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum RunStatus {
82 /// Its sandbox is starting.
83 Queued,
84 Running,
85 Succeeded,
86 Failed,
87 /// A person stopped it.
88 Stopped,
89}
90
91impl RunStatus {
92 pub fn as_str(self) -> &'static str {
93 match self {
94 RunStatus::Queued => "queued",
95 RunStatus::Running => "running",
96 RunStatus::Succeeded => "succeeded",
97 RunStatus::Failed => "failed",
98 RunStatus::Stopped => "stopped",
99 }
100 }
101
102 pub fn parse(value: &str) -> Option<RunStatus> {
103 [
104 RunStatus::Queued,
105 RunStatus::Running,
106 RunStatus::Succeeded,
107 RunStatus::Failed,
108 RunStatus::Stopped,
109 ]
110 .into_iter()
111 .find(|status| status.as_str() == value)
112 }
113
114 pub fn is_active(self) -> bool {
115 matches!(self, RunStatus::Queued | RunStatus::Running)
116 }
117}
118
119/// One step of a run, as one short line.
120#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct RunStep {
122 /// RFC 3339.
123 pub at: String,
124 pub text: String,
125}
126
127/// One sandbox g1t started: a run of checks, a merge queue build or a
128/// merge check (or, on older rows, one of its former agents at work).
129#[derive(Clone, Debug, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct AgentRun {
132 pub id: String,
133 pub repo: RepoPath,
134 /// The pull request it works on; for a plan, none.
135 pub number: Option<u32>,
136 /// That pull request's title, or the plan's brief.
137 pub title: Option<String>,
138 pub kind: RunKind,
139 /// Who is working: `g1t` for g1t's own agent and for a sandbox that
140 /// runs commands.
141 pub agent: String,
142 /// The model, by its public name. Members only.
143 pub model: Option<String>,
144 pub status: RunStatus,
145 /// What it is doing now, in one line.
146 pub step: Option<String>,
147 /// Every step so far, oldest first. Empty in lists.
148 #[serde(default)]
149 pub steps: Vec<RunStep>,
150 /// How many steps there are.
151 #[serde(default)]
152 pub step_count: u32,
153 /// Who put it to work, when a person did.
154 pub started_by: Option<String>,
155 /// Why it failed.
156 pub error: Option<String>,
157 /// What it cost, in US dollars, as the harness reported it. Members only.
158 pub cost_usd: Option<f64>,
159 pub turns: Option<u32>,
160 /// The most it may cost, in US dollars, from its guardrails. None: no
161 /// cap. Members only.
162 #[serde(default)]
163 pub budget_usd: Option<f64>,
164 /// The longest it may take, in minutes, from its guardrails.
165 #[serde(default)]
166 pub time_cap_minutes: Option<u32>,
167 /// `budget` or `time` when g1t stopped it for reaching a cap.
168 #[serde(default)]
169 pub halted: Option<String>,
170 /// RFC 3339.
171 pub created_at: String,
172 pub started_at: Option<String>,
173 pub finished_at: Option<String>,
174 pub updated_at: String,
175}
176
177/// What lets a sandbox, and nothing else, report on its run.
178#[derive(Clone, Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct AgentRunTicket {
181 pub run_id: String,
182 pub token: String,
183}
184
185/// `open_run`: records a sandbox the runner is starting. Called by the
186/// runner service only. Returns `Outcome<AgentRunTicket>`.
187#[derive(Debug, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct OpenRunArgs {
190 /// Who the sandbox acts as.
191 pub actor: User,
192 pub repo: RepoPath,
193 #[serde(default)]
194 pub number: Option<u32>,
195 #[serde(default)]
196 pub pull_id: Option<String>,
197 /// For a run with no pull request, such as a plan: what it is about.
198 #[serde(default)]
199 pub title: Option<String>,
200 pub kind: RunKind,
201 #[serde(default)]
202 pub agent: Option<String>,
203 #[serde(default)]
204 pub model: Option<String>,
205 /// The sandbox's name, which stopping it needs.
206 pub sandbox: String,
207 #[serde(default)]
208 pub started_by: Option<String>,
209 /// Its cost cap, from its guardrails, in US dollars.
210 #[serde(default)]
211 pub budget_usd: Option<f64>,
212 /// Its time cap, from its guardrails, in minutes.
213 #[serde(default)]
214 pub time_cap_minutes: Option<u32>,
215}
216
217/// `report_run`: a sandbox telling how its run goes, with the run's token.
218/// Steps are added, the current step replaced, and an outcome ends the run.
219/// A finished run accepts nothing more. Returns `Outcome<RunStatus>`: the
220/// run's status after the report, so a sandbox can tell it was stopped.
221#[derive(Debug, Default, Serialize, Deserialize)]
222#[serde(rename_all = "camelCase", default)]
223pub struct ReportRunArgs {
224 pub run_id: String,
225 pub token: String,
226 pub steps: Vec<String>,
227 pub step: Option<String>,
228 pub cost_usd: Option<f64>,
229 pub turns: Option<u32>,
230 /// `succeeded` or `failed`, to end the run.
231 pub outcome: Option<RunStatus>,
232 pub error: Option<String>,
233 /// `budget` or `time`: the run reached a cap of its guardrails and
234 /// stopped. Ends it as stopped, like a person's stop.
235 pub halt: Option<crate::guardrails::Halt>,
236}
237
238/// `list_runs`: runs in a repository, or with `workspace` instead, in every
239/// repository of a workspace (members only), newest first. Returns
240/// `Outcome<Vec<AgentRun>>`.
241#[derive(Debug, Default, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase", default)]
243pub struct ListRunsArgs {
244 pub viewer: Viewer,
245 pub repo: Option<RepoPath>,
246 pub workspace: Option<String>,
247 /// Only those queued or running.
248 pub active: bool,
249 pub kind: Option<RunKind>,
250 pub status: Option<RunStatus>,
251 /// Only those on this pull request.
252 pub number: Option<u32>,
253 /// At most 200; 50 if not given.
254 pub limit: Option<u32>,
255}
256
257/// `get_run`: one run with all its steps. Returns `Outcome<AgentRun>`.
258#[derive(Debug, Serialize, Deserialize)]
259pub struct GetRunArgs {
260 pub viewer: Viewer,
261 pub repo: RepoPath,
262 pub id: String,
263}
264
265/// A pull request's recorded session, summed up for a list.
266#[derive(Clone, Debug, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct SessionSummary {
269 pub number: u32,
270 pub title: String,
271 pub status: PullStatus,
272 /// The agent label on the pull request, such as `g1t`.
273 pub agent: String,
274 pub entries: u32,
275 /// How many tools it called.
276 pub tools: u32,
277 /// The start of the first prompt.
278 pub prompt: Option<String>,
279 /// The kinds of the runs g1t made for it.
280 pub kinds: Vec<RunKind>,
281 pub runs: u32,
282 /// What its runs cost together. Members only.
283 pub cost_usd: Option<f64>,
284 /// Whether one of its runs is under way.
285 pub active: bool,
286 /// RFC 3339.
287 pub started_at: String,
288 pub last_at: String,
289}
290
291/// `list_sessions`: the pull requests of a repository that have a session,
292/// most recently active first. Returns `Outcome<Vec<SessionSummary>>`.
293#[derive(Debug, Default, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase", default)]
295pub struct ListSessionsArgs {
296 pub viewer: Viewer,
297 pub repo: Option<RepoPath>,
298 /// Only those with a run of this kind.
299 pub kind: Option<RunKind>,
300 /// The pull request's status: `draft`, `open`, `merged` or `closed`.
301 pub outcome: Option<PullStatus>,
302 pub number: Option<u32>,
303}
304
305/// `get_session`: one pull request's session in full, with its runs.
306/// Returns `Outcome<SessionView>`.
307#[derive(Debug, Serialize, Deserialize)]
308pub struct GetSessionArgs {
309 pub viewer: Viewer,
310 pub repo: RepoPath,
311 pub number: u32,
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize)]
315#[serde(rename_all = "camelCase")]
316pub struct SessionView {
317 pub pull: Pull,
318 /// Oldest first; the first 2000.
319 pub entries: Vec<SessionEntry>,
320 /// Newest first, with their steps.
321 pub runs: Vec<AgentRun>,
322 /// Members only.
323 pub cost_usd: Option<f64>,
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn kinds_and_statuses_round_trip() {
332 for kind in RunKind::ALL {
333 assert_eq!(RunKind::parse(kind.as_str()), Some(kind));
334 }
335 assert_eq!(RunStatus::parse("stopped"), Some(RunStatus::Stopped));
336 assert!(RunStatus::Queued.is_active() && !RunStatus::Failed.is_active());
337 }
338}