Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 1 | /** |
| 2 | * Agents at work and what they remember, kept by the work service. Mirrors | |
| 3 | * `g1t_contracts::agents`. | |
| 4 | */ | |
| 5 | import type { ServiceBinding } from "./clients"; | |
| 6 | import type { User, Viewer } from "./identity"; | |
| 7 | import type { RepoPath } from "./repos"; | |
| 8 | import type { Result } from "./result"; | |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 9 | import type { Confidence, Pull, PullStatus, SessionEntry } from "./work"; |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 10 | |
| 11 | /** The work a run does. checks, queue and mergecheck run commands, not a model. */ | |
| 12 | export type RunKind = | |
| 13 | | "implement" | |
| 14 | | "revise" | |
| 15 | | "review" | |
| 16 | | "answer" | |
| 17 | | "update" | |
| 18 | | "plan" | |
| 19 | | "checks" | |
| 20 | | "queue" | |
| 21 | | "mergecheck"; | |
| 22 | ||
| 23 | export const RUN_KINDS: RunKind[] = ["implement", "revise", "review", "answer", "update", "plan", "checks", "queue", "mergecheck"]; | |
| 24 | ||
| 25 | /** How a kind of run reads in a sentence: "is implementing", "a review run". */ | |
| 26 | export const RUN_KIND_LABEL: Record<RunKind, string> = { | |
| 27 | implement: "Implementing", | |
| 28 | revise: "Revising", | |
| 29 | review: "Reviewing", | |
| 30 | answer: "Answering", | |
| 31 | update: "Catching up", | |
| 32 | plan: "Planning", | |
| 33 | checks: "Checks", | |
| 34 | queue: "Merge queue", | |
| 35 | mergecheck: "Merge check", | |
| 36 | }; | |
| 37 | ||
| 38 | /** Whether a message reaches the agent while it runs, at its next step. */ | |
| 39 | export function takesMessages(kind: RunKind): boolean { | |
| 40 | return kind === "implement" || kind === "revise" || kind === "answer"; | |
| 41 | } | |
| 42 | ||
| 43 | export function isAgentKind(kind: RunKind): boolean { | |
| 44 | return kind !== "checks" && kind !== "queue" && kind !== "mergecheck"; | |
| 45 | } | |
| 46 | ||
| 47 | export type AgentRunStatus = "queued" | "running" | "succeeded" | "failed" | "stopped"; | |
| 48 | ||
| 49 | export function isActiveRun(status: AgentRunStatus): boolean { | |
| 50 | return status === "queued" || status === "running"; | |
| 51 | } | |
| 52 | ||
| 53 | export type RunStep = { at: string; text: string }; | |
| 54 | ||
| 55 | export type AgentRun = { | |
| 56 | id: string; | |
| 57 | repo: RepoPath; | |
| 58 | number: number | null; | |
| 59 | title: string | null; | |
| 60 | kind: RunKind; | |
| 61 | /** `g1t-agent`, or `g1t` for a sandbox that runs commands. */ | |
| 62 | agent: string; | |
| 63 | /** Members only. */ | |
| 64 | model: string | null; | |
| 65 | status: AgentRunStatus; | |
| 66 | step: string | null; | |
| 67 | /** Oldest first; empty in lists. */ | |
| 68 | steps: RunStep[]; | |
| 69 | stepCount: number; | |
| 70 | startedBy: string | null; | |
| 71 | error: string | null; | |
| 72 | /** Members only. */ | |
| 73 | costUsd: number | null; | |
| 74 | turns: number | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 75 | /** The most it may cost, from its guardrails. Null: no cap, or not a member. */ |
| 76 | budgetUsd?: number | null; | |
| 77 | /** The longest it may take, in minutes, from its guardrails. */ | |
| 78 | timeCapMinutes?: number | null; | |
| 79 | /** `budget` or `time` when g1t stopped it for reaching that cap. */ | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 80 | halted?: "budget" | "time" | "abuse" | null; |
| Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step | 81 | /** How sure g1t was of the change as this run left it, for a run that made or revised one. */ |
| 82 | confidence?: Confidence | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 83 | createdAt: string; |
| 84 | startedAt: string | null; | |
| 85 | finishedAt: string | null; | |
| 86 | updatedAt: string; | |
| 87 | }; | |
| 88 | ||
| 89 | export type AgentRunTicket = { runId: string; token: string }; | |
| 90 | ||
| 91 | export type OpenRunInput = { | |
| 92 | actor: User; | |
| 93 | repo: RepoPath; | |
| 94 | number?: number | null; | |
| 95 | pullId?: string | null; | |
| 96 | title?: string | null; | |
| 97 | kind: RunKind; | |
| 98 | agent?: string | null; | |
| 99 | model?: string | null; | |
| 100 | sandbox: string; | |
| 101 | startedBy?: string | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 102 | /** Its caps, from its guardrails. */ |
| 103 | budgetUsd?: number | null; | |
| 104 | timeCapMinutes?: number | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 105 | }; |
| 106 | ||
| 107 | export type RunFilter = { | |
| 108 | repo?: RepoPath; | |
| 109 | workspace?: string; | |
| 110 | active?: boolean; | |
| 111 | kind?: RunKind; | |
| 112 | status?: AgentRunStatus; | |
| 113 | number?: number; | |
| 114 | limit?: number; | |
| 115 | }; | |
| 116 | ||
| 117 | export type SessionSummary = { | |
| 118 | number: number; | |
| 119 | title: string; | |
| 120 | status: PullStatus; | |
| 121 | agent: string; | |
| 122 | entries: number; | |
| 123 | tools: number; | |
| 124 | prompt: string | null; | |
| 125 | kinds: RunKind[]; | |
| 126 | runs: number; | |
| 127 | costUsd: number | null; | |
| 128 | active: boolean; | |
| 129 | startedAt: string; | |
| 130 | lastAt: string; | |
| 131 | }; | |
| 132 | ||
| 133 | export type SessionFilter = { kind?: RunKind; outcome?: PullStatus; number?: number }; | |
| 134 | ||
| 135 | export type SessionView = { | |
| 136 | pull: Pull; | |
| 137 | entries: SessionEntry[]; | |
| 138 | runs: AgentRun[]; | |
| 139 | costUsd: number | null; | |
| 140 | }; | |
| 141 | ||
| 142 | export type MemoryScope = "project" | "workspace"; | |
| 143 | export type MemoryKind = "fact" | "convention" | "decision" | "gotcha"; | |
| 144 | export const MEMORY_KINDS: MemoryKind[] = ["fact", "convention", "decision", "gotcha"]; | |
| 145 | ||
| 146 | export type MemorySource = { | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 147 | /** `person`, `agent` or `run`; for captured memory `review`, `pr` or `doc` too (see `./context`). */ |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 148 | kind: string; |
| 149 | runId: string | null; | |
| 150 | repo: RepoPath | null; | |
| 151 | number: number | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 152 | /** What it was captured from: `run:<id>`, `comment:<id>`, `pull:<repo id>#<n>`, `doc:<repo id>:<path>`. */ |
| 153 | reference?: string | null; | |
| 154 | /** What it was learned from, quoted. */ | |
| 155 | evidence?: string | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 156 | }; |
| 157 | ||
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 158 | /** Kept memories are given to agents; candidates wait for review; dismissed ones are never suggested again. */ |
| 159 | export type MemoryStatus = "candidate" | "kept" | "dismissed"; | |
| 160 | ||
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 161 | export type Memory = { |
| 162 | id: string; | |
| 163 | scope: MemoryScope; | |
| 164 | workspace: string; | |
| 165 | repo: RepoPath | null; | |
| 166 | text: string; | |
| 167 | kind: MemoryKind; | |
| 168 | source: MemorySource; | |
| 169 | createdBy: string; | |
| 170 | pinned: boolean; | |
| 171 | createdAt: string; | |
| 172 | updatedAt: string; | |
| 173 | lastUsedAt: string | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 174 | /** Absent from services that predate capture: `kept`. */ |
| 175 | status?: MemoryStatus; | |
| 176 | /** 0 to 1: how sure its source was. Null for what people wrote. */ | |
| 177 | confidence?: number | null; | |
| 178 | /** How many independent sources said it. */ | |
| 179 | seen?: number; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 180 | }; |
| 181 | ||
| 182 | export type Memories = { project: Memory[]; workspace: Memory[] }; | |
| 183 | ||
| 184 | export type NewMemory = { | |
| 185 | scope: MemoryScope; | |
| 186 | repo?: RepoPath | null; | |
| 187 | text: string; | |
| 188 | kind?: MemoryKind; | |
| 189 | pinned?: boolean; | |
| 190 | }; | |
| 191 | ||
| 192 | export type MemoryChange = { text?: string; kind?: MemoryKind; pinned?: boolean }; | |
| 193 | ||
| 194 | export interface AgentsApi { | |
| 195 | /** For the runner: records a sandbox it is starting. */ | |
| 196 | openRun(input: OpenRunInput): Promise<Result<AgentRunTicket>>; | |
| 197 | /** For the runner: ends a run when its sandbox stops. Refused once it has ended. */ | |
| 198 | closeRun(runId: string, token: string, outcome: "succeeded" | "failed", error?: string): Promise<Result<AgentRunStatus>>; | |
| 199 | /** Marks a run stopped and says where its sandbox is. Members only. */ | |
| 200 | stopRun(actor: User, repo: RepoPath, id: string): Promise<Result<{ run: AgentRun; sandbox: string }>>; | |
| 201 | listRuns(viewer: Viewer, filter: RunFilter): Promise<Result<AgentRun[]>>; | |
| 202 | getRun(viewer: Viewer, repo: RepoPath, id: string): Promise<Result<AgentRun>>; | |
| 203 | listSessions(viewer: Viewer, repo: RepoPath, filter?: SessionFilter): Promise<Result<SessionSummary[]>>; | |
| 204 | getSession(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<SessionView>>; | |
| 205 | listMemories(viewer: Viewer, workspace: string, repo?: RepoPath | null): Promise<Result<Memories>>; | |
| 206 | addMemory(actor: User, workspace: string, memory: NewMemory): Promise<Result<Memory>>; | |
| 207 | updateMemory(actor: User, workspace: string, id: string, change: MemoryChange): Promise<Result<Memory>>; | |
| 208 | deleteMemory(actor: User, workspace: string, id: string): Promise<Result<boolean>>; | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 209 | /** |
| 210 | * For the runner: what to tell an agent starting in `repo`, marked used. | |
| 211 | * `requester` is the person the run acts for: one who is not a member of | |
| 212 | * the workspace (an outside collaborator) is given the project's memory | |
| 213 | * only, never the workspace's. | |
| 214 | */ | |
| 215 | memoryContext(repo: RepoPath, budget?: number, requester?: User | null): Promise<{ text: string | null; count: number }>; | |
| 216 | /** For the runner: agent runs the workspace has queued or running, for its plan's cap. */ | |
| 217 | activeAgents(workspace: string): Promise<number>; | |
| 218 | /** For the runner: what an issue's agents have spent; `number` may be one of its pull requests. */ | |
| 219 | issueSpend(repo: RepoPath, number: number): Promise<Result<IssueSpend>>; | |
| 220 | /** For the runner: gives back a lifecycle step it could not start for want of a slot. */ | |
| 221 | waitForSlot(pullId: string, reason: string): Promise<boolean>; | |
| 222 | /** For the runner: a comment from g1t-agent on an issue or pull request. */ | |
| 223 | agentComment(repo: RepoPath, number: number, body: string): Promise<boolean>; | |
| 224 | /** For the runner: a run a person asked for, waiting for a slot. */ | |
| 225 | addWait(workspace: string, kind: string, payload: unknown): Promise<Result<boolean>>; | |
| 226 | waitingWorkspaces(): Promise<string[]>; | |
| 227 | /** The oldest run waiting in a workspace, taken out of the queue. */ | |
| 228 | takeWait(workspace: string): Promise<AgentWait | null>; | |
| 229 | /** What a run's model cost, in dollars, with the run's token. */ | |
| 230 | runCost(runId: string, token: string): Promise<number | null>; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 231 | } |
| 232 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 233 | /** What an issue's agents have spent so far. Mirrors `IssueSpend` in agents.rs. */ |
| 234 | export type IssueSpend = { issue: number; spentMicros: number }; | |
| 235 | ||
| 236 | /** A run waiting for one of its workspace's agent slots. */ | |
| 237 | export type AgentWait = { id: string; workspace: string; kind: string; payload: unknown; createdAt: string }; | |
| 238 | ||
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 239 | /** The agents and memory methods of the work service. */ |
| 240 | export function agentsClient(service: ServiceBinding): AgentsApi { | |
| 241 | const call = async <T>(method: string, args: object): Promise<T> => { | |
| 242 | const response = await service.fetch(`https://service/rpc/${method}`, { | |
| 243 | method: "POST", | |
| 244 | headers: { "content-type": "application/json" }, | |
| 245 | body: JSON.stringify(args), | |
| 246 | }); | |
| 247 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); | |
| 248 | return (await response.json()) as T; | |
| 249 | }; | |
| 250 | return { | |
| 251 | openRun: (input) => call("open_run", input), | |
| 252 | closeRun: (runId, token, outcome, error) => call("report_run", { runId, token, outcome, error }), | |
| 253 | stopRun: (actor, repo, id) => call("stop_run", { actor, repo, id }), | |
| 254 | listRuns: (viewer, filter) => call("list_runs", { viewer, ...filter }), | |
| 255 | getRun: (viewer, repo, id) => call("get_run", { viewer, repo, id }), | |
| 256 | listSessions: (viewer, repo, filter = {}) => call("list_sessions", { viewer, repo, ...filter }), | |
| 257 | getSession: (viewer, repo, number) => call("get_session", { viewer, repo, number }), | |
| 258 | listMemories: (viewer, workspace, repo) => call("list_memories", { viewer, workspace, repo: repo ?? null }), | |
| 259 | addMemory: (actor, workspace, memory) => | |
| 260 | call("add_memory", { actor, workspace, repo: memory.repo ?? null, scope: memory.scope, text: memory.text, kind: memory.kind ?? "fact", pinned: memory.pinned ?? false }), | |
| 261 | updateMemory: (actor, workspace, id, change) => call("update_memory", { actor, workspace, id, ...change }), | |
| 262 | deleteMemory: (actor, workspace, id) => call("delete_memory", { actor, workspace, id }), | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 263 | memoryContext: (repo, budget, requester) => call("memory_context", { repo, budget, requester: requester ?? null }), |
| 264 | activeAgents: (workspace) => call("active_agents", { workspace }), | |
| 265 | issueSpend: (repo, number) => call("issue_spend", { repo, number }), | |
| 266 | waitForSlot: (pullId, reason) => call("wait_for_slot", { pullId, reason }), | |
| 267 | agentComment: (repo, number, body) => call("agent_comment", { repo, number, body }), | |
| 268 | addWait: (workspace, kind, payload) => call("add_wait", { workspace, kind, payload }), | |
| 269 | waitingWorkspaces: () => call("waiting_workspaces", {}), | |
| 270 | takeWait: (workspace) => call("take_wait", { workspace }), | |
| 271 | runCost: (runId, token) => call("run_cost", { runId, token }), | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 272 | }; |
| 273 | } |