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
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ describe('AgentRuntime', () => {

expect(metadataService.get).toHaveBeenCalledWith('agent', 'data_chat');
expect(agent?.name).toBe('data_chat');
expect(agent?.role).toBe('Business Data Analyst');
expect(agent?.role).toBe('Business Application Assistant');
});

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

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

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

Expand Down Expand Up @@ -998,14 +998,14 @@ describe('Agent Routes', () => {
describe('DATA_CHAT_AGENT', () => {
it('should be a valid agent definition', () => {
expect(DATA_CHAT_AGENT.name).toBe('data_chat');
expect(DATA_CHAT_AGENT.role).toBe('Business Data Analyst');
expect(DATA_CHAT_AGENT.role).toBe('Business Application Assistant');
expect(DATA_CHAT_AGENT.active).toBe(true);
expect(DATA_CHAT_AGENT.visibility).toBe('global');
});

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

it('should have guardrails configured', () => {
Expand Down
49 changes: 37 additions & 12 deletions packages/services/service-ai/src/agents/data-chat-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { Agent } from '@objectstack/spec/ai';
* - identity (name / label / role)
* - persona (system prompt)
* - model + safety config
* - skills attached → `skills: ['data_explorer']`
* - skills attached → `skills: [...]` (ADR-0040: data + authoring)
*
* To grant data-exploration powers to a different agent, just add
* `data_explorer` to its `skills[]`. To revoke globally, set the
Expand Down Expand Up @@ -40,36 +40,61 @@ export const DEFAULT_DATA_AGENT_NAME = 'data_chat';

export const DATA_CHAT_AGENT: Agent = {
name: DEFAULT_DATA_AGENT_NAME,
label: 'Data Assistant',
role: 'Business Data Analyst',
instructions: `You are a helpful data assistant that helps users explore and understand their business data through natural language.
label: 'Assistant',
role: 'Business Application Assistant',
// ADR-0040 — the unified platform assistant. End users never pick an
// agent; this one persona answers BOTH registers, and the FIRST job of
// every turn is classifying which register the user is in. The per-register
// disciplines (plan-first blueprints, draft semantics, no failure
// narration, data-query guidance) live in the attached skills.
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).

INTENT FIRST — before acting, classify the request:
- 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.
- DATA intent ("how many…", "show/list…", "查/统计/看一下…"): use the data-exploration tools and answer concisely with real numbers.
Never mix the registers in one reply: a build turn reports what was built and its verification status; a data turn answers the question.

Always answer in the same language the user is using. Detailed tool-usage guidance is supplied by the skills attached to this agent.`,

model: {
provider: 'openai',
model: 'gpt-4',
temperature: 0.3,
// The stricter of the merged personas: authoring needs determinism.
temperature: 0.2,
maxTokens: 4096,
},

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

active: true,
visibility: 'global',

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

planning: {
strategy: 'react',
maxIterations: 5,
allowReplan: false,
// Builds take more steps than data answers (blueprint → drafts → verify).
maxIterations: 10,
allowReplan: true,
},

memory: {
Expand Down
Loading