Services Overview
Understand what services are in the Agent SDK, where they fit, and how they differ from tools and sessions
Services Overview
In the Agent SDK, services means a set of long-lived capability instances.
Typical usage:
import { Agent, ChatService } from "@downcity/agent";
const agent = new Agent({
id: "repo-helper",
path: "/path/to/project",
tools: {},
services: [
new ChatService({
telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN!,
},
}),
],
});One sentence to remember
toolsare closer to per-call functionsservicesare closer to long-lived runtime instances owned by the agent
What services are good for
Services are especially good for:
- channel bot state
- queue workers
- schedulers or cron engines
- shell session pools
- contact or inbox state
- your own domain-level long-lived runtime objects
If a capability should outlive a single session, it usually belongs in a service more than a tool.
Relationship between services and sessions
A session is the object that executes one conversation or task run.
A service is an object the agent keeps for a longer lifetime.
That means:
- one agent can have many sessions
- those sessions can share the same service instance
This is also why SDK service state should usually live on instance fields instead of module-level singletons.
What the SDK publicly exposes today
The main public service-related SDK exports today are:
BaseServiceChatService
Where:
BaseServiceis the base class for custom servicesChatServiceis the most direct built-in service entry for SDK usage today
What the full runtime currently has built in
The Downcity runtime currently registers these built-in services:
chatcontacttaskmemoryshell
But keep the distinction clear:
- "exists in the runtime registry" does not automatically mean "fully documented as a primary public SDK API"
- in explicit SDK injection scenarios,
ChatServiceis still the most direct built-in service path today
When services start
This is an important real boundary.
In the current SDK:
new Agent({ services: [...] })only assembles instances and does not start I/O during constructionagent.start({ http: { ... }, rpc: true })callsensureServicesStarted()first- so the most stable automatic trigger for
service.lifecycle.start()is when you expose the agent over HTTP or RPC
If your custom service only provides actions or instance methods, that is usually fine.
If it depends on an explicit startup phase, design around this boundary.
System prompt injection
The current local SDK session prompt path injects service.system(context) from explicitly provided services.
The SDK path currently injects:
- the SDK core fallback, when no
instructionwas provided - caller-provided static
instruction service.system(context)- registered
plugin.system(context) - stable session context, including the session creation reference time and timezone
So services in the SDK can provide:
- lifecycle
- long-lived state
- channel and runtime capability ownership
- stable service-owned system rules