agentified
v0.3.0
Published
Context intelligence for AI agents. Register tools, assemble the right context per turn.
Readme
agentified
Context intelligence for AI agents. Register tools, assemble the right context per turn.
TypeScript SDK for Agentified — register tools, discover relevant ones via hybrid ranking, and track sessions across turns.
Install
npm install agentifiedQuick Start
import { Agentified } from "agentified";
const ag = new Agentified();
await ag.connect("http://localhost:9119");
const dataset = await ag.dataset("my-agent").register({
tools: [
{ name: "get_weather", description: "Get current weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] }, handler: async (args) => ({ temp: 22 }) },
{ name: "book_flight", description: "Book a flight", parameters: { type: "object", properties: { from: { type: "string" }, to: { type: "string" } }, required: ["from", "to"] }, handler: async (args) => ({ booked: true }) },
],
});
const session = dataset.session("chat-1");
// Assemble context — tools + messages for this turn
const ctx = await session.context
.messages({ strategy: "recent", maxTokens: 4000 })
.assemble();
// ctx.tools → { get_weather, book_flight } (ranked by relevance)
// ctx.messages → conversation history
// ctx.tokenEstimate → estimated token countSee ts-sdk-smoke for a runnable version of this.
Authentication
Pass custom headers (e.g. for Cloud Run IAM, API gateways) via connect():
await ag.connect("https://my-service.run.app", {
headers: { Authorization: `Bearer ${identityToken}` },
});Headers are sent on every request, including the initial health check.
Hierarchy
Agentified
├─ .connect(serverUrl?, options?) → void
├─ .adaptTo(adapter) → T (framework-specific wrapper)
└─ .dataset(name) → DatasetRef
└─ .register({ tools }) → Instance
├─ .discoverTool — DiscoverTool
├─ .prepareStep — PrepareStepFn
├─ .session(id) → Session
│ ├─ .discoverTool
│ ├─ .getMessagesTool — agent-callable tool for navigating conversation history
│ ├─ .prepareStep (persists messages)
│ ├─ .context.messages(opts).recall(opts).assemble()
│ ├─ .updateConversation({ messages })
│ ├─ .getMessages(opts)
│ └─ .conversation → Conversation
└─ .namespace(id) → Namespace
├─ .tools (stub)
└─ .session(id) → SessionAPI Reference
ContextBuilder
Fluent API for assembling context per agent turn. Access via session.context:
// Basic: messages only
const ctx = await session.context
.messages({ strategy: "recent", maxTokens: 4000 })
.assemble();
// With tool recall: auto-discovers tools based on last user message
const ctx = await session.context
.tools({ custom_tool: myTool })
.messages({ strategy: "compacted", maxTokens: 4000 })
.recall({ tools: { limit: 10 } })
.limitTokens(8000)
.assemble();
// Preserve the first user message + summarize older messages
const ctx = await session.context
.messages({ strategy: "compacted", maxTokens: 4000, keepFirst: true })
.assemble();
// ctx.messages: [first user msg, annotated summary, ...recent messages]
// Summary is auto-constructed with seq range annotation
// Custom compaction strategy (client-side)
const ctx = await session.context
.messages({
strategy: "compacted",
maxTokens: 4000,
compactionStrategy: async (olderMessages) => {
const summary = await myCustomSummarizer(olderMessages);
return { summary };
},
})
.assemble();Strategies: recent, full, compacted
compacted— recent messages (60% budget) + LLM summary of older messages (40% budget). Long tool results are pruned before summarization.- Falls back to
recentif LLM fails (fallback: truein response)
Message options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| strategy | ContextStrategy | "recent" | Message selection strategy |
| maxTokens | number | 4000 | Token budget for messages |
| keepFirst | boolean | false | Always include the first user message |
| pruneThreshold | number | 500 | Char threshold for pruning tool results before summarization |
| compactionStrategy | CompactionStrategy | — | Custom client-side compaction function |
See Chat Management for the full guide.
Recall: .recall() with no args defaults to { tools: true }. Pass { tools: { limit, minSimilarity } } for fine-grained control. Recalled tools persist within the session (session continuity).
Token budget: .limitTokens(n) caps total output (tools + messages). Tool token cost is subtracted from the message budget.
AssembledContext<T>
interface AssembledContext<T> {
tools: Record<string, T>; // explicit + discovered tools
messages: StoredMessage[]; // conversation messages
recalled: { // recalled context
tools: RankedTool[]; // auto-discovered tools with scores
memories: unknown[]; // reserved for future memory recall
};
strategyUsed: ContextStrategy; // strategy applied
fallback: boolean; // true if LLM summary failed
summary?: string; // summary text (when using summary strategies)
tokenEstimate: number; // estimated token count
conversationMessages: number; // total in conversation
totalMessages: number; // total messages stored
includedMessages: number; // messages included in context
}session.discoverTool
Agent-callable tool for runtime discovery. The agent can call this to find relevant tools on-the-fly.
session.getMessagesTool
Agent-callable tool for navigating conversation history. The agent can call this to retrieve messages that were summarized or excluded from the current context window.
// Exposed as "agentified_get_messages" in prepareStep activeTools
// Parameters: { limit?: number, afterSeq?: number, aroundSeq?: number }
// Returns: { messages: StoredMessage[], hasMore: boolean, maxSeq: number }Works with summary annotation — when the agent sees [Summary of messages 1–85 (85 messages compacted)], it can call getMessagesTool with afterSeq: 0, limit: 20 to read the compacted messages.
session.updateConversation({ messages })
Persist messages with deduplication for multi-turn context.
session.getMessages(opts)
Retrieve conversation history with strategy-based filtering.
Deferred Tool Loading
By default, all registered tools are candidates for discovery. Mark critical tools with alwaysInclude to ensure they're always present in the agent's tool set, regardless of discovery:
const instance = await ag.register({
tools: [
{ name: "escalate", description: "Escalate to human agent", parameters: {}, alwaysInclude: true, handler: escalateHandler },
{ name: "get_weather", description: "Get weather forecast", parameters: { ... }, handler: weatherHandler },
],
});- Tools with
alwaysInclude: trueare injected byprepareStepon every turn and excluded fromdiscover()results (they don't need to be ranked). - All other tools are deferred — they only appear after being found via
discoverToolor.recall(). - Discovered tool names persist within the session, so they're automatically available in subsequent turns.
Events
Subscribe to lifecycle events via onEvent in the config:
const agent = new ApiClient({
serverUrl: "http://localhost:9119",
tools: [...],
onEvent: (event) => {
switch (event.type) {
case "agentified:prefetch:start": // { messages }
case "agentified:prefetch:complete": // { tools, durationMs, tokenUsage? }
case "agentified:prefetch:skipped": // { tools, durationMs }
case "agentified:discover:start": // { query }
case "agentified:discover:complete": // { query, tools, durationMs, tokenUsage? }
}
},
});Types
interface ServerTool {
name: string;
description: string;
parameters: Record<string, unknown>;
metadata?: Record<string, unknown>;
fields?: { name: string; description: string; inputSchema?: string; outputSchema?: string };
alwaysInclude?: boolean; // bypass discovery — always present in the agent's tool set
}
interface BackendTool extends ServerTool {
type?: "backend";
alwaysInclude?: boolean;
handler: (args: Record<string, unknown>) => Promise<unknown>;
}
interface RankedTool extends ServerTool {
score: number;
graphExpanded?: boolean;
}
interface Message {
role: string;
content: string;
}
interface TokenUsage {
input: number;
output: number;
cached: number;
reasoning: number;
}Observability
Subscribe once at startup to receive events from every .recall() / .assemble() call — no need to destructure the assembled context in every turn.
const ag = new Agentified();
await ag.connect("http://localhost:9119");
const offCtx = ag.on("context:assembled", (evt) => {
metrics.emit("ctx", evt);
});
const offRecall = ag.on("recall", (evt) => {
console.log(`recalled ${evt.matches.length} tools in ${evt.durationMs}ms`);
});
// later: offCtx(); offRecall();Event names + payloads
| Event | Payload fields |
| --- | --- |
| context:assembled | sessionId, datasetId, strategyUsed, totalMessages, includedMessages, tokenEstimate, fallback, recalled: { tools }, durationMs |
| recall | sessionId, datasetId, config, matches, durationMs (only fires when .recall(...) was configured) |
Listeners can be sync or async. Errors thrown inside listeners are swallowed; each subscriber owns its own batching / backpressure. ag.on(...) returns a disposer — call it to unsubscribe.
For agent-step events (step), see the Mastra adapter — it exposes mag.on("step", cb) and mag.onStepFinish to wire into agent.generate(...).
Links
- Root README
- Documentation
- Architecture
- agentified-core
- Frontend Client
- React Bindings
- Mastra Adapter
- Python SDK
- ts-sdk-smoke example — runnable smoke test
