g1t/crates/contracts/src/work.rs

238 lines6,528 bytesCodeBlame
1//! The work service: intents, attempts and sessions.
2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
5
6use serde::{Deserialize, Serialize};
7
8use crate::repos::RepoPath;
9use crate::{User, Viewer};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum IntentStatus {
14 Open,
15 Shipped,
16 Withdrawn,
17}
18
19/// A goal stated against a repo: the issue, and the home of every attempt
20/// made for it.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Intent {
24 pub id: String,
25 pub repo_id: String,
26 /// Sequential per repo, shown as `#12`.
27 pub number: u32,
28 pub title: String,
29 /// The goal in prose: what an agent is given to work from.
30 pub brief: String,
31 /// Commands that must pass for an attempt to be accepted.
32 pub checks: Vec<String>,
33 pub status: IntentStatus,
34 pub author: User,
35 /// RFC 3339.
36 pub created_at: String,
37 pub attempt_count: u32,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum AttemptStatus {
43 Working,
44 Submitted,
45 Shipped,
46 Abandoned,
47}
48
49impl AttemptStatus {
50 pub fn as_str(self) -> &'static str {
51 match self {
52 AttemptStatus::Working => "working",
53 AttemptStatus::Submitted => "submitted",
54 AttemptStatus::Shipped => "shipped",
55 AttemptStatus::Abandoned => "abandoned",
56 }
57 }
58
59 /// Whether the attempt can still be changed or shipped.
60 pub fn is_active(self) -> bool {
61 matches!(self, AttemptStatus::Working | AttemptStatus::Submitted)
62 }
63}
64
65/// Where the agent runs: on g1t's sandboxes, or in someone's own session.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "lowercase")]
68pub enum AttemptRuntime {
69 Hosted,
70 External,
71}
72
73/// One agent's or person's run at an intent, in its own fork.
74#[derive(Clone, Debug, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct Attempt {
77 pub id: String,
78 pub intent_id: String,
79 pub repo_id: String,
80 /// Sequential per intent.
81 pub number: u32,
82 /// A label for the agent doing the work, e.g. `claude-code`.
83 pub agent: String,
84 pub runtime: AttemptRuntime,
85 pub status: AttemptStatus,
86 /// The agent's own account of what it did, set on submit.
87 pub summary: Option<String>,
88 pub fork: RepoPath,
89 /// The fork's repository id.
90 pub fork_repo_id: String,
91 pub head_commit: Option<String>,
92 /// For a shipped attempt, what the branch pointed to before it landed.
93 /// Comparing against it shows what the attempt changed.
94 pub landed_base: Option<String>,
95 pub started_by: User,
96 /// RFC 3339.
97 pub created_at: String,
98 /// RFC 3339.
99 pub updated_at: String,
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum SessionEntryKind {
105 Prompt,
106 Message,
107 ToolCall,
108 ToolResult,
109 Note,
110}
111
112/// One step of an agent's session: the "why" behind an attempt's commits.
113#[derive(Clone, Debug, Serialize, Deserialize)]
114pub struct SessionEntry {
115 pub seq: u32,
116 pub kind: SessionEntryKind,
117 pub text: String,
118 /// For tool calls and results.
119 pub tool: Option<String>,
120 /// The fork's head commit when this entry was recorded, if known.
121 pub commit: Option<String>,
122 /// RFC 3339.
123 pub at: String,
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct NewSessionEntry {
128 pub kind: SessionEntryKind,
129 pub text: String,
130 #[serde(default)]
131 pub tool: Option<String>,
132 #[serde(default)]
133 pub commit: Option<String>,
134}
135
136#[derive(Clone, Debug, Serialize, Deserialize)]
137pub struct IntentDetail {
138 pub intent: Intent,
139 pub attempts: Vec<Attempt>,
140}
141
142#[derive(Clone, Debug, Serialize, Deserialize)]
143pub struct AttemptDetail {
144 pub attempt: Attempt,
145 pub intent: Intent,
146}
147
148/// `open_intent`. Returns `Outcome<Intent>`.
149#[derive(Debug, Serialize, Deserialize)]
150pub struct OpenIntentArgs {
151 pub actor: User,
152 pub repo: RepoPath,
153 pub title: String,
154 #[serde(default)]
155 pub brief: String,
156 #[serde(default)]
157 pub checks: Vec<String>,
158}
159
160/// `list_intents`. Returns `Outcome<Vec<Intent>>`.
161#[derive(Debug, Serialize, Deserialize)]
162pub struct ListIntentsArgs {
163 pub repo: RepoPath,
164 pub viewer: Viewer,
165 #[serde(default)]
166 pub status: Option<IntentStatus>,
167}
168
169/// `get_intent`. Returns `Outcome<IntentDetail>`.
170#[derive(Debug, Serialize, Deserialize)]
171pub struct GetIntentArgs {
172 pub repo: RepoPath,
173 pub number: u32,
174 pub viewer: Viewer,
175}
176
177/// `withdraw_intent`. Returns `Outcome<Intent>`.
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct IntentActionArgs {
181 pub actor: User,
182 pub intent_id: String,
183}
184
185/// `start_attempt`: forks the repo for the agent and returns the attempt to
186/// push to. Returns `Outcome<Attempt>`.
187#[derive(Debug, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct StartAttemptArgs {
190 pub actor: User,
191 pub intent_id: String,
192 pub agent: String,
193 pub runtime: AttemptRuntime,
194}
195
196/// `get_attempt` and `read_session`.
197#[derive(Debug, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct AttemptViewArgs {
200 pub attempt_id: String,
201 pub viewer: Viewer,
202 /// For `read_session`: only entries after this sequence number.
203 #[serde(default)]
204 pub after_seq: u32,
205}
206
207/// `submit_attempt`, `abandon_attempt` and `ship_attempt`.
208/// Each returns `Outcome<Attempt>`.
209#[derive(Debug, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct AttemptActionArgs {
212 pub actor: User,
213 pub attempt_id: String,
214 /// For `submit_attempt`: what changed and why.
215 #[serde(default)]
216 pub summary: String,
217}
218
219/// `list_active_attempts`: attempts in progress that the viewer started,
220/// newest first. Returns `Vec<AttemptDetail>`.
221#[derive(Debug, Serialize, Deserialize)]
222pub struct ViewerArgs {
223 pub viewer: Viewer,
224}
225
226/// `append_session`. Returns `Outcome<Appended>`.
227#[derive(Debug, Serialize, Deserialize)]
228#[serde(rename_all = "camelCase")]
229pub struct AppendSessionArgs {
230 pub actor: User,
231 pub attempt_id: String,
232 pub entries: Vec<NewSessionEntry>,
233}
234
235#[derive(Debug, Serialize, Deserialize)]
236pub struct Appended {
237 pub count: u32,
238}