Agent SDK Architecture

Responsibilities and composition of City, Agent, Workspace, Session, Power, and Shell

Downcity separates the execution subject, project resources, and Power runtime. Agent owns identity, instruction, model, custom Tools, and Sessions. Workspace owns project resources and Shell. City owns unique Power instances, lifecycle, and Agent/Group collections.

import { Agent } from "@downcity/agent";
import { City } from "@downcity/city";
import { create_builtin_power_registrations } from "@downcity/powers";
import { Shell, Workspace } from "@downcity/city";

const agent = new Agent({ id, model, instruction });
const workspace = new Workspace({ id: "project", path, shell });
const city = new City({
  workspaces: [workspace],
  powers: create_builtin_power_registrations(),
});
city.agents.add(agent);
// Session 在创建时选择 Workspace。
const session = await agent.sessions.create({ workspace });

Responsibility model

The dependency direction stays explicit:

  • @downcity/agent depends only on neutral resource protocols from @downcity/type.
  • @downcity/city depends on Agent and injects Workspace, Tools, and SessionHooks through its internal binding; Agent does not depend back on City.
  • Hosts choose a Sandbox Provider when constructing each Shell; City does not understand the backend.
  • Edge adapters use @downcity/type/workspace without loading Node.js local implementations.

Agent is the subject

An Agent is reusable across projects. It is never configured with one permanent Workspace.

const agent = new Agent({
  id: "repo-helper",
  model,
  instruction: "Maintain the current project.",
  tools: { company_search: company_search_tool },
});

city.agents.add(agent);

const frontend = await agent.sessions.create({ workspace: frontend_workspace });
const backend = await agent.sessions.create({ workspace: backend_workspace });

Agent does not own Powers. City owns one instance per Power ID and projects every Power into an Agent/Workspace execution entry. Every Power Action receives the current PowerContext with directly callable restricted Agent, Workspace, Session, and Turn handles. Powers use one City-level initialize/dispose lifecycle; Agent/Workspace only defines the current call scope.

Workspace is the resource boundary

Workspace owns:

  • A stable Workspace ID and normalized project path.
  • Rooted project file access and File/Search tools.
  • Workspace environment variables.
  • An optional Shell and its Sandbox adapter.
    • It never owns Agent or Session runtime storage. City provides the domain-neutral StorageProvider.

Workspace does not create SessionStore, understand Agent IDs, register Powers, call models, or create Storage. The constructor has no runtime data-root option.

Shell belongs to Workspace because command execution operates on Workspace resources. The public entry is:

import { Shell, Workspace } from "@downcity/city";

There is no separate @downcity/shell package. Platform Sandbox packages remain independently installable and are injected into Shell.

Session is the execution boundary

agent.sessions.create({ workspace }) combines Agent and Workspace for one Session without merging ownership. Agent, Session, and the City execution projection provide:

  • The final Tool set from Workspace tools, Agent custom tools, and Power bridge tools.
  • AgentSessions and the Agent domain LocalSessionStore.
  • Agent/Workspace/Session/Turn-aware PowerContext and an execution lease.
  • Logs, scheduled Power Actions, and Shell binding.

Tool name conflicts fail during composition; no source silently overwrites another. The same Agent can hold multiple Sessions with different Workspace resources. Workspace resources may be registered in City or managed by the host outside City.

Private storage

When City receives a local persistent Storage Provider, runtime state is centralized outside the project:

~/.downcity/
└── agents/
    └── <agent_id>/
        ├── sessions/
        ├── powers/
        ├── logs/
        └── schedule.jsonl

City Storage is a bottom-level file/database capability. Agent code interprets the agents/<agent_id>/ business scope and creates Session and Power stores inside it. Project File/Search tools use a different rooted FileSystem, so they cannot inspect or change Session history, Power state, or runtime data.

Environment and checkpoints

Environment variables describe the project execution environment and therefore belong to Workspace:

<workspace>/.env < WorkspaceOptions.env

set_env() and patch_env() update the Workspace snapshot and synchronize Shell. Existing Sessions commit the new environment at the next Step checkpoint, avoiding context changes halfway through a model call.

Power registry changes, Session model changes, and compaction follow the same checkpoint rule.

Lifecycle

city.close() stops registered Agents and Sessions, disposes registered Workspace resources, closes the City Storage Provider, and then closes HTTP/RPC transports. A Workspace managed outside City remains owned by its host.

Package boundaries

PackageOwnsDoes not own
@downcity/typeNeutral cross-package resource protocolsConcrete resources and lifecycle implementations
@downcity/agentAgent, AgentSessions, Session, Store, SessionHooks, model Tool LoopCity composition, Power lifecycle, project resource implementation, platform Sandbox
@downcity/cityCity, Workspace/Shell implementations, Power SDK/runtime, Hook scheduling, RemoteAgent, HTTP/RPC transports, local host dataAgent execution internals, concrete built-in Powers
Sandbox packagesNative process isolation for one platformWorkspace, Agent, Session, or Power business behavior

Continue with Runtime architecture.