Skip to content

Fix EvalOps service registration contract#46

Merged
haasonsaas merged 3 commits intomainfrom
codex/fix-evalops-registration-contract
Apr 21, 2026
Merged

Fix EvalOps service registration contract#46
haasonsaas merged 3 commits intomainfrom
codex/fix-evalops-registration-contract

Conversation

@haasonsaas
Copy link
Copy Markdown
Collaborator

Summary

  • point Kestrel defaults and CSPs at production EvalOps service hosts instead of cluster-internal URLs
  • register through the canonical agents.v1 AgentService contract with Platform-valid capabilities
  • persist service-specific URLs for agent-registry, approvals, skills, memory, and traces in settings

Validation

  • npm ci --ignore-scripts
  • npm run build
  • npm --prefix sdk/js run build
  • git diff --check

Note: plain npm ci still fails in postinstall because better-sqlite3 does not rebuild against Electron 41 headers in this local toolchain. npm test reaches the native ContextKit audio integration after npm run contextkit:build, then fails an existing WAV duration assertion unrelated to EvalOps registration.

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 21, 2026

PR Summary

Medium Risk
Changes network endpoints and the agent registration/list contract, which can break connectivity or provisioning if service URLs or request mappings are incorrect, but the changes are localized to EvalOps integration/config.

Overview
Switches EvalOps defaults (and renderer CSP connect-src) from cluster-internal hosts to production *.evalops.dev endpoints, including a new default approvals base URL.

Adds per-service routing in the consumer SDK (serviceBaseUrls and optional query support) and wires the desktop app config/settings UI to persist and use service-specific URLs for agent-registry/approvals/skills/memory/traces (including services status checks).

Updates agent registry calls to the canonical agents.v1.AgentService contract: normalizes list filters (notably status), adjusts fallback shapes to include total, and changes Kestrel agent registration payload/capabilities to match the new contract (dropping prior label/version/reason fields).

Reviewed by Cursor Bugbot for commit 945e92d. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/main/evalops/registration.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: SDK interface missing approvalsBaseUrl endpoint field
    • Added approvalsBaseUrl to the exported EvalOpsServiceEndpoints interface so the SDK matches the rest of the EvalOps endpoint configuration surfaces.
Preview (945e92df88)
diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts
--- a/sdk/js/src/index.ts
+++ b/sdk/js/src/index.ts
@@ -1,6 +1,5 @@
 export const KESTREL_SDK_NAME = '@evalops/kestrel-sdk'
-export const DEFAULT_EVALOPS_LLM_GATEWAY_URL =
-  'http://llm-gateway-service.evalops.svc.cluster.local:8080/v1'
+export const DEFAULT_EVALOPS_LLM_GATEWAY_URL = 'https://llm-gateway.evalops.dev/v1'
 
 export interface EvalOpsServiceEndpoints {
   identityBaseUrl?: string
@@ -8,6 +7,7 @@
   skillsBaseUrl?: string
   memoryBaseUrl?: string
   tracesBaseUrl?: string
+  approvalsBaseUrl?: string
   llmGatewayBaseUrl?: string
 }
 

diff --git a/src/main/evalops/config.ts b/src/main/evalops/config.ts
--- a/src/main/evalops/config.ts
+++ b/src/main/evalops/config.ts
@@ -4,6 +4,7 @@
   EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL,
   EVALOPS_DEFAULT_AGENT_ID,
   EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL,
+  EVALOPS_DEFAULT_APPROVALS_BASE_URL,
   EVALOPS_DEFAULT_MEMORY_BASE_URL,
   EVALOPS_DEFAULT_PROVIDER_REF,
   EVALOPS_DEFAULT_RESOURCE,
@@ -23,6 +24,7 @@
   token?: string
   llmGatewayBaseUrl: string
   agentRegistryBaseUrl: string
+  approvalsBaseUrl: string
   skillsBaseUrl: string
   memoryBaseUrl: string
   tracesBaseUrl: string
@@ -46,6 +48,7 @@
   token?: unknown
   llmGatewayBaseUrl?: unknown
   agentRegistryBaseUrl?: unknown
