Automated pipeline that syncs customer meeting recordings from Grain into a structured Notion Account Tracking database. Built to run as Oz cloud agents.
After each day of customer meetings, Grainiac:
- Pulls all external meeting recordings from Grain for a given date
- Identifies the customer company from participant email domains
- Fetches the full transcript for each meeting
- Analyzes the transcript against a structured enterprise customer template
- Creates or updates the company's page in the Notion Account Tracking database
The result is a living record per customer that tracks stakeholders, tech stack, requirements, use cases, deal status, follow-ups, and more — all extracted directly from meeting transcripts.
grainiac/
├── .agents/
│ └── skills/
│ ├── grainiac-orchestrator/
│ │ └── SKILL.md # Oz skill for the main orchestrator agent
│ ├── grainiac-meeting-processor/
│ │ ├── SKILL.md # Oz skill for per-meeting child agents
│ │ └── references/
│ │ └── notion-template.md # Notion page template (single source of truth)
│ └── grainiac-slack-summary/
│ └── SKILL.md # Oz skill: summarize a recording to Slack
├── scripts/
│ ├── grain_client.py # Grain API client (list, get, transcript, hydrate)
│ ├── notion_client.py # Notion API client (CRUD, block builders, section finders)
│ └── fetch_daily_meetings.py # CLI: fetch external meetings for a date
├── .env.example # Sample environment variables for local testing
├── LICENSE
├── README.md
└── SECURITY.md
Orchestrator agent (.agents/skills/grainiac-orchestrator/) — the main agent that runs once per batch:
- Runs
scripts/fetch_daily_meetings.pyto get the day's external meetings - For a single meeting, processes it inline
- For multiple meetings, spawns one child cloud agent per meeting via
oz agent run-cloud
Meeting processor agent (.agents/skills/grainiac-meeting-processor/) — one per meeting:
- Fetches the full transcript from Grain
- Reads and analyzes the entire transcript (not just the AI summary)
- Checks if the company already has a Notion page
- Creates a new page or surgically updates the existing one
Slack summary skill (.agents/skills/grainiac-slack-summary/) — optional, one recording at a time:
- Fetches a recording and its transcript from Grain
- Generates a concise meeting summary
- Posts a formatted summary to a Slack channel
The orchestrator supports three modes based on the prompt:
| Prompt | Behavior |
|---|---|
| (no parameters) | Process today's meetings (Pacific time default) |
| "Process meetings from 2026-03-10" | Process all external meetings on that date |
| "Process Acme Corp meetings from 2026-03-10" | Process only that company's meetings on that date |
| Variable | Required | Description |
|---|---|---|
GRAINIAC_GRAIN_TOKEN |
Yes | Grain personal access token |
GRAINIAC_NOTION_TOKEN |
Yes | Notion integration token |
GRAINIAC_NOTION_DATABASE_ID |
Yes | Notion database ID for the Account Tracking database |
GRAINIAC_NOTION_TITLE_PROPERTY |
No | Name of the database's title property that holds the company name (default: Company). Set this if your title property has a different name (new Notion databases default to Name). |
GRAINIAC_SLACK_TOKEN |
Slack skill only | Slack Bot OAuth token (xoxb-...) |
GRAINIAC_SLACK_CHANNEL |
Slack skill only | Slack channel to post summaries to (e.g. #meeting-summaries) |
GRAINIAC_INTERNAL_DOMAIN |
Recommended | Your company's email domain for filtering internal participants (e.g. yourcompany.com). If unset, only participants Grain explicitly marks as internal are treated as internal. |
GRAINIAC_TIMEZONE |
No | Timezone for resolving "today" (defaults to America/Los_Angeles) |
Grainiac writes one page per customer into a Notion database. Before the first run:
- Create a database in Notion (or use an existing one) for account tracking.
- Note the title property name. Every Notion database has exactly one title property. New databases call it
Nameby default, while Grainiac assumesCompany. Either rename the title property toCompany, or setGRAINIAC_NOTION_TITLE_PROPERTYto match your title property's name. - Create an internal integration at https://www.notion.so/my-integrations and copy its token into
GRAINIAC_NOTION_TOKEN. - Share the database with the integration (database
⋯menu → Connections → add your integration). Without this, the Notion API returns 404s. - Copy the database ID from the URL —
notion.so/<workspace>/<DATABASE_ID>?v=...— intoGRAINIAC_NOTION_DATABASE_ID.
No other database properties are required; everything else lives in the page body, which follows the structure in the Notion template.
Grainiac runs as an Oz cloud agent. You need the Oz CLI and an authenticated session.
-
Install and authenticate the Oz CLI. The CLI ships with the Warp app; otherwise see Installing the CLI. Then sign in:
oz login
For CI or headless environments, export an API key instead:
export WARP_API_KEY="wk-...". -
Create an Oz environment named
grainiacwith this repo checked out. The orchestrator discovers the environment by this exact name, so it must match. List environments (and copy the ID for later) with:oz environment list
-
Add secrets. Warp injects team secrets into cloud runs as environment variables. Each command prompts for the value securely:
oz secret create --team GRAINIAC_GRAIN_TOKEN oz secret create --team GRAINIAC_NOTION_TOKEN oz secret create --team GRAINIAC_NOTION_DATABASE_ID oz secret create --team GRAINIAC_INTERNAL_DOMAIN
Add
GRAINIAC_SLACK_TOKENandGRAINIAC_SLACK_CHANNELas well if you use the Slack summary skill. Find your Notion database ID in the database URL:notion.so/<workspace>/<DATABASE_ID>?v=.... -
Run manually (replace
<ENV_ID>with yourgrainiacenvironment ID):oz agent run-cloud \ --environment <ENV_ID> \ --prompt "Read the grainiac-orchestrator skill for instructions. Process today's meetings."
-
Schedule a daily run:
oz schedule create \ --name "Grainiac Daily" \ --cron "0 5 * * *" \ --environment <ENV_ID> \ --prompt "Read the grainiac-orchestrator skill for instructions. Process today's meetings."
This runs at 05:00 UTC (roughly 9–10pm Pacific depending on DST).
All scripts use the Python 3.8+ standard library only — no pip dependencies required.
For local testing, copy .env.example to .env, fill in your values, and export them before running (e.g. set -a; source .env; set +a). The scripts read configuration directly from environment variables.
python scripts/fetch_daily_meetings.py <YYYY-MM-DD | today>Outputs a JSON array of external customer meetings to stdout. Stderr shows progress.
Importable Grain API client:
get_recording(id)— fetch recording metadata and participantsget_transcript_text(id)— fetch full plain-text transcriptlist_all_recordings()— paginate through all recordingshydrate_with_participants(recordings)— enrich list results with per-recording participant datafilter_external_meetings(recordings)— filter to external-only, identify company names
Importable Notion API client:
find_company_page(name)— check if a company page existscreate_page(name, blocks)— create a new page in the databaseget_all_blocks(page_id)— get all blocks from a pageappend_blocks(parent_id, blocks, after=)— insert blocks at a specific positionupdate_block(block_id, data)— update a block's contentfind_heading_block(),get_last_block_in_section()— locate insertion points- Block builders:
heading1(),heading2(),bullet(),todo(),paragraph(),divider(),rich()
The template that defines the structure of each customer's Notion page lives at .agents/skills/grainiac-meeting-processor/references/notion-template.md. This is the single source of truth for what information is tracked per customer. Key sections:
- Follow-Up Items (checkbox list)
- Key Activities & Decisions (reverse-chronological timeline)
- Meeting Log (hyperlinked Grain recordings)
- Company Overview, People & Org
- Tech Stack & Environment
- Current Tooling & Competitive Landscape
- Requirements (deployment, inference/LLM, security, governance, integrations, blockers)
- Use Cases (primary and secondary workflows)
- Commercial (pricing, procurement/legal)
- POC / Pilot Tracker
- Communication preferences
- Sentiment & Relationship Notes