怎么使用 Downcity
从安装到日常使用,理解 Downcity 如何作为 Agent 基础设施支撑多个产品和工作流
怎么使用 Downcity
Downcity 的核心用法可以理解成一句话:用一套可复用 Agent 基础设施,承载多个本机 Agent、产品表层和自动化工作流。
最常用的是本机 Town 路线:安装 downcity,启动 town,在你的代码仓库里创建 agent,然后通过 CLI 或 Console 与它对话。需要嵌入到应用时,再使用 @downcity/agent SDK。需要多个产品或线上服务复用模型、账号、usage、支付和 Service 能力时,再接入 City runtime。
你会用到什么
| 入口 | 什么时候用 |
|---|---|
town | 本机启动和管理 Agent、Console、City 连接、插件和任务。 |
city | 管理已部署的 City runtime、模型、token 和服务资源。 |
@downcity/agent | 在 Node 应用里嵌入本地 Agent,或调用远程 Agent。 |
@downcity/city | 构建可部署的 City runtime,让多个产品或 Town 复用后端能力。 |
| Console | 在浏览器里查看和操作本机 Agent。 |
| Chrome Extension | 把网页内容、选区和浏览器上下文发送给 Agent。 |
1. 安装 CLI
npm install -g downcity
# 或
pnpm add -g downcity安装后会得到两个命令:
town --version
city --version日常本机使用主要靠 town。city 主要用于部署或管理 City runtime。
2. 首次初始化 Town
town init这一步会初始化全局配置和本机存储,默认位于 ~/.downcity/。Console token、运行状态和全局环境都会放在这里。
3. 连接 City
Agent 启动前需要连接到 City。模型目录、provider、key 和服务资源由 city 管理;town 只把当前 City 会话导入本机 Agent runtime:
city
town city use
town city status如果已经在某个 agent 项目里,可以用 City 里的模型 ID 更新当前项目绑定:
town config set execution.modelId <modelId>项目里的 downcity.json 会记录这份绑定:
{
"name": "my-agent",
"version": "1.0.0",
"execution": {
"type": "api",
"modelId": "quality"
}
}4. 把一个项目变成 Agent
进入你的项目根目录:
cd /path/to/your-repo
town agent create .创建后通常会生成:
PROFILE.md:agent 的角色、边界和工作方式SOUL.md:更偏长期人格和协作风格的描述downcity.json:项目运行配置.downcity/:运行时数据、日志、会话、缓存和任务目录.agents/skills/:项目内技能目录
创建完成后,优先检查三件事:
downcity.json.execution.modelId是否指向可用模型PROFILE.md是否写清楚这个 Agent 应该怎么帮助你- 当前项目是否真的需要启用聊天渠道或其他插件
5. 启动 Town 和 Agent
先启动本机 Town runtime:
town start如果想同时打开浏览器 Console:
town start --console然后在 agent 项目里启动 Agent:
town agent start
town agent status
town agent list --running默认是后台 daemon,适合长期运行。如果想在当前终端看日志:
town agent start --foreground停止和重启:
town agent stop
town agent restart6. 和 Agent 对话
一次性发送消息:
town agent chat --message "总结一下这个仓库的结构"指定目标 agent:
town agent chat --to <agentId> --message "检查最近一次任务状态"进入持续对话模式:
town agent chat在持续对话里输入 /exit 或 /quit 退出。
如果你启动了 Console,可以在浏览器里打开本机控制台。默认 Console 端口是:
http://127.0.0.1:53157. 使用内建能力
Downcity 的日常能力大多通过 plugin 命令暴露。常见命令包括:
town skill
town task list
town memory
town shell
town web
town asr
town tts典型场景:
- 用
town skill管理 Agent 技能 - 用
town task创建长期存在的自动化任务 - 用
town memory管理可复用记忆 - 用
town shell执行项目相关命令 - 用
town web处理网页访问能力 - 用
town asr/town tts接入语音能力
如果某个能力需要延迟执行或定时执行,优先查看 town task 和 plugin schedule 文档。
8. 接入 Chrome Extension
Chrome Extension 适合把网页内容交给 Agent:
- 在网页中选中文本,使用页内输入框把选区发送给 Agent。
- 没有选区时,可以按页面全文模式发送。
- 通过 Extension Popup 选择 Agent、查看本页发送历史,并快速回填内容。
它适合做网页总结、网页问答、资料整理、页面上下文写作和浏览器内工作流。
9. 用 SDK 嵌入应用
如果你要在自己的 Node 应用里直接创建 Agent,可以用 @downcity/agent:
import { Agent } from "@downcity/agent";
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY!,
});
const agent = new Agent({
id: "repo-helper",
path: "/path/to/project",
tools: {},
});
const session = await agent.createSession();
await session.set({
model: openai.responses("gpt-5"),
});
const turn = await session.prompt({
query: "总结当前项目结构",
});
const result = await turn.finished;
console.log(result.text);SDK 模式下,模型由你的宿主应用创建并注入。@downcity/agent 负责 Agent runtime,不负责解析 City 模型目录里的 modelId。
如果 Agent 已经作为 HTTP 服务运行,也可以使用远程 Agent:
import { RemoteAgent } from "@downcity/agent";
const agent = new RemoteAgent({
url: "http://127.0.0.1:15314",
});
const session = await agent.createSession();
const turn = await session.prompt({
query: "检查最近一次任务执行状态",
});
const result = await turn.finished;
console.log(result.text);9.1 不使用 OpenAI SDK 传入模型
@downcity/agent 不要求你一定使用 OpenAI SDK。model 支持两种输入:
- AI SDK
LanguageModel - City runtime 返回的
CityModel
如果你使用 City,City catalog 返回的 model 可以直接传给 Agent:
import { Agent } from "@downcity/agent";
import { City } from "@downcity/city";
const city = new City({
role: "user",
city_url: "http://127.0.0.1:43127",
town_id: "town_xxx",
user_token: "ub_xxx",
});
const catalog = await city.ai.listModels();
const model = catalog.default();
if (!model) {
throw new Error("No available City model");
}
const agent = new Agent({
id: "city-backed-agent",
path: "/path/to/project",
model,
});如果你不使用 City,也可以自己提供一个 AI SDK LanguageModel。这时 model 不能只是一个 (prompt) => string 函数;它需要实现模型协议。最小需要提供:
specificationVersionprovidermodelIdsupportedUrlsdoGenerate()doStream()
一个最小 echo model 可以这样写:
import type { LanguageModel } from "ai";
const echo_model = {
specificationVersion: "v3",
provider: "local",
modelId: "echo",
supportedUrls: {},
async doGenerate(options) {
const prompt = options.prompt
.flatMap((message) => message.content)
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n");
return {
content: [{ type: "text", text: `Echo: ${prompt}` }],
finishReason: { unified: "stop", raw: "stop" },
usage: {
inputTokens: {
total: undefined,
noCache: undefined,
cacheRead: undefined,
cacheWrite: undefined,
},
outputTokens: {
total: undefined,
text: undefined,
reasoning: undefined,
},
},
warnings: [],
};
},
async doStream(options) {
const prompt = options.prompt
.flatMap((message) => message.content)
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n");
const text = `Echo: ${prompt}`;
return {
stream: new ReadableStream({
start(controller) {
controller.enqueue({ type: "stream-start", warnings: [] });
controller.enqueue({ type: "text-start", id: "text-1" });
controller.enqueue({
type: "text-delta",
id: "text-1",
delta: text,
});
controller.enqueue({ type: "text-end", id: "text-1" });
controller.enqueue({
type: "finish",
finishReason: { unified: "stop", raw: "stop" },
usage: {
inputTokens: {
total: undefined,
noCache: undefined,
cacheRead: undefined,
cacheWrite: undefined,
},
outputTokens: {
total: undefined,
text: undefined,
reasoning: undefined,
},
},
});
controller.close();
},
}),
};
},
} satisfies LanguageModel;
const agent = new Agent({
id: "local-echo-agent",
path: "/path/to/project",
model: echo_model,
});如果你要接自己的网关或私有模型,也是在 doGenerate() / doStream() 里调用你的 HTTP API,然后把返回结果转换成 AI SDK 的模型协议。
10. City SDK 怎么使用
只在本机使用 Agent 时,通常不需要先部署 City runtime。
当你需要下面这些能力时,再考虑 City SDK:
- 多个 Town 或多个产品复用同一套 City 模型目录
- 给终端用户签发
user_token - 统一处理模型调用、用量、余额、支付和账号
- 把 Downcity 能力接入正式产品后端
- 在 Node 或 Edge 环境里部署 City runtime
City SDK 当前由 @downcity/city 提供,分成三侧:
- 服务端用
City、AIService、Provider创建可部署 runtime - 可信后端用
Admin City创建 town、签发user_token - 产品侧用
User City调用 AIService 或自定义 Service
10.1 安装
pnpm add @downcity/city @hono/node-server drizzle-orm better-sqlite3 ai10.2 服务端创建 City runtime
下面是一个最小可运行的本地 City runtime。它使用 SQLite,并注册一个 echo 模型,方便你先把调用链跑通:
import { serve } from "@hono/node-server";
import { AIService, City, Provider } from "@downcity/city";
import { createUIMessageStreamResponse } from "ai";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
const sqlite = new Database("./data.sqlite");
sqlite.pragma("journal_mode = WAL");
const db = Object.assign(drizzle(sqlite), {
$client: { exec: (sql: string) => sqlite.exec(sql) },
});
const base = new CityBase({ db, dialect: "sqlite", raw: sqlite });
const echo = new Provider("echo", {
text: async (ctx) => ({
id: crypto.randomUUID(),
role: "assistant",
parts: [
{
type: "text",
text: `Echo: ${String(ctx.input.prompt ?? "")}`,
state: "done",
},
],
}),
stream: async (ctx) =>
createUIMessageStreamResponse({
execute: async ({ writer }) => {
writer.write({
type: "text-delta",
textDelta: `Echo: ${String(ctx.input.prompt ?? "")}`,
});
},
}),
});
const ai = new AIService();
ai.use(
echo.model({
id: "local-echo",
name: "Local Echo",
default: true,
}),
);
base.use(ai);
await base.health();
serve({
fetch: base.router().fetch,
port: 43127,
hostname: "127.0.0.1",
});启动后,City 会暴露这些核心路由:
GET /v1/ai/models
POST /v1/ai/text
POST /v1/ai/stream
POST /v1/ai/chat/completions10.3 可信后端签发 user_token
user_token 只能在可信环境签发,不要把 admin_secret_key 放到浏览器前端。
import { City } from "@downcity/city";
const admin = new City({
role: "admin",
city_url: "http://127.0.0.1:43127",
admin_secret_key: process.env.DOWNCITY_CITY_ADMIN_SECRET_KEY,
});
const town = await admin.towns.create({
name: "Demo Town",
});
const user = await admin.towns.tokens.apply({
town_id: town.town_id,
user_id: "user_123",
metadata: {
plan: "pro",
},
ttl: "7d",
});
console.log({
town_id: town.town_id,
user_token: user.user_token,
});你的产品后端把 town_id 和 user_token 返回给产品侧 client。后续所有用户侧请求都带这两个值。
10.4 产品侧调用 City
产品侧使用 User City:
import { City } from "@downcity/city";
const client = new City({
role: "user",
city_url: "http://127.0.0.1:43127",
town_id: "town_xxx",
user_token: "ub_xxx",
});
const catalog = await client.ai.listModels();
const model = catalog.default();
const result = await client.ai.text({
model,
prompt: "你好 Downcity",
});
console.log(result.parts);同一个 model 也可以直接传给 Agent:
import { Agent } from "@downcity/agent";
const agent = new Agent({
id: "city-backed-agent",
path: "/path/to/project",
model,
});如果你想把某个模型固定成操作句柄:
const local_echo = client.ai.model("local-echo");
const result = await local_echo.text({
prompt: "解释一下 City SDK 的调用链",
});10.5 调用自定义 Service
服务端可以挂载自己的业务 Service:
import { Service } from "@downcity/city";
const notes = new Service({
id: "notes",
name: "Notes",
});
notes.action("create", async (ctx) => ({
ok: true,
title: String(ctx.input.title ?? ""),
}));
base.use(notes);产品侧调用:
const result = await client
.service("notes")
.action("create")
.invoke({ title: "First note" });10.6 用 OpenAI 兼容通路
如果你已经有 OpenAI SDK,也可以直接调用 City runtime 的 OpenAI 兼容端点:
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: "http://127.0.0.1:43127/v1/ai",
apiKey: "ub_xxx",
});
const completion = await openai.chat.completions.create({
model: "local-echo",
messages: [{ role: "user", content: "hello" }],
});City SDK 的核心调用链是:
产品前端 / 产品后端
-> User City 携带 town_id + user_token
-> City runtime 校验 token
-> AIService / 自定义 Service
-> Provider 或业务 action更完整的 City 路线:
city --help然后按City 快速开始文档完成:
- 创建 City runtime
- 添加模型
- 签发 user token
- 在 client 侧通过 User City 调用 City 里的 Service
对应文档:
推荐日常工作流
# 1. 启动本机 runtime 和 Console
town start --console
# 2. 进入项目并启动 agent
cd /path/to/your-repo
town agent start
# 3. 检查状态
town status
town agent status
# 4. 直接在终端对话
town agent chat --message "今天先帮我读一下项目结构"
# 5. 创建长期任务
town task create --title "Daily check" --description "Run tests" --when "@manual"排障优先看什么
如果 Agent 没有正常工作,按这个顺序查:
town status:确认 Town runtime 是否运行town agent status:确认当前 Agent 是否运行town city status:确认 Town 已导入当前 City 连接- 实际发送一次请求:确认模型真实可调用
.downcity/logs/:查看当前项目运行日志town agent doctor:诊断僵尸进程或损坏状态