Crobot documented its own architecture.
The task produced an architecture guide, a standalone SVG diagram, a Mermaid lifecycle sequence, and a README link in PR #175.
This page preserves the original task package. The public copy now includes further factual corrections. Read the accuracy findings and limits.
https://crobot-architecture-page.vercel.app — guide, diagram, downloads and review summary. Verified accessible without a login.
The final guide and diagram passed the focused local artifact review after two correction rounds. PR #175 is ready for normal repository review; direct GitHub diff verification remains unavailable to this local account.
What the live test established
The installed crobot-tasks skill created one real task, polled it, inspected its artifacts, and delivered two focused review follow-ups on the same PR. Local review caught incorrect API examples, credential descriptions and diagram labels, which Crobot revised.
The test also exposed a monitoring bug in the skill: the gateway can report running after the model finishes. The helper now checks for an idle execution session and a completed final assistant reply before permitting a follow-up. An empty session map alone is insufficient during startup. Fourteen local tests and skill validation passed.
The skill description now uses Crobot; the mistaken “Probot” spoken alias was removed. The installed skill and downloadable package include these changes.
Review evidence
| Check | Result and scope |
|---|---|
| Requested artifacts | Retrieved docs/architecture.md, docs/architecture.svg and README.md. The README links the guide; the guide includes a text alternative and a Mermaid lifecycle sequence. |
| Final corrections | Checked the permission route and valid reply values, provider API keys versus Workload Identity, stale-running monitoring caveat, correct session route labels, and shortened SVG text. All five corrections are present. |
| SVG validation | Valid XML with no scripts or external assets. Rendered locally at 1600px and inspected the complete diagram. The affected labels fit; Mount /workspace points to the task PVC. Final SVG bytes match the rendered copy. |
| Relative source links | 88 relative link occurrences across 52 distinct targets resolve against the local source snapshot, with the new SVG checked in the retrieved artifacts. This checks paths, not every anchor or a commit-pinned checkout. |
| Remote verification | Crobot reports git diff --check passing, relative links validated in its checkout, and the Mermaid lifecycle rendered successfully with Mermaid 12.0.0. These checks were not all rerun locally. |
| CI at final head | Crobot’s CI watcher reports 1 check passed, 0 pending and no failures on 55f1b4e7291d. This is gateway-reported evidence, not an independent GitHub CLI check. |
| Skill validation | 14 local tests passed, including both stale-running completion and startup-idle cases; the skill validator passed. Live create, poll and same-task follow-ups succeeded. |
| Change scope | The retrieved deliverables are documentation. Crobot reports the changes pushed on the same documentation PR. Full PR file scope remains subject to normal GitHub review because local repository access is unavailable. |
The local GitHub identity cannot resolve this private repository, and no connected browser was available. I reviewed the actual workspace files through the read-only OpenCode file API and compared relevant source locally. The final commit and CI below are reported by Crobot; I could not independently inspect the GitHub diff or confirm the workspace bytes against that commit.
Task api-ms2lt3akrcna · Branch crobot/api-ms2lt3akrcna · Base main
Reported final head: 55f1b4e7291d80216e327a3895052bada8e154aa
Observed task model cost: $5.56, excluding infrastructure and local orchestration. Evidence collected 2026-09-16T00:33:33.710783+00:00. No merge or deployment was requested.
Read the complete architecture guide
Crobot Architecture and System Guide
Crobot is Coframe's autonomous coding platform. It receives
development tasks from GitHub, Slack, a web dashboard, or external
orchestrators via a REST API. Each task executes within its own
hardened, isolated sandbox running on Google Kubernetes Engine (GKE)
with gVisor (runsc). Inside the sandbox, an autonomous
agent inspects code, runs local tests and builds, verifies visual
changes using a headless browser, commits focused adjustments, pushes a
git branch, and opens a GitHub Pull Request.
This document describes the current architecture of Crobot, detailing how components interact, where state lives, how authentication and trust boundaries are enforced, how the task lifecycle progresses from request to pull request, and how external agents can automate and monitor tasks programmatically.
Table of Contents
- System Architecture Overview
- Component Responsibilities
- Architecture Diagram
- Task Lifecycle and Execution Flow
- Task Identity, Sessions, and State Persistence
- Authentication, Security, and Trust Boundaries
- External-Agent API
Workflow (
/api/v1) - Operational Resilience and Failure Recovery
- Source Code Map
System Architecture Overview
Crobot is organized into two primary tiers:
- Centralized Gateway (
gateway/): A Node.js/TypeScript service built on Hono that acts as the ingress controller, API gateway, task orchestrator, event relay, and Kubernetes controller. The gateway runs as thecrobot-gatewaydeployment and service inside thecrobotnamespace (configured byGATEWAY_NAMESPACE, defaulting tocrobotingateway/src/config.ts). The gateway handles webhooks, authenticates callers, tracks usage and infrastructure costs, coordinates pull request reviews and CI checks, and manages the lifecycle of sandbox pods and volumes. - Ephemeral Execution Sandboxes
(
sandbox/): Per-task, isolated container environments executing under the gVisor (runsc) container runtime inside thecrobot-tasksnamespace. Each sandbox mounts a dedicated Kubernetes PersistentVolumeClaim (PVC), boots an OpenCode server daemon (opencode serve), syncs team knowledge and secrets, and executes shell tools, compilers, test suites, git commands, and browser QA scripts.
The gateway does not execute arbitrary user repository code or compilers itself; all repository modifications, test runs, and git pushes take place exclusively within the untrusted execution sandboxes. Conversely, sandboxes do not hold long-lived GitHub App private keys or Jarvis admin credentials; they request short-lived credentials on demand from the gateway over an internal cluster network.
Component Responsibilities
Entry Points and Ingress
Crobot accepts work from four main ingress surfaces, all routed
through the gateway (gateway/src/routes.ts):
- GitHub App Webhooks
(
POST /webhooks/github): Validates HMAC-SHA256 signatures (x-hub-signature-256) againstGITHUB_WEBHOOK_SECRET. Handles issue mentions, comment triggers (@coframe), pull request review feedback, andcheck_runfailures (gateway/src/github.ts,gateway/src/pr-feedback.ts,gateway/src/ci-fix.ts). - Slack Events & Interactivity
(
POST /webhooks/slack,POST /webhooks/slack/interactions): Validates Slack signatures (x-slack-signature) againstSLACK_SIGNING_SECRET. Supports direct messages, channel thread mentions, assistant thread view, and Block Kit interactive repository pickers (gateway/src/slack.ts,gateway/src/slack-installs.ts). - Web UI (
ui/): A React/Vite single-page application served by the gateway. Allows team members and customers to initiate tasks, view real-time streaming tool outputs, answer pending questions, inspect usage and cost ledgers, and manage repository settings (ui/src/App.tsx,ui/src/CrobotPage.tsx). - Programmatic API (
/api/v1/*): An authenticated REST API designed for automated pipelines, external scripts, and agent orchestrators (gateway/src/routes.ts).
Gateway Control Plane
The gateway runs as the crobot-gateway service in the
crobot namespace and coordinates task execution across
several modular controllers:
- Task Service (
gateway/src/tasks.ts): Manages Kubernetes resources for tasks (PVC, Secret, Job) in thecrobot-tasksnamespace. Implementscreate,ensureRunning,sendPrompt,stop,reap, and volume archival/revival. - Standby Pool (
gateway/src/standby.ts): Maintains a pool of pre-warmed sandbox pods for frequently used repositories. Standbys pre-clone the default branch and boot OpenCode ahead of time, allowing new tasks to start in seconds rather than waiting for cold pod scheduling and repository cloning. - Relay Event Loop (
gateway/src/relay.ts): Connects to the sandbox's OpenCode/eventSSE stream. Tracks turn activity, captures live token and dollar costs, extracts pull request URLs from model replies, delivers interim progress messages to Slack/GitHub, handles model retry loops, and dispatches completion webhooks. - Pull Request & CI Automation (
gateway/src/pulls.ts,gateway/src/pr-feedback.ts,gateway/src/ci-watch.ts,gateway/src/ci-fix.ts): Discovers pull requests opened by sandboxes, injects session link footers into PR descriptions, batches review comments into follow-up turns, polls CI check status, and prompts sandboxes to fix failing CI checks when permitted by repository settings. - Settings, Knowledge & Guides (
gateway/src/settings.ts,gateway/src/guides.ts): Stores team knowledge notes, repository-specific environment setup/maintenance commands, and Coframe guide playbooks in Kubernetes ConfigMaps in namespacecrobot. - Usage & Cost Ledger (
gateway/src/usage.ts,gateway/src/infra.ts): Aggregates AI token usage, model costs, and GCP node/disk infrastructure hours into monthly ConfigMaps.
Execution Sandboxes
Sandboxes execute in the crobot-tasks namespace under
GKE Standard:
- Container Environment (
sandbox/Dockerfile,sandbox/entrypoint.sh): Built on Debian with Node.js, Python, git, build essentials, and headless Google Chrome for Testing. Sandboxes run as non-root (uid: 1000,crobot). - OpenCode Daemon (
opencode serve): Listens on port 4096 inside the pod, protected by a per-task password. It exposes REST and SSE endpoints for session management, tool invocation, and event streaming. - Sandbox Helper Shims (
sandbox/bin/):gh: Wrapper around the real GitHub CLI that requests a fresh, short-lived token from the gateway viacrobot-github-token.git-credential-crobot: Git credential helper providing GitHub tokens dynamically forgit fetchandgit push.crobot-sync: Fetches repository knowledge, team secrets, and environment commands fromGET /internal/sandboxes/:name/context.crobot-screenshot&crobot-video: Uploads browser verification artifacts to the gateway blob store and outputs formatted Markdown tables and GIF previews.crobot-ci: Queries the gateway to print CI failure annotations and logs for the active PR.crobot-suggest-knowledge: Submits proposed knowledge notes back to the gateway for team approval.
- Browser QA Tooling: Employs
agent-browser(Vercel agent browser CLI) to drive headless Chrome under gVisor. Sandboxes record WebM video walkthroughs via ffmpeg, whichcrobot-videotrims and optimizes into inline GIF previews.
Persistence and Storage
Crobot utilizes Kubernetes-native primitives in namespace
crobot / crobot-tasks and Google Cloud Storage
(GCS) for persistence:
- Task Disks (PVCs): Each active task owns a 20Gi
standard-rwoPersistentVolumeClaim in namespacecrobot-tasksformatted with ext4. The PVC is mounted at/workspaceand stores git repositories, package caches (/workspace/.cache), and OpenCode session databases (/workspace/.opencode-data). - Task Secrets: An
OpaqueKubernetes Secret in namespacecrobot-tasksholds the OpenCode HTTP basic auth password (OPENCODE_SERVER_PASSWORD), the internal gateway communication token (CROBOT_TASK_TOKEN), and optional user delegation keys (CROBOT_JARVIS_KEY). - Archived Tasks (
TaskArchive,gateway/src/task-archive.ts): When a task's PVC is released to conserve disk quota, task metadata (annotations, status, PR URLs, token totals) is preserved in a ConfigMap in namespacecrobotlabeledcrobot/archived-task: <taskId>. - Archived Transcripts (
TranscriptArchive,gateway/src/transcripts.ts): Session message histories are compressed (gzip) and chunked across ConfigMaps namedcrobot-transcript-<taskId>[-<chunk>]in namespacecrobot. Tool outputs and text fields are safely trimmed to fit within Kubernetes object size boundaries. - Blob Storage (
gateway/src/blobs.ts): Large attachments, screenshots, and video recordings are stored in a GCS bucket (or ConfigMap fallback) and served publicly via random 128-bit unguessable URLs (/shots/<taskId>/<random>.<ext>).
External Services
- GitHub: Source code repositories, webhooks, pull
requests, commit checks, and Actions job logs. Authenticated via a
GitHub App (
coframe[bot]) in App mode, or via personal access token (GITHUB_TOKEN) in token mode. - Slack: Incoming mentions, interactive pickers, and live status updates via the Slack Events and Web APIs.
- Jarvis (Coframe Core): Authenticates client apps, resolves user organizations and roles, provides Coframe project mappings, and mints acting-user credentials.
- LLM Providers:
- Provider API keys such as
OPENROUTER_API_KEY,ANTHROPIC_API_KEYandOPENAI_API_KEYenter throughcrobot-provider-keys. Vertex AI authenticates via the sandbox service account's Workload Identity and the GKE metadata server. - The active default and allowed models are discovered at runtime via
GET /api/v1/models. While source code defaults fall back togoogle-vertex/gemini-3.8-flashingateway/src/config.ts, live deployments configure defaults dynamically via environment variables (MODEL_DEFAULT).
- Provider API keys such as
Architecture Diagram
The standalone architecture overview diagram is located at docs/architecture.svg:
Text Alternative and Component Flow
- Ingress: External callers (GitHub Webhooks, Slack
API, Web UI, Programmatic API
/api/v1) send requests to the Gateway in namespacecrobot. - Authentication & Validation: The Gateway verifies signatures (HMAC-SHA256 for GitHub and Slack) or tokens (Jarvis JWT, Jarvis API key, Crobot API key) and validates organization scope against Jarvis.
- Orchestration: The Gateway TaskService either
claims an existing ready sandbox from the Standby Pool or provisions a
new Kubernetes PVC, Secret, and Job in namespace
crobot-tasks. The task creation endpoint returns201 Createdasynchronously. - Execution Startup:
- The Sandbox container boots under gVisor (
runsc), mounts/workspacefrom the PVC, runs initialcrobot-sync, and startsopencode serveon port 4096. - The Gateway detects the sandbox is healthy, calls OpenCode to create
the session, and uses the OpenCode shell endpoint to fetch origin, check
out the task working branch
crobot/<taskId>, and run repository maintenance commands.
- The Sandbox container boots under gVisor (
- Relay & Control: The Gateway Relay connects to
the sandbox's
/eventSSE stream, sends the user prompt, streams live progress, updates Slack/GitHub status lines, and monitors token costs. - Tool Operations:
- Git operations call
git-credential-crobot-> gateway/internal/sandboxes/:name/github-tokento obtain short-lived repository tokens. - Browser QA runs via
agent-browserand captures screenshots/video sent to gateway/internal/sandboxes/:name/screenshots, which stores them in GCS. - The agent creates a pull request via
gh pr create.
- Git operations call
- Completion & Archival: The Relay detects the PR
URL, injects session link footers, annotates the task as
idle, saves compressed transcripts to ConfigMaps in namespacecrobot, and releases the PVC after retention expiry, moving metadata toTaskArchive.
Task Lifecycle and Execution Flow
Persisted Task Statuses
A task record (TaskRecord, defined in gateway/src/manifests.ts)
persists exactly one of five statuses:
| Status | Meaning | Sandbox State |
|---|---|---|
starting |
Pod is scheduling, cloning the repository, booting OpenCode, or executing setup checkout. | Pod starting / initializing |
running |
OpenCode is actively processing a turn (reasoning, tool execution, or waiting for an answer to a question tool call). | Pod Running, OpenCode active |
idle |
No active turn executing in OpenCode.
Either the turn completed normally, or the turn was explicitly stopped
via POST /stop (statusDetail contains
"Stopped by..."). |
Pod may be awake (running) or asleep (Job reaped) |
failed |
Unrecoverable error (setup failure, node capacity timeout, repeated pod crash). | Job stopped / terminated |
archived |
Volume was released to save disk quota;
record lives in ConfigMap TaskArchive. |
No pod, no PVC (resumable) |
In addition to status, the task detail endpoint
(GET /api/v1/tasks/:id) returns
running: boolean, which indicates whether an actual
Kubernetes pod currently exists and is scheduled. Thus, a task may have
status: "idle" with running: true (sandbox is
awake and warm) or status: "idle" with
running: false (sandbox is sleeping to save
memory/CPU).
When an agent invokes the question tool, the relay sets
the task's statusDetail annotation with the prefix
"Waiting for your answer". The task remains in
status: "running". This signals the UI and external callers
that input is needed and prevents the idle reaper from terminating the
sandbox pod.
End-to-End Lifecycle Sequence
sequenceDiagram
autonumber
actor Caller as Caller / User / API
participant GW as Gateway (Namespace: crobot)
participant K8s as Kubernetes (GKE / gVisor)
participant SB as Sandbox Pod (Namespace: crobot-tasks)
participant GH as GitHub API / Remote
Caller->>GW: POST /api/v1/tasks (repo, prompt)
alt Standby Sandbox Available
GW->>K8s: Claim standby PVC & Secret (adopt)
else Cold Provisioning
GW->>K8s: Create PVC (20Gi), Secret, and Job
end
GW-->>Caller: 201 Created (task record returned asynchronously)
K8s->>SB: Boot pod (gVisor runtime, uid 1000)
SB->>GW: GET /internal/sandboxes/:name/context (crobot-sync)
GW-->>SB: Knowledge notes, secrets, env scripts
SB->>SB: Start opencode serve on port 4096
GW->>SB: Poll until healthy (waitHealthy)
GW->>SB: Create Session (createSession)
GW->>SB: Run branch setup through session shell (/session/:sessionId/shell)
GW->>SB: Run repo maintenance commands (/session/:sessionId/shell)
GW->>SB: Deliver initial prompt asynchronously (/session/:sessionId/prompt_async)
loop Agent Execution Turn
SB->>GW: SSE Events (tool execution, text delta, tokens)
GW->>GW: Relay updates (cost ledger, Slack/GH status)
opt Git Push & Pull Request
SB->>GW: GET /internal/sandboxes/:name/github-token
GW-->>SB: Scoped repository token
SB->>GH: git push origin crobot/taskId
SB->>GH: gh pr create --base baseBranch
end
opt Clarification Question
SB-->>GW: question.asked event
GW->>GW: Set statusDetail: "Waiting for your answer"
Caller->>GW: POST /api/v1/tasks/:id/prompt (or question reply route)
GW->>SB: replyQuestion (structured answer)
end
end
SB-->>GW: session.idle / turn complete (reply text + PR URL)
GW->>GW: Extract PR URL, annotate task status="idle"
GW->>GH: Inject session link footer into PR description
GW->>K8s: Save compressed transcript ConfigMap
opt Webhook Configured
GW->>Caller: POST webhookUrl (event: "task.completed")
end
opt Idle Timeout (Source default: 30 min)
GW->>K8s: Delete Job (pod sleep, PVC preserved)
end
opt Retention Window Expired (1 day) / PR Merged
GW->>K8s: Delete PVC & Secret, move meta to TaskArchive (status="archived")
end
opt Follow-Up on Archived Task
Caller->>GW: POST /api/v1/tasks/:id/prompt
GW->>K8s: Revive: claim standby / create PVC, clone & checkout origin branch
GW->>SB: New session + resume preface quoting archived transcript
end
Task Identity, Sessions, and State Persistence
Task IDs vs. Session IDs
- Task ID (
taskId): The immutable identifier representing the entire unit of work from request to pull request resolution. Generated at inception (gateway/src/taskId.ts):- API requests:
api-<12 alnum chars>(e.g.,api-ms2lt3akrcna) - UI requests:
ui-<12 alnum chars> - GitHub issues/PRs:
gh-<owner>-<repo>-i<number>orgh-<owner>-<repo>-pr<number> - Slack threads:
slack-<channel>-<timestamp>Task IDs are attached to Kubernetes labels (crobot/task: <id>), annotations, transcript ConfigMaps, and blob paths.
- API requests:
- OpenCode Session ID (
sessionId): An internal session identifier generated by theopencodedaemon within the sandbox pod. A single task maintains one primary OpenCode session per sandbox lifetime. If a sandbox volume is released and later revived, a new OpenCode session ID is created on the fresh sandbox.
Sub-Agent Hierarchy
When the primary agent uses the task tool to spawn
sub-agents (e.g., for codebase exploration or parallel research),
OpenCode creates child sessions with parentID set to the
primary session ID.
- The Gateway Relay discovers descendant sessions via
gateway/src/opencode.ts. - Token usage and financial costs across all descendant sessions are
aggregated into the root task's total (
gateway/src/usage.ts). - Sub-agent transcripts are captured alongside the main transcript in
ConfigMap storage (
gateway/src/transcripts.ts) and can be viewed viaGET /api/v1/tasks/:id/subagents/:sessionId.
Persistence Across Sleep, Release, and Revival
| Resource / State | Active Turn (running) |
Sleeping (idle, pod
reaped) |
Archived (archived, volume
released) |
Revived (Resumed from archive) |
|---|---|---|---|---|
| Kubernetes Job / Pod | Running on gVisor node | Deleted | Deleted | New Job / Pod provisioned |
PVC (Disk
/workspace) |
Mounted (20Gi) | Preserved | Deleted | New 20Gi disk created / claimed |
| Unpushed Git Commits | On disk | On disk | Lost | Lost (starts from remote git branch) |
| Pushed Git Branch | On GitHub origin | On GitHub origin | On GitHub origin | Fetched and checked out
(origin/crobot/<id>) |
| OpenCode Session DB | /workspace/.opencode-data |
/workspace/.opencode-data |
Destroyed | New Session ID generated |
| Transcript History | In OpenCode memory/disk | ConfigMap
crobot-transcript-* |
ConfigMap
crobot-transcript-* |
Quoted back into prompt via resume preface |
| Task Metadata | PVC annotations | PVC annotations | ConfigMap TaskArchive |
Restored to new PVC annotations |
Authentication, Security, and Trust Boundaries
[External Callers / Webhooks]
|
HTTPS / Ingress
v
+-----------------------------------------------------------+
| Gateway Control Plane (crobot namespace) |
| - Validates JWT / HMAC / API Keys |
| - Holds GitHub App Private Key & Master Secrets |
| - Mints scoped repository tokens for sandboxes |
+-----------------------------------------------------------+
| Internal Cluster Network (HTTP / Port 4096)
| Auth: Basic (OPENCODE_SERVER_PASSWORD) / Bearer (CROBOT_TASK_TOKEN)
v
+-----------------------------------------------------------+
| Sandbox Pod (crobot-tasks namespace, gVisor runsc) |
| - Security: runAsNonRoot (uid 1000), 16Gi ephemeral layer|
| - NetworkPolicy: Public egress (Cloud NAT), DNS, Gateway |
| - Provider Secrets: envFrom crobot-provider-keys |
| - Workspace: /workspace (20Gi ext4 PVC) |
+-----------------------------------------------------------+
Ingress Authentication and Scoping
Callers authenticate to the Gateway via one of three methods:
- Jarvis JWT: Verified using
JARVIS_JWT_SECRET(HS256). - Jarvis API Key (
jrv_...): Validated upstream against Jarvis/api/auth/me. - Crobot API Key: Configured in gateway environment
(
CROBOT_API_KEY/CROBOT_API_KEYS). Grants administrative API access. The headerX-Crobot-User: user@coframe.comis honored specifically when authenticating via a configured Crobot API key to attribute task ownership. It does not allow arbitrary user impersonation when using standard user tokens.
Organization Scoping & Multi-Tenancy:
- Non-team callers must supply an
x-crobot-orgheader specifying their active Jarvis organization. The gateway verifies membership and role against Jarvis (admin,editor,approver,viewer). - Callers may only access repositories connected to their organization.
- Customer Redaction: For non-team viewers, the
gateway strips internal cost numbers, token usage, model names, and
provider details from task records, transcripts, and model lists (
gateway/src/routes.ts).
Sandbox Hardening and Isolation
Sandboxes run untrusted code and execute user commands. They are isolated using defense-in-depth:
- gVisor Runtime
(
runtimeClassName: "gvisor"): Intercepts and virtualizes Linux kernel syscalls in userspace (runsc), mitigating container breakout vulnerabilities. - Unprivileged Execution: Pod security context
mandates
runAsNonRoot: true,runAsUser: 1000,runAsGroup: 1000,fsGroup: 1000. - Ephemeral Storage Budget: The container writable
layer has an explicit request and limit of 16Gi ephemeral storage (
gateway/src/manifests.ts). Package manager caches are directed to/workspace/.cacheon the persistent disk to prevent ephemeral exhaustion. - NetworkPolicy (
infra/k8s/base/sandbox-networkpolicy.yaml):- Restricts ingress to port 4096 from the
crobotnamespace only. - Permits egress to UDP/TCP port 53 (cluster DNS and NodeLocal DNSCache).
- Permits egress to
169.254.169.254/32(GKE metadata server for Workload Identity tokens). - Permits egress to port 8080 on pods in namespace
crobot(the gateway internal service). - Permits egress to the public internet (
0.0.0.0/0via Cloud NAT), explicitly excluding private RFC 1918 networks, CGNAT ranges, and the cluster Services CIDR.
- Restricts ingress to port 4096 from the
Credential Delegation and Token Isolation
- Provider Keys: Provider API keys such as
OPENROUTER_API_KEY,ANTHROPIC_API_KEYandOPENAI_API_KEYenter throughcrobot-provider-keys(envFrom: [{ secretRef: { name: "crobot-provider-keys" } }]). Vertex AI authenticates via the sandbox service account's Workload Identity and the GKE metadata server. - Jarvis Delegation Isolation: When a user runs a
task from Jarvis, a personal delegation key
(
CROBOT_JARVIS_KEY) is stored in the task's Secret for the gateway to act as that user against Jarvis Core (e.g., creating Coframe metrics). This key is never injected into the sandbox pod's container environment; only the gateway reads it. - GitHub Tokens: Sandboxes do not store GitHub App
private keys. The sandbox CLI shims query the gateway at
GET /internal/sandboxes/:name/github-tokenusingCROBOT_TASK_TOKEN. In GitHub App mode, the gateway mints a short-lived GitHub App installation access token scoped strictly to the task's repositories, expiring in 1 hour. If the gateway is configured in token mode (GITHUB_AUTH_MODE=token), the gateway returns the configuredGITHUB_TOKEN.
External-Agent API Workflow
(/api/v1)
The programmatic API allows external agents (such as orchestrator agents or CI runners) to manage Crobot tasks end-to-end.
Authentication
Pass a personal Jarvis API key or a configured Crobot API key via
Authorization: Bearer or X-API-Key:
export API_KEY="jrv_your_personal_jarvis_key"
export CROBOT_BASE_URL="https://crobot.coframe.com"
Note: If using a shared CROBOT_API_KEY, you can
attribute task creation to a specific email using
X-Crobot-User: user@coframe.com.
Task Creation
Submit a new task via POST /api/v1/tasks. The endpoint
creates the Kubernetes record and returns 201 Created
immediately while execution starts in the background:
curl -s -X POST "$CROBOT_BASE_URL/api/v1/tasks" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"repo": "Coframe/jarvis",
"prompt": "Fix null pointer in user profile component and verify test suite",
"baseBranch": "main",
"webhookUrl": "https://my-service.com/api/crobot-webhook"
}'
Response (201 Created):
{
"id": "api-ms2lt3akrcna",
"source": "api",
"repo": "Coframe/jarvis",
"baseBranch": "main",
"title": "Fix null pointer in user profile component",
"status": "starting",
"createdAt": "2026-09-15T12:00:00.000Z",
"taskUrl": "https://crobot.coframe.com/tasks/api-ms2lt3akrcna"
}
(Note: running is omitted in the creation
response).
Polling and Status Inspection
Inspect task status via GET /api/v1/tasks/:id. The
detail endpoint adds running: boolean to indicate whether a
sandbox pod is alive:
curl -s "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna" \
-H "Authorization: Bearer $API_KEY"
Response (200 OK):
{
"id": "api-ms2lt3akrcna",
"status": "idle",
"running": true,
"prUrl": "https://github.com/Coframe/jarvis/pull/2450",
"prs": [
{
"repo": "Coframe/jarvis",
"number": 2450,
"url": "https://github.com/Coframe/jarvis/pull/2450"
}
],
"cost": 0.42,
"tokens": {
"input": 12500,
"output": 3200,
"reasoning": 850,
"cacheRead": 4000,
"cacheWrite": 0
},
"lastActive": "2026-09-15T12:08:30.000Z"
}
Note: If statusDetail starts with
"Waiting for your answer", the agent is waiting on human
input while status remains "running".
Monitoring Caveat: The persisted running annotation can be
stale after a completed turn. An external controller can refine it with
/opencode/session/status and the latest session messages.
An idle session-status map plus a completed final assistant reply is
evidence that the turn ended; an empty map alone can occur during
startup. A pending question/permission still takes precedence.
Event Streaming (SSE)
For live real-time observation, external agents can stream execution events via Server-Sent Events (SSE):
curl -N "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/events" \
-H "Authorization: Bearer $API_KEY"
Note: The sandbox pod must be running. If the sandbox is
sleeping, this endpoint returns 409 Conflict.
Inspecting the Transcript
Read the compressed transcript snapshot of the last completed turn:
curl -s "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/transcript" \
-H "Authorization: Bearer $API_KEY"
Note: This endpoint serves a possibly trimmed snapshot
archived at turn completion. If called during the very first turn before
an archive exists, it returns 404 Not Found.
Answering Questions and Sending Follow-Ups
Simple Prompt Answer: Sending a text prompt to
/promptwill automatically apply that text to all currently pending questions on the task:curl -s -X POST "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/prompt" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Use option A and continue the implementation."}'Structured Answers for Distinct Questions: When multiple questions are pending and need distinct answers, interact with OpenCode via the reverse proxy:
- Query pending questions:
curl -s "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/opencode/question" \ -H "Authorization: Bearer $API_KEY" - Reply with distinct answers:
curl -s -X POST "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/opencode/question/<requestId>/reply" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"answers": [["First answer"], ["Second answer"]]}' - Permission requests are handled separately via the task proxy (
ui/src/opencode.ts):- Query pending permissions:
curl -s "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/opencode/api/session/<sessionId>/permission" \ -H "Authorization: Bearer $API_KEY" - Reply to a permission request (valid values for
replyare"once","always", or"reject"):curl -s -X POST "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/opencode/api/session/<sessionId>/permission/<requestId>/reply" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"reply": "once"}'
- Query pending permissions:
- Query pending questions:
Stopping a Task
Interrupt an active turn without destroying the volume:
curl -s -X POST "$CROBOT_BASE_URL/api/v1/tasks/api-ms2lt3akrcna/stop" \
-H "Authorization: Bearer $API_KEY"
Client-Side Filtering and Acceptance Criteria
- Task Retrieval & Filtering: The
GET /api/v1/tasksendpoint does not support cursor-based pagination. It acceptsrepo,status,source,experiment, andlimit. Note thatlimitis evaluated on the server before client-side filtering. External agents needing to inspect their own tasks should retrieve without a narrow limit, filter by repository, or look up known task IDs directly. - Independent Acceptance Verification: A task state
of
status: "idle"combined with aprUrlindicates that the agent finished its turn and opened a PR. It does not guarantee that the solution passes all user acceptance criteria or tests. Orchestrators must independently inspect the PR diff, evaluate CI check results, and verify functionality.
Operational Resilience and Failure Recovery
Cluster Capacity and Slot Waits
- When nodes in the GKE cluster are fully occupied, a new sandbox pod
cannot schedule (
PodScheduled=False). - The gateway detects this condition (
gateway/src/tasks.ts) and updatesstatusDetailto:"Waiting for a sandbox slot: every node is full (N min so far)". - Standby Eviction: After 30 seconds of waiting, the
gateway instructs the standby pool (
gateway/src/standby.ts) to retire an idle pre-warmed standby (StandbyPool.makeRoom) to free node capacity. - The task waits up to
capacityWaitMs(default 60 minutes) for a node slot before failing. The initial prompt text is preserved in the PVC annotationcrobot/requestand dispatched once the pod starts.
Pod Losses, Evictions, and OOM
- If a pod crashes due to out-of-memory or node eviction, the
Kubernetes Job automatically retries (up to
backoffLimit: 3). - All package manager caches (npm, uv, pip, Playwright) are placed on
the persistent volume at
/workspace/.cacheto prevent exhausting the container's 16Gi ephemeral storage. - The gateway monitors pod loss reasons via Kubernetes status
(
podLoss). It counts losses incrobot/pod-losses. If a pod fails twice in a single turn (MAX_POD_LOSSES = 2), the task fails with the exact diagnostic message rather than continuing to loop. - Upon pod recovery, the relay replays the request with
RESUME_PREFACE, informing the agent that its sandbox was restarted.
Model Provider Failures and Fallbacks
- If a model call fails due to rate limits (429) or transient provider
outages (500, 502, 503, socket timeouts),
gateway/src/relay.tsretries the turn after exponential delays: 30s, 90s, and 180s. - If the primary provider remains unavailable after retries, the relay
automatically swaps the model to the next allowed fallback in
MODEL_FALLBACKS. Available models should be queried fromGET /api/v1/models.
Poisoned Thought Signatures
- Gemini models require function-call thought signatures to be echoed
back verbatim. Occasionally, third-party proxies mangle
reasoning_details, causing subsequent turns to fail permanently with HTTP 400 (Corrupted thought signature). - The relay classifies this as a poisoned conversation. It
calculates the
earliestBrokenRequestacross the transcript, rolls back the corrupted turn, and replays execution on Vertex AI.
Stalled Sub-Agents
- If a sub-agent session becomes stuck (e.g., waiting for interactive
input or hanging on an external API), the idle reaper aborts
sub-sessions that have made no progress for
stallMinutes(default 45 minutes). The parent agent receives an abort error and proceeds with the rest of its task.
Idle Reaping vs. Question Retention
- Pods idle longer than
idleMinutes(source default 30 minutes ingateway/src/config.ts, configurable per environment) have their Kubernetes Job deleted to free CPU/RAM. The PVC and disk contents remain intact. - Question Exception: If an agent asks a question
(
statusDetailbegins with"Waiting for your answer"), the pod is kept alive for up to 6 hours (WAITING_MAX_MS = 6 * 3600_000), allowing humans ample time to answer without incurring a cold boot on resumption.
Source Code Map
| Functional Area | Source Files | Responsibility |
|---|---|---|
| API & Routing | gateway/src/routes.tsgateway/src/index.tsgateway/src/html.ts |
HTTP server (Hono), REST API endpoints
(/api/v1), webhooks, static UI serving, reverse proxy to
OpenCode. |
| Task Lifecycle | gateway/src/tasks.tsgateway/src/manifests.tsgateway/src/taskId.ts |
K8s resource management in
crobot-tasks (PVC/Secret/Job), task creation, status
transitions, question answering, pod reaping. |
| Standby Pool | gateway/src/standby.ts |
Pre-warmed sandbox pool management, pre-cloning repositories, standby claim and capacity eviction. |
| Event Relay & Loop | gateway/src/relay.tsgateway/src/opencode.tsgateway/src/titles.ts |
Consumes OpenCode SSE events, tracks live token cost, retries provider errors, extracts PR URLs, triggers webhooks. |
| Transcripts & Archives | gateway/src/transcripts.tsgateway/src/task-archive.tsgateway/src/blobs.ts |
Gzip transcript compression, ConfigMap
chunking in namespace crobot, blob storage for screenshots
and video recordings. |
| Auth & Organizations | gateway/src/auth.tsgateway/src/jarvis.tsgateway/src/orgs.ts |
Jarvis JWT and API key verification, webhook signature checks, multi-tenant organization scoping. |
| GitHub Integration | gateway/src/github.tsgateway/src/pulls.tsgateway/src/pr-link.ts |
GitHub App / PAT authentication, PR discovery, comment triggers, session link footer injection. |
| PR Feedback & CI | gateway/src/pr-feedback.tsgateway/src/ci-watch.tsgateway/src/ci-fix.tsgateway/src/pr-status.ts |
PR review comment batching, loop guard, CI status polling, automated CI fix turn execution. |
| Usage & Accounting | gateway/src/usage.tsgateway/src/infra.tsgateway/src/openrouter-billing.ts |
Monthly token and dollar usage accounting, GCP node/disk cost ledger, OpenRouter billing reconciliation. |
| Workspace Settings | gateway/src/settings.tsgateway/src/guides.tsgateway/src/learn.ts |
Knowledge note management, repository environment configs, Coframe guides, automated learning from reviews. |
| Sandbox Container | sandbox/Dockerfilesandbox/entrypoint.shsandbox/CROBOT.mdsandbox/crobot-system.md |
Docker container definition, non-root execution, 16Gi ephemeral budget, system prompt and agent instructions. |
| Sandbox CLI Tools | sandbox/bin/ghsandbox/bin/git-credential-crobotsandbox/bin/crobot-syncsandbox/bin/crobot-screenshotsandbox/bin/crobot-videosandbox/bin/crobot-ci |
Ephemeral token fetching, context synchronization, QA screenshot and video publishing, CI check inspection. |
| Frontend UI | ui/src/App.tsxui/src/CrobotPage.tsxui/src/transcript.ts |
Vite + React web interface for task creation, real-time message streaming, question answering, and admin settings. |
| Infrastructure & K8s | infra/k8s/infra/scripts/infra/cloudbuild.yaml |
Kubernetes manifests (RBAC, Ingress,
NetworkPolicy in crobot), GKE setup scripts, Cloud Build
CI/CD configurations. |
Assignment and review record
Original task brief
# Crobot self-documentation and architecture diagram Create a focused documentation pull request in Coframe/crobot describing Crobot itself as it currently works. This is a real task requested by Robert Nowell, and an end-to-end test of the external crobot-tasks skill. Use the repository's default base branch and your already prepared task branch. Do not merge or deploy. ## Reader and outcome A new engineer, or an external agent managing Crobot through its API, should be able to answer: what are the components, where does code execute, where is task state stored, how does one task move from request to PR, and how do I monitor and continue it? Before: the README mixes setup steps and historical implementation notes; a reader must reconstruct the current architecture. After: a linked architecture guide and a legible diagram show the current system, with links to the implementing source and clear distinctions between historical design and current behavior. ## Deliverables 1. Add `docs/architecture.md` (or update the existing canonical architecture document if one now exists). Keep it focused: system overview; component responsibilities; task lifecycle; persistence; authentication/trust boundaries; external-agent API workflow; operational failure/recovery notes; source map. 2. Add a standalone `docs/architecture.svg` overview diagram with readable text, labeled arrows, and boundaries separating entry points, gateway/control services, task sandboxes, persistence, and external services. Embed/link it from the guide. Use editable SVG or retain a small diagram source if a renderer is already available; do not add a new application dependency merely to render it. Avoid external assets and scripts in the SVG. Include a text alternative in the guide. 3. Include a concise Mermaid lifecycle/sequence diagram in the Markdown if it clarifies create -> run -> question/answer -> idle/PR -> archived/resumed. Ensure its labels match the prose and source. 4. Add a prominent relative link to this guide near the README's introduction or Layout section. Preserve useful existing setup and historical notes. ## Starting evidence; verify against your current checkout The external orchestrator inspected a local source snapshot and these live API facts. Use them as investigation pointers, not as a substitute for reading the actual current files: - `gateway/src/routes.ts`: HTTP API, GitHub/Slack ingress, auth/scope, task CRUD-style routes, transcripts and OpenCode reverse proxy. - `gateway/src/tasks.ts`: task lifecycle, sandbox setup, continuation, pending-question answers, sleeping/revival. - `gateway/src/manifests.ts`: task record fields, Kubernetes metadata, five persisted statuses and the waiting-for-answer prefix. - `gateway/src/relay.ts`, `transcripts.ts`, `task-archive.ts`: progress/turn completion, transcript storage and archived task records. - `gateway/src/auth.ts`, `jarvis.ts`, `orgs.ts`: user identity and organization/repository access; distinguish user API authentication from the credentials used by sandboxes. Describe responsibilities, never secret values. - `gateway/src/github.ts`, `pr-feedback.ts`, `ci-watch.ts`, `ci-fix.ts`: GitHub PR creation, review/CI feedback and continuation. Verify where each responsibility actually lives. - `ui/`, `sandbox/`, `infra/`: React UI, per-task OpenCode sandbox and helper tools, GKE/Kubernetes infrastructure. Include Jarvis's actual role without claiming it owns execution if the code says otherwise. Verified API behavior on 15 September 2026: - The API index at `/api/v1` advertises create/list/get/prompt/stop/transcript/events, plus `/me`, `/repos`, `/models`. - Detail can return `status: idle` with `running: true`: the latter describes a live sandbox pod, not whether the model is busy. The list omits `running`. - A task awaiting a question can still be `running`; `statusDetail` beginning `Waiting for your answer` is a coarse signal. Actual pending questions/permissions are read through the OpenCode proxy. - A proxy GET does not wake a sleeping sandbox; it returns 409. Archived transcript reads remain available. - The simple prompt endpoint can answer pending questions, but applies one text answer to every pending question. Distinct answers require structured question replies. - A team caller's task list is not restricted to their own createdBy; external clients must filter against their identity. Server limit is applied before client-side creator filtering. - Per-task SSE and optional completion/failure webhooks exist. An external agent can nevertheless use polling without a receiver. - Idle and PR existence do not prove the user's acceptance criteria passed. The external orchestrator reviews the diff and CI independently. - The current local GitHub CLI identity cannot resolve the private Crobot repo, though the Crobot credential can manage tasks. You should still produce the normal PR and clear artifact paths; do not change access controls to work around the orchestrator's access. ## Acceptance and scope - Every major component and lifecycle claim is supported by relative links to real implementation files. Explain task IDs vs sandbox session IDs and what persists across sleep/archive/resume. - Diagram and prose agree about the gateway, execution sandbox, storage and external services. Arrows carry meaningful labels. Any behavior not established by source is called out as uncertain, not presented as implemented. - Include small API examples for create, poll, inspect transcript, answer/continue, and stop; use credential placeholders only. Omit brittle hard-coded model choices and environment-specific secret values. - Focus on documentation and diagrams. Do not change runtime behavior, production configuration, access controls, dependencies, or the external skill. No broad refactor. ## Verification and delivery Follow repo instructions. At minimum run `git diff --check`, check all newly added relative documentation/source links, parse the SVG as XML, and visually inspect the diagram for legibility, overlapping labels and clipped content. Validate/render Mermaid with existing tooling if available and state any limitation accurately. Run any documentation checks the repository already requires. Report the exact checks and results; do not claim a gateway test suite ran if it did not. Commit and open one normal pull request, with a concise body summarizing the new guide, diagram, source evidence and verification. No merge/deploy. In your final reply include the PR URL, head commit SHA, changed documentation paths, a short summary, and any remaining uncertainty.
First consolidated review feedback
I reviewed the actual docs/architecture.md and SVG through the read-only file API, compared implementation files from your current sandbox, and rendered the SVG locally. Please correct the following before we accept this documentation. Continue on the same branch/PR; no merge or runtime changes. 1. Fix the gateway namespace throughout prose, ASCII diagram and SVG. `gateway/src/config.ts` defaults `gatewayNamespace` / `GATEWAY_NAMESPACE` to `crobot`; `infra/k8s/base/sandbox-networkpolicy.yaml` also selects namespace `crobot`. `crobot-gateway` is not the namespace shown by those sources. Distinguish service/deployment name from namespace. 2. Correct the sandbox security claims. The ASCII diagram says `readOnlyRootFilesystem`, but `buildSandboxJob` in manifests.ts does not set it and explicitly budgets a writable ephemeral layer. The NetworkPolicy permits public internet egress (not a hostname allowlist of DNS/LLM/GitHub), plus DNS, GKE metadata and the gateway exceptions. Do not assert that all inter-namespace traffic is blocked when the gateway is intentionally allowed. Show `envFrom: crobot-provider-keys`: provider credentials are present in the sandbox, so do not imply all sandbox credentials are ephemeral. A per-task Jarvis delegation key is deliberately NOT injected into the agent environment; show that boundary accurately. `github.ts:repoToken` returns the configured PAT when mode is `token`; scope/one-hour expiration guarantees apply to App mode, not universally. Qualify the statement that sandboxes never receive a GitHub PAT accordingly. 3. Separate source defaults, deployment settings, and discovered model choice. `config.ts` defaults `IDLE_MINUTES` to 30, while the draft says default 10 (including the Mermaid and SVG). If deployed YAML overrides it, link and label that override rather than asserting it is the source default. `/api/v1/models` currently reports an OpenRouter default, even though source MODEL_DEFAULT falls back to Vertex. Describe provider choices and query /models rather than calling Vertex universally primary and OpenRouter only fallback. Remove brittle specific model names from the diagram where they do not explain architecture. 4. Correct the API examples and edge cases to match the actual contract: - The create response does not add `running`; only detail GET does (`routes.ts`). - UsageTokens use `input`, `output`, `reasoning`, `cacheRead`, `cacheWrite`; not `prompt` and `completion`. - The transcript endpoint is a possibly trimmed snapshot of the last completed turn, can be 404 before the first archive, and is not guaranteed to be the full current conversation at any time. - `/tasks` has no pagination cursor. Do not recommend unsupported pagination; explain no-limit retrieval, repo filtering, and direct known-ID retrieval. - A simple /prompt answer applies the same text to all pending questions. Add the current question GET and structured reply route with distinct arrays for distinct answers, and mention permission replies are separate. - `running` status can include waiting for a question; `idle` can mean explicitly stopped. Fix the status table and avoid saying idle plus PR proves a completed successful turn. - Use a neutral follow-up example like “Use option A and continue the implementation”; remove “proceed with the merge.” Prefer a personal Jarvis key example, explaining X-Crobot-User attribution is for configured Crobot API-key auth, not an override for every caller. 5. Correct lifecycle ordering against entrypoint.sh and tasks.ts. The gateway must be able to reach OpenCode before creating a session and executing the setup checkout through its shell endpoint. The draft sequence shows context sync/checkout/opencode boot as one sandbox action before gateway health/session creation. Show the actual division/order, and show the asynchronous 201 response rather than implying create waits for the whole execution startup. 6. Fix the diagram's concrete visual/semantic defects. At a 1600px rendered width, the sandbox section heading is clipped at the right edge, provider text extends outside its box, and several tool labels nearly touch/escape their bounds. The arrow labeled `Mount /workspace` currently ends at Blob Store (GCS), but the mount is the task PVC. Route it to the Task PVCs box and show blob upload as a separate path if needed. Simplify the diagram or wrap text so labels and arrows are readable. Re-render and inspect the whole exported SVG after changes. Please recheck all new relative links and Mermaid after the corrections, run git diff --check and SVG XML validation, and return the updated PR URL and head SHA with actual verification results. Keep the change documentation-only and group these corrections into one push once ready.
Final focused clarifications
The revised guide and diagram address most of the review. I read the new files directly and rendered the SVG. Finish these narrow corrections on the same PR; no broad rewrite or runtime changes:
1. The new permission example is incorrect. `ui/src/opencode.ts` uses GET `/api/session/${sessionId}/permission` and POST `/api/session/${sessionId}/permission/${requestId}/reply` through the task proxy, with `reply` equal to `once`, `always`, or `reject`. Replace the invented `/opencode/permission/<requestId>/reply` plus `grant|deny` sentence with `/api/v1/tasks/:taskId/opencode/api/session/:sessionId/permission/:requestId/reply` and a valid `{"reply":"once"}` example. Link ui/src/opencode.ts.
2. In the External Services and Provider Keys sections, Workload Identity tokens are incorrectly described as injected via the crobot-provider-keys Secret. Replace those claims with: “Provider API keys such as OPENROUTER_API_KEY, ANTHROPIC_API_KEY and OPENAI_API_KEY enter through crobot-provider-keys. Vertex AI authenticates via the sandbox service account's Workload Identity and the GKE metadata server.” The manifest envFrom and the NetworkPolicy metadata exception support that distinction. Reflect the distinction in the SVG provider box too; no new secrets or actual values.
3. The revised SVG's Task Service box still has one long blue line listing all five statuses that overflows its right border. Shorten it to “tasks.ts · manifests.ts · five persisted statuses” or wrap within the box. Preserve the now-correct Mount /workspace arrow to the PVC.
4. Add this observed monitoring caveat near the task status/API discussion: “The persisted running annotation can be stale after a completed turn. An external controller can refine it with /opencode/session/status and the latest session messages. An idle session-status map plus a completed final assistant reply is evidence that the turn ended; an empty map alone can occur during startup. A pending question/permission still takes precedence.” We observed this on this task and fixed the external skill accordingly. Do not change gateway behavior as part of this docs PR.
5. In the Mermaid startup sequence, `/session/shell` and `/session/prompt` are not the actual routes. Use descriptive labels (“Run branch setup through the session shell”, “Deliver initial prompt asynchronously”) or the actual `/session/:sessionId/shell` and `/session/:sessionId/prompt_async` from gateway/src/opencode.ts. Keep the corrected startup order.
Recheck these exact passages, SVG XML/rendering, relative links, and git diff --check. Push the small correction once, then return the PR URL, final head SHA and verification. No merge or deploy.
Crobot’s final reply
I am addressing your follow-up feedback by updating `docs/architecture.md` and `docs/architecture.svg` on PR #175 to correct the permission proxy routes, clarify provider auth vs. Workload Identity, shorten the status text in the SVG, incorporate the observed running-state monitoring caveat, and refine the Mermaid sequence labels.
### Specific Changes Implemented
1. **Permission Proxy Route and Payload Correction**:
- Replaced the invented permission endpoint with the exact path implemented in [`ui/src/opencode.ts`](../ui/src/opencode.ts:26):
- Query pending permissions: `GET /api/v1/tasks/:taskId/opencode/api/session/:sessionId/permission`
- Structured reply: `POST /api/v1/tasks/:taskId/opencode/api/session/:sessionId/permission/:requestId/reply` with `{"reply": "once"}` (valid options: `"once"`, `"always"`, or `"reject"`).
- Added a relative link directly to [`ui/src/opencode.ts`](../ui/src/opencode.ts).
2. **Provider Keys vs. Workload Identity Distinction**:
- Updated the External Services and Credential Delegation sections in `docs/architecture.md`:
> "Provider API keys such as `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` enter through `crobot-provider-keys`. Vertex AI authenticates via the sandbox service account's Workload Identity and the GKE metadata server."
- Updated the LLM Provider card in `docs/architecture.svg` to reflect this exact distinction (`API keys via crobot-provider-keys` and `Vertex AI: Workload Identity / metadata`).
3. **SVG Status Line & Layout Refinement**:
- Shortened the status annotation line in the Task Service card of `docs/architecture.svg` to `tasks.ts · manifests.ts · five persisted statuses`, eliminating text overflow across the right border.
- Preserved the clean `Mount /workspace` connector pointing directly to the `Task PVCs (Disks)` card.
4. **Monitoring Caveat Added**:
- Added the observed controller caveat under the Task Status / Polling section in `docs/architecture.md`:
> "*Monitoring Caveat*: The persisted running annotation can be stale after a completed turn. An external controller can refine it with `/opencode/session/status` and the latest session messages. An idle session-status map plus a completed final assistant reply is evidence that the turn ended; an empty map alone can occur during startup. A pending question/permission still takes precedence."
5. **Mermaid Sequence Startup Routes**:
- Replaced `/session/shell` and `/session/prompt` with descriptive labels indicating the actual OpenCode routes from [`gateway/src/opencode.ts`](../gateway/src/opencode.ts):
- `GW->>SB: Run branch setup through session shell (/session/:sessionId/shell)`
- `GW->>SB: Run repo maintenance commands (/session/:sessionId/shell)`
- `GW->>SB: Deliver initial prompt asynchronously (/session/:sessionId/prompt_async)`
### Pull Request & Commit Details
- **Pull Request**: https://github.com/Coframe/crobot/pull/175
- **Head Commit SHA**: `55f1b4e7291d80216e327a3895052bada8e154aa`
### Verification Performed
- `git diff --check`: Passed with zero formatting warnings or trailing whitespace.
- **Link Integrity**: Automated script checked all 86 local relative links in `docs/architecture.md` and 2 relative links in `README.md`; 100% verified to resolve to existing files.
- **SVG XML & 1600px Render Inspection**: Parsed with Python `xml.etree.ElementTree` (100% valid XML); rendered at viewport `1600x1020` in `agent-browser` (headless Chrome) and visually verified that the Task Service card text fits cleanly inside its borders with no overflow or clipping.
- **Mermaid Diagram Verification**: Validated and rendered the updated lifecycle sequence in Mermaid 12.0.0; confirmed clean rendering without syntax errors.
- **CI Status**: Ran `crobot-ci`; all checks passing/skipped with no failures.
https://github.com/Coframe/crobot/pull/175