+  approvalsBaseUrl?: unknown
   skillsBaseUrl?: unknown
   memoryBaseUrl?: unknown
   tracesBaseUrl?: unknown
@@ -86,6 +89,11 @@
       asString(stored.agentRegistryBaseUrl),
       EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL
     ),
+    approvalsBaseUrl: cleanUrl(
+      process.env.EVALOPS_APPROVALS_BASE_URL,
+      asString(stored.approvalsBaseUrl),
+      EVALOPS_DEFAULT_APPROVALS_BASE_URL
+    ),
     skillsBaseUrl: cleanUrl(
       process.env.EVALOPS_SKILLS_BASE_URL,
       asString(stored.skillsBaseUrl),

diff --git a/src/main/evalops/consumer-sdk/clients.ts b/src/main/evalops/consumer-sdk/clients.ts
--- a/src/main/evalops/consumer-sdk/clients.ts
+++ b/src/main/evalops/consumer-sdk/clients.ts
@@ -3,6 +3,7 @@
   AgentRegistryListRequest,
   AgentRegistryListResponse,
   AgentRegistryRecord,
+  AgentRegistryRegisterRequest,
   ApprovalGetRequest,
   ApprovalGetResponse,
   ApprovalListPendingRequest,
@@ -171,28 +172,55 @@
     return this.transport.request({
       service: 'agent-registry',
       operation: 'list',
-      path: '/agent-registry.v1.AgentRegistryService/ListAgents',
-      body: request,
+      path: '/agents.v1.AgentService/List',
+      body: normalizeAgentListRequest(request),
       signal: options?.signal,
-      fallback: (reason) => ({ agents: [], ...offline(reason) })
+      fallback: (reason) => ({ agents: [], total: 0, ...offline(reason) })
     })
   }
 
   register(
-    agent: AgentRegistryRecord,
+    agent: AgentRegistryRegisterRequest,
     options?: { signal?: AbortSignal }
   ): Promise<{ agent?: AgentRegistryRecord; offline?: boolean; reason?: string }> {
     return this.transport.request({
       service: 'agent-registry',
       operation: 'register',
-      path: '/agent-registry.v1.AgentRegistryService/RegisterAgent',
-      body: agent,
+      path: '/agents.v1.AgentService/Register',
+      body: {
+        workspaceId: agent.workspaceId,
+        name: agent.name,
+        description: agent.description,
+        agentType: agent.agentType,
+        capabilities: agent.capabilities,
+        surfaces: agent.surfaces,
+        ownerId: agent.ownerId
+      },
       signal: options?.signal,
       fallback: (reason) => ({ ...offline(reason) })
     })
   }
 }
 
+function normalizeAgentListRequest(request: AgentRegistryListRequest): Record<string, unknown> {
+  return {
+    workspaceId: request.workspaceId,
+    agentType: request.agentType,
+    capability: request.capability,
+    surface: request.surface,
+    status: normalizeAgentStatus(request.status),
+    limit: request.limit,
+    offset: request.offset
+  }
+}
+
+function normalizeAgentStatus(status: string | undefined): string | undefined {
+  if (!status) return undefined
+  const normalized = status.trim().toUpperCase().replace(/[-\s]+/gu, '_')
+  if (!normalized) return undefined
+  return normalized.startsWith('AGENT_STATUS_') ? normalized : `AGENT_STATUS_${normalized}`
+}
+
 export class SkillsClient {
   constructor(private readonly transport: EvalOpsTransport) {}
 

diff --git a/src/main/evalops/consumer-sdk/http.ts b/src/main/evalops/consumer-sdk/http.ts
--- a/src/main/evalops/consumer-sdk/http.ts
+++ b/src/main/evalops/consumer-sdk/http.ts
@@ -13,6 +13,7 @@
   path: string
   method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
   body?: unknown
+  query?: Record<string, string | number | boolean | null | undefined>
   signal?: AbortSignal
   fallback?: (reason: string) => TFallback
 }
