@tarileo/core
v0.1.2
Published
Core engine for the Tari framework - AI agent runtime, sessions, and planning
Readme
@tarileo/core
Core engine for the Tari framework. Provides the AI agent runtime, session management, tool execution, sub-agents, planning, event publishing, and database schema.
Additional guides:
Installation
npm install @tarileo/coreCore concepts
- Tari: root container that owns the session store, agent registry, event integration, and the high-level
execute()API. - Agent: LLM runtime configured with a model, system prompt, tools, prompt layers, plugins, optional state, and optional sub-agents.
- Sub-agent: specialist agent delegated by an orchestrator through the framework-provided
delegate_to_subagenttool. - AISession: persisted conversation/runtime state. Stores messages, stream parts, metadata, active executions, cancellation state, and planner task state.
- State: typed accessors over session metadata, registered with
defineState()and consumed from runtime contexts withctx.state(token). - Tari tool definition: lazy tool definition created with
createTariToolFactory(tari). It is resolved with the runtime context so tools can access typedctx,tari,services, logger, and execution metadata without capturing stale context. - Planner: optional capability that lets an agent generate a task plan and execute tasks concurrently through sub-agents.
- Events: optional integration with
@tarileo/eventsto publish session, message, stream, and execution lifecycle events.
Quick start
Create a Tari Instance
import { createTari } from "@tarileo/core";
const tari = createTari<AppContext>({
database: db,
eventBus,
context: {
getTenantId: (ctx) => ctx.restaurantId,
getActorId: (ctx) => ctx.userId,
getActorType: () => "user",
getLogger: (ctx) => ctx.log,
},
});database must be a Drizzle instance with Tari tables registered. eventBus is optional, but required for execution lifecycle events and standalone sub-agent generation.
Define and register an agent
import { AgentBuilder, createTariToolFactory } from "@tarileo/core";
import { z } from "zod";
const tools = createTariToolFactory(tari);
const assistantTools = {
get_status: tools.define({
description: "Get the current status.",
inputSchema: z.object({}),
execute: async (_input, { ctx, tari, log }) => {
log.info({ tenantId: ctx.restaurantId }, "Status requested");
return { status: "ok", hasAgent: tari.hasAgent("assistant") };
},
}),
};
const assistant = AgentBuilder.define<AppContext>("assistant")
.withModel(() => model)
.withSystemPrompt("You are a helpful assistant.")
.withMaxSteps(8)
.addTools(assistantTools)
.build();
tari.registerAgent("assistant", assistant);Execute a prompt
const result = await tari.execute("assistant", ctx, {
content: "Hola, revisa el estado actual.",
});
console.log(result.sessionId, result.executionId);execute() creates a new session unless sessionId is provided. It persists the user message, starts an async agent execution, and returns immediately with identifiers.
Public API
createTari(config)
Creates a Tari root instance.
type TariConfig<TContext> = {
database: TariDatabase;
eventBus?: EventBus;
context: {
getTenantId: (ctx: TContext) => string;
getActorId?: (ctx: TContext) => string | undefined;
getActorType?: (ctx: TContext) => "user" | "system" | "agent";
getLogger: (ctx: TContext) => TariLogger;
};
};The context callbacks are used for tenant isolation, actor attribution, and logging. All session operations receive the application context explicitly.
Tari
Main runtime container.
tari.registerAgent(name, agent);
tari.getAgent(name);
tari.hasAgent(name);
tari.execute(agentName, ctx, options);registerAgent(name, agent)
Registers an agent by name and binds it to the Tari runtime. The registration name must match agent.getName().
getAgent(name)
Returns a registered agent or throws if it is missing.
hasAgent(name)
Checks whether an agent is registered.
execute(agentName, ctx, options)
High-level prompt API for application code.
await tari.execute("assistant", ctx, {
sessionId: "optional-existing-session",
content: "User prompt",
attachments: [
{ url: "https://...", mimeType: "image/png", name: "photo.png", intent: "reference" },
],
selection: [
{ id: "prod_1", type: "product", label: "Pizza", metadata: { category: "food" } },
],
uiTools: [
{
name: "open_product_picker",
description: "Open product picker in the UI.",
inputSchema: { type: "object", properties: {} },
},
],
userContext: [
{ key: "currentPage", value: "/products" },
],
});Returns:
{ sessionId: string; executionId: string }createTariToolFactory(tari)
Creates a typed factory for lazy Tari tool definitions. The factory captures the Tari<TContext> instance once and preserves TContext in each tool's execution environment.
import { createTariToolFactory } from "@tarileo/core";
import { z } from "zod";
const tools = createTariToolFactory(tari);
const productTools = {
create_product: tools.define<{ name: string }, { productId: string }>({
description: "Create a product draft.",
inputSchema: z.object({
name: z.string(),
}),
execute: async ({ name }, { ctx, tari, services, log, execution, toolCall }) => {
log.info({ toolCallId: toolCall.toolCallId }, "Creating product");
const product = await services.productService.createDraft(ctx, { name });
return {
productId: product.id,
sessionId: execution?.runtime?.sessionId,
};
},
}),
};The execution environment contains:
ctx: the current runtime context, typed fromTari<TContext>.tari: the rootTari<TContext>instance captured by the factory.log:ctx.log.services: inferred fromTContext["services"]when the application context has aservicesproperty; otherwisenever.execution: optional runtime execution metadata. It is present only when the context has been enhanced by the agent runtime.toolCall: AI SDK tool-call options such astoolCallId,messages, andabortSignal.
tools.define(...) returns a lazy TariToolDefinition, not an eager AI SDK Tool. Core resolves it with resolveTariToolDefinition(definition, ctx) at runtime so tools use the current request/session context.
UI-only tools can omit execute:
const uiTools = {
open_product_picker: tools.define({
description: "Ask the frontend to open a product picker.",
inputSchema: z.object({
mode: z.enum(["single", "multiple"]),
}),
}),
};tari.sessions
Session facade for persisted conversations.
const session = await tari.sessions.create(ctx, { source: "web" });
const existing = await tari.sessions.load(ctx, session.id);
const sessions = await tari.sessions.list(ctx, { limit: 20 });
const running = await tari.sessions.isRunning(ctx, session.id);
const cancelled = await tari.sessions.cancel(ctx, session.id, "user-cancelled");
await tari.sessions.delete(ctx, session.id);create(ctx, metadata?): creates a root session.load(ctx, sessionId): loads an active session, throwing when not found or archived.list(ctx, filters?): lists sessions using repository filters.delete(ctx, sessionId): archives a session and publishes deletion events when events are configured.isRunning(ctx, sessionId): checks whether a session has an active execution.cancel(ctx, sessionId, reason?): cancels the active execution for a session.
AISession
Represents a persisted session and runtime cancellation boundary.
Metadata
await session.refresh();
await session.update({ metadata: { source: "web" } });
await session.patchMetadata((metadata) => ({ ...metadata, activePanel: "chat" }));Messages
await session.addMessage({
role: "user",
parts: [{ type: "text", text: "Hola" }],
});
const messages = await session.getMessages();
const recent = await session.listMessages({ limit: 20, order: "desc" });
const message = await session.getMessage("message-id");
await session.upsertMessage("message-id", "assistant", [{ type: "text", text: "Listo" }]);
await session.deleteMessage("message-id");Stream parts
const parts = await session.listParts();
const part = await session.getPart("message-id", "part-id");
await session.createPart({ messageId: "message-id", type: "text", sequence: 0, payload: {} });
await session.deletePart("message-id", "part-id");Runtime control
session.abort("manual-cancel");
session.onCancel((reason) => console.log(reason));
session.cancelActiveExecution("user-cancelled");
session.isStreaming();
session.getCurrentExecution();Task-level cancellation is available with registerTaskController(), cancelTask(), unregisterTask(), and hasTask().
Planner helpers
session.hasActivePlan();
session.getActivePlan();
await session.getTasks();
await session.getTask("task-id");
await session.getTaskDetail("task-id");Agent API
AgentBuilder
Builds orchestrator agents.
const agent = AgentBuilder.define<AppContext>("catalog-agent")
.withModel(() => model)
.withSystemPrompt(SYSTEM_PROMPT)
.withMaxSteps(12)
.withProviderOptions({ openai: { reasoningEffort: "low" } })
.withTools(staticTools)
.addTools(tariToolDefinitions)
.addToolProvider((ctx) => createTenantTools(ctx))
.withPromptLayer({ priority: "policy", content: "Follow restaurant policies." })
.addContextLayer(async (ctx) => [
{ priority: "context", content: `Restaurant: ${ctx.restaurantName}` },
])
.withSubAgent(productSubAgent)
.addPlugin(plugin)
.addState(cartState)
.build();Common methods:
withModel(factory): sets the Vercel AI SDK language model factory.withSystemPrompt(prompt): sets the base system prompt.withMaxSteps(steps): limits tool-call steps.withProviderOptions(options): passes provider-specific options tostreamText().withDeps(deps): stores dependency references on the definition.withTools(tools): registers static AI SDK tools.addTools(tools): registers direct tool objects. Values may be AI SDKToolobjects or lazyTariToolDefinitionobjects fromcreateTariToolFactory(tari).addToolProvider(provider): resolves tools per request/context.withPromptLayer(layer): adds a static layered prompt section.addContextLayer(provider): injects dynamic prompt layers.withSubAgent(agent): registers a specialist agent.addPlugin(plugin): contributes tools and runtime prompt layers.addState(token): enables typed session state for runtime tools.withPlanner(config): enables planner tools and state.withSessionStore(store): required when enabling planner directly on an unregistered agent.build(): returns anAgent.
When an agent is registered with tari.registerAgent(), Tari binds the session store, event bus, and sub-agent session creation automatically.
SubAgentBuilder
Builds specialist agents for delegated work.
const productSubAgent = SubAgentBuilder.define<AppContext>("product-writer")
.withLabel("Product Writer")
.withDescription("Creates and improves product descriptions.")
.withWhenToUse("Use when the task is about product copy.")
.withInputFormat("A clear task with product identifiers and constraints.")
.withOutputFormat("A concise summary and structured result.")
.withModel(() => model)
.withSystemPrompt(PRODUCT_PROMPT)
.withMaxSteps(6)
.addTools(productTools)
.withAutonomyRules({
whenContextMissing: "make_best_assumption",
whenUncertain: "proceed_with_caution",
defaultAssumptionBehavior: "Document assumptions in the final response.",
})
.build();Specialist metadata is used in the orchestrator prompt so the parent agent knows when and how to delegate.
Agent
Runtime instance returned by the builders.
agent.getName();
agent.getDefinition();
agent.getMode();
agent.getModel();
agent.getMaxSteps();
agent.getTools(ctx);
agent.getSubAgents();
agent.buildSystemPrompt(ctx);
agent.prepare(ctx, messages);
agent.execute(input);
agent.prompt(ctx, { content: "..." });
agent.stream(ctx, input, { session });
agent.generate(ctx, "task", tools);Use tari.execute() or agent.prompt() for application-level chat flows. Use prepare() + stream() when integrating with lower-level HTTP streaming handlers.
State API
defineState() creates a typed token over session metadata. The token must be registered with addState() and is then available from runtime tools through ctx.state(token).
import { defineState } from "@tarileo/core";
const cartState = defineState((meta) => ({
getCart: async () => meta.get("cart"),
setCart: async (cart) => meta.update((current) => ({ ...current, cart })),
clearCart: async () => meta.update((current) => ({ ...current, cart: undefined })),
}));
const agent = AgentBuilder.define<AppContext>("cart-agent")
.withModel(() => model)
.withSystemPrompt("Manage the cart.")
.addState(cartState)
.addToolProvider(() => ({
clear_cart: tool({
description: "Clear the active cart.",
inputSchema: z.object({}),
execute: async (_input, { experimental_context }) => {
const ctx = experimental_context as AppContextWithState;
await ctx.state(cartState).clearCart();
return { ok: true };
},
}),
}))
.build();State is backed by session metadata, so it is scoped to one session and persists across turns.
Plugins
Plugins can add tools, prompt layers, and execution hooks.
import { createPlugin } from "@tarileo/core";
const plugin = createPlugin<AppContext>("restaurant-plugin")
.addTools(restaurantTools)
.addSystemPromptLayer(() => ({
priority: "policy",
content: "Use restaurant data safely.",
}))
.build();createPlugin<TContext>() is generic so plugin tool definitions receive the same application context as the agent. Legacy plugins can still implement getTools(ctx) directly or use .addTool((ctx, deps) => tools).
Planner capability
Enable planner support when an orchestrator needs to split a batch request into multiple tasks and execute them concurrently.
const agent = AgentBuilder.define<AppContext>("batch-products")
.withModel(() => model)
.withSystemPrompt(SYSTEM_PROMPT)
.withSubAgent(productSubAgent)
.withPlanner({
model: () => fastModel,
concurrency: 3,
maxTasks: 50,
taskTimeoutMs: 300_000,
})
.build();Defaults:
concurrency:3maxTasks:50taskTimeoutMs:300000
The planner injects framework tools for plan generation/execution and stores the active plan in session metadata. Use session.getTasks() and session.getTaskDetail(taskId) to inspect progress.
Prompt layers
LayeredPromptBuilder composes prompt sections by priority. Agents use it internally for base prompts, context layers, plugin layers, planner layers, and sub-agent descriptions.
const prompt = new LayeredPromptBuilder()
.addLayer("base", "You are an assistant.")
.addLayer("context", "Restaurant: Central")
.addLayer("policy", "Never expose secrets.")
.build();Database schema
The package exports Drizzle tables and TypeScript types:
import {
tariAiSessions,
tariAiMessages,
tariAiMessageParts,
tariAiSessionKinds,
tariSessionStatuses,
} from "@tarileo/core";Register these tables in the consuming application's Drizzle schema before passing the database to createTari().
Export reference
Root
createTariTariTariConfigTariDatabaseTariSchemaTariContextTariActorTypeTariLoggerContextWithLogger
Agents and runtime
AgentBuilderSubAgentBuilderAgentAgentPluginAgentMetaAgentAutonomyRulesPreparedAgentInputAgentStreamOptionsUIStrategyStructuredOutputConfigStructuredOutputResolver
Sessions
AISessionSessionRepository- session schemas and DTO types from
@tarileo/core/agents/session
State
defineStateStateTokenStateMethods
Planner
PlannerCapabilityConfigcreateGeneratePlanToolcreateExecutePlanTool- planner types, state, and runtime helpers
Tools
createDelegateToolcreateTariToolFactoryresolveTariToolDefinitionisTariToolDefinitionTariToolFactoryTariToolDefinitionTariToolDefinitionInputTariToolExecuteTariToolExecuteApiTariToolExecutionTariToolServicesTariToolCallOptionsInferTariContext
Prompt and context
LayeredPromptBuilderPromptLayerPromptLayerPrioritymergeContributionsconsumeNextTurnContributionsnormalizeContributions
Events and schema
TariEventPublishertariAiSessionstariAiMessagestariAiMessagePartstariAiSessionKindstariSessionStatuses- Drizzle row/insert types for sessions, messages, and parts
License
MIT
