createAgent()
Configure and create an AI agent with a single function call.
createAgent()
createAgent(config) is the main entry point of the Agent SDK. It takes an AgentConfig and returns a Ploy<PloyEnv> export that handles fetch requests, timer events, and the durable agent workflow.
AgentConfig
import { createAgent } from "@meetploy/agent-sdk";
export default createAgent({
// Required
systemPrompt: "You are a helpful assistant.",
handle: myHandler,
// Optional
model: "auto",
contextCompression: {
enabled: true,
maxTokens: 2000,
summaryModel: "gpt-4o-mini",
preserveRecentMessages: 12,
},
streaming: { enabled: false },
features: { memory: true, artifacts: true, scheduling: true, mcp: true },
tools: [myTool],
hooks: {
onRunPartial(ctx, partial) {
/* ... */
},
onRunComplete(ctx, response) {
/* ... */
},
},
maxSteps: 30,
});systemPrompt
The system prompt sent to the AI on every turn. Can be a static string or a function that returns a per-user prompt:
// Static
systemPrompt: "You are a helpful assistant.";
// Dynamic per-user
systemPrompt: async (userId) => {
const prefs = await loadPreferences(userId);
return `You are a helpful assistant. Language: ${prefs.language}.`;
};The SDK automatically appends the user's stored memories to the system prompt. When context compression is enabled, it also appends the running conversation summary.
handle
Your custom request handler. This is where you parse incoming requests (HTTP, webhooks, etc.), call agent.handleMessage(), and return a response:
async function handle(req: Request, env: PloyEnv, agent: AgentContext) {
const body = (await req.json()) as {
userId: string;
chatId: string;
text: string;
};
const messenger = new MyMessenger();
await agent.handleMessage({
userId: body.userId,
chatId: body.chatId,
platform: "http",
text: body.text,
messenger,
});
return Response.json({ ok: true });
}The AgentContext passed to your handler provides:
| Property | Type | Description |
|---|---|---|
handleMessage | (params) => Promise<void> | Enqueue a user message for processing |
commands | AgentCommands | Built-in commands (reset, debug, tools, MCP) |
state | StateManager | Direct access to the state binding |
env | PloyEnv | The full Ploy environment bindings |
model
The model name passed to the AI gateway. Defaults to "auto".
model: "gpt-4o";contextCompression
Enable summary-based context compression for long-running conversations:
contextCompression: {
enabled: true,
maxTokens: 2000,
summaryModel: "gpt-4o-mini",
preserveRecentMessages: 12,
}Options:
| Property | Type | Description |
|---|---|---|
enabled | boolean | Enables summary-based compression. Disabled by default. |
maxTokens | number | Estimated prompt budget before older history is summarized. |
summaryModel | string | Optional model used for summary generation. Defaults to the main model. |
preserveRecentMessages | number | Number of most recent chat messages to keep verbatim. Defaults to 12. |
summaryPrompt | string | Optional prompt template for the summarizer. Supports {summary} and {messages} placeholders. |
When the estimated prompt size exceeds maxTokens, the SDK summarizes older history, stores the result in the user's workspace, and keeps only the most recent messages verbatim. That summary is then injected into future system prompts.
This is currently based on estimated prompt size, not exact tokenizer counts. The API is stable, but the threshold is approximate until the SDK grows native tokenizer support.
features
Toggle built-in tool categories. All default to true:
features: {
memory: true, // memory_set, memory_get, memory_delete, memory_list
artifacts: true, // artifact_create
scheduling: true, // schedule_task, list_scheduled_tasks, get_scheduled_task, update_scheduled_task, delete_scheduled_task
mcp: true, // mcp_call_tool (when MCP servers are installed)
}streaming
Enable first-class partial generation inside the SDK workflow:
streaming: {
enabled: true,
throttleMs: 400,
minCharsDelta: 24,
maxPartialLength: 4096,
}When enabled, the SDK requests stream: true from the AI gateway, emits throttled partial updates to hooks.onRunPartial during generation, and still persists only the final assistant message into workspace history.
Because the durable workflow does not carry a live runtime messenger instance today, streamed partials are currently consumed through hooks. The Messenger.sendPartialMessage method is available for transports or adapters that can bridge partial delivery themselves.
Example:
import { createAgent } from "@meetploy/agent-sdk";
export default createAgent({
systemPrompt: "You are a helpful assistant.",
handle: myHandler,
streaming: {
enabled: true,
throttleMs: 250,
minCharsDelta: 12,
},
hooks: {
async onRunPartial(ctx, partial) {
await ctx.state.setJSON(`draft:${ctx.chatId}`, {
key: partial.key,
text: partial.text,
step: partial.step,
isFinal: partial.isFinal,
});
},
async onRunComplete(ctx, response) {
await ctx.state.delete(`draft:${ctx.chatId}`);
await ctx.state.set(`last_response:${ctx.chatId}`, response);
},
},
});In that flow, onRunPartial receives the current accumulated assistant text as it grows. partial.key stays stable for the in-progress assistant turn, so your UI or adapter can replace the same draft message instead of appending a new one on every update.
tools
Array of custom tool definitions. Use defineTool() for type-safe args:
import { defineTool } from "@meetploy/agent-sdk";
const myTool = defineTool({
name: "lookup_user",
description: "Look up a user by ID",
parameters: {
type: "object",
properties: { userId: { type: "string" } },
required: ["userId"],
},
async execute(args, ctx) {
// args.userId is typed as string
const data = await ctx.state.get(`user:${args.userId}`);
return data ?? "User not found";
},
});See Tools for full documentation.
hooks
Lifecycle hooks that fire during agent execution. Every hook receives a HookContext with access to env, state, and an execute() function for calling tools:
hooks: {
async onRunComplete(ctx, response) {
await ctx.state.set(`last_response:${ctx.userId}`, response);
},
async onRunError(ctx, error) {
console.error(`Agent error for ${ctx.userId}: ${error.message}`);
},
}See Hooks for the full list.
maxSteps
Maximum iterations of the AI-call-then-tool-execution loop per run. Prevents runaway tool loops. Defaults to 30.
maxSteps: 50;Messenger Interface
The Messenger you pass to handleMessage() defines how the agent sends responses back to the user. Implement it for your platform:
import type { Messenger } from "@meetploy/agent-sdk";
class TelegramMessenger implements Messenger {
sendMessage(chatId: string, text: string) {
return fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: "POST",
body: JSON.stringify({ chat_id: chatId, text }),
}).then(() => undefined);
}
sendDocument(chatId: string, filename: string, content: string | Uint8Array) {
// Upload file to Telegram
}
sendTypingIndicator(chatId: string) {
return fetch(`https://api.telegram.org/bot${token}/sendChatAction`, {
method: "POST",
body: JSON.stringify({ chat_id: chatId, action: "typing" }),
}).then(() => undefined);
}
// Optional for transports that can bridge partial delivery outside
// the durable workflow. The SDK's built-in streaming flow currently
// emits partials through hooks.onRunPartial.
sendPartialMessage?(chatId: string, partial: PartialMessage) {
return updateDraft(chatId, partial.key, partial.text);
}
}Return Value
createAgent() returns a Ploy<PloyEnv> object with:
fetch-- Handles HTTP requests (health check + delegates to yourhandlefunction)timer-- Handles scheduled task triggersworkflows.ploy_agent_run-- The durable agent tick loop
The SDK owns the timer and workflows handlers. You do not need to define
these yourself -- only implement the handle function.
Next Steps
- Tools -- Define custom tools with
defineTool() - Hooks -- React to agent lifecycle events
- State & Workspaces -- Understand per-user state
How is this guide?
Last updated on