Install
openclaw skills install communication-protocolDefines how the OpenClaw agent should communicate with Tidy during a build session, ensuring clear, predictable, and build-focused interactions.
openclaw skills install communication-protocolFrontend: renders chat + timeline from backend events.Backend (Tidy): starts build session, runs OpenClaw turns, stores events.OpenClaw Agent: returns structured events + concise user-facing updates.Environment variables passed to each turn:
TIDY_BUILD_IDTIDY_BUILD_PROMPTTIDY_SESSION_IDThe same TIDY_SESSION_ID is reused for follow-up turns (after user answers).
Messages from Tidy are wrapped with machine headers:
[FROM:TIDY]
[BUILD_ID:<uuid>]
[SESSION_ID:<uuid>]
[MESSAGE_TYPE:BUILD_REQUEST|USER_ANSWER|USER_ANSWERS]
[QUESTION_ID:<id>] # only for USER_ANSWER
[QUESTION_IDS:<id1,id2,...>] # only for USER_ANSWERS
System note: headers above are transport metadata from Tidy. Do not repeat them in user-facing replies.
<message body>
Agent handling rules:
BUILD_REQUEST: start/continue build conversation for the request body.USER_ANSWER / USER_ANSWERS: continue from clarification response(s).All events should be represented as:
{
"type": "event.type",
"payload": {}
}
payload.source must be "agent" for agent-originated events.
assistant.message.createdUse for short, user-facing chat text only.
Payload:
{
"text": "string",
"source": "agent"
}
Rules:
progress.step.startedStarts a user-facing stepper step.
Payload:
{
"step_id": "build_record|parse|research|design|assemble|validate|finalize",
"title": "short friendly title",
"description": "one user-friendly sentence",
"index": 1,
"total": 7,
"source": "agent"
}
progress.step.completedMarks a prior step as complete.
Payload:
{
"step_id": "build_record|parse|research|design|assemble|validate|finalize",
"source": "agent"
}
status.changedMajor lifecycle transitions only.
Payload:
{
"status": "running|complete|failed",
"source": "agent"
}
Optional on completion/failure:
{
"status": "complete",
"output": "final result summary",
"source": "agent"
}
question.requestedUse when blocked and one answer is required.
Payload:
{
"question_id": "uuid-or-stable-id",
"prompt": "single clear question",
"input": "single_choice|text",
"required": true,
"options": [
{ "id": "option_a", "label": "Option A", "description": "optional" },
{ "id": "option_b", "label": "Option B", "description": "optional" }
],
"source": "agent"
}
questions.requestedUse when blocked and multiple answers are needed together.
Payload:
{
"question_set_id": "qs_123",
"prompt": "I need a few details before I continue.",
"required_all": true,
"questions": [
{
"question_id": "q1",
"prompt": "Budget range?",
"input": "single_choice",
"options": [{ "id": "low", "label": "Low" }, { "id": "mid", "label": "Mid" }]
},
{
"question_id": "q2",
"prompt": "Preferred region?",
"input": "text"
}
],
"source": "agent"
}
session.completed / session.failedTerminal events.
Payload:
{
"source": "agent"
}
session.failed may include:
{
"reason": "short failure reason",
"source": "agent"
}
When moving between steps:
progress.step.completed for the previous step.progress.step.started for the next step.title/description user-friendly.assistant.message.created for the same transition.Do not emit phase.changed for new builds.
progress.step.* events only (no extra chat message unless truly useful).question.requested or questions.requested and keep prompt plain.Use this exact step_id set and tone:
| step_id | title | description |
|---|---|---|
build_record | Starting your build | I’m setting up your build session. |
parse | Understanding your request | I’m reading your request and mapping the plan. |
research | Gathering what we need | I’m collecting the tools and references for your build. |
design | Planning your agent | I’m creating the build plan for your agent. |
assemble | Building your agent | I’m assembling the pieces now. |
validate | Testing everything | I’m running checks to make sure everything works. |
finalize | Finalizing | I’m wrapping up and preparing your result. |
Never send user-facing text like:
Instead, use:
session.started.status.changed => runningprogress.step.started (build_record)progress.step.completed (previous)progress.step.started (next)question.requested or questions.requestedstatus.changed (complete or failed)session.completed or session.failed