Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
"cors": "2.8.5",
"dotenv": "16.4.5",
"express": "4.19.2",
"express-rate-limit": "7.3.1",
"viem": "2.17.0"
"express-rate-limit": "7.3.1"
},
"devDependencies": {
"@types/cors": "2.8.17",
Expand Down
12 changes: 4 additions & 8 deletions backend/src/agentRunner.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
/**
* agentRunner.ts — Agent-to-Agent runner (Casper)
* agentRunner.ts — Single-agent HTTP route handler (Casper)
*
* NOTE: The primary A2A orchestration loop now lives in coordinator.ts.
* This file provides the /agent/:capability/run HTTP route handler used
* by server.ts for direct single-agent invocations.
*
* Each capability runs Venice AI inference. On-chain hiring for standalone
* A2A runs is handled via the coordinator's callContractEntry helper,
* which this module re-exports for server.ts compatibility.
* The primary orchestration loop lives in coordinator.ts.
* This file provides the /agent/:capability/run HTTP route handler
* used by server.ts for direct single-agent invocations.
*/

import { veniceChat } from "./agents/venice";
Expand Down
84 changes: 72 additions & 12 deletions backend/src/agentStore.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import fs from "fs";
import path from "path";

interface AgentRecord {
export interface AgentRecord {
accountHash: string;
endpoint: string;
capability: string;
pricePerTask: string;
active: boolean;
reputationScore: number;
tasksCompleted: number;
source: "on-chain" | "local";
}

const STORE_PATH = path.resolve(__dirname, "..", "data", "agents.json");
const ACCOUNT_HASH_RE = /^00[0-9a-f]{64}$/i;

let agents: Map<string, AgentRecord> = new Map();
let loaded = false;
let lastChainSync = 0;
const CHAIN_SYNC_INTERVAL_MS = 30_000;

function load(): void {
function loadLocal(): void {
if (loaded) return;
try {
const dir = path.dirname(STORE_PATH);
Expand All @@ -30,36 +34,66 @@ function load(): void {
removed++;
continue;
}
agents.set(k, rec);
agents.set(k, { ...rec, source: rec.source ?? "local" });
}
if (removed > 0) {
console.warn(`[AgentStore] Removed ${removed} agents with invalid account hashes`);
save();
saveLocal();
}
}
} catch (e) {
console.warn(`[AgentStore] Failed to load: ${e}`);
console.warn(`[AgentStore] Failed to load local cache: ${e}`);
}
loaded = true;
}

function save(): void {
function saveLocal(): void {
try {
const obj: Record<string, AgentRecord> = {};
for (const [k, v] of agents) obj[k] = v;
const dir = path.dirname(STORE_PATH);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(STORE_PATH, JSON.stringify(obj, null, 2));
} catch (e) {
console.warn(`[AgentStore] Failed to save: ${e}`);
console.warn(`[AgentStore] Failed to save local cache: ${e}`);
}
}

/**
* On-chain agent discovery is limited by the AgentRegistry contract design:
* agents are stored in an Odra Mapping<Address, AgentRecord> which is not
* exposed via CSPR.cloud's named-keys API. Full on-chain discovery would
* require a `get_all_agents()` entry point on the contract.
*
* For now, the local cache (seeded on startup + updated on user registration)
* is the primary agent registry.
*/

/**
* Sync on-chain state into the local agent store.
* On-chain agent discovery is limited (no get_all_agents entry point),
* so this is a best-effort merge. Local agents are always preserved.
*/
export async function syncWithChain(): Promise<void> {
const now = Date.now();
if (now - lastChainSync < CHAIN_SYNC_INTERVAL_MS) return;
lastChainSync = now;

// On-chain discovery is not yet supported (Odra Mapping not queryable).
// Local cache is the primary registry, populated by seedCoordinatorAgents()
// and updated by /agent/register/submit.
console.log(`[AgentStore] Chain sync: ${agents.size} agents in local cache`);
}

// ── Public API ──────────────────────────────────────────────────────────────

export function addAgent(
accountHash: string,
endpoint: string,
capability: string,
priceMotes: string,
): void {
load();
loadLocal();
if (!ACCOUNT_HASH_RE.test(accountHash)) {
console.warn(`[AgentStore] Rejecting invalid account hash: ${accountHash}`);
return;
Expand All @@ -71,30 +105,52 @@ export function addAgent(
pricePerTask: priceMotes,
active: true,
reputationScore: 5000,
tasksCompleted: 0,
source: "local",
});
save();
saveLocal();
}

export function getAllAgents(): AgentRecord[] {
load();
loadLocal();
return Array.from(agents.values())
.filter(a => a.active)
.sort((a, b) => b.reputationScore - a.reputationScore);
}

export function getAgentsByCapability(capability: string): AgentRecord[] {
load();
loadLocal();
return Array.from(agents.values())
.filter(a => a.active && a.capability === capability)
.sort((a, b) => b.reputationScore - a.reputationScore);
}

export function updateAgentReputation(accountHash: string, score: number): void {
loadLocal();
const agent = agents.get(accountHash);
if (agent) {
agent.reputationScore = score;
agents.set(accountHash, agent);
saveLocal();
}
}

export function incrementAgentTasks(accountHash: string): void {
loadLocal();
const agent = agents.get(accountHash);
if (agent) {
agent.tasksCompleted += 1;
agents.set(accountHash, agent);
saveLocal();
}
}

/**
* Seed agents using the coordinator's own account hash.
* Called on startup when no agents exist (e.g. fresh Render deploy).
*/
export async function seedCoordinatorAgents(): Promise<void> {
load();
loadLocal();
if (agents.size > 0) return;

try {
Expand Down Expand Up @@ -124,3 +180,7 @@ export async function seedCoordinatorAgents(): Promise<void> {
console.warn(`[AgentStore] Could not seed coordinator agents: ${err}`);
}
}

// Auto-sync with chain on module load (non-blocking)
loadLocal();
syncWithChain().catch(() => {});
Loading
Loading