Skip to content

Commit 6e3cf03

Browse files
os-zhuangclaude
andauthored
feat(service-ai): ADR-0040 — unify the platform assistant (data + authoring registers) (#1735)
The default data_chat agent becomes THE platform assistant: - skills: + metadata_authoring + solution_design (registered by the cloud AI Studio plugin; deployments without it degrade gracefully to data-only — the skill registry ignores unresolved names). - instructions: intent preamble — classify build/change vs data intent FIRST, apply that register's discipline, never mix registers or narrate failures. - persona: label 'Assistant', role 'Business Application Assistant'. - model: temperature 0.2 (authoring determinism wins). - guardrails: blocklist union MINUS alter_schema/drop_table (the build register IS schema work, and it is already draft-gated per ADR-0033); 60s execution budget for whole-app builds. - planning: react ×10 with replan (builds take more steps than answers). Motivation (staging 2026-06-11): the data-only persona accepted a whole-app build — tools are registry-global — without the authoring disciplines, hand- rolled the schema, collided with leftovers, and narrated its debugging to a business user. ADR-0040: the end user never picks an agent; one assistant carries both registers and the skills system does per-intent behavior. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent a491d12 commit 6e3cf03

2 files changed

Lines changed: 43 additions & 18 deletions

File tree

packages/services/service-ai/src/__tests__/chatbot-features.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ describe('AgentRuntime', () => {
566566

567567
expect(metadataService.get).toHaveBeenCalledWith('agent', 'data_chat');
568568
expect(agent?.name).toBe('data_chat');
569-
expect(agent?.role).toBe('Business Data Analyst');
569+
expect(agent?.role).toBe('Business Application Assistant');
570570
});
571571

572572
it('should return undefined for unknown agent', async () => {
@@ -587,7 +587,7 @@ describe('AgentRuntime', () => {
587587
const messages = runtime.buildSystemMessages(DATA_CHAT_AGENT);
588588
expect(messages).toHaveLength(1);
589589
expect(messages[0].role).toBe('system');
590-
expect(messages[0].content).toContain('helpful data assistant');
590+
expect(messages[0].content).toContain('assistant for this business application platform');
591591
});
592592

593593
it('should include context when provided', () => {
@@ -625,7 +625,7 @@ describe('AgentRuntime', () => {
625625
it('should derive model config from agent', () => {
626626
const options = runtime.buildRequestOptions(DATA_CHAT_AGENT, []);
627627
expect(options.model).toBe('gpt-4');
628-
expect(options.temperature).toBe(0.3);
628+
expect(options.temperature).toBe(0.2);
629629
expect(options.maxTokens).toBe(4096);
630630
});
631631

@@ -667,7 +667,7 @@ describe('AgentRuntime', () => {
667667
]);
668668
const agents = await runtime.listAgents();
669669
expect(agents).toHaveLength(2);
670-
expect(agents[0]).toEqual({ name: 'data_chat', label: 'Data Assistant', role: 'Business Data Analyst' });
670+
expect(agents[0]).toEqual({ name: 'data_chat', label: 'Assistant', role: 'Business Application Assistant' });
671671
expect(agents[1]).toEqual({ name: 'metadata_assistant', label: 'Metadata Assistant', role: 'Schema Architect' });
672672
});
673673

@@ -998,14 +998,14 @@ describe('Agent Routes', () => {
998998
describe('DATA_CHAT_AGENT', () => {
999999
it('should be a valid agent definition', () => {
10001000
expect(DATA_CHAT_AGENT.name).toBe('data_chat');
1001-
expect(DATA_CHAT_AGENT.role).toBe('Business Data Analyst');
1001+
expect(DATA_CHAT_AGENT.role).toBe('Business Application Assistant');
10021002
expect(DATA_CHAT_AGENT.active).toBe(true);
10031003
expect(DATA_CHAT_AGENT.visibility).toBe('global');
10041004
});
10051005

10061006
it('should reference the data_explorer skill (capability bundle moved to skill metadata)', () => {
10071007
expect(DATA_CHAT_AGENT.tools ?? []).toHaveLength(0);
1008-
expect(DATA_CHAT_AGENT.skills).toEqual(['data_explorer', 'actions_executor']);
1008+
expect(DATA_CHAT_AGENT.skills).toEqual(['data_explorer', 'actions_executor', 'metadata_authoring', 'solution_design']);
10091009
});
10101010

10111011
it('should have guardrails configured', () => {

packages/services/service-ai/src/agents/data-chat-agent.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { Agent } from '@objectstack/spec/ai';
1212
* - identity (name / label / role)
1313
* - persona (system prompt)
1414
* - model + safety config
15-
* - skills attached → `skills: ['data_explorer']`
15+
* - skills attached → `skills: [...]` (ADR-0040: data + authoring)
1616
*
1717
* To grant data-exploration powers to a different agent, just add
1818
* `data_explorer` to its `skills[]`. To revoke globally, set the
@@ -40,36 +40,61 @@ export const DEFAULT_DATA_AGENT_NAME = 'data_chat';
4040

4141
export const DATA_CHAT_AGENT: Agent = {
4242
name: DEFAULT_DATA_AGENT_NAME,
43-
label: 'Data Assistant',
44-
role: 'Business Data Analyst',
45-
instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
43+
label: 'Assistant',
44+
role: 'Business Application Assistant',
45+
// ADR-0040 — the unified platform assistant. End users never pick an
46+
// agent; this one persona answers BOTH registers, and the FIRST job of
47+
// every turn is classifying which register the user is in. The per-register
48+
// disciplines (plan-first blueprints, draft semantics, no failure
49+
// narration, data-query guidance) live in the attached skills.
50+
instructions: `You are the assistant for this business application platform. You can both ANSWER QUESTIONS about the user's data and BUILD or CHANGE the application itself (objects, fields, views, dashboards, whole apps).
51+
52+
INTENT FIRST — before acting, classify the request:
53+
- BUILD/CHANGE intent ("build…", "create an app/object/field…", "add/change/remove …", "建/做一个…系统/应用/字段"): follow the solution-design and metadata-authoring disciplines from your skills — plan-first for whole systems, drafts are not live, verify after building, and never narrate tool errors or internal retries to the user; present outcomes, not your debugging.
54+
- DATA intent ("how many…", "show/list…", "查/统计/看一下…"): use the data-exploration tools and answer concisely with real numbers.
55+
Never mix the registers in one reply: a build turn reports what was built and its verification status; a data turn answers the question.
4656
4757
Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,
4858

4959
model: {
5060
provider: 'openai',
5161
model: 'gpt-4',
52-
temperature: 0.3,
62+
// The stricter of the merged personas: authoring needs determinism.
63+
temperature: 0.2,
5364
maxTokens: 4096,
5465
},
5566

56-
// Capability bundle lives on the skill; the agent only references it.
57-
// `data_explorer` = read side, `actions_executor` = write side.
58-
skills: ['data_explorer', 'actions_executor'],
67+
// Capability bundles live on skills; the agent only references them.
68+
// `data_explorer`/`actions_executor` = the data register;
69+
// `metadata_authoring`/`solution_design` = the build register (ADR-0040).
70+
// The authoring skills are registered by the cloud AI Studio plugin — on
71+
// deployments without it these references simply don't resolve and the
72+
// assistant gracefully degrades to data-only (the skill registry ignores
73+
// unknown names).
74+
skills: ['data_explorer', 'actions_executor', 'metadata_authoring', 'solution_design'],
5975

6076
active: true,
6177
visibility: 'global',
6278

6379
guardrails: {
6480
maxTokensPerInvocation: 8192,
65-
maxExecutionTimeSec: 30,
66-
blockedTopics: ['delete_records', 'drop_table', 'alter_schema'],
81+
// Whole-app builds (blueprint + per-artifact drafting + verification)
82+
// legitimately run past the old 30s data-answer budget.
83+
maxExecutionTimeSec: 60,
84+
// Union of both personas' blocklists MINUS the ones that contradict the
85+
// build register: `alter_schema`/`drop_table` were the data-only agent's
86+
// way of refusing schema work, but authoring IS schema work — and it is
87+
// already draft-gated (ADR-0033: nothing is live until publish, and
88+
// destructive changes carry their own warning + HITL). What remains is
89+
// genuinely off-limits in both registers.
90+
blockedTopics: ['delete_records', 'drop_database', 'raw_sql', 'system_tables'],
6791
},
6892

6993
planning: {
7094
strategy: 'react',
71-
maxIterations: 5,
72-
allowReplan: false,
95+
// Builds take more steps than data answers (blueprint → drafts → verify).
96+
maxIterations: 10,
97+
allowReplan: true,
7398
},
7499

75100
memory: {

0 commit comments

Comments
 (0)