Agent Constructor Options
Detailed explanation of Workspace env, WorkspaceTools, and local Agent identity, description, instruction, model, tools, and plugins
Agent Constructor Options
The local Agent is constructed like this:
const workspace = new Workspace({
id,
path,
shell,
env,
});
const agent = new Agent({
id,
name,
description,
model,
instruction,
tools,
plugins,
Session,
});
// Session 在创建时选择 Workspace。id
The stable identifier of the agent.
It affects the user-level Agent data path:
~/.downcity/agents/<agent_id>/...Recommended properties:
- stable
- URL-safe
- not changed casually
That is because it directly controls the session storage partition.
name and description
name is the user-visible Agent name and falls back to id when omitted or empty. description is a concise capability profile used by hosts for display and by Group dispatch when selecting members semantically.
const reviewer = new Agent({
id: "reviewer",
name: "Code Reviewer",
description: "Reviews code quality, test coverage, and delivery risks",
instruction: "Give concrete, actionable review suggestions.",
});description does not become a regular AgentSession system instruction and does not replace instruction: the former says what the Agent is suited for, while the latter controls how it works. Group dispatch sees only member id/name/description, not the complete instruction.
workspace and agent.sessions.create({ workspace })
The project resource and security boundary available to the Agent. Workspace owns the project root, file and search tools, and an optional Shell:
const workspace = new Workspace({
id: "project",
path: "/path/to/project",
shell,
});Key points:
- macOS, Linux, and Windows use the same
Workspace pathis resolved to a real absolute directory during construction- file and search tools remain confined to the Workspace
- Shell and file tools share the same project root
- Agent is not bound to a Workspace at construction time;
agent.sessions.create({ workspace })creates a Session execution scope - one Agent can enter multiple Workspaces, each with isolated Sessions, logs, Shell, and plugin context
- multiple Agents may target the same directory, using distinct agent IDs and data partitions
Workspace requires only a stable id, the real project path, and optional Shell and env values. The implementation resolves Downcity's internal data root automatically:
const workspace = new Workspace({
id: "project",
path: "/path/to/project",
shell,
});
// Session 在创建时选择 Workspace。Workspace construction fails immediately when path is empty, missing, or not a directory.
The local implementation writes private data under ~/.downcity by default. Tests and multi-instance hosts can override the root with DC_PLATFORM_ROOT; that environment variable is not part of the Workspace API.
instruction
Static caller-provided instructions for the local SDK Agent.
new Agent({
id: "repo-helper",
instruction: [
"You are a concise code assistant.",
"Prefer direct file references when explaining code.",
],
});Key points:
instructionis static and cache-friendly- the SDK does not render dynamic variables inside it
- the SDK does not read project prompt config files
- if you omit
instruction, the SDK uses its minimal core instruction fallback
Hosts that need additional base instructions should pass them explicitly through instruction.
model
The default ModelClient held by the Agent. Use a CityModel returned by the Federation catalog or create an in-process model through a Provider Adapter.
const model = create_openai_compatible_model({
id: "gpt-5",
upstream_model: "gpt-5",
base_url: "https://api.openai.com/v1",
api_key: process.env.OPENAI_API_KEY!,
});
new Agent({
id: "repo-helper",
model,
});Key points:
- the SDK does not select, persist, or restore model IDs; it only holds
ModelClientinstances CityModelimplementsModelClient, so Agent callsstream(call, signal)directly- a Session without its own model falls back to the Agent model
- a local Session can override the Agent model with
session.set({ model }) - effective model resolution always checks the Session model before the Agent model
Workspace env
The project execution environment owned by the Workspace.
const workspace = new Workspace({
id: "project",
path: "/path/to/project",
env: {
OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "",
},
});Key points:
- the SDK reads the project
.envfirst, then applies explicit Workspaceenvoverrides - env belongs to Workspace rather than Agent
- if a host needs layered env merging, it should merge first and then pass the final result through
env - these values become the Workspace runtime env, but are not written back to the system environment automatically
Updating env at runtime
You can update or replace Workspace environment variables at runtime:
workspace.set_env({ FOO: "1" });
workspace.get_env(); // => { FOO: "1" }
workspace.patch_env({ BAR: "2" }); // merge
workspace.patch_env({ FOO: null }); // null means delete
workspace.set_env({ ONLY: "ok" }); // replace allKey points:
get_env()returns a shallow snapshotpatch_env()treatsnull/undefinedas deleteset_env()replaces the current Workspace envagent.set_instruction()affects only Sessions created afterwards and does not retroactively change existing in-memory Sessions- restored Sessions regenerate from the Agent's current instruction and plugins when
instruction.mdis absent; usesession.syncshot()to refresh an existing Session andsession.snapshot()to persist it - Workspace env and plugin changes commit in existing Sessions at the next Session step checkpoint
- an in-flight provider request and its tool calls keep the env that was effective for the current step
- if the current Session turn has no later step, the queued change is committed when the next Session turn starts
- the Session timeline emits a completed action message when the change becomes effective
- Runtime changes live in memory only; they are not written back to the project
.envor to the system environment
tools
Additional caller-defined tools registered on the Agent.
Key points:
- Workspace provides file, search, and optional Shell tools as WorkspaceTools
- PluginRegistry provides
plugin_readandplugin_call AgentOptions.toolsprovides caller-defined tools- optional tools such as
ask_questionmust be imported from@downcity/agent/toolsand passed explicitly - Agent owns the final merged collection shared by Sessions
- duplicate names across any source throw instead of silently overriding tools
This is a good fit when multiple sessions should share a stable default tool set.
Workspace shell
Optional built-in shell capability.
import { Agent } from "@downcity/agent";
import { Workspace } from "@downcity/city";
import { Shell } from "@downcity/city";
import { MicrosandboxProvider } from "@downcity/sandbox-microsandbox";
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",
});Key points:
- Shell is not a plugin
- Shell only mounts
shell_execandshell_session - Workspace mounts
grep,find,read,write, andedit, even without Shell - session and turn context are wired internally
- approvals belong to a specific Session; use
session.interactions(),session.respond(...), andsession.set({ security }) - file and search tools are always rooted in the Agent project and do not expose a host execution target
Plugins are not Agent constructor options
AgentOptions does not accept plugins. After City receives CityPluginRegistration objects, every registered Agent automatically receives every Plugin:
import { Agent } from "@downcity/agent";
import { City } from "@downcity/city";
import { create_builtin_plugin_registrations } from "@downcity/plugins";
const agent = new Agent({ id: "repo-helper", tools: {} });
const city = new City({
workspaces: [workspace],
plugins: create_builtin_plugin_registrations(),
});
city.agents.add(agent);City owns unique Plugin instances, configuration projection, and lifecycle. Agent owns identity, model, instructions, Tools, and Sessions. Web capabilities still require host configuration; see the Web Plugin guide.
session_composer
Use a factory when you only need to customize model input or context maintenance. The factory is called once for every created, restored, or forked Session, so Composer instances are never shared across Sessions.
import { Agent, DefaultSessionComposer } from "@downcity/agent";
const agent = new Agent({
id: "repo-helper",
session_composer: () => new DefaultSessionComposer(),
});What a Session Composer is
A Composer is the single place that answers what the model sees this step. The caller passes canonical history and the derived store in, and the Composer reads them to produce the model input:
| Method | Purpose |
|---|---|
initialize(input) | Initialize its own derived schema in this Session's session.db; implementations without derived state may leave this empty |
compose(input) | Return the system, messages, and tools for the current model Step |
advance_context(input) | Try to advance derived state under a usage_pressure or provider_context_limit trigger; return true when something actually advanced |
compose(input) may:
- add, remove, or reorder system messages
- filter, transform, or inject model messages for the current Step
- change the tools visible to the current Step
Compaction is not a separate concept; it is one implementation of advance_context(). FullHistorySessionComposer is the built-in implementation that never advances derived state. To use a different compaction algorithm, extend DefaultSessionComposer and override advance_context().
A Composer does not consume the Prompt Queue, control Turns, or modify canonical Messages. It may only write rebuildable derived tables namespaced as composer_<name>_*. Use session_class only when replacing Session behavior itself.