Skip to content
226 linesCodeBlameRaw
1/**
2 * Runs, recorded sessions and memory, kept by the work service. Mirrors
3 * `g1t_contracts::agents`.
4 */
5import type { ServiceBinding } from "./clients";
6import type { User, Viewer } from "./identity";
7import type { RepoPath } from "./repos";
8import type { Result } from "./result";
9import type { Pull, PullStatus, SessionEntry } from "./work";
10
11/**
12 * The work a run does. Only checks, queue and mergecheck are started now;
13 * the others are runs of the agents g1t once ran itself, kept so older
14 * runs still read.
15 */
16export type RunKind =
17 | "implement"
18 | "revise"
19 | "review"
20 | "answer"
21 | "update"
22 | "plan"
23 | "checks"
24 | "queue"
25 | "mergecheck";
26
27export const RUN_KINDS: RunKind[] = ["implement", "revise", "review", "answer", "update", "plan", "checks", "queue", "mergecheck"];
28
29/** How a kind of run reads in a sentence: "is implementing", "a review run". */
30export const RUN_KIND_LABEL: Record<RunKind, string> = {
31 implement: "Implementing",
32 revise: "Revising",
33 review: "Reviewing",
34 answer: "Answering",
35 update: "Catching up",
36 plan: "Planning",
37 checks: "Checks",
38 queue: "Merge queue",
39 mergecheck: "Merge check",
40};
41
42export type AgentRunStatus = "queued" | "running" | "succeeded" | "failed" | "stopped";
43
44export function isActiveRun(status: AgentRunStatus): boolean {
45 return status === "queued" || status === "running";
46}
47
48export type RunStep = { at: string; text: string };
49
50export type AgentRun = {
51 id: string;
52 repo: RepoPath;
53 number: number | null;
54 title: string | null;
55 kind: RunKind;
56 /** `g1t`, for its own agent and for a sandbox that runs commands. */
57 agent: string;
58 /** Members only. */
59 model: string | null;
60 status: AgentRunStatus;
61 step: string | null;
62 /** Oldest first; empty in lists. */
63 steps: RunStep[];
64 stepCount: number;
65 startedBy: string | null;
66 error: string | null;
67 /** Members only. */
68 costUsd: number | null;
69 turns: number | null;
70 /** The most it may cost, from its guardrails. Null: no cap, or not a member. */
71 budgetUsd?: number | null;
72 /** The longest it may take, in minutes, from its guardrails. */
73 timeCapMinutes?: number | null;
74 /** `budget` or `time` when g1t stopped it for reaching that cap. */
75 halted?: "budget" | "time" | "abuse" | null;
76 createdAt: string;
77 startedAt: string | null;
78 finishedAt: string | null;
79 updatedAt: string;
80};
81
82export type AgentRunTicket = { runId: string; token: string };
83
84export type OpenRunInput = {
85 actor: User;
86 repo: RepoPath;
87 number?: number | null;
88 pullId?: string | null;
89 title?: string | null;
90 kind: RunKind;
91 agent?: string | null;
92 model?: string | null;
93 sandbox: string;
94 startedBy?: string | null;
95 /** Its caps, from its guardrails. */
96 budgetUsd?: number | null;
97 timeCapMinutes?: number | null;
98};
99
100export type RunFilter = {
101 repo?: RepoPath;
102 workspace?: string;
103 active?: boolean;
104 kind?: RunKind;
105 status?: AgentRunStatus;
106 number?: number;
107 limit?: number;
108};
109
110export type SessionSummary = {
111 number: number;
112 title: string;
113 status: PullStatus;
114 agent: string;
115 entries: number;
116 tools: number;
117 prompt: string | null;
118 kinds: RunKind[];
119 runs: number;
120 costUsd: number | null;
121 active: boolean;
122 startedAt: string;
123 lastAt: string;
124};
125
126export type SessionFilter = { kind?: RunKind; outcome?: PullStatus; number?: number };
127
128export type SessionView = {
129 pull: Pull;
130 entries: SessionEntry[];
131 runs: AgentRun[];
132 costUsd: number | null;
133};
134
135export type MemoryScope = "project" | "workspace";
136export type MemoryKind = "fact" | "convention" | "decision" | "gotcha";
137export const MEMORY_KINDS: MemoryKind[] = ["fact", "convention", "decision", "gotcha"];
138
139export type MemorySource = {
140 /** `person`, `agent` or `run`; on memory captured before capture was retired, `review`, `pr` or `doc` too. */
141 kind: string;
142 runId: string | null;
143 repo: RepoPath | null;
144 number: number | null;
145 /** What it was captured from: `run:<id>`, `comment:<id>`, `pull:<repo id>#<n>`, `doc:<repo id>:<path>`. */
146 reference?: string | null;
147 /** What it was learned from, quoted. */
148 evidence?: string | null;
149};
150
151/** Only kept memories are recalled; candidates and dismissed ones are from automatic capture, which is retired. */
152export type MemoryStatus = "candidate" | "kept" | "dismissed";
153
154export type Memory = {
155 id: string;
156 scope: MemoryScope;
157 workspace: string;
158 repo: RepoPath | null;
159 text: string;
160 kind: MemoryKind;
161 source: MemorySource;
162 createdBy: string;
163 pinned: boolean;
164 createdAt: string;
165 updatedAt: string;
166 lastUsedAt: string | null;
167 /** Absent from services that predate capture: `kept`. */
168 status?: MemoryStatus;
169 /** 0 to 1: how sure its source was. Null for what people wrote. */
170 confidence?: number | null;
171 /** How many independent sources said it. */
172 seen?: number;
173};
174
175export type Memories = { project: Memory[]; workspace: Memory[] };
176
177export type NewMemory = {
178 scope: MemoryScope;
179 repo?: RepoPath | null;
180 text: string;
181 kind?: MemoryKind;
182 pinned?: boolean;
183};
184
185export type MemoryChange = { text?: string; kind?: MemoryKind; pinned?: boolean };
186
187export interface AgentsApi {
188 /** For the runner: records a sandbox it is starting. */
189 openRun(input: OpenRunInput): Promise<Result<AgentRunTicket>>;
190 /** For the runner: ends a run when its sandbox stops. Refused once it has ended. */
191 closeRun(runId: string, token: string, outcome: "succeeded" | "failed", error?: string): Promise<Result<AgentRunStatus>>;
192 listRuns(viewer: Viewer, filter: RunFilter): Promise<Result<AgentRun[]>>;
193 getRun(viewer: Viewer, repo: RepoPath, id: string): Promise<Result<AgentRun>>;
194 listSessions(viewer: Viewer, repo: RepoPath, filter?: SessionFilter): Promise<Result<SessionSummary[]>>;
195 getSession(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<SessionView>>;
196 listMemories(viewer: Viewer, workspace: string, repo?: RepoPath | null): Promise<Result<Memories>>;
197 addMemory(actor: User, workspace: string, memory: NewMemory): Promise<Result<Memory>>;
198 updateMemory(actor: User, workspace: string, id: string, change: MemoryChange): Promise<Result<Memory>>;
199 deleteMemory(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
200}
201
202/** The runs, sessions and memory methods of the work service. */
203export function agentsClient(service: ServiceBinding): AgentsApi {
204 const call = async <T>(method: string, args: object): Promise<T> => {
205 const response = await service.fetch(`https://service/rpc/${method}`, {
206 method: "POST",
207 headers: { "content-type": "application/json" },
208 body: JSON.stringify(args),
209 });
210 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
211 return (await response.json()) as T;
212 };
213 return {
214 openRun: (input) => call("open_run", input),
215 closeRun: (runId, token, outcome, error) => call("report_run", { runId, token, outcome, error }),
216 listRuns: (viewer, filter) => call("list_runs", { viewer, ...filter }),
217 getRun: (viewer, repo, id) => call("get_run", { viewer, repo, id }),
218 listSessions: (viewer, repo, filter = {}) => call("list_sessions", { viewer, repo, ...filter }),
219 getSession: (viewer, repo, number) => call("get_session", { viewer, repo, number }),
220 listMemories: (viewer, workspace, repo) => call("list_memories", { viewer, workspace, repo: repo ?? null }),
221 addMemory: (actor, workspace, memory) =>
222 call("add_memory", { actor, workspace, repo: memory.repo ?? null, scope: memory.scope, text: memory.text, kind: memory.kind ?? "fact", pinned: memory.pinned ?? false }),
223 updateMemory: (actor, workspace, id, change) => call("update_memory", { actor, workspace, id, ...change }),
224 deleteMemory: (actor, workspace, id) => call("delete_memory", { actor, workspace, id }),
225 };
226}