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
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ L3 Composio MCP HTTP server — actions executed via Composio

---

## Personas (exactly 13 — DO NOT add Health Monitor; it was dropped per audit)
## Personas (9 specialists — Revenue Operations and Insights are composite synthesizers that absorbed three sub-personas each)

| Department | Specialists |
|---|---|
| Sales | researcher, qualifier, strategist, writer, scheduler, brief-writer |
| CS | activation |
| RevOps | crm-logger, pipeline-reporter, slack-digest |
| Insight | feedback-tagger, theme-synthesizer, linear-filer |
| RevOps | revenue-operations (crm log + pipeline summary + slack digest in one envelope) |
| Insight | insights (feedback tagging + theme synthesis + Linear/GitHub filing in one envelope) |

Plus Conductor (L1) and 4 Department Heads (L2) which exist as `AgentDefinition` objects nested inside the Conductor's `query()` call.

Expand Down
8 changes: 2 additions & 6 deletions app/api/test-persona/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@ const VALID_PERSONAS: ReadonlySet<string> = new Set([
"scheduler",
"brief-writer",
"activation",
"crm-logger",
"pipeline-reporter",
"slack-digest",
"feedback-tagger",
"theme-synthesizer",
"linear-filer",
"revenue-operations",
"insights",
]);

export async function POST(request: Request) {
Expand Down
10 changes: 5 additions & 5 deletions lib/orchestrator/conductor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ Each manager owns a fixed roster of specialists. Your job:
"id": string, // unique across the whole DAG
"specialistId": "researcher" | "qualifier" | "strategist" | "writer" | "scheduler" | "brief-writer"
| "activation"
| "crm-logger" | "pipeline-reporter" | "slack-digest"
| "feedback-tagger" | "theme-synthesizer" | "linear-filer",
| "revenue-operations"
| "insights",
"input": object, // values may contain the literal token "\${each}" when fanoutOver is set
"dependsOn"?: string[], // ids of upstream tasks in this same DAG
"passOutput"?: string[], // whitelist of output keys to expose to downstream tasks (default: expose all)
Expand All @@ -45,13 +45,13 @@ FANOUT TEMPLATES — read carefully:
- A task with "fanoutOver" is a TEMPLATE the system expands into one materialized task per source item.
- Use the literal token "\${each}" inside the input wherever the per-item id should land (e.g. { "leadId": "\${each}" }).
- Within a single fanout chain (e.g. researcher -> qualifier -> writer all with fanoutOver: "leads"), dependsOn references stay as the SHORT template id ("researcher", not "researcher-1"). The system rewires each instance correctly.
- A non-fanout task (e.g. "slack-digest") that depends on a fanout template ("crm-logger") will wait for ALL N instances of that template to complete.
- A non-fanout task (e.g. "revenue-operations") that depends on a fanout template ("writer") will wait for ALL N instances of that template to complete.
- Downstream tasks read upstream outputs via previousOutputs.<upstreamTaskId>.<field>. Use passOutput on the upstream task to whitelist which output keys flow through (default: all).

STRICT OUTPUT RULES:
- Return ONLY the JSON object. No prose, no markdown code fences, no commentary before or after.
- The "tasks" array must be flat — no nesting per department. Each task must use one of the 13 specialist ids above.
- Cross-department dependencies are allowed (e.g. crm-logger depends on writer). Use the task ids returned by the managers.
- The "tasks" array must be flat — no nesting per department. Each task must use one of the 9 specialist ids above.
- Cross-department dependencies are allowed (e.g. revenue-operations depends on writer). Use the task ids returned by the managers.
- If an objective involves no work for a department, simply do not invoke that manager.
`;

Expand Down
20 changes: 9 additions & 11 deletions lib/orchestrator/managers/insight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,28 @@ export const INSIGHT_MANAGER_AGENT_NAME = "insight-mgr" as const;

export const insightManager: AgentDefinition = {
description:
"Insight department head. Decomposes a customer-feedback objective into tagging, theme synthesis, and Linear issue filing.",
"Insight department head. Decomposes a customer-feedback objective into a single insights specialist task that tags feedback, synthesizes themes, and queues Linear/GitHub issues.",
model: "claude-opus-4-7",
mcpServers: ["composio"],
tools: ["mcp__composio__LINEAR_CREATE_ISSUE"],
tools: ["mcp__composio__LINEAR_CREATE_LINEAR_ISSUE"],
prompt: `You are the Insight Department Head at GMaestro.

You manage exactly three specialists:
- "feedback-tagger" — given raw customer feedback, classifies it (bug / feature / churn-signal / praise) — read-only tagging.
- "theme-synthesizer" — clusters tagged feedback into 3–5 themes and writes a Notion page.
- "linear-filer" — files actionable themes as Linear issues (or GitHub issues).
You manage exactly ONE specialist:
- "insights" — pure synthesizer that produces a composite Insight artifact: tagged feedback + synthesized themes + issues to file (Linear or GitHub). The dashboard's post-approval handler dispatches the actual writes.

OUTPUT FORMAT — strict. Output ONLY a JSON array of tasks, nothing else. No prose, no markdown fences.

Each task:
{
"id": string, // e.g. "feedback-tagger-1"
"specialistId": "feedback-tagger" | "theme-synthesizer" | "linear-filer",
"input": object,
"id": string, // e.g. "insights"
"specialistId": "insights",
"input": object, // pass the feedback array as input.item.feedback
"dependsOn"?: string[]
}

Rules:
- theme-synthesizer typically depends on at least one feedback-tagger task.
- linear-filer typically depends on theme-synthesizer.
- ALWAYS a single task — insights runs once per workflow, never fanned out.
- input must include the feedback batch the persona should classify + cluster.
- If no Insight work is required, return [].
`,
};
30 changes: 11 additions & 19 deletions lib/orchestrator/managers/revops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,36 @@ export const REVOPS_MANAGER_AGENT_NAME = "revops-mgr" as const;

export const revopsManager: AgentDefinition = {
description:
"Revenue Operations department head. Decomposes a RevOps objective into CRM logging, pipeline reporting, and Slack-digest tasks.",
"Revenue Operations department head. Decomposes a RevOps objective into a single revenue-operations specialist task that emits CRM updates, pipeline summary, and Slack digest in one envelope.",
model: "claude-opus-4-7",
mcpServers: ["composio"],
tools: ["mcp__composio__SLACK_POST_MESSAGE"],
tools: ["mcp__composio__SLACK_SEND_MESSAGE"],
prompt: `You are the Revenue Operations Department Head at GMaestro.

You manage exactly three specialists:
- "crm-logger" — writes lead/deal updates into HubSpot or Google Sheets.
- "pipeline-reporter" — produces a structured pipeline summary from CRM data.
- "slack-digest" — posts an end-of-run summary to a Slack channel.
You manage exactly ONE specialist:
- "revenue-operations" — pure synthesizer that produces a composite RevOps artifact: per-lead CRM updates + pipeline summary + Slack digest. The dashboard's post-approval handler dispatches the actual HubSpot writes / Slack posts.

OUTPUT FORMAT — strict. Output ONLY a JSON array of tasks, nothing else. No prose, no markdown fences.

Each task:
{
"id": string,
"specialistId": "crm-logger" | "pipeline-reporter" | "slack-digest",
"specialistId": "revenue-operations",
"input": object,
"dependsOn"?: string[],
"passOutput"?: string[],
"triggerRule"?: "all_success" | "all_done",
"fanoutOver"?: "leads" | "trial-signals"
"triggerRule"?: "all_success" | "all_done"
}

PATTERN — log every processed lead, then post one summary digest:
PATTERN — one final task that closes out the workflow with the full RevOps envelope:
[
{ "id": "crm-logger", "specialistId": "crm-logger", "input": { "leadId": "\${each}" }, "fanoutOver": "leads", "mode": "batch", "dependsOn": ["writer"], "passOutput": ["crmContactId"], "triggerRule": "all_done" },
{ "id": "slack-digest", "specialistId": "slack-digest", "input": {}, "dependsOn": ["crm-logger"], "triggerRule": "all_done" }
{ "id": "revenue-operations", "specialistId": "revenue-operations", "input": {}, "dependsOn": ["writer"], "triggerRule": "all_done" }
]

MODE — crm-logger should ALWAYS run in "batch" mode when fanoutOver is set. One LLM call writes all N HubSpot updates via COMPOSIO_MULTI_EXECUTE_TOOL — vastly faster than 47 separate sessions. slack-digest is a single task (no fanout).

Note on cross-department deps: the sales manager will typically emit a fanout chain for "writer", "scheduler" etc. When you reference "writer" in dependsOn, the system understands you mean ALL writer instances (when crm-logger is also fanned out per-lead, the system pairs them by item id).

Rules:
- crm-logger should typically be fanned out per-lead (use fanoutOver: "leads") with dependsOn: ["writer"].
- slack-digest is the final task — single instance, depends on crm-logger, triggerRule "all_done" so it runs even if some chains failed.
- pipeline-reporter is single instance, no fanout, depends on crm-logger.
- ALWAYS a single task — revenue-operations runs once per workflow, never fanned out.
- depends on the upstream sales chain (typically "writer", or whatever the latest sales stage is) so it sees per-lead outputs via previousOutputs.
- triggerRule "all_done" so the run still wraps up even if some chains failed.
- If no RevOps work is required, return [].
`,
};
76 changes: 0 additions & 76 deletions lib/personas/prompts/crm-logger.md

This file was deleted.

48 changes: 0 additions & 48 deletions lib/personas/prompts/feedback-tagger.md

This file was deleted.

Loading