Local Agent

Tools and Plugins

Configure Agent Tools and provide Plugins through City

Tools and Plugins

Tools and Plugins both participate in Session execution, but ownership differs: Agent owns custom Tools; City owns unique Plugin instances, configuration projection, and lifecycle.

Custom Tools

A local Agent directly receives custom Tools:

const agent = new Agent({
  id: "repo-helper",
  tools: {
    my_tool,
  },
});

They are merged with the Tools of the Workspace selected by the Session. Name conflicts throw instead of silently replacing a Tool.

Workspace and Shell

Workspace provides grep, find, read, write, and edit. Configure Shell explicitly when command execution is required:

import { Agent } from "@downcity/agent";
import { MicrosandboxProvider } from "@downcity/sandbox-microsandbox";
import { Shell, Workspace } from "@downcity/city";

const agent = new Agent({ id: "repo-helper" });
const workspace = new Workspace({
  id: "project",
  path: "/path/to/project",
  shell: new Shell({
    sandbox_provider: new MicrosandboxProvider(),
  }),
  runtime_path: "/path/to/downcity-runtime/project",
});

Shell adds shell_exec and shell_session. File and search Tools remain restricted to the Workspace root. Approvals and asynchronous questions belong to a specific Session and use session.interactions() and session.respond(...).

City provides Plugins

The Agent constructor does not accept Plugins. Once registrations are provided to City, every Agent receives them automatically:

import { Agent } from "@downcity/agent";
import { City } from "@downcity/city";
import { create_builtin_plugin_registrations } from "@downcity/plugins";
import { Workspace } from "@downcity/city";

const workspace = new Workspace({
  id: "project",
  path: "/path/to/project",
});
const agent = new Agent({ id: "repo-helper", model });
const city = new City({
  workspaces: [workspace],
  plugins: create_builtin_plugin_registrations(),
});

city.agents.add(agent);

City automatically exposes every registered Plugin to every Agent. A running City can update the collection through city.plugins.add() and city.plugins.remove().

Each Plugin ID identifies one instance inside a City with a City-level initialize/dispose lifecycle. Agents and Workspaces provide call context; they do not own Plugin lifecycle.

City-level configuration

The host provides one Config store for each Plugin. Configuration does not enter Agent or determine instance count:

const city = new City({
  plugin_host: {
    config: (plugin_id) => open_plugin_config_store(plugin_id),
  },
});

Each Plugin ID has one configuration inside a City. A Plugin models its own account collection and selection rules when needed; City does not provide a generic named-configuration layer. After saving Config, the Plugin refreshes any affected long-lived resources.

Calling Plugins

Host code calls Actions and Hooks through an Agent/Workspace execution entry:


const result = await city.plugins.scope({ agent_id: agent.id, workspace_id: workspace.id }).run_action({
  plugin: "memory",
  action: "search",
  payload: { query: "project conventions" },
});

const enriched = await city.plugins.scope({ agent_id: agent.id, workspace_id: workspace.id }).pipeline("chat.enrich_message", input);
await city.plugins.scope({ agent_id: agent.id, workspace_id: workspace.id }).guard("review.require_checked", enriched);

When Actions exist, Session receives the plugin_read and plugin_call Tools. The model can inspect an Action schema before sending a validated payload. Plugin system(context, execution_context) text is injected at Session prompt checkpoints.

Direct communication through PluginContext

Plugin author APIs come from @downcity/city/plugin. Action, Hook, System, and Availability receive a PluginContext with restricted city, agent, workspace, session, turn, config, and storage handles.

context.session and context.agent.sessions are directly callable in-process objects; a Plugin does not need to retain only session_id and query the host again. Use context.workspace.files for Workspace files and context.storage.files for private Plugin files.

HTTP

A Plugin may declare structured HTTP routes. City mounts and authenticates them and injects the request-specific PluginContext. Plugins do not create independent servers.

See also