agent-expert
Expert agent — answers questions about AGENTS using docs search
agent-expert is the AGENTS developer assistant. It connects to a broker, registers interactive tools (e.g., ask-agents, write-tdd), uses vault-backed secrets for model calls, consults a remote memory service to recall prior conversations, and exposes an HTTP chat UI. Its purpose is to let developers ask questions about the AGENTS platform, generate tests/TDDs, and provide guidance backed by searchable docs and prior conversation context.
Architecture
Section titled “Architecture”- Core runtime: KadiClient (from @kadi.build/core) is the primary runtime object. agent-expert constructs a KadiClient and uses it to:
- load native abilities (secret-ability) to fetch secrets from vaults,
- register broker tools via registerTools(…),
- call remote services via client.invokeRemote (memory-recall, memory-store).
- Secrets: secret-ability is loaded as a native ability at startup and used to populate an in-process secretCache.
- Memory: For context-aware answers, agent-expert invokes the remote memory service with “memory-recall” and archives exchanges with “memory-store”.
- Broker: agent-expert connects to a WebSocket broker (configured in agent.json/config.toml) to advertise tools and receive broker-invoked actions.
- HTTP: startServer(…) launches an HTTP server and chat UI (the server implementation is provided in server.js). The server can operate in HTTP-only mode if broker connection fails.
- Lifecycle: graceful shutdown listens for SIGINT, disconnects client, and exits.
Data flow summary:
- Startup: load agent.json, build KadiClient, fetch secrets into secretCache via secret-ability.
- Register tools with the broker (registerTools).
- Connect to broker (client.connect). If unavailable, server remains HTTP-only.
- Interactions: user request -> HTTP UI or broker tool -> agent handlers use recallRelevantContext to fetch memory context, call models (via secrets in secretCache), respond, then call storeExchange to persist a short memory fragment.
Tools / API
Section titled “Tools / API”Below are functions and the key broker registration entry-point used by the package (all names are taken from the source):
| Name | Type | Description | Key parameters |
|---|---|---|---|
| registerTools | function (imported from ./tools.js) | Registers broker-facing tools (commented to include tools such as ask-agents, write-tdd, etc.). This is where most broker tool handlers are attached. | (client: KadiClient, secretCache: Record<string,string>) |
| secretCache | exported constant | Runtime in-memory cache of secrets loaded at startup from secret-ability. Populated with keys attempted in startup. | Record<string,string> |
| recallRelevantContext(client, question) | async function | Queries the remote memory service (“memory-recall”) to fetch recent relevant conversation fragments, returns an aggregated markdown block or null. | (client: KadiClient, question: string) -> Promise<string |
| storeExchange(client, question, answer, conversationId?) | function | Stores a short summary (“Q: … A: …”) of an exchange via the remote memory service (“memory-store”). | (client: KadiClient, question: string, answer: string, conversationId?: string) |
| startServer | function (imported from ./server.js) | Starts the HTTP chat server used for the UI. Called with the KadiClient and a port. | (client: KadiClient, port: number, brokerConnectedFn: ()=>boolean) |
Notes:
- registerTools is the central place where broker tools are provided. The source comment explicitly mentions it registers “ask-agents” and “write-tdd” among others — see tools.js for the exact tool signatures and parameter schemas.
- The secret-ability native ability is invoked with invoke(‘get’, { vault, key }) to fetch secrets. See the code example below.
Configuration
Section titled “Configuration”agent-expert reads configuration from config.toml and environment variables. Important fields:
From config.toml:
- [agent]
- ID = “agent-expert”
- VERSION = “0.1.0”
- [logging]
- LEVEL = “debug”
- [broker.remote]
- URL = “wss://broker.dadavidtseng.com/kadi”
- NETWORKS = [“global”]
- [secrets]
- VAULTS = [“model-manager”, “anthropic”, “arcadedb”]
- KEYS = [“MODEL_MANAGER_API_KEY”, “MODEL_MANAGER_BASE_URL”, “ARCADE_USERNAME”, “ARCADE_PASSWORD”]
- [arcadedb]
- HOST, PORT, USERNAME, DATABASE — arcadedb connection metadata used by the logging/archival backend.
From agent.json (deployment-related / build):
- abilities: “secret-ability”, “ability-log” — abilities the agent expects to use.
- brokers.remote: “wss://broker.dadavidtseng.com/kadi” — default remote broker used when running deployed.
- deploy.*.secrets.vaults: defines required secret keys per vault. Example vaults:
- model-manager: required [“MODEL_MANAGER_API_KEY”,“MODEL_MANAGER_BASE_URL”]
- arcadedb: required [“ARCADE_USERNAME”,“ARCADE_PASSWORD”]
- deploy.*.secrets.delivery = “broker” — indicates secret delivery via broker when deployed.
Environment variables:
- BROKER_URL — override broker URL (index.ts checks process.env.BROKER_URL).
- PORT — port for the HTTP server (defaults to 3500).
- NODE_ENV — build/runtime environment; deployment sets NODE_ENV=production.
- ARCADE_HOST / ARCADE_PORT — used by deployed service for ArcadeDB access (set in deploy config).
- In containerized deploy command examples, ARCADE_HOST, ARCADE_PORT are provided via env.
Secrets / vaults
- agent-expert attempts to load secrets via secret-ability at startup and fills secretCache with any found secrets.
- The startup sequence attempts to read keys named ‘MM-1_API_KEY’ and ‘MEMORY_API_KEY’ from vaults ‘model-manager’ and ‘anthropic’ (see code example). Ensure these keys exist in your vault configuration if you expect model/memory integration to work.
Secrets usage (as seen in code):
- client.loadNative(‘secret-ability’) -> secrets.invoke(‘get’, { vault, key }) -> result.value
Secrets delivery on deploy
- The agent.json deployment section indicates secrets are delivered via the broker when deployed to Akash (delivery: “broker”).
Code Examples
Section titled “Code Examples”Key snippets taken directly from the source to show initialization, secret loading, memory recall/storage patterns.
// src/index.ts (startup, secret loading, broker connect, server start)import { KadiClient } from '@kadi.build/core';import { readFileSync } from 'node:fs';import { dirname, join } from 'node:path';import { fileURLToPath } from 'node:url';import { startServer } from './server.js';import { registerTools } from './tools.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
// ── Load agent.json ───────────────────────────────────────────────────
function loadAgentJson(): Record<string, any> { try { return JSON.parse(readFileSync(join(__dirname, '..', 'agent.json'), 'utf-8')); } catch { return JSON.parse(readFileSync(join(__dirname, '..', '..', 'agent.json'), 'utf-8')); }}
const agentJson = loadAgentJson();const brokerUrl = process.env.BROKER_URL ?? agentJson.brokers?.local ?? 'ws://localhost:8080/kadi';
// ── Create client ─────────────────────────────────────────────────────
const client = new KadiClient({ name: agentJson.name ?? 'agent-expert', version: agentJson.version ?? '1.0.0', description: agentJson.description, defaultBroker: 'default', brokers: { default: { url: brokerUrl, networks: agentJson.networks ?? ['default'] }, },});
// ── Load secrets ──────────────────────────────────────────────────────
export const secretCache: Record<string, string> = {};
try { const secrets = await client.loadNative('secret-ability'); console.log('[agent-expert] secret-ability loaded');
for (const key of ['MM-1_API_KEY', 'MEMORY_API_KEY']) { for (const vault of ['model-manager', 'anthropic']) { try { const result = await secrets.invoke('get', { vault, key }) as { value?: string }; if (result?.value) { secretCache[key] = result.value; console.log(`[agent-expert] ${key} loaded from vault "${vault}"`); break; } } catch { /* not in this vault */ } } }
await secrets.disconnect();} catch { console.warn('[agent-expert] secret-ability not available — model calls may fail');}
// ── Register broker tools ─────────────────────────────────────────────
registerTools(client, secretCache);
// ── Connect to broker ─────────────────────────────────────────────────
let brokerConnected = false;
try { await client.connect(); brokerConnected = true; console.log(`[agent-expert] Connected to broker: ${brokerUrl}`);} catch (err) { console.warn(`[agent-expert] Broker connection failed: ${(err as Error).message}`); console.warn('[agent-expert] Running in HTTP-only mode (no broker tools)');}
// ── Start HTTP server ─────────────────────────────────────────────────
const port = Number(process.env.PORT) || 3500;startServer(client, port, () => brokerConnected);
// ── Graceful shutdown ─────────────────────────────────────────────────
process.on('SIGINT', () => { console.log('[agent-expert] Shutting down…'); client.disconnect?.().catch(() => {}); process.exit(0);});// src/lib/conversation-memory.ts (recall + store helpers)import type { KadiClient } from '@kadi.build/core';
const AGENT_ID = 'agent-expert';const RECALL_TIMEOUT_MS = 2000;const ARCHIVAL_THRESHOLD = 20;
interface MemoryResult { content: string; score?: number; properties?: Record<string, unknown>;}
interface RecallResponse { results: MemoryResult[];}
export async function recallRelevantContext( client: KadiClient, question: string,): Promise<string | null> { try { const result = await Promise.race([ client.invokeRemote<RecallResponse>('memory-recall', { query: question, agent: AGENT_ID, limit: 5, mode: 'hybrid', }), new Promise<null>((_, reject) => setTimeout(() => reject(new Error('timeout')), RECALL_TIMEOUT_MS), ), ]);
if (!result?.results?.length) return null;
const fragments = result.results .filter((r) => r.score && r.score > 0.3) .slice(0, 3) .map((r) => r.content);
if (fragments.length === 0) return null;
return `## Prior Conversations\n${fragments.join('\n---\n')}`; } catch { return null; }}
export function storeExchange( client: KadiClient, question: string, answer: string, conversationId?: string,): void { const content = `Q: ${question}\nA: ${answer.slice(0, 2000)}`;
client.invokeRemote('memory-store', { content, agent: AGENT_ID, conversationId, });}Usage patterns:
- Use recallRelevantContext before calling a model to include prior context in the prompt.
- After producing an answer, call storeExchange to persist a brief memory fragment.
Dependencies
Section titled “Dependencies”Direct runtime dependencies (from package manifest):
- @kadi.build/core — Kadi runtime (KadiClient, client.invokeRemote, loadNative, etc.)
- express — used by the HTTP server (server.js)
Dev dependencies:
- @types/express, @types/node, typescript, tsx
Abilities required/requested (from agent.json):
- secret-ability — used at startup via client.loadNative(‘secret-ability’) to read vault secrets via secrets.invoke(‘get’, { vault, key }).
- The code expects to retrieve at least MM-1_API_KEY and MEMORY_API_KEY from vaults model-manager or anthropic.
- ability-log — declared in agent.json; may be used by tools for structured logging (not shown in the excerpt).
What depends on agent-expert
- Consumers: other agents or UIs in the AGENTS ecosystem may call its broker tools once registered. The registerTools implementation publishes tools (ask-agents, write-tdd, …) that other agents or clients can invoke via the broker.
- The deployment configuration expects interactions with a model-manager service (vault-provided credentials) and ArcadeDB for logs (arcadedb vault).
Notes for developers
Section titled “Notes for developers”- Tools are wired in registerTools(client, secretCache). For changing tool signatures or adding tools, edit tools.js and ensure registration happens after secretCache is populated.
- Secret loading is best-effort: missing secret-ability will not fatal-start the agent, but model/memory calls will likely fail. Ensure the runtime environment or broker-delivered secrets match the vault/key names attempted in startup.
- Memory calls are made through client.invokeRemote(‘memory-recall’|‘memory-store’). Ensure the memory service is available on the target broker/networks.
- The HTTP server is started via startServer(client, port, () => brokerConnected) — the third argument is a function that indicates whether broker-based tools are available; server-side UI can use that to enable/disable broker actions.
If you need details on the registered tools’ exact RPC schemas, inspect tools.js (registerTools) and the server implementation (server.js) in this package.