AI Agents
The two platform agents (ask and build), how skills extend them, and the shape of an AgentSchema record
AI Agents
Part of the AI module. In the open edition, agents, tools, and
skills are typed metadata: you author them as source with defineAgent /
defineSkill / defineTool from the open @objectstack/spec/ai package, and an
external AI client (Claude, Cursor, a local model — any MCP client) reaches your
objects, queries, and business Actions through @objectstack/mcp (BYO-AI),
all governed by RLS. This page describes that metadata and the AgentSchema it
validates against. Like the rest of your metadata, you don't hand-write agents —
Claude Code authors them from the objectstack-ai skill; you
os validate and test the tools in the Console
(the loop).
ObjectOS — the in-product chat runtime. The authoring format on
this page is open. The in-product chat runtime that runs agents for end users —
the two platform agents ask and build, all in-product chat, and the
/api/v1/ai/* chat endpoints — ships in ObjectOS, not in the open-source
framework, which uses @objectstack/mcp (BYO-AI) instead. The next two sections
describe that ObjectOS runtime.
Per ADR-0063 the ObjectOS runtime ships exactly two platform agents, bound by the surface the user is in — the user never picks from a roster:
| Agent | Surface | Does | Edition |
|---|---|---|---|
ask | data console | Read / query / explore records + run the business actions the app exposes. RLS-bounded. | ObjectOS |
build | Studio | Author metadata (objects, fields, views, flows) via plan → draft → verify → publish. | ObjectOS |
Within the ObjectOS runtime there is no per-turn intent classifier and
no agent dropdown: the surface binds the agent (data console → ask, Studio →
build). A build-shaped request that reaches ask is declined and redirected to
the Builder, never silently re-routed. (For data query and source-mode authoring,
the open edition ships neither agent and uses @objectstack/mcp (BYO-AI)
instead — see the callout in the AI Overview.)
Connect your AI (BYO-AI over MCP)
Step-by-step client setup (Claude Code, Claude Desktop, .mcp.json, API keys)
with verification and troubleshooting lives in
Connect an MCP Client. The summary below covers the
architecture.
Every deployment serves MCP at /api/v1/mcp by default (a core platform
capability — set OS_MCP_SERVER_ENABLED=false to opt out), with two
authentication tracks: The in-product entry point is the
Setup → Connect an Agent page: per-client connect snippets, the portable
SKILL.md download (GET /api/v1/mcp/skill), and API-key minting.
- OAuth 2.1 (interactive clients — recommended). Each deployment is its
own spec-compliant authorization server: the endpoint publishes
.well-known/oauth-protected-resource/.well-known/oauth-authorization-serverdiscovery metadata, clients self-register via Dynamic Client Registration (RFC 7591) and run an authorization-code + PKCE flow. Nothing is pre-registered with Anthropic or any central service, so self-hosted and private deployments work out of the box. You authorize the client in the browser; it then acts as an agent on your behalf (principalKind: 'agent'), its data access bounded by the intersection of the consent scopes and your own permissions/RLS — it can never exceed either. For object CRUD the consent scopes (data:read,data:write) are a real ceiling, not just a tool-family filter: they narrow the exposed tools and cap what the agent can do at the data layer — adata:readtoken can never write a record even where you could. Business actions are gated differently (#2849):actions:execute+ the action's declared capabilities decide which actions the agent may invoke, and only actions the app author explicitly exposed to AI (ai: { exposed: true }) are invokable at all — but an invoked action's body runs as trusted application code with the app's full data authority, not under the agent's data ceiling. The author's AI opt-in, not the data layer, is the boundary for what actions can do. - API key (headless). Mint a key (
POST /api/v1/keys, shown once) and send it asx-api-key— for CI, scripts, and agents without a browser.
Per client:
| Client | How to connect |
|---|---|
| Claude Code | claude mcp add --transport http objectstack https://<your-deployment>/api/v1/mcp — a browser login opens on first use. Headless alternative: add --header "x-api-key: osk_...". |
| Claude Desktop | Settings → Connectors → Add custom connector → paste the MCP URL → sign in when prompted. |
| claude.ai (web) | Settings → Connectors → Add custom connector → paste the MCP URL. |
| Other MCP clients | Any client implementing MCP authorization discovers the flow automatically; header-based clients can send the API key instead. |
Private / intranet deployments. OAuth requires HTTPS (localhost is exempt, per OAuth 2.1). Local clients — Claude Code and Claude Desktop — run on your machine, so they can reach an intranet-only deployment (an internal CA works). claude.ai web connectors connect from Anthropic's servers, so they additionally need the MCP endpoint reachable from the public internet — a network decision, not a platform switch. On a plain-HTTP non-localhost deployment the OAuth track stays dark and the endpoint is API-key-only, fail-closed.
You extend the platform with skills, not agents
"Agent skill" ≠ "authoring skill." The *.skill.ts / defineSkill files
here are agent skills — runtime capability bundles the ask / build
agents consume. They are a different thing from the SKILL.md authoring
skills on the AI Skills System page, which teach a coding
assistant to write your metadata and never run. Same word, two layers.
*.agent.ts is closed to third parties — the agent metadata type is
allowRuntimeCreate:false, allowOrgOverride:false, reserved for the two platform
agents and platform-owned subagents (ADR-0063 §2). To give the ask agent a new
capability you author an agent skill (*.skill.ts) whose tools reference
your Actions / Flows / queries; it then attaches to ask. Every skill declares
surface: 'ask' | 'build' | 'both', and an agent's tool set is the union of its
surface-compatible skills' tools — there is no global fall-through, so a skill
reaches an agent only when their surfaces match
(ADR-0064).
Both surface:'ask' and surface:'build' skills run only where the in-UI AI
runtime exists — ObjectOS. On the open framework
there is no in-product agent to attach them to; author capability
as Actions / Flows and reach it through @objectstack/mcp instead.
The shape of an agent
An agent is typed metadata validated by AgentSchema — exported, together with the
defineAgent factory, from the open @objectstack/spec/ai package (the platform's
own ask / build records use exactly these fields):
| Field | Meaning |
|---|---|
surface | 'ask' | 'build' — the product surface this agent binds (ADR-0063 §1) |
role | Free-text persona string (e.g. "Senior Support Engineer") — not an enum |
instructions | System prompt / prime directives |
model | Provider + model config (provider: openai | azure_openai | anthropic | local) |
skills | Skill names to attach (the primary Agent → Skill → Tool capability model) |
tools | Direct tool references { type, name, description } — type is action | flow | query | vector_search; name points at an existing Action/Flow/query |
knowledge | RAG access: { topics: string[], indexes: string[] } |
There is no type field and no fixed agent "type" taxonomy — behaviour comes from
persona, instructions, skills, and tools. There are no triggers / schedule
fields on an agent; drive agents from Flows/Workflows, or — on
the ObjectOS runtime — via the in-product chat endpoint
(/api/v1/ai/*). On the open-source framework, invoke the underlying Actions/Flows through
@objectstack/mcp.
Agent tools are references to existing Actions, Flows, or queries — you do not define ad-hoc tool names with inline parameter schemas here. See Actions as Tools for how an Action becomes LLM-callable.
The agent definitions below illustrate agent anatomy — how an AgentSchema
record is shaped (that schema and the defineAgent factory are part of the open
@objectstack/spec/ai package). Note that the agent metadata type is
allowRuntimeCreate:false / allowOrgOverride:false (ADR-0063 §2): the cloud /
Enterprise in-product runtime honors only the two platform agents ask and
build, so you do not ship a custom agent to that runtime — you extend it by
authoring a skill (open metadata) that attaches to ask. In the open edition,
express the same capability as Actions / Flows plus the skills/tools its references
point at, and reach it through @objectstack/mcp (BYO-AI).
Sales Assistant Agent
import { defineAgent } from '@objectstack/spec/ai';
export const SalesAssistantAgent = defineAgent({
name: 'sales_assistant',
label: 'Sales Assistant',
role: 'Sales Development Assistant',
instructions: `You are a sales assistant AI.
Your responsibilities:
1. Qualify incoming leads (BANT criteria)
2. Suggest next best actions
3. Draft personalized emails
4. Analyze win/loss patterns
Always be professional and data-driven.`,
model: {
provider: 'openai',
model: 'gpt-4',
temperature: 0.7,
maxTokens: 2000,
},
// References to Actions/Flows exposed as tools (see "Actions as Tools").
tools: [
{ type: 'flow', name: 'analyze_lead', description: 'Analyze a lead and provide a qualification score' },
{ type: 'flow', name: 'suggest_next_action', description: 'Suggest the next best action for an opportunity' },
{ type: 'action', name: 'generate_email', description: 'Generate a personalized email template' },
],
// RAG access: topics to recruit knowledge from + vector store indexes.
knowledge: {
topics: ['sales-playbook', 'leads', 'opportunities'],
indexes: ['sales_docs'],
},
});Customer Service Agent
export const ServiceAgent = defineAgent({
name: 'service_agent',
label: 'Customer Service Agent',
role: 'Customer Service Specialist',
instructions: `You are a customer service AI agent.
Your responsibilities:
1. Triage incoming cases
2. Suggest relevant knowledge articles
3. Draft response templates
4. Escalate critical issues
Always be empathetic and solution-focused.`,
model: {
provider: 'openai',
model: 'gpt-4',
temperature: 0.5,
maxTokens: 1500,
},
// `search_knowledge` is provided by the Knowledge Protocol tool, not declared inline.
tools: [
{ type: 'flow', name: 'triage_case', description: 'Analyze a case and assign priority' },
{ type: 'action', name: 'generate_response', description: 'Generate a customer response' },
],
knowledge: {
topics: ['support-kb', 'cases'],
indexes: ['support_docs'],
},
});Lead Enrichment Agent
export const LeadEnrichmentAgent = defineAgent({
name: 'lead_enrichment',
label: 'Lead Enrichment Agent',
role: 'Data Enrichment Worker',
instructions: `You enrich lead records with additional data.
Tasks:
1. Look up company information
2. Enrich contact details
3. Add firmographic data
4. Research technology stack
Use reputable data sources.`,
model: {
provider: 'openai',
model: 'gpt-3.5-turbo',
temperature: 0.3,
maxTokens: 1000,
},
tools: [
{ type: 'flow', name: 'lookup_company', description: 'Look up company information' },
{ type: 'flow', name: 'enrich_contact', description: 'Enrich contact information' },
],
});To run enrichment when a lead is created or on a schedule, trigger this agent
from a Flow or Workflow — agents themselves carry no
triggers/schedule fields.
Revenue Intelligence Agent
export const RevenueIntelligenceAgent = defineAgent({
name: 'revenue_intelligence',
label: 'Revenue Intelligence Agent',
role: 'Revenue Operations Analyst',
instructions: `You analyze sales data and provide insights.
Responsibilities:
1. Analyze pipeline health
2. Identify at-risk deals
3. Summarize trends
4. Generate executive summaries
Use the data tools to query records and aggregate metrics.`,
model: {
provider: 'openai',
model: 'gpt-4',
temperature: 0.2,
maxTokens: 3000,
},
// Built-in data tools (query_records / get_record / aggregate_data) are
// available to agents automatically once registered — see "Natural Language Queries".
tools: [
{ type: 'flow', name: 'analyze_pipeline', description: 'Analyze sales pipeline health' },
],
knowledge: {
topics: ['opportunities', 'pipeline'],
indexes: ['sales_docs'],
},
});