with one click
agent-builder
// Build AI-powered DenchClaw apps that interact with the OpenClaw agent — create chat sessions, send and receive messages with streaming, expose app tools for agent invocation, and access agent memory.
// Build AI-powered DenchClaw apps that interact with the OpenClaw agent — create chat sessions, send and receive messages with streaming, expose app tools for agent invocation, and access agent memory.
| name | agent-builder |
| description | Build AI-powered DenchClaw apps that interact with the OpenClaw agent — create chat sessions, send and receive messages with streaming, expose app tools for agent invocation, and access agent memory. |
| metadata | {"openclaw":{"inject":true,"always":true,"emoji":"🤖"}} |
This skill covers building apps that interact with the AI agent. For core app structure and manifest basics, see the parent app-builder skill (app-builder/SKILL.md).
agent permission required)// Create a new chat session
const { sessionId } = await dench.chat.createSession("My Analysis Session");
// List existing sessions
const sessions = await dench.chat.getSessions({ limit: 20 });
// Returns array of { id, title, createdAt, ... }
const result = await dench.chat.send(sessionId, "Analyze the people table and summarize trends", {
onEvent(event) {
switch (event.type) {
case "text-delta":
appendToChat(event.data);
break;
case "reasoning-delta":
updateThinking(event.data);
break;
case "tool-input-start":
showToolCall(event.name, event.args);
break;
case "tool-output-available":
showToolResult(event.result);
break;
}
},
});
// result contains the full accumulated response: { text, toolCalls, reasoning }
// Get message history for a session
const messages = await dench.chat.getHistory(sessionId);
// Returns array of { role, content, toolCalls?, ... }
// Check if a session has an active run
const isActive = await dench.chat.isActive(sessionId);
// Abort an active run
await dench.chat.abort(sessionId);
// Send a one-off message to the agent (no streaming, no session management)
await dench.agent.send("Remind me to check the reports tomorrow at 9am");
agent permission required)Apps can expose themselves as tools that the agent can invoke. Declare tools in the manifest:
name: "Chart Analyzer"
permissions:
- agent
tools:
- name: "analyze-chart"
description: "Generates a visual chart analysis from structured data"
inputSchema:
type: object
properties:
data:
type: array
description: "Array of data points"
chartType:
type: string
enum: ["bar", "line", "pie", "scatter"]
required: ["data", "chartType"]
Register tool handlers in the app:
dench.tool.register("analyze-chart", async (input) => {
const { data, chartType } = input;
// Process the data and generate the chart
const chart = renderChart(data, chartType);
const analysis = analyzeData(data);
// Return result to the agent
return {
analysis: analysis,
chartImageUrl: chart.toDataURL(),
summary: `Generated ${chartType} chart with ${data.length} data points`,
};
});
When the agent invokes the tool, the app receives the input, processes it, and returns the result. The app must be open (active tab) for tool invocation to work.
agent permission required)// Read the agent's memory (MEMORY.md + daily logs)
const memory = await dench.memory.get();
// Returns { memory: "...", dailyLogs: [...] }
For advanced apps that want to build their own chat UI or need direct Gateway access, here's the WebSocket protocol:
const ws = new WebSocket("ws://127.0.0.1:18789");
// Port is configurable via gateway.port in ~/.openclaw-dench/openclaw.json
// Request (client -> gateway)
{ type: "req", id: "uuid", method: "agent", params: { ... } }
// Response (gateway -> client)
{ type: "res", id: "uuid", ok: true, payload: { ... } }
// Event (gateway -> client)
{ type: "event", event: "agent", seq: 1, payload: { ... } }
ws.onopen = () => {
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "connect",
params: {
minProtocol: 3,
maxProtocol: 3,
client: { id: "my-app", version: "1.0", platform: "web", mode: "backend" },
role: "user",
scopes: ["agent", "chat"],
caps: ["tool-events"],
},
}),
);
};
// Start an agent run
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "agent",
params: {
message: "Hello, analyze this data",
sessionKey: "agent:main:web:my-session-id",
channel: "webchat",
lane: "web",
},
}),
);
// Abort a run
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "chat.abort",
params: { sessionKey: "agent:main:web:my-session-id" },
}),
);
ws.onmessage = (e) => {
const frame = JSON.parse(e.data);
if (frame.type !== "event" || frame.event !== "agent") return;
const { stream, data } = frame.payload;
switch (stream) {
case "lifecycle":
// data.phase: "start" | "end" | "error"
break;
case "thinking":
// data.delta: incremental reasoning text
break;
case "assistant":
// data.delta: incremental response text
// data.text: full text (on completion)
// data.stopReason: "end_turn" | "tool_use" | etc.
break;
case "tool":
// data.phase: "start" | "update" | "result"
// data.name, data.args, data.result
break;
}
};
NOTE: For most apps, the bridge chat API (dench.chat.*) is much simpler than direct WebSocket usage. Use the Gateway WS only when you need full control over the connection.
let currentSessionId = null;
const chatContainer = document.getElementById("chat");
async function startNewChat() {
const { sessionId } = await dench.chat.createSession("App Chat");
currentSessionId = sessionId;
chatContainer.innerHTML = "";
}
async function sendMessage(text) {
appendMessage("user", text);
const responseEl = appendMessage("assistant", "");
await dench.chat.send(currentSessionId, text, {
onEvent(event) {
if (event.type === "text-delta") {
responseEl.textContent += event.data;
}
},
});
}
function appendMessage(role, content) {
const div = document.createElement("div");
div.className = `message ${role}`;
div.textContent = content;
chatContainer.appendChild(div);
chatContainer.scrollTop = chatContainer.scrollHeight;
return div;
}
async function analyzeData(objectName) {
const schema = await dench.objects.getSchema(objectName);
const { sessionId } = await dench.chat.createSession("Data Analysis");
const result = await dench.chat.send(
sessionId,
`Analyze the ${objectName} object. It has these fields: ${schema.fields.map((f) => f.name).join(", ")}. ` +
`Query the data and provide insights.`,
{
onEvent(event) {
if (event.type === "text-delta") updateAnalysisPanel(event.data);
},
},
);
showFinalAnalysis(result.text);
}
Build data-driven DenchClaw apps with full CRUD access to workspace objects (.object.yaml tables), DuckDB queries and mutations, data dashboards with Chart.js and D3.js, and interactive tools.
Build and manage DenchClaw apps — self-contained web applications that run inside the workspace with access to DuckDB data, workspace objects, AI chat, and the full DenchClaw platform API.
DuckDB schema initialization, field types reference, auto-generated PIVOT views, and SQL CRUD operations for workspace objects, fields, and entries.
Full 3-step workflow for creating workspace objects (SQL → filesystem → verify), CRM patterns for common object types, kanban boards, and the post-mutation checklist.
Manage DuckDB CRM data, aggressive relation-linked fields, and synced markdown documents in the workspace. Use when creating or updating objects, fields, entries, foreign-table links, row notes, or entry-linked edit logs.
.object.yaml format and template, view type settings (kanban, calendar, timeline, gallery, list), saved views with filter operators, and date format rules.