@@ -69,6 +70,7 @@
 export class EvalOpsTransport {
   readonly baseUrl: string
   private readonly token?: string
+  private readonly serviceBaseUrls: Partial<Record<EvalOpsServiceName, string>>
   private readonly headersOverride: Record<string, string>
   private readonly featureFlags?: FeatureFlags
   private readonly offlineFallback: boolean
@@ -80,6 +82,12 @@
       config.baseUrl ?? getEnvValue('EVALOPS_BASE_URL') ?? DEFAULT_BASE_URL
     )
     this.token = config.token ?? getEnvValue('EVALOPS_TOKEN')
+    this.serviceBaseUrls = Object.fromEntries(
+      Object.entries(config.serviceBaseUrls ?? {})
+        .map(([service, baseUrl]) => [service, baseUrl?.trim()])
+        .filter((entry): entry is [EvalOpsServiceName, string] => Boolean(entry[1]))
+        .map(([service, baseUrl]) => [service, trimTrailingSlash(baseUrl)])
+    )
     this.headersOverride = config.headers ?? {}
     this.featureFlags = config.featureFlags
     this.offlineFallback = config.offlineFallback ?? false
@@ -116,6 +124,16 @@
     }
   }
 
+  private requestUrl(options: EvalOpsTransportOptions<unknown>): string {
+    const baseUrl = this.serviceBaseUrls[options.service] ?? this.baseUrl
+    const url = new URL(`${baseUrl}${options.path}`)
+    for (const [key, value] of Object.entries(options.query ?? {})) {
+      if (value === undefined || value === null || value === '') continue
+      url.searchParams.set(key, String(value))
+    }
+    return url.toString()
+  }
+
   async request<TResponse>(
     options: EvalOpsTransportOptions<TResponse>
   ): Promise<TResponse> {
@@ -123,7 +141,7 @@
     const method = options.method ?? 'POST'
 
     try {
-      const response = await this.fetchImpl(`${this.baseUrl}${options.path}`, {
+      const response = await this.fetchImpl(this.requestUrl(options), {
         method,
         headers: this.headers(),
         signal: options.signal,

diff --git a/src/main/evalops/consumer-sdk/types.ts b/src/main/evalops/consumer-sdk/types.ts
--- a/src/main/evalops/consumer-sdk/types.ts
+++ b/src/main/evalops/consumer-sdk/types.ts
@@ -21,6 +21,7 @@
 
 export interface EvalOpsClientConfig {
   baseUrl?: string
+  serviceBaseUrls?: Partial<Record<EvalOpsServiceName, string>>
   token?: string
   headers?: Record<string, string>
   featureFlags?: FeatureFlags
@@ -47,13 +48,19 @@
 
 export interface AgentRegistryRecord extends JsonObject {
   id?: string
+  workspace_id?: string
   workspaceId?: string
+  organization_id?: string
+  organizationId?: string
   name?: string
   description?: string
+  agent_type?: string
   agentType?: string
   capabilities?: string[]
+  surface?: string
   surfaces?: string[]
   status?: string
+  metadata?: Record<string, string>
   activeConfigVersion?: number
   ownerId?: string
   version?: string
@@ -70,6 +77,16 @@
   offset?: number
 }
 
+export interface AgentRegistryRegisterRequest extends JsonObject {
+  workspaceId?: string
+  name: string
+  description?: string
+  agentType: string
+  capabilities: string[]
+  surfaces: string[]
+  ownerId?: string
+}
+
 export interface AgentRegistryListResponse extends JsonObject {
   agents: AgentRegistryRecord[]
   total?: number

diff --git a/src/main/evalops/consumer.ts b/src/main/evalops/consumer.ts
--- a/src/main/evalops/consumer.ts
+++ b/src/main/evalops/consumer.ts
@@ -8,6 +8,13 @@
   const session = getStoredEvalOpsSession()
   return new EvalOpsClient({
     baseUrl: config.baseUrl,
+    serviceBaseUrls: {
+      'agent-registry': config.agentRegistryBaseUrl,
+      approvals: config.approvalsBaseUrl,
+      memory: config.memoryBaseUrl,
+      skills: config.skillsBaseUrl,
+      traces: config.tracesBaseUrl
+    },
     token,
     headers: cleanHeaders({
       'X-Organization-ID': session?.organizationId,

diff --git a/src/main/evalops/handlers.ts b/src/main/evalops/handlers.ts
--- a/src/main/evalops/handlers.ts
+++ b/src/main/evalops/handlers.ts
@@ -28,7 +28,7 @@
   ipcMain.handle('evalops:authStatus', async () => getEvalOpsAuthStatus())
   ipcMain.handle('evalops:login', async (_event, options?: EvalOpsLoginOptions) => {
     const status = await loginEvalOps(options)
-    if (status.authenticated) registerKestrelAgentInBackground('login')
+    if (status.authenticated) registerKestrelAgentInBackground()
     return status
   })
   ipcMain.handle('evalops:logout', async () => logoutEvalOps())

diff --git a/src/main/evalops/registration.ts b/src/main/evalops/registration.ts
--- a/src/main/evalops/registration.ts
+++ b/src/main/evalops/registration.ts
@@ -1,22 +1,22 @@
-import { APP_ID, APP_NAME, APP_VERSION } from '../../shared/config'
+import { APP_NAME } from '../../shared/config'
 import { getEvalOpsAuthStatus, getStoredEvalOpsSession } from './auth'
 import { getEvalOpsConfig } from './config'
 import { getEvalOpsConsumerClient } from './consumer'
 
 const KESTREL_CAPABILITIES = [
-  'context.capture',
-  'llm.chat',
-  'mcp.client',
-  'meeting.recording',
-  'memory.recall',
-  'memory.store',
-  'trace.ingest',
-  'approval.request'
+  'x-kestrel:context.capture',
+  'responses:create',
+  'mcp',
+  'x-kestrel:meeting.recording',
+  'x-kestrel:memory.recall',
+  'x-kestrel:memory.store',
+  'x-kestrel:trace.ingest',
+  'x-kestrel:approval.request'
 ]
 
 const KESTREL_SURFACES = ['kestrel', 'desktop', 'chat', 'mcp', 'meetings']
 
-export async function registerKestrelAgent(reason: 'startup' | 'login' = 'startup'): Promise<void> {
+export async function registerKestrelAgent(): Promise<void> {
   const status = await getEvalOpsAuthStatus()
   if (!status.authenticated && !status.tokenConfigured) return
 
@@ -25,25 +25,13 @@
   const client = await getEvalOpsConsumerClient()
 
   const response = await client.agentRegistry.register({
-    id: config.agentId,
     workspaceId: config.workspaceId,
     name: `${APP_NAME} Desktop`,
     description: 'Context-aware AI desktop assistant for macOS.',
-    agentType: 'desktop-assistant',
+    agentType: 'desktop',
     capabilities: KESTREL_CAPABILITIES,
     surfaces: KESTREL_SURFACES,
-    status: 'active',
-    version: APP_VERSION,
-    ownerId: session?.organizationId,
-    labels: cleanLabels({
-      app_id: APP_ID,
-      app_name: APP_NAME,
-      platform: process.platform,
-      runtime: 'electron',
-      registration_reason: reason,
-      organization_id: session?.organizationId,
-      workspace_id: config.workspaceId
-    })
+    ownerId: session?.organizationId
   })
 
   if (response.offline) {
@@ -51,16 +39,8 @@
   }
 }
 
-export function registerKestrelAgentInBackground(reason: 'startup' | 'login' = 'startup'): void {
-  registerKestrelAgent(reason).catch((err) => {
+export function registerKestrelAgentInBackground(): void {
+  registerKestrelAgent().catch((err) => {
     console.warn('[evalops:registration] Agent registration failed:', err)
   })
 }
-
-function cleanLabels(labels: Record<string, string | undefined>): Record<string, string> {
-  const result: Record<string, string> = {}
-  for (const [key, value] of Object.entries(labels)) {
-    if (value) result[key] = value
-  }
-  return result
-}

diff --git a/src/main/evalops/services.ts b/src/main/evalops/services.ts
--- a/src/main/evalops/services.ts
+++ b/src/main/evalops/services.ts
@@ -136,11 +136,11 @@
     baseUrl: string
     run: () => Promise<unknown>
   }> = [
-    { service: 'agent-registry', baseUrl: config.baseUrl, run: () => listEvalOpsAgents({ limit: 1 }) },
-    { service: 'skills', baseUrl: config.baseUrl, run: () => listEvalOpsSkills({ limit: 1 }) },
-    { service: 'memory', baseUrl: config.baseUrl, run: () => recallEvalOpsMemory({ query: 'kestrel', topK: 1 }) },
-    { service: 'approvals', baseUrl: config.baseUrl, run: () => listEvalOpsApprovals({ limit: 1 }) },
-    { service: 'traces', baseUrl: config.baseUrl, run: () => listEvalOpsTraces({ limit: 1 }) }
+    { service: 'agent-registry', baseUrl: config.agentRegistryBaseUrl, run: () => listEvalOpsAgents({ limit: 1 }) },
+    { service: 'skills', baseUrl: config.skillsBaseUrl, run: () => listEvalOpsSkills({ limit: 1 }) },
+    { service: 'memory', baseUrl: config.memoryBaseUrl, run: () => recallEvalOpsMemory({ query: 'kestrel', topK: 1 }) },
+    { service: 'approvals', baseUrl: config.approvalsBaseUrl, run: () => listEvalOpsApprovals({ limit: 1 }) },
+    { service: 'traces', baseUrl: config.tracesBaseUrl, run: () => listEvalOpsTraces({ limit: 1 }) }
   ]
 
   return Promise.all(checks.map(async (check) => {

diff --git a/src/main/index.ts b/src/main/index.ts
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -107,7 +107,7 @@
   registerMCPHandlers(mcpManager)
   registerPermissionHandlers()
   registerEvalOpsHandlers()
-  registerKestrelAgentInBackground('startup')
+  registerKestrelAgentInBackground()
   registerUpdateHandlers()
 
   // Context IPC handlers

diff --git a/src/renderer/main/index.html b/src/renderer/main/index.html
--- a/src/renderer/main/index.html
+++ b/src/renderer/main/index.html
@@ -5,7 +5,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta
       http-equiv="Content-Security-Policy"
-      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://*.evalops.svc.cluster.local:* https://api.openai.com"
+      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://*.evalops.dev https://api.openai.com"
     />
     <title>Kestrel</title>
   </head>

diff --git a/src/renderer/main/src/components/settings/EvalOpsSettings.tsx b/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
--- a/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
+++ b/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
@@ -2,12 +2,17 @@
 import { AlertCircle, Check, LogIn, LogOut, RefreshCw, Save } from 'lucide-react'
 import {
   EVALOPS_DEFAULT_AGENT_ID,
+  EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL,
+  EVALOPS_DEFAULT_APPROVALS_BASE_URL,
   EVALOPS_DEFAULT_BASE_URL,
   EVALOPS_DEFAULT_IDENTITY_BASE_URL,
   EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL,
+  EVALOPS_DEFAULT_MEMORY_BASE_URL,
   EVALOPS_DEFAULT_PROVIDER_REF,
   EVALOPS_DEFAULT_RESOURCE,
   EVALOPS_DEFAULT_SCOPES,
+  EVALOPS_DEFAULT_SKILLS_BASE_URL,
+  EVALOPS_DEFAULT_TRACES_BASE_URL,
   EVALOPS_DEFAULT_WORKSPACE_ID
 } from '@shared/config'
 import type { EvalOpsAuthStatus, EvalOpsServiceStatus } from '@shared/ipc'
@@ -20,6 +25,7 @@
   resource?: string
   scopes?: string[]
   agentRegistryBaseUrl?: string
+  approvalsBaseUrl?: string
   skillsBaseUrl?: string
   memoryBaseUrl?: string
   tracesBaseUrl?: string
@@ -38,6 +44,11 @@
   const [identityBaseUrl, setIdentityBaseUrl] = useState(EVALOPS_DEFAULT_IDENTITY_BASE_URL)
   const [baseUrl, setBaseUrl] = useState(EVALOPS_DEFAULT_BASE_URL)
   const [llmGatewayBaseUrl, setLlmGatewayBaseUrl] = useState(EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL)
+  const [agentRegistryBaseUrl, setAgentRegistryBaseUrl] = useState(EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL)
+  const [approvalsBaseUrl, setApprovalsBaseUrl] = useState(EVALOPS_DEFAULT_APPROVALS_BASE_URL)
+  const [skillsBaseUrl, setSkillsBaseUrl] = useState(EVALOPS_DEFAULT_SKILLS_BASE_URL)
+  const [memoryBaseUrl, setMemoryBaseUrl] = useState(EVALOPS_DEFAULT_MEMORY_BASE_URL)
+  const [tracesBaseUrl, setTracesBaseUrl] = useState(EVALOPS_DEFAULT_TRACES_BASE_URL)
   const [token, setToken] = useState('')
   const [resource, setResource] = useState(EVALOPS_DEFAULT_RESOURCE)
   const [scopes, setScopes] = useState(EVALOPS_DEFAULT_SCOPES.join(' '))
@@ -60,6 +71,11 @@
     if (stored?.identityBaseUrl) setIdentityBaseUrl(stored.identityBaseUrl)
     if (stored?.baseUrl) setBaseUrl(stored.baseUrl)
     if (stored?.llmGatewayBaseUrl) setLlmGatewayBaseUrl(stored.llmGatewayBaseUrl)
+    if (stored?.agentRegistryBaseUrl) setAgentRegistryBaseUrl(stored.agentRegistryBaseUrl)
+    if (stored?.approvalsBaseUrl) setApprovalsBaseUrl(stored.approvalsBaseUrl)
+    if (stored?.skillsBaseUrl) setSkillsBaseUrl(stored.skillsBaseUrl)
+    if (stored?.memoryBaseUrl) setMemoryBaseUrl(stored.memoryBaseUrl)
+    if (stored?.tracesBaseUrl) setTracesBaseUrl(stored.tracesBaseUrl)
     if (stored?.token) setToken(stored.token)
     if (stored?.resource) setResource(stored.resource)
     if (stored?.scopes?.length) setScopes(stored.scopes.join(' '))
@@ -84,6 +100,11 @@
         identityBaseUrl: identityBaseUrl.trim(),
         baseUrl: baseUrl.trim(),
         llmGatewayBaseUrl: llmGatewayBaseUrl.trim(),
+        agentRegistryBaseUrl: agentRegistryBaseUrl.trim(),
+        approvalsBaseUrl: approvalsBaseUrl.trim(),
+        skillsBaseUrl: skillsBaseUrl.trim(),
+        memoryBaseUrl: memoryBaseUrl.trim(),
+        tracesBaseUrl: tracesBaseUrl.trim(),
         token: token.trim(),
         resource: resource.trim(),
         scopes: parsedScopes,
@@ -104,7 +125,25 @@
     } finally {
       setBusy(false)
     }
-  }, [agentId, baseUrl, identityBaseUrl, llmGatewayBaseUrl, parsedScopes, provider, providerCredentialName, providerEnvironment, providerTeamId, resource, scopes, token, workspaceId])
+  }, [
+    agentId,
+    agentRegistryBaseUrl,
+    approvalsBaseUrl,
+    baseUrl,
+    identityBaseUrl,
+    llmGatewayBaseUrl,
+    memoryBaseUrl,
+    parsedScopes,
+    provider,
+    providerCredentialName,
+    providerEnvironment,
+    providerTeamId,
+    resource,
+    skillsBaseUrl,
+    token,
+    tracesBaseUrl,
+    workspaceId
+  ])
 
   const signIn = useCallback(async () => {
     setBusy(true)
@@ -230,6 +269,39 @@
           placeholder={EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL}
         />
 
+        <div className="grid grid-cols-2 gap-4">
+          <TextSetting
+            label="Agent Registry URL"
+            value={agentRegistryBaseUrl}
+            onChange={setAgentRegistryBaseUrl}
+            placeholder={EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL}
+          />
+          <TextSetting
+            label="Approvals URL"
+            value={approvalsBaseUrl}
+            onChange={setApprovalsBaseUrl}
+            placeholder={EVALOPS_DEFAULT_APPROVALS_BASE_URL}
+          />
+          <TextSetting
+            label="Skills URL"
+            value={skillsBaseUrl}
+            onChange={setSkillsBaseUrl}
+            placeholder={EVALOPS_DEFAULT_SKILLS_BASE_URL}
+          />
+          <TextSetting
+            label="Memory URL"
+            value={memoryBaseUrl}
+            onChange={setMemoryBaseUrl}
+            placeholder={EVALOPS_DEFAULT_MEMORY_BASE_URL}
+          />
+          <TextSetting
+            label="Traces URL"
+            value={tracesBaseUrl}
+            onChange={setTracesBaseUrl}
+            placeholder={EVALOPS_DEFAULT_TRACES_BASE_URL}
+          />
+        </div>
+
         <TextSetting
           label="Bearer Token"
           value={token}
@@ -306,7 +378,7 @@
             <div>
               <h4 className="text-sm font-medium mb-1">Platform Services</h4>
               <p className="text-xs text-muted-foreground">
-                Agent registry, skills, memory, approvals, and traces are checked through the unified EvalOps platform API.
+                Agent registry, approvals, skills, memory, and traces are checked through their configured EvalOps service URLs.
               </p>
             </div>
             <button

diff --git a/src/renderer/overlay/index.html b/src/renderer/overlay/index.html
--- a/src/renderer/overlay/index.html
+++ b/src/renderer/overlay/index.html
@@ -5,7 +5,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta
       http-equiv="Content-Security-Policy"
-      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://*.evalops.svc.cluster.local:* https://api.openai.com"
+      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://*.evalops.dev https://api.openai.com"
     />
     <title>Kestrel - Quick Access</title>
   </head>

diff --git a/src/shared/config.ts b/src/shared/config.ts
--- a/src/shared/config.ts
+++ b/src/shared/config.ts
@@ -25,13 +25,14 @@
 
 // ── EvalOps Platform Defaults ──
 
-export const EVALOPS_DEFAULT_IDENTITY_BASE_URL = 'http://identity-service.evalops.svc.cluster.local:8080'
+export const EVALOPS_DEFAULT_IDENTITY_BASE_URL = 'https://identity.evalops.dev'
 export const EVALOPS_DEFAULT_BASE_URL = 'https://api.evalops.dev'
-export const EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL = 'http://llm-gateway-service.evalops.svc.cluster.local:8080/v1'
-export const EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL = 'http://agent-registry-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_SKILLS_BASE_URL = 'http://skills-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_MEMORY_BASE_URL = 'http://memory-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_TRACES_BASE_URL = 'http://traces-service.evalops.svc.cluster.local:8080'
+export const EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL = 'https://llm-gateway.evalops.dev/v1'
+export const EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL = 'https://agent-registry.evalops.dev'
+export const EVALOPS_DEFAULT_APPROVALS_BASE_URL = 'https://approvals.evalops.dev'
+export const EVALOPS_DEFAULT_SKILLS_BASE_URL = 'https://skills.evalops.dev'
+export const EVALOPS_DEFAULT_MEMORY_BASE_URL = 'https://memory.evalops.dev'
+export const EVALOPS_DEFAULT_TRACES_BASE_URL = 'https://traces.evalops.dev'
 export const EVALOPS_DEFAULT_RESOURCE = EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL
 export const EVALOPS_DEFAULT_WORKSPACE_ID = 'default'
 export const EVALOPS_DEFAULT_AGENT_ID = 'kestrel-desktop'

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 40dbd8e. Configure here.

Comment thread sdk/js/src/index.ts
@haasonsaas haasonsaas merged commit 0fa6499 into main Apr 21, 2026
5 checks passed
@haasonsaas haasonsaas deleted the codex/fix-evalops-registration-contract branch April 21, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants