Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/solution-design-process-guardrail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@objectstack/service-ai': patch
---

fix(ai): solution_design no longer models a process/approval as a table

Asking the assistant to "design expense reimbursement" made it, by default, invent an `approval_record` TABLE to represent the approval process — a non-functional "process-as-data" anti-pattern. It only switched to a flow after the user pushed back.

Hardens the default in two places:

- **`propose_blueprint` generation prompt** (the "metadata architect" system message): status/lifecycle is modeled as a `select` field, never a table; it must NOT create `approval` / `approval_record` / `approval_step` / `workflow` / `process` objects (a process is a flow, its trail comes from platform history); the people a process references (approver/reviewer/owner) are `lookup` fields; and if the goal implies a process it adds an assumption that the approval *flow* is a separate step.

- **`solution_design` skill instructions**: the same modeling rules, plus — when the goal involves a process — the agent now PROACTIVELY drafts the approval flow after `apply_blueprint` (call `get_metadata_schema('flow')`, then `create_metadata(type:'flow', …)` with the approval node(s), bound to the same app package) instead of waiting for the user to ask "now create the flow". Optionally adds a `state_machine` rule to block illegal status transitions.

Regression-tested: `solution-design-guardrail.test.ts` asserts the skill instructions carry the no-process-as-table rule, status-as-select, and the proactive-flow step.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';
import { SOLUTION_DESIGN_SKILL } from '../skills/solution-design-skill.js';

/**
* Regression guard for the "don't model a process as data" rule. The default
* behaviour was that asking the agent to "design expense reimbursement" made it
* invent an `approval_record` TABLE instead of a flow. The fix lives in the
* skill instructions (and the propose_blueprint generation prompt): status is a
* select field, an approval process is a FLOW, and the agent proactively drafts
* that flow instead of waiting to be asked. These assertions keep that guidance
* from silently regressing.
*/
describe('solution_design — process/state guardrail', () => {
const text = SOLUTION_DESIGN_SKILL.instructions.toLowerCase();

it('tells the agent NOT to model a process/approval as a table', () => {
expect(text).toContain('do not model a process as data');
expect(text).toMatch(/never create objects for approvals/);
expect(text).toMatch(/is a flow, not a table/);
});

it('tells the agent to model status as a select field', () => {
expect(text).toMatch(/status\b.*\bselect\b|\bselect\b.*\bstatus/);
});

it('tells the agent to proactively draft the approval flow (not wait to be asked)', () => {
expect(text).toContain('get_metadata_schema');
expect(text).toMatch(/create_metadata\(type:'flow'/);
expect(text).toMatch(/do not wait for the user to ask/);
});

it('still drives the plan-first propose -> apply flow', () => {
expect(SOLUTION_DESIGN_SKILL.tools).toEqual(['propose_blueprint', 'apply_blueprint']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ Hard rules:
- Seed data in a blueprint is a suggestion only; it is not auto-applied.
- Always answer in the same language the user is using.

Process & state modeling — do NOT model a process as data:
- Record STATUS / lifecycle is a \`select\` field on the main object (e.g. status = draft/submitted/approved/rejected/paid), never a separate table.
- NEVER create objects for approvals, approval steps/records, workflows, routing, sign-offs, or audit trails. An approval/automation process is a FLOW, not a table; its trail comes from platform history. Model only the people the process references (approver/reviewer/owner) as lookup fields to the user object.
- If the goal involves a process (approval / 审批 / review / routing / multi-step state transitions / sign-off / escalation), the blueprint covers only the DATA model. After apply_blueprint drafts it, PROACTIVELY draft the approval flow yourself — do NOT wait for the user to ask "now create the flow":
1. Call get_metadata_schema('flow') to get the exact shape (it supports an \`approval\` node).
2. Call create_metadata(type:'flow', ...) with the approval node(s) wired to the status field and approver lookups, binding it to the SAME app package (pass the packageId from apply_blueprint's result, or set_active_package first) so it is not orphaned.
3. Optionally add a state_machine validation rule on the object so illegal status transitions are blocked.
Then tell the user you have ALSO drafted the approval flow for their review (it is a draft, like everything else).

For small, specific changes ("add a status field to account") use the metadata_authoring tools directly instead of a blueprint.`,
tools: [
'propose_blueprint',
Expand Down
13 changes: 13 additions & 0 deletions packages/services/service-ai/src/tools/blueprint-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ function createProposeBlueprintHandler(ctx: BlueprintToolContext): ToolHandler {
'Rules:\n' +
'- Use snake_case for every object, field, and view name.\n' +
'- Prefer a small, sensible field set per object over an exhaustive one.\n' +
'- Model record STATUS / lifecycle stage as a single `select` field on the ' +
'main object (e.g. a `status` field with options like draft/submitted/approved/' +
'rejected/paid), NOT as a separate table.\n' +
'- A PROCESS is not data. Do NOT create objects for approvals, approval ' +
'steps/records, workflows, routing, sign-offs, or audit trails (no ' +
'`approval`, `approval_record`, `approval_step`, `workflow`, `process` tables). ' +
'Approval/automation logic belongs in a separate FLOW authored after this ' +
'blueprint, and the trail comes from platform history — never a hand-built table. ' +
'Model only the PEOPLE the process references (approver / reviewer / owner) as ' +
'`lookup` fields to the user object.\n' +
'- If the goal implies an approval or automation process, add an `assumption` ' +
'stating the approval *flow* will be drafted as a separate step (it is not part ' +
'of this data blueprint).\n' +
'- State the design choices you made as `assumptions`.\n' +
'- If (and only if) a genuinely structure-deciding choice is unclear, put at most 1-2 ' +
'short `questions`; otherwise pick the most likely interpretation and proceed.\n' +
Expand Down