| title | AI Agents |
|---|---|
| description | The two platform agents (ask and build), how skills extend them, and the shape of an AgentSchema record |
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).
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.)
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. |
*.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.
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.
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'],
},
});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'],
},
});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.
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'],
},
});