Local Agent

Agent Constructor Options

Detailed explanation of the local Agent id, path, instruction, model, env, tools, and plugins options

Agent Constructor Options

The local Agent is constructed like this:

new Agent({
  id,
  path,
  model,
  env,
  instruction,
  tools,
  shell,
  plugins,
  Session,
})

id

The stable identifier of the agent.

It affects the SDK session storage path:

<projectRoot>/.downcity/agents/<agentId>/...

Recommended properties:

  • stable
  • URL-safe
  • not changed casually

That is because it directly controls the session storage partition.

path

The project root directory bound to the current agent.

The SDK uses it to:

  • load project config
  • organize session storage

If path is wrong, many later behaviors will feel confusing because the agent will read config and write data in the wrong context.

instruction

Static caller-provided instructions for the local SDK Agent.

new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  instruction: [
    "You are a concise code assistant.",
    "Prefer direct file references when explaining code.",
  ],
});

Key points:

  • instruction is static and cache-friendly
  • the SDK does not render dynamic variables inside it
  • the SDK does not read PROFILE.md or SOUL.md by itself
  • if you omit instruction, the SDK uses its minimal core instruction fallback

If a host such as city wants project markdown files to affect the agent, that host should read those files and pass their final text through instruction.

model

The default LanguageModel instance for sessions created under this agent.

new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  model: openai.responses("gpt-5"),
});

Key points:

  • the SDK does not resolve provider or model IDs for you
  • the host must still create the LanguageModel instance first
  • this becomes the default model for session execution
  • session.set({ model }) can still override it for one specific session

env

The environment snapshot visible to this agent.

new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  env: {
    OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "",
  },
});

Key points:

  • the SDK only consumes the explicit environment map you pass in
  • the SDK no longer splits global env and agent env
  • if a host needs layered env merging, it should merge first and then pass the final result through env
  • these values participate in config resolution and runtime context assembly, but are not written back to the system environment automatically

Updating env at runtime

After constructing the agent, you can also update or replace its environment variables at runtime. All plugins, sessions, and the shell share the same env reference, so the following methods take effect immediately:

const agent = new Agent({ id, path, env: { FOO: "1" } });

agent.getEnv();              // => { FOO: "1" }
agent.patchEnv({ BAR: "2" }); // merge
agent.patchEnv({ FOO: null }); // null means delete
agent.setEnv({ ONLY: "ok" });  // replace all

Key points:

  • getEnv() returns a shallow snapshot, so mutating it does not affect the agent
  • patchEnv() treats null / undefined as delete; other values are coerced to string
  • setEnv() clears the current env first and then writes the new values, keeping the same underlying reference
  • Runtime changes live in memory only; they are not written back to the project .env or to the system environment

tools

The default tool set at the agent level.

Key points:

  • it belongs to the agent, not the session
  • sessions created by this agent reuse the same tool collection

This is a good fit when multiple sessions should share a stable default tool set.

shell

Optional built-in shell capability.

import { Agent } from "@downcity/agent";
import { Shell } from "@downcity/shell";

const agent = new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  shell: new Shell(),
});

Key points:

  • Shell is not a plugin
  • the Agent mounts shell tools from the Shell instance
  • session and turn context are wired internally
  • approval APIs are agent.approvals(), agent.approve(...), and agent.deny(...)

plugins

This accepts already-instantiated BasePlugin objects.

For example:

import { Agent } from "@downcity/agent";
import { SkillPlugin, WebPlugin } from "@downcity/plugins";

const agent = new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  tools: {},
  plugins: [new SkillPlugin(), new WebPlugin()],
});

The SDK creates an independent plugin registry for the current Agent.

Key points:

  • pass instances such as new SkillPlugin() rather than raw plugin definitions
  • it does not register every built-in plugin by default
  • it does not reuse the global plugin manager
  • duplicate plugin names throw immediately
  • agent.plugins and AgentContext.plugins use this registry
  • if you want the full built-in set, pass plugins: createBuiltinPlugins() from @downcity/plugins

Session

Advanced local-only custom Session class for sessions created by this agent.

Use this when you want to replace the local session implementation or inject custom Session-level Composers. Agent only uses the class to create and restore sessions; it does not inspect Composer behavior.

import {
  Agent,
  Session,
  type SessionOptions,
  type SessionSystemComposer,
} from "@downcity/agent";

const systemComposer: SessionSystemComposer = {
  name: "custom_system",
  async resolve() {
    return [{ role: "system", content: "Always answer tersely." }];
  },
};

class CustomSession extends Session {
  constructor(options: SessionOptions) {
    super({
      ...options,
      composers: {
        systemComposer,
      },
    });
  }
}

const agent = new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  Session: CustomSession,
});

Key points:

  • pass a class, not an already-created session instance
  • Agent can then create multiple sessions with different sessionId values
  • Composer customization stays inside the custom Session class
  • RemoteAgent does not support local Session classes because it only accesses a remote runtime