g1t/packages/contracts/src/agents.ts
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"; | |
| 9 | import type { Pull, PullStatus, SessionEntry } from "./work"; | |
| 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. */ | |
| 80 | halted?: "budget" | "time" | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 81 | createdAt: string; |
| 82 | startedAt: string | null; | |
| 83 | finishedAt: string | null; | |
| 84 | updatedAt: string; | |
| 85 | }; | |
| 86 | ||
| 87 | export type AgentRunTicket = { runId: string; token: string }; | |
| 88 | ||
| 89 | export type OpenRunInput = { | |
| 90 | actor: User; | |
| 91 | repo: RepoPath; | |
| 92 | number?: number | null; | |
| 93 | pullId?: string | null; | |
| 94 | title?: string | null; | |
| 95 | kind: RunKind; | |
| 96 | agent?: string | null; | |
| 97 | model?: string | null; | |
| 98 | sandbox: string; | |
| 99 | startedBy?: string | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 100 | /** Its caps, from its guardrails. */ |
| 101 | budgetUsd?: number | null; | |
| 102 | timeCapMinutes?: number | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 103 | }; |
| 104 | ||
| 105 | export type RunFilter = { | |
| 106 | repo?: RepoPath; | |
| 107 | workspace?: string; | |
| 108 | active?: boolean; | |
| 109 | kind?: RunKind; | |
| 110 | status?: AgentRunStatus; | |
| 111 | number?: number; | |
| 112 | limit?: number; | |
| 113 | }; | |
| 114 | ||
| 115 | export type SessionSummary = { | |
| 116 | number: number; | |
| 117 | title: string; | |
| 118 | status: PullStatus; | |
| 119 | agent: string; | |
| 120 | entries: number; | |
| 121 | tools: number; | |
| 122 | prompt: string | null; | |
| 123 | kinds: RunKind[]; | |
| 124 | runs: number; | |
| 125 | costUsd: number | null; | |
| 126 | active: boolean; | |
| 127 | startedAt: string; | |
| 128 | lastAt: string; | |
| 129 | }; | |
| 130 | ||
| 131 | export type SessionFilter = { kind?: RunKind; outcome?: PullStatus; number?: number }; | |
| 132 | ||
| 133 | export type SessionView = { | |
| 134 | pull: Pull; | |
| 135 | entries: SessionEntry[]; | |
| 136 | runs: AgentRun[]; | |
| 137 | costUsd: number | null; | |
| 138 | }; | |
| 139 | ||
| 140 | export type MemoryScope = "project" | "workspace"; | |
| 141 | export type MemoryKind = "fact" | "convention" | "decision" | "gotcha"; | |
| 142 | export const MEMORY_KINDS: MemoryKind[] = ["fact", "convention", "decision", "gotcha"]; | |
| 143 | ||
| 144 | export type MemorySource = { | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 145 | /** `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 | 146 | kind: string; |
| 147 | runId: string | null; | |
| 148 | repo: RepoPath | null; | |
| 149 | number: number | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 150 | /** What it was captured from: `run:<id>`, `comment:<id>`, `pull:<repo id>#<n>`, `doc:<repo id>:<path>`. */ |
| 151 | reference?: string | null; | |
| 152 | /** What it was learned from, quoted. */ | |
| 153 | evidence?: string | null; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 154 | }; |
| 155 | ||
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 156 | /** Kept memories are given to agents; candidates wait for review; dismissed ones are never suggested again. */ |
| 157 | export type MemoryStatus = "candidate" | "kept" | "dismissed"; | |
| 158 | ||
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 159 | export type Memory = { |
| 160 | id: string; | |
| 161 | scope: MemoryScope; | |
| 162 | workspace: string; | |
| 163 | repo: RepoPath | null; | |
| 164 | text: string; | |
| 165 | kind: MemoryKind; | |
| 166 | source: MemorySource; | |
| 167 | createdBy: string; | |
| 168 | pinned: boolean; | |
| 169 | createdAt: string; | |
| 170 | updatedAt: string; | |
| 171 | lastUsedAt: string | null; | |
| Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API | 172 | /** Absent from services that predate capture: `kept`. */ |
| 173 | status?: MemoryStatus; | |
| 174 | /** 0 to 1: how sure its source was. Null for what people wrote. */ | |
| 175 | confidence?: number | null; | |
| 176 | /** How many independent sources said it. */ | |
| 177 | seen?: number; | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 178 | }; |
| 179 | ||
| 180 | export type Memories = { project: Memory[]; workspace: Memory[] }; | |
| 181 | ||
| 182 | export type NewMemory = { | |
| 183 | scope: MemoryScope; | |
| 184 | repo?: RepoPath | null; | |
| 185 | text: string; | |
| 186 | kind?: MemoryKind; | |
| 187 | pinned?: boolean; | |
| 188 | }; | |
| 189 | ||
| 190 | export type MemoryChange = { text?: string; kind?: MemoryKind; pinned?: boolean }; | |
| 191 | ||
| 192 | export interface AgentsApi { | |
| 193 | /** For the runner: records a sandbox it is starting. */ | |
| 194 | openRun(input: OpenRunInput): Promise<Result<AgentRunTicket>>; | |
| 195 | /** For the runner: ends a run when its sandbox stops. Refused once it has ended. */ | |
| 196 | closeRun(runId: string, token: string, outcome: "succeeded" | "failed", error?: string): Promise<Result<AgentRunStatus>>; | |
| 197 | /** Marks a run stopped and says where its sandbox is. Members only. */ | |
| 198 | stopRun(actor: User, repo: RepoPath, id: string): Promise<Result<{ run: AgentRun; sandbox: string }>>; | |
| 199 | listRuns(viewer: Viewer, filter: RunFilter): Promise<Result<AgentRun[]>>; | |
| 200 | getRun(viewer: Viewer, repo: RepoPath, id: string): Promise<Result<AgentRun>>; | |
| 201 | listSessions(viewer: Viewer, repo: RepoPath, filter?: SessionFilter): Promise<Result<SessionSummary[]>>; | |
| 202 | getSession(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<SessionView>>; | |
| 203 | listMemories(viewer: Viewer, workspace: string, repo?: RepoPath | null): Promise<Result<Memories>>; | |
| 204 | addMemory(actor: User, workspace: string, memory: NewMemory): Promise<Result<Memory>>; | |
| 205 | updateMemory(actor: User, workspace: string, id: string, change: MemoryChange): Promise<Result<Memory>>; | |
| 206 | deleteMemory(actor: User, workspace: string, id: string): Promise<Result<boolean>>; | |
| 207 | /** For the runner: what to tell an agent starting in `repo`, marked used. */ | |
| 208 | memoryContext(repo: RepoPath, budget?: number): Promise<{ text: string | null; count: number }>; | |
| 209 | } | |
| 210 | ||
| 211 | /** The agents and memory methods of the work service. */ | |
| 212 | export function agentsClient(service: ServiceBinding): AgentsApi { | |
| 213 | const call = async <T>(method: string, args: object): Promise<T> => { | |
| 214 | const response = await service.fetch(`https://service/rpc/${method}`, { | |
| 215 | method: "POST", | |
| 216 | headers: { "content-type": "application/json" }, | |
| 217 | body: JSON.stringify(args), | |
| 218 | }); | |
| 219 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); | |
| 220 | return (await response.json()) as T; | |
| 221 | }; | |
| 222 | return { | |
| 223 | openRun: (input) => call("open_run", input), | |
| 224 | closeRun: (runId, token, outcome, error) => call("report_run", { runId, token, outcome, error }), | |
| 225 | stopRun: (actor, repo, id) => call("stop_run", { actor, repo, id }), | |
| 226 | listRuns: (viewer, filter) => call("list_runs", { viewer, ...filter }), | |
| 227 | getRun: (viewer, repo, id) => call("get_run", { viewer, repo, id }), | |
| 228 | listSessions: (viewer, repo, filter = {}) => call("list_sessions", { viewer, repo, ...filter }), | |
| 229 | getSession: (viewer, repo, number) => call("get_session", { viewer, repo, number }), | |
| 230 | listMemories: (viewer, workspace, repo) => call("list_memories", { viewer, workspace, repo: repo ?? null }), | |
| 231 | addMemory: (actor, workspace, memory) => | |
| 232 | call("add_memory", { actor, workspace, repo: memory.repo ?? null, scope: memory.scope, text: memory.text, kind: memory.kind ?? "fact", pinned: memory.pinned ?? false }), | |
| 233 | updateMemory: (actor, workspace, id, change) => call("update_memory", { actor, workspace, id, ...change }), | |
| 234 | deleteMemory: (actor, workspace, id) => call("delete_memory", { actor, workspace, id }), | |
| 235 | memoryContext: (repo, budget) => call("memory_context", { repo, budget }), | |
| 236 | }; | |
| 237 | } |