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
86 changes: 86 additions & 0 deletions libs/ai-orchestrator/src/__tests__/mesh-router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createMeshRouterNode,
DEFAULT_MESH_CONFIG,
_test_createDefaultMeshClient,
MESH_SYSTEM_PROMPT,
type MeshRouterConfig,
} from '../orchestrator/mesh-router';
import { createDefaultAgentState } from '../schema';
Expand Down Expand Up @@ -119,6 +120,68 @@ describe('analyzeMeshQuery', () => {
expect(result).toEqual({ target: null });
});
});

describe('generic query inference (without explicit persona tags)', () => {
it('infers po for status/requirements queries', async () => {
const client = fakeClient('{"target": "po"}');
const messages = [
makeMessage('@isolate- What is the status of this issue?'),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'po' });
});

it('infers po for design token queries', async () => {
const client = fakeClient('{"target": "po"}');
const messages = [
makeMessage('@isolate- What design tokens are available for colors?'),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'po' });
});

it('infers architect for monorepo structure queries', async () => {
const client = fakeClient('{"target": "architect"}');
const messages = [
makeMessage(
'@isolate- How are the presets structured in the monorepo?',
),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'architect' });
});

it('infers architect for dependency queries', async () => {
const client = fakeClient('{"target": "architect"}');
const messages = [
makeMessage('@isolate- What dependencies do we use for testing?'),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'architect' });
});

it('infers a11y for accessibility compliance queries', async () => {
const client = fakeClient('{"target": "a11y"}');
const messages = [
makeMessage(
'@isolate- How do we ensure ARIA compliance in components?',
),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'a11y' });
});

it('infers a11y for keyboard navigation queries', async () => {
const client = fakeClient('{"target": "a11y"}');
const messages = [
makeMessage(
'@isolate- What keyboard navigation patterns are supported?',
),
];
const result = await analyzeMeshQuery(messages, client);
expect(result).toEqual({ target: 'a11y' });
});
});
});

// ── createMeshRouterNode ──────────────────────────────────────────────────────
Expand Down Expand Up @@ -339,3 +402,26 @@ describe('_test_createDefaultMeshClient', () => {
expect(() => _test_createDefaultMeshClient()).toThrow(/OPENAI_API_KEY/);
});
});

describe('MESH_SYSTEM_PROMPT', () => {
it('includes all six persona domain descriptions with proper bullet format', () => {
const domainBullets = [
'- po:',
'- architect:',
'- dev:',
'- a11y:',
'- qa:',
'- docs:',
];

domainBullets.forEach((bullet) => {
expect(MESH_SYSTEM_PROMPT).toContain(bullet);
});
});

it('contains required routing instruction keywords', () => {
expect(MESH_SYSTEM_PROMPT).toContain('@isolate-');
expect(MESH_SYSTEM_PROMPT).toContain('ONLY');
expect(MESH_SYSTEM_PROMPT).toContain('JSON');
});
});
21 changes: 16 additions & 5 deletions libs/ai-orchestrator/src/orchestrator/mesh-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,26 @@ export interface MeshQueryResult {

// ── LLM prompt ────────────────────────────────────────────────────────────────

const MESH_SYSTEM_PROMPT = `You are a routing classifier for an AI multi-agent orchestration system.
export const MESH_SYSTEM_PROMPT = `You are a routing classifier for an AI multi-agent orchestration system.

Analyze the provided message and determine whether it contains a directed query or request aimed at a specific agent persona.
Analyze the provided message and identify the target specialist persona, or return null if no cross-persona routing is needed.

Valid persona IDs: po, architect, dev, a11y, qa, docs
Valid persona IDs and their domains:
- po: Project status, requirements, design tokens, high-level planning
- architect: Monorepo structure, tooling, dependencies, presets
- dev: Code implementation, Panda CSS logic, TypeScript types
- a11y: Accessibility standards, ARIA, keyboard navigation
- qa: Test coverage, edge cases, bug verification
- docs: Documentation, Storybook, usage examples

Rules:
- Return a non-null target ONLY when the message explicitly asks a question of, or directs a task to, a specific persona — for example: "I need @isolate-po to clarify the token choice" or "Can qa verify this edge case?".
- Return null for standard work outputs, APPROVED/REJECTED decisions, inline explanations, or any message that does not address a specific persona as the recipient of a query.
- ONLY for messages starting with @isolate- that contain a query (?): infer the best-fit persona based on question content. For example:
* "What is the status of this issue?" → po
* "How are the presets structured?" → architect
* "How do we ensure ARIA compliance?" → a11y
- For explicit persona tags (e.g., "@isolate-po", "Can qa verify"): return the explicitly named persona.
- For ALL OTHER messages (no @isolate- prefix and no explicit persona tag): return null, even if they contain questions.
- Return null for standard work outputs, APPROVED/REJECTED decisions, and inline explanations.

Respond with ONLY valid JSON in this exact format, with no additional text:
{"target": "persona_id"}
Expand Down
Loading