Packages

@downcity/agent

Agent, AgentSessions, Session, and execution runtime.

@downcity/agent provides Agent, AgentSessions, Session, and the execution runtime. @downcity/city provides RemoteAgent and City. Agents do not own Plugins. City owns unique Plugin instances, configuration projection, and lifecycle; Plugin author contracts live in @downcity/city/plugin. Workspace resources come from @downcity/city.

const city = new City({ workspaces: [workspace] });
const agent = new Agent({ id: "assistant" });
city.agents.add(agent);
const session = await agent.sessions.create({ workspace });
const turn = await session.prompt({ query: "Hello" });
const result = await turn.finished;

Provide Plugin instances through CityOptions.plugins or city.plugins.add() before use. city.agents.add() only registers an Agent; every City Plugin becomes available automatically.

Read file changes for each turn

A Session bound to a Workspace persists file edits successfully committed by the built-in structured write and edit tools in the final Assistant Message. The diff comes directly from each atomic file-tool mutation instead of scanning the worktree before and after a turn, so changes made by shell commands, scripts, external editors, or other Sessions are excluded. Git is not required.

The data-session-turn-file-diff data part contains per-file status, line counts, and unified diff. Use the exported parser to read it safely:

import { read_session_turn_file_diff_data } from "@downcity/agent/session";

const page = await session.messages();
for (const message of page.items) {
  if (message.type !== "assistant") continue;
  for (const part of message.parts) {
    if (part.type !== "data") continue;
    const file_diff = read_session_turn_file_diff_data(part);
    if (file_diff) console.log(file_diff.files, file_diff.additions, file_diff.deletions);
  }
}

Group and GroupSession

Group is a collaborative subject with its own model, members, dispatch strategy, and GroupSession collection. A GroupSession persists shared messages and dispatch state under groups/<group_id>/sessions/<group_session_id>/.

After the first user message is persisted, the GroupSession uses Group.model to generate a short title asynchronously. The title is canonical metadata stored in meta.json; it is separate from preview_text, which remains the latest-message preview. Later messages do not replace the title. Subscribe to { type: "title", title } for live title changes, or call group_session.rename(title) to persist a manual title and prevent background generation from overwriting it.

const group_session = await group.sessions.create({ workspace });

group_session.subscribe((event) => {
  if (event.type === "title") console.log(event.title);
});

await group_session.prompt({ query: "Review the login flow" });
await group_session.rename("Login flow review");

const sessions = await group.sessions.list();
console.log(sessions[0].title, sessions[0].preview_text);