Skip to content
Pilotoo
v0.1

Agents

An agent is a definition: a prompt, a model profile, and the tools, skills and knowledge it may use. Definitions are data, so adding or editing an agent never needs a deploy. The demo API ships four of them as YAML files.

Drop a file in .pilotoo/agents/. The file name is the id.

.pilotoo/agents/researcher.yaml
id: researcher
name: Researcher
description: "Use to find current, verifiable information on the web. Returns findings with their sources."
status: active
runtime:
systemPrompt: You are the Researcher. Search before you answer, and cite every source.
profileId: default
tools: [web_search]
builtinTools: []
skills: []
knowledge: []
presentation: {}

Restart is not needed for an edit to a system agent: the next run uses it. The profileId names a model profile; tools names tools you registered.

runtime.tools decides which registered tools the agent gets:

tools: null # every registered tool except opt-in ones
tools: [web_search] # exactly these, opt-in tools included
tools: [] # no registered tool

runtime.builtinTools is a separate, closed set: an agent gets exactly the built-ins it lists, chosen from ls, read_file, write_file, edit_file, delete, glob, grep, execute, task.

An orchestrator that delegates to the other demo agents lists task:

.pilotoo/agents/orchestrator.yaml
runtime:
builtinTools: [task]
subagents: [researcher, writer, analyst]

See Subagents.

A catalog read and a run that names no agent use one rule:

  1. the agent the AGENT_DEFAULT_ID environment variable names, when it is active (it may be a system agent). When the variable is unset, the built-in seed agent is used;
  2. else the first active agent by name among the tenant’s own;
  3. else the first among the system agents.

If the configured id matches no active agent, the rule falls through to the next step. A tenant with no active agent has no default: the catalog omits defaultAgentId, so a client must handle its absence.

Both are YAML files under the configuration folder, with different lifetimes.

System agents Templates
Location PILOTOO_CONFIG_DIR/agents/*.yaml (default .pilotoo/agents/) PILOTOO_CONFIG_DIR/templates/*.yaml (default .pilotoo/templates/; AGENT_TEMPLATES_DIR moves it alone)
Seen by Every tenant, always Copied into a tenant on its first access, once
Edits An edit of the file reaches every tenant on its next run. Tenants cannot edit one. A copy never changes when the template does. A deleted agent is not copied again.

A missing folder means none, and an invalid file stops the boot. A template whose id a system agent holds also stops the boot. Templates name profile default, never a model, so a host with its own profile source must provide that profile.

To customize a system agent, a manager duplicates it into a new id of the tenant and edits the copy. Creating, updating, importing or duplicating onto a system agent’s id is a 409 with code system-agent.

By default a tenant’s own agents live in PostgreSQL. To keep them elsewhere, implement one read port:

import type { AgentDefinitionSource } from '@pilotoo/agent-runtime';
const definitions: AgentDefinitionSource = {
getDefinition: (identity, id) => db.agents.find(identity.tenant, id),
listDefinitions: (identity) => db.agents.all(identity.tenant),
};

Pass it as agentDefinitionSource. A host without a database can use createDirectoryAgentDefinitionSource(dir), which reads one <id>.yaml per agent for every caller and versions it by modification time.

The kit guarantees:

  • Definitions are read by the caller’s tenant, so an id another tenant holds is an unknown agent, in the catalog and in a run.
  • What the source returns is validated against agentDefinitionSchema. A draft or malformed definition never serves a run.
  • A graph is cached per tenant and id, and rebuilt only when the definition’s updatedAt, or the profile’s id or updatedAt, changes.

Writing is a separate, optional port. Without it there is no editor, and every /agent-admin route except capabilities answers 501.

import type { AgentDefinitionStore } from '@pilotoo/agent-runtime';
const store: AgentDefinitionStore = {
create: (identity, definition) => db.agents.insert(identity.tenant, definition),
update: (identity, definition) => db.agents.replace(identity.tenant, definition),
delete: (identity, id) => db.agents.remove(identity.tenant, id),
};

Pass it as agentDefinitionStore. Managing is authorized against the roles in AGENT_MANAGER_ROLES (comma-separated, empty by default, so nobody manages). The roles are the vocabulary of your authentication.

The store owns the refusals that must be atomic with the write: AgentAlreadyExistsError, AgentNotFoundError and LastActiveAgentError (deleting or drafting a tenant’s last active agent). update replaces the whole definition, and the version (updatedAt) is the store’s.

The demo web app’s Settings page, built from SettingsLayout in @pilotoo/ui/settings, is the reference editor, and createAgentAdmin in @pilotoo/chat-core is the typed client.

Field Meaning
id, name, description description also tells an orchestrator when to use this agent as a subagent.
status active, or draft. A draft never serves a run.
runtime.profileId The model profile the agent runs on.
runtime.tools null for every registered tool except opt-in ones; a list for exactly those names.
runtime.builtinTools Required. A closed set of built-in tools.
runtime.skills null enables every available skill; a list enables exactly those names. See Skills.
runtime.knowledge Opaque ids only your knowledge source interprets.
runtime.subagents Ids of other agents this one delegates to. Optional.
presentation Free-form data for your UI. The runtime never reads it.

The schema is agentDefinitionSchema in packages/agent-runtime/src/agent-definition.ts.

All under /agent-admin, for managers:

Route Answer
GET capabilities { canManageAgents } for any identified caller; false without a store.
GET agents, GET agents/:id Managed agents, drafts included. Malformed rows are listed in invalid.
POST agents 201.
PUT agents/:id 200, full replace; the URL’s id wins.
DELETE agents/:id 204.
GET agents/:id/export text/yaml.
POST agents/import { yaml }; creates the agent in the caller’s tenant.
POST agents/:id/duplicate { id, name? }; 201, a copy created in the caller’s tenant.
GET profiles, GET options Profile summaries; and the tools, skills and agents an agent can select.
Status Meaning
403 Not a manager.
400 Validation, with issues of { path, message }.
404 Unknown id.
409 With code: agent-exists, last-active-agent, system-agent or referenced-agent.
422 A stored agent that fails its schema (delete still removes it).
501 No store.