Local Agent

Expose City over RPC and HTTP

Use City-owned HTTP and RPC services to expose Agents

Expose City over RPC and HTTP

External services belong to City. Agents remain the execution subjects; City owns the network listeners and routes requests to its Agents and Workspaces.

pnpm add @downcity/agent @downcity/workspace

Start a City transport

import { Agent, City } from "@downcity/agent";
import { Workspace } from "@downcity/workspace";

const workspace = new Workspace({ id: "project", path: process.cwd() });
const agent = new Agent({ id: "assistant", model });
const city = new City({ workspaces: [workspace] });
city.agents.add(agent);

const rpc = await city.rpc({ host: "127.0.0.1", port: 15314 });
const http = await city.http({ host: "127.0.0.1", port: 5314 });
console.log(rpc.binding()?.url); // rpc://127.0.0.1:15314
console.log(http.binding()?.url); // http://127.0.0.1:5314

await city.close();

City owns one HTTP transport and one RPC transport. Both route by Agent ID and Workspace ID:

  • HTTP: http://127.0.0.1:5314/agents/assistant/workspaces/project
  • RPC: rpc://127.0.0.1:15314/assistant/project

The protocol is NDJSON for RPC. Authentication, CORS, and other host-level policies belong to the City host.

Start both transports together

await city.listen({
  http: { host: "127.0.0.1", port: 5314 },
  rpc: { host: "127.0.0.1", port: 15314 },
});

await city.close();

city.listen() returns after both listeners are ready. Use city.http() or city.rpc() when the transports need independent lifecycle control.

Relationship with RemoteAgent

import { RemoteAgent } from "@downcity/agent";

const agent = new RemoteAgent({
  url: "http://127.0.0.1:5314/agents/assistant/workspaces/project",
});
const session = await agent.sessions.create();

RemoteAgent consumes the City endpoint; it does not create or configure the server-side Agent.