Product Intelligence

Read every call, not the three you had time for

The honest answer about a feature or an objection is spread across dozens of transcripts. Reading them one at a time doesn't scale, and the tools holding them can usually only be queried by attendee or date — never by what was actually said.

What breaks when you do this by hand

  • The truth about a feature or objection is buried across dozens of transcripts, and reading them one by one doesn't scale.

  • Ad-hoc questions stall because the underlying tools can only be queried by attendee or date, not by topic.

  • Findings arrive as prose nobody can aggregate or trend over time.

What you get instead

  • Topic-level answers across an entire call corpus

  • Objections and feature signals returned as structured data

  • The same analysis re-runnable next quarter, unchanged

This is the whole thing

The opening of Voice of Customer Research — the system prompt, its typed parameters, and the tools it's allowed to reach, all declared up front. No canvas, no hidden nodes. 187 lines of source you can review in a pull request.

Read all 187 lines →

voc.fml

Copy
system("You are an elite Product Marketing Manager and senior Voice of the Customer analyst for Barndoor AI. Your job is to extract raw, unfiltered truth from customer calls and provide sharp, actionable answers to strategic questions.")

parameter("keyword", type=string, title="Target Keyword to Scan For")
parameter("question", type=string, title="Research Question")
parameter("max_meetings", type=int, default=10, title="Max Meetings to Analyze")
parameter("attendee_email", type=string, title="Attendee Email")

require mcp Avoma
require mcp Google-Drive

# ── Session 1: Fetch meetings ─────────────────────────────────────────────────
session("fetch_meetings") {
    use mcp Avoma

    + Call get_current_datetime to get the current UTC time.
      Then call list_meetings for the last 30 days, using strict ISO 8601 UTC format (e.g. YYYY-MM-DDThh:mm:ssZ) for from_date and to_date.
      Use attendee_emails: ["{{ .params.attendee_email }}"]
      Paginate if needed, up to {{ .params.max_meetings }} total meetings.
      Only include meetings where a transcript or recording is available.

    - For each meeting, extract the UUID, date, title, and infer the external company name
      from the title or participant list. Exclude internal only meetings.
      Limit output strictly to {{ .params.max_meetings }} meetings.

    schema {
        meetings: {
            uuid: string
            date: string
            title: string
            company: string   # External company name — infer from title or attendees
        }[]
    }
}

What every plan here guarantees

Zero prompt drift

Every plan is a versioned contract. Run 1 and run 10,000 behave identically.

Scoped sessions

Each LLM call sees only the context it needs — no one giant prompt, no context rot.

Typed output

Plans return validated objects pinned to a schema, not text you have to parse.

Reusable like an API

Parameterise once and call it from anywhere — versioned, auditable, shareable.