Install
$ agentstack add skill-corezoid-simulator-ai-plugin-simulator-smart-forms-logic ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Smart Form Backend Logic
You orchestrate the Corezoid process(es) that make a Simulator.Company Smart Form interactive. Layout, i18n, styles, and viewModel defaults live in the Smart Form actor itself (see the simulator-smart-forms skill). Everything dynamic — initial viewModel, page transitions, submit handling, notifications, write-backs to actors — runs in Corezoid.
This skill does not author processes itself. Instead it:
- Confirms the Corezoid plugin is installed and its skills are available.
- Translates the user's intent into precise briefs in the exact format
corezoid-create / corezoid-edit expect (purpose · inputs · expected output · process type · concrete node skeleton).
- Invokes the matching Corezoid skill with that brief and lets it run
create-process / push-process / lint-process / run-task.
- Provisions an API key (
create-api-keyfor a new one, orfind-principal
for an existing apiLogin) and shares the bound process to it (share-object) so the Smart Form runtime can call it.
- Finally binds the process to the Smart Form env (
createSmartFormor
updateSmartFormEnv) via the simulator MCP server.
0. Preflight — confirm the Corezoid plugin is installed
Before producing any brief, verify the Corezoid plugin is available in this session:
- Check whether the
create-process,push-process,create-alias,
create-api-key, find-principal, and share-object MCP tools are listed in the tool registry. If they are, the corezoid MCP server is running. (create-api-key / find-principal / share-object are needed by Step E below — provisioning the API key the Smart Form runtime authenticates with.)
- Check whether the
corezoid-create,corezoid-edit, and (optionally)
corezoid-access skills are reachable (they appear in the available-skills list when installed).
- If either is missing, stop and instruct the user (reply in the user's
language):
> "This workflow needs the Corezoid plugin. Install it from > github.com/corezoid/corezoid-ai-plugin > and run /corezoid-init to authenticate, then ask me to continue."
Do not attempt to author .conv.json files yourself or call PAPI directly when the Corezoid plugin is absent.
1. The runtime contract
The platform binds one Corezoid process per env to a Smart Form via the env's procId. That bound process receives a POST whenever a page is rendered or a form submission event fires. It must respond to {{__callback_url}} with the right shape:
| path | Trigger | Required response shape | |---------|---------------------------------------|------------------------------------------------------------| | /get | A page is being opened or re-rendered | { "code": 200, "viewModel": { … } } | | /send | A button clicked or any element with submitOnChange: true changed | { "code": 200, "data": { "changes": [], "notifications": [] } } |
Two distinct /send triggers — always distinguish them:
| Source | body.buttonId | body.buttonData | |---|---|---| | Button click | button's id | {} (empty object) | | submitOnChange element | element's id | { "action": "select"/"check"/…, "value": "newValue" } |
When an element has submitOnChange: true (e.g. a select, radio, or checkbox), the platform fires /send immediately on value change without waiting for a button. body.buttonId is the element's own id and body.buttonData carries the interaction detail (action + value). body.data still contains the full current snapshot of all field values.
Other status codes: 205 re-render whole page; 302 redirect to another page (data.nextPage); 4xx/5xx surfaces an error toast.
> Topology is up to the developer. The bound process may handle both paths > itself in a single graph, or fan out to sub-processes (one per path / page / > buttonId) via api_copy. The contract is the response shape, not the layout. > Ask the user which approach fits the form's complexity before generating briefs.
Sample /get request (incoming)
{
"__callback_url": "https://cb-apigw.corezoid.com/callback/sync_api/…",
"body": {
"context": { "appId": "", "rootActorId": "",
"browser": "Chrome", "language": "en", "timeZoneOffset": -180 },
"page": "index",
"query": {}
},
"path": "/get",
"sessionData": { "userInfo": { "id": 52731, "login": "user@x.com", "saId": 5501,
"memberGroups": [79693] } }
}
Sample /send request — button click
{
"__callback_url": "https://cb-apigw.corezoid.com/callback/sync_api/…",
"body": {
"buttonId": "submit_btn",
"buttonData": {},
"context": { "actorId": "", "rootActorId": "", "appId": "" },
"data": { "day_comment": "…", "self_score": "4" },
"formId": "",
"page": "index",
"sectionId": "",
"query": {}
},
"path": "/send",
"sessionData": { /* same as /get */ }
}
Sample /send request — submitOnChange element
buttonData is non-empty: action describes the interaction type, value is the newly selected value. buttonId is the field's id, not a button.
{
"__callback_url": "https://cb-apigw.corezoid.com/callback/sync_api/…",
"__headers": {},
"body": {
"buttonId": "project_name",
"buttonData": {
"action": "select",
"value": "energy_efficiency"
},
"context": {
"appId": "0118a16b-bf08-4e13-b3e7-0f97dfa8b6db",
"browser": "Chrome",
"language": "en",
"timeZoneOffset": -180
},
"data": {
"grant_category": "ecology",
"project_desc": "",
"project_name": "energy_efficiency"
},
"formId": "project_section",
"page": "index",
"sectionId": "project_body",
"query": {}
},
"path": "/send",
"sessionData": {}
}
Sample /get callback (outgoing)
{
"code": 200,
"viewModel": {
"userName": "Alice",
"submit_btn_visibility": "visible"
}
}
Sample /send callback
{
"code": 200,
"data": {
"changes": [
{ "id": "submit_btn", "visibility": "hidden" },
{ "id": "day_comment", "visibility": "hidden" }
],
"notifications": [
{ "title": "Thank you, we received your answer", "type": "success" }
]
}
}
changes[] is a surgical patch — only listed component ids are touched. changeRules (e.g. { "options": { "action": "replace" } }) controls how arrays merge. Full reference: $CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-page-protocol.md.
Realistic viewModel shape
Every key returned in viewModel must match a {{placeholder}} somewhere in pages//config or in viewModel defaults. A representative payload:
{
"userName": "Mykhailo Sydoreiko",
"metric_total_value": "245 min",
"metric_total_sub": "4.08 hr · daily activity",
"items_table_body": [
{ "value": "row_1",
"options": [
{ "title": "[url=https://…]Some entry[/url]", "value": "item_name" },
{ "title": "16min", "value": "item_duration" }
] }
],
"submit_btn_visibility": "hidden",
"self_score_visibility": "disabled"
}
Adding a new metric to the page? Add the matching key here and the matching {{key}} in pages//config.
2. Reusable node fragments
These are the contract-bound JSON shapes any brief should embed verbatim. They are reusable building blocks — you decide how they compose into a single process or several.
2.1 Condition on path (dispatch /get vs /send)
{
"type": "go_if_const",
"to_node_id": "",
"conditions": [
{ "param": "path", "const": "/get", "fun": "eq", "cast": "string" }
]
}
A second go_if_const does the same for /send; the trailing go falls through to a default branch.
2.2 Condition on body.page (dispatch per-page)
For a multi-page form, branch on body.page after dispatching by path:
{
"type": "go_if_const",
"to_node_id": "",
"conditions": [
{ "param": "body.page", "const": "index", "fun": "eq", "cast": "string" }
]
}
2.3 Condition on body.buttonId (dispatch per-event source for /send)
body.buttonId identifies the source of every /send event — both button clicks and submitOnChange field changes. Use go_if_const on it to route each case:
{
"type": "go_if_const",
"to_node_id": "",
"conditions": [
{ "param": "body.buttonId", "const": "submit_btn", "fun": "eq", "cast": "string" }
]
}
2.3a Detect submitOnChange vs button click
When the same /send handler must behave differently depending on whether the user clicked a button or changed a submitOnChange field, branch on body.buttonData.action — it is present and non-empty only for field-change events:
{
"type": "go_if_const",
"to_node_id": "",
"conditions": [
{ "param": "body.buttonData.action", "const": "", "fun": "ne", "cast": "string" }
]
}
Alternatively, check the specific field id and action together:
{
"type": "go_if_const",
"to_node_id": "",
"conditions": [
{ "param": "body.buttonId", "const": "project_name", "fun": "eq", "cast": "string" },
{ "param": "body.buttonData.action", "const": "", "fun": "ne", "cast": "string" }
]
}
Read the new value from body.data. (the full field snapshot) or from body.buttonData.value (just the newly selected value). Use whichever is cleaner for your logic; both contain the same information for single-select fields.
2.4 Fan-out to a sub-process (api_copy, fire-and-forget)
Use when you want the bound process to delegate work to a separate Corezoid process. The sub-process becomes responsible for the callback.
{
"type": "api_copy",
"user_id": ,
"conv_id": "@",
"ref": "",
"mode": "create",
"group": "all",
"data": {},
"data_type": {},
"err_node_id": ""
}
2.5 Build viewModel (Code Node, api_code)
JavaScript pattern for assembling viewModel from an actor lookup or other sources:
const viewModel = data?.viewModel || {};
const eventData = data?.event_actor?.data || {};
viewModel.userName = eventData.user_name || "";
viewModel.metric_total_value = `${eventData.total_min ?? 0} min`;
if (eventData.status === "complete") {
viewModel.submit_btn_visibility = "hidden";
viewModel.self_score_visibility = "disabled";
} else {
viewModel.submit_btn_visibility = "visible";
viewModel.self_score_visibility = "visible";
}
data.viewModel = viewModel;
2.6 Build changes + notifications (Code Node)
data.changes.push(
{ "id": "submit_btn", "visibility": "hidden" },
{ "id": "day_comment", "visibility": "hidden" }
);
data.notifications.push({
"title": "Thank you, we received your answer",
"type": "success"
});
data.responseData = { changes: data.changes, notifications: data.notifications };
2.7 Final callback API node (/get)
{
"type": "api",
"method": "POST",
"url": "{{__callback_url}}",
"rfc_format": true,
"content_type": "application/json",
"extra": { "code": "200", "viewModel": "{{viewModel}}" },
"extra_type": { "code": "number", "viewModel": "object" },
"extra_headers": { "content-type": "application/json; charset=utf-8" },
"err_node_id": "",
"version": 2
}
2.8 Final callback API node (/send)
Identical shape, with data in place of viewModel:
{
"type": "api",
"method": "POST",
"url": "{{__callback_url}}",
"rfc_format": true,
"content_type": "application/json",
"extra": { "code": "200", "data": "{{responseData}}" },
"extra_type": { "code": "number", "data": "object" },
"extra_headers": { "content-type": "application/json; charset=utf-8" },
"err_node_id": "",
"version": 2
}
2.9 Complete process skeleton (bound, single-process topology)
The fragments above (§2.1–2.8) are pieces; this is how they wire together in the most common topology — a single bound process that owns both /get and /send. Use this as the default starting shape and only depart from it when the form's complexity demands a multi-process layout (see §3).
> Why this matters. The single most common failure when writing a Smart-Form > backend is forgetting that /send has two sources — a button click and > any submitOnChange field change — and processing both as a real submit. The > form then tries to persist a half-filled record on every field interaction. The > skeleton below makes that second fork explicit.
2.9.1 Flowchart
┌───────┐
│ Start │
└───┬───┘
│
┌───────────▼───────────┐
│ Condition on `path` │ (§2.1)
└─┬───────────────────┬─┘
│ │
path=/get path=/send
│ │
┌──────────▼─────────┐ ┌──────▼──────────────────────┐
│ Build viewModel │ │ Extract │
│ (api_code) §2.5 │ │ data.buttonAction = │
└──────────┬─────────┘ │ body.buttonData.action │
│ │ (api_code, safe ?? "") │
│ └──────┬──────────────────────┘
│ │
│ ┌───────────▼───────────────────┐
│ │ Condition on buttonAction │ (§2.3a)
│ │ "" ─→ real submit │
│ │ "select" ─→ submitOnChange │
│ │ "check" ─→ submitOnChange │
│ │ … │
│ └─┬───────────────────────┬─────┘
│ │ │
│ real submit submitOnChange
│ (buttonId = (lightweight
│ submit_btn, ack: empty
│ dispatch §2.3) changes[])
│ │ │
│ ┌─────▼────────┐ ┌──────▼─────────┐
│ │ Persist / │ │ Build empty │
│ │ call CREATE │ │ sendResponse │
│ │ ACTOR / api │ │ Data │
│ │ (api_rpc / │ │ (api_code §2.6)│
│ │ api §2.6) │ └──────┬─────────┘
│ └──────┬───────┘ │
│ │ ok │
│ ┌──────▼─────────┐ │
│ │ Build success │ │
│ │ sendResponse │ │
│ │ Data (api_code)│ │
│ └──────┬─────────┘ │
│ │ │
│ │ ┌── on err ────────┤
│ │ │ │
│ ┌──────▼───▼─────┐ │
│ │ Build error │ │
│ │ sendResponse │ │
│ │ (notification) │ │
│ └──────┬─────────┘ │
│ │ │
┌──────────▼───┐ ┌──▼──────────────────────▼──┐
│ Callback │ │ Callback to │
│ to │ │ {{__callback_url}} │
│ {{__callback_│ │ POST { code:200, │
│ url}}
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [corezoid](https://github.com/corezoid)
- **Source:** [corezoid/simulator-ai-plugin](https://github.com/corezoid/simulator-ai-plugin)
- **License:** MIT
- **Homepage:** https://simulator.company
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.