Agents
End-user AI assistants — Agent → Skill → Tool — wired from your data and actions.
Agents are the AI assistants your end users chat with — a help desk co-pilot, a sales BDR, an internal HR Q&A bot. They sit on top of the data and actions you've already defined; you don't write new code, you compose existing primitives into a persona.
Three-tier architecture, aligned with Salesforce Agentforce, Microsoft Copilot Studio, and ServiceNow Now Assist:
Agent ──→ Skill ──→ Tool
(persona) (capability) (callable function)| Tier | What it is | Example |
|---|---|---|
| Tool | One callable function (action, query, knowledge search, MCP method) | create_ticket, get_order_status, search_kb |
| Skill | A named bundle of related tools with shared LLM instructions | ticket_management = create + update + close + escalate |
| Agent | A persona with a role, a system prompt and attached skills | tier1_support = empathetic, verifies identity, has ticket_management + kb_search |
Define an Agent (one file)
// src/agents/tier1_support.agent.ts
import { defineAgent } from '@objectstack/spec/ai';
export const tier1Support = defineAgent({
name: 'tier1_support',
label: 'First Line Support',
role: 'Help Desk Assistant',
instructions: `
You are a friendly first-line support agent.
Always verify the user's identity before discussing account specifics.
Search the FAQ and the policy articles before you answer a how-to question.
Escalate to tier 2 if the issue involves billing or security.
`,
skills: ['ticket_management', 'knowledge_search'],
model: { provider: 'openai', model: 'gpt-4o', temperature: 0.3 },
memory: { longTerm: { enabled: true, maxEntries: 50 }, reflectionInterval: 10 },
});An agent has no knowledge block. Which sources the search_knowledge tool
may read is restricted per source at the knowledge service; say in
instructions which sources the agent should ground its answers in.
Or in the UI: Studio → Agents → New Agent.
Or — and this is the point — say to the AI Builder:
"Create a tier-1 support agent that handles ticket management and searches the FAQ. It should verify identity before discussing account details."
Define a Skill
// src/skills/ticket_management.skill.ts
import { defineSkill } from '@objectstack/spec/ai';
export const ticketManagement = defineSkill({
name: 'ticket_management',
label: 'Ticket Management',
instructions: `
Always confirm the ticket subject and priority before creating one.
Use 'urgent' priority sparingly — only for outages or security incidents.
`,
tools: [
'create_ticket',
'update_ticket',
'close_ticket',
'escalate_ticket',
'action_*', // wildcard: pick up any future actions on the active object
],
});Skills are the right unit for reuse. One skill works across many agents.
Tools come from your declared metadata
Every *.action.ts you declare automatically materializes as an
action_<name> tool — no separate wiring. So if you've already
defined escalate_ticket as an Action on the support_ticket object,
both the AI Builder and your Agents can call it. Permissions still
apply: the agent calls the action as the user, so the user's
permission set decides whether it succeeds.
You can also expose:
| Tool type | Source |
|---|---|
| Action | Any *.action.ts in any installed package |
| Flow | A type: 'flow' action — the agent calls the action, which starts the flow |
| Query | Saved ObjectQL queries (*.query.ts) |
| Knowledge search | The search_knowledge tool, over the sources the user may read |
| MCP method | Anything exposed by an attached MCP server |
| Built-in metadata tools | create_object, add_field, … — but only to admin agents |
Ambient assistant pattern
If you want one chat box for the whole app (Claude Code /
Agentforce style) instead of forcing the user to pick an agent,
declare a defaultAgent on the App metadata and call the ambient
chat endpoint with the app context:
POST /api/v1/ai/chat { context: { appName: 'crm' }, ... }When context.appName resolves to an app that declares a
defaultAgent, the runtime auto-selects that agent — the user never
picks from a list. The built-in AI panel uses this. The runtime
resolves:
- The default agent for the active app (the app's
defaultAgent), or the first agent the user has access to. - The active skills — the agent's
skills:list loaded from the Skill Registry, filtered by user permission set and the current object/record context.
You don't have to wire which agent is shown where. Declare an app, set
its defaultAgent, and it appears.
Permissions
| Capability | Permission |
|---|---|
| Chat with an agent | ai:chat (and access to the agent's skills' tools) |
| Approve metadata changes | ai:approve |
| Define / edit agents and skills | ai:author (typically Setup Administrator) |
| Read AI conversations (audit) | ai:read |
Conversations are scoped to the user — one user can't see another's chat history unless they have a delegated grant.
Memory and conversation state
The agent's memory block configures long-term memory: distilled notes
about each user that carry across sessions.
| Field | What it does |
|---|---|
longTerm.enabled | Keep notes across sessions (default false) |
longTerm.maxEntries | How many notes are kept per user; the newest are recalled first. Required when enabled is true |
reflectionInterval | How many interactions pass between reflections — a reflection is what writes a note. Required when enabled is true |
There is no short-term block: the context window of one request is bounded by the token budget. Where the notes are stored is the platform's choice, not agent metadata.
Pruning of the live context window is governed by the conversation's
token-budget strategy — sliding_window (default), fifo,
importance, semantic, or summary.
Conversation rows live in ai_conversations. Tool-call results
and pending actions cross-reference back to the originating
conversation for audit.
Observability
Every agent run emits:
audit:ai:chatevents (per turn)audit:ai:toolevents (per tool call, with inputs + outputs)audit:ai:pending_actionevents (when a mutation queues)- token-count metrics (per model, per provider) into the audit log for chargeback
You can wire these into your usual observability stack — see Observability.
Multi-tenant note
Agents are per-Environment. Tenant A's tier1_support agent never
sees tenant B's data, conversations, or knowledge — even if you ship
the same agent definition in a marketplace package.
Where to go next
- AI Builder — the build-time assistant
- IDE Skills —
npx skills add objectstack-ai/objectstack/skillsso your IDE agent authors metadata correctly - Actions — declare the tools your agents will use
- AI Service — provider, embedder, MCP setup
@objectstack/spec/ai— full schemas