Downcity
Services

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

  • tools are closer to per-call functions
  • services are 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:

  • BaseService
  • ChatService

Where:

  • BaseService is the base class for custom services
  • ChatService is 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:

  • chat
  • contact
  • task
  • memory
  • shell

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, ChatService is 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 construction
  • agent.start({ http: { ... }, rpc: true }) calls ensureServicesStarted() 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 instruction was 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
  1. Which built-in services exist today
  2. Built-in service overview
  3. ChatService
  4. Custom service
  5. Service lifecycle