Appearance
Agents
Agents are LLM-powered entities with defined personalities, tools, skills, and behavioral rules. The Lota SDK provides the infrastructure and extension points for agents but does not define specific agents itself -- the host application defines its own agent roster, personalities, and capabilities.
Agent Roster
The agent roster is the set of agent identifiers your application supports. Configure it via config.agents when calling createLotaRuntime:
ts
import { createLotaRuntime } from '@lota-sdk/core'
const runtime = await createLotaRuntime({
// ...other config
agents: {
roster: ['chief', 'cto', 'cfo'],
displayNames: {
chief: 'Chief Executive',
cto: 'Chief Technology Officer',
cfo: 'Chief Financial Officer',
},
shortDisplayNames: {
chief: 'CEO',
cto: 'CTO',
cfo: 'CFO',
},
teamConsultParticipants: ['chief', 'cto', 'cfo'],
},
})| Field | Type | Description |
|---|---|---|
roster | readonly string[] | Array of agent IDs. These are the canonical identifiers used throughout the SDK. |
displayNames | Record<string, string> | Human-readable full names, keyed by agent ID. Used in UI and prompt rendering. |
shortDisplayNames | Record<string, string> | Optional abbreviated names (e.g., "CTO" instead of "Chief Technology Officer"). |
teamConsultParticipants | readonly string[] | Subset of the roster that participates in team consultation fan-out calls. |
The SDK uses isAgentName(value) and resolveAgentNameAlias(value) internally to validate and resolve agent references, including @mentions in user messages (e.g., @cto).
Creating Agents
The createAgent field provides a factory registry -- a map of agent IDs to factory functions. Each factory receives runtime options and returns a configured agent instance:
ts
agents: {
createAgent: {
chief: (options) => createChiefAgent(options),
cto: (options) => createCTOAgent(options),
cfo: (options) => createCFOAgent(options),
},
}The SDK calls the appropriate factory during workstream turns. The factory receives options including mode, tools, extra instructions, retry limits, and step constraints. The returned agent is a Vercel AI SDK ToolLoopAgent or compatible instance.
Agent Definitions
The SDK provides helper functions for structuring agent behavior into composable rules and skills. Import them from @lota-sdk/core/ai/definitions.
Rules
Rules are behavioral constraints or protocols that an agent must follow. Define them with defineRule:
ts
import { defineRule, renderRuleInstructions } from '@lota-sdk/core/ai/definitions'
const memoryRule = defineRule({
name: 'memory-usage',
instructions: 'Always check memory before answering questions about past decisions...',
})
const formalToneRule = defineRule({
name: 'formal-tone',
instructions: 'Maintain a professional and formal tone in all responses.',
})Render rules into prompt text with renderRuleInstructions:
ts
const rulesBlock = renderRuleInstructions([memoryRule, formalToneRule])
// Produces:
// <rules>
//
// Always check memory before answering questions about past decisions...
//
// Maintain a professional and formal tone in all responses.
//
// </rules>Skills
Skills are capabilities with associated tool sets. Define them with defineSkill:
ts
import { defineSkill, renderSkillInstructions } from '@lota-sdk/core/ai/definitions'
const planningSkill = defineSkill({
name: 'execution-planning',
description: 'Create and manage contract-driven execution runs',
instructions: 'When the user requests a complex multi-step task...',
tools: ['createExecutionPlan', 'submitExecutionNodeResult', 'getActiveExecutionPlan'],
})| Field | Type | Description |
|---|---|---|
name | string | Unique identifier for the skill. |
description | string | Short summary of the skill's purpose. |
instructions | string | Full prompt instructions for using the skill. |
tools | readonly string[] | Tool names associated with this skill. |
Render skills into prompt text with renderSkillInstructions:
ts
const skillsBlock = renderSkillInstructions([planningSkill])
// Produces:
// <skills>
//
// When the user requests a complex multi-step task...
//
// </skills>Built-in Definitions
The SDK ships several reusable definitions:
| Definition | Type | Purpose |
|---|---|---|
generalRule | Rule | Core behavioral guidelines (concise, direct, professional) |
memr3Rule | Rule | MemR3 evidence-gap retrieval protocol for grounded answers |
domainReasoningFallbackRule | Rule | First-principles reasoning when no specific skill matches |
askingUserQuestionsSkill | Skill | Structured user clarification questions via userQuestions tool |
researchSkill | Skill | Delegated web research via researchTopic, fetchWebpage, inspectWebsite |
Learned Skills
Agents can accumulate learned skills from previous interactions. Render them with renderLearnedSkillInstructions:
ts
import { renderLearnedSkillInstructions } from '@lota-sdk/core/ai/definitions'
const learnedBlock = renderLearnedSkillInstructions([
{ name: 'Weekly Report Format', instructions: 'Use bullet points with metrics...' },
])Prompts
For reusable instruction blocks that are not rules or skills, use definePrompt:
ts
import { definePrompt } from '@lota-sdk/core/ai/definitions'
const onboardingPrompt = definePrompt({
name: 'onboarding-welcome',
instructions: 'Welcome the user and explain available capabilities...',
})Building Agent Tools
The buildAgentTools function assembles the tool set for each agent on every turn. It receives the agent ID and a context object containing workstream, organization, and user references:
ts
agents: {
buildAgentTools: (agentId, context) => {
const baseTools = {
rememberMemory: createRememberMemoryTool(context),
memoryBlock: createMemoryBlockTool(context),
memorySearch: createMemorySearchTool(context.orgIdString, agentId),
}
if (agentId === 'chief') {
return { ...baseTools, consultTeam: createConsultTeamTool(context) }
}
return baseTools
},
}The SDK calls this function during turn preparation. The returned tool map is merged with any toolProviders registered via configureRuntimeExtensions.
Agent Runtime Config
The getAgentRuntimeConfig function returns model selection, reasoning settings, and instruction overrides for each agent. It is called per turn to resolve the agent's runtime behavior:
ts
agents: {
getAgentRuntimeConfig: ({ agentId, workstreamMode, reasoningProfile }) => {
return {
id: agentId,
model: bifrostChatModel('openrouter/anthropic/claude-sonnet-4'),
providerOptions: { openai: { reasoningEffort: 'medium' } },
maxSteps: 15,
maxOutputTokens: 8192,
extraInstructions: buildAgentSystemPrompt(agentId),
}
},
}Parameters passed to the function include:
| Parameter | Description |
|---|---|
agentId | The agent being configured |
workstreamMode | 'direct' (1:1 agent) or 'group' (multi-agent workstream) |
mode | Resolution mode ('fixedWorkstreamMode' or 'dynamic') |
reasoningProfile | 'fast', 'standard', or 'deep' |
onboardingActive | Whether workspace onboarding is in progress |
systemWorkspaceDetails | Workspace context summary |
preSeededMemoriesSection | Pre-loaded memory context |
retrievedKnowledgeSection | Retrieved knowledge base content |
additionalInstructionSections | Extra instruction blocks from hooks |
responseGuardSection | Guard instructions for response formatting |
System Agents
The SDK includes several internal system agents that power background processing. These are not consumer-facing -- they run autonomously as part of the SDK's internal machinery:
| Agent | File | Purpose |
|---|---|---|
| Context Compacter | context-compacter.agent.ts | Compresses long conversation histories to stay within context limits |
| Memory Extractor | memory.agent.ts | Extracts durable memories from conversation turns |
| Memory Reranker | memory-reranker.agent.ts | Re-ranks retrieved memories by relevance |
| Skill Extractor | skill-extractor.agent.ts | Identifies reusable skills from conversation patterns |
| Skill Manager | skill-manager.agent.ts | Manages the learned skill lifecycle |
| Researcher | researcher.agent.ts | Powers the researchTopic delegated agent tool |
| Title Generator | title-generator.agent.ts | Generates workstream titles from conversation content |
| Recent Activity Title Refiner | recent-activity-title-refiner.agent.ts | Refines titles for the recent activity feed |
| Regular Chat Memory Digest | regular-chat-memory-digest.agent.ts | Produces periodic memory digests from ongoing chats |
System agents use the same bifrostChatModel infrastructure as consumer agents and are triggered by BullMQ workers.
Delegated Agents
Delegated agents are single-purpose sub-agents created as tools. They run a complete tool loop to accomplish a specific task and return the result to the calling agent.
ts
import { createDelegatedAgentTool } from '@lota-sdk/core/system-agents/delegated-agent-factory'
const myResearchTool = createDelegatedAgentTool({
id: 'myResearcher',
description: 'Research a topic and return a synthesized report',
model: bifrostChatModel('openrouter/anthropic/claude-sonnet-4'),
instructions: 'You are a research assistant. Search the web and synthesize findings...',
tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
maxSteps: 10,
maxOutputTokens: 4096,
temperature: 0.2,
})The generated tool accepts a { task: string } input. When called by the parent agent, the delegated agent runs its own tool loop (up to maxSteps), then returns the final text result.
For delegated agents that need runtime context (e.g., organization or workstream references), use createDelegatedAgentToolWithContext:
ts
import { createDelegatedAgentToolWithContext } from '@lota-sdk/core/system-agents/delegated-agent-factory'
const contextualTool = createDelegatedAgentToolWithContext<MyContext>({
id: 'contextualResearcher',
description: 'Research with org context',
model: bifrostChatModel('openrouter/anthropic/claude-sonnet-4'),
instructions: '...',
createTools: (context) => ({
searchMemory: createMemorySearchTool(context.orgId),
}),
})Both variants automatically inject time-awareness instructions and termination guards into the delegated agent's system prompt.
Core Workstream Profiles
Core workstream profiles define specialized configurations for built-in workstream types. Register a profile resolver via getCoreWorkstreamProfile:
ts
agents: {
getCoreWorkstreamProfile: (coreType) => {
if (coreType === 'strategy') {
return {
config: { coreType: 'strategy', agentId: 'chief', title: 'Strategy' },
tools: ['createExecutionPlan', 'submitExecutionNodeResult', 'researchTopic'],
skills: ['execution-planning', 'research'],
instructions: 'You manage strategic planning for the organization...',
}
}
// Return a default profile for unknown types
return { config: { coreType, agentId: '', title: '' }, tools: [], skills: [], instructions: '' }
},
}The CoreWorkstreamProfile interface:
| Field | Type | Description |
|---|---|---|
config.coreType | string | The workstream type identifier |
config.agentId | string | Default agent assigned to this workstream |
config.title | string | Display title for the workstream |
tools | readonly string[] | Tool names available in this workstream |
skills | readonly string[] | Skill names active in this workstream |
instructions | string | System instructions specific to this workstream type |