Meeting Action Items
Every meeting ends the same way: a page of notes, a few decisions everyone remembers differently, and action items that quietly evaporate before the next call. This prompt turns a transcript or a set of meeting notes into a structured follow-up — a list of actions with an owner and a deadline, the decisions that were actually taken, and the date of the next meeting. The traps are all in the details: “someone should check with legal” has no owner, “next Friday” is not a date until it is converted to YYYY-MM-DD, and a decision buried halfway through a debate is easy to miss entirely. When no deadline was agreed, the field stays null instead of a made-up date, and when no next meeting was scheduled, it stays null too. The result drops straight into a task tracker, so nothing agreed in the room gets lost on the way out.
System Prompt
You are a precise meeting notes extractor. Read the provided meeting transcript or notes and return ONLY a valid JSON object matching the schema below. Do not add explanations, markdown, or extra fields. action_items is a list with one object per action agreed in the meeting, each with task, owner, and due_date; use an empty array when no action was agreed. task and owner are required non-empty strings: extract the best values stated in the meeting and never invent a person or a task that was not mentioned. due_date is the agreed deadline as a YYYY-MM-DD date, or null when no deadline was set — never use an empty string for a missing deadline. decisions is a list of the decisions taken, as short plain sentences; use an empty array when nothing was decided. next_meeting is the date of the next meeting as YYYY-MM-DD, or null when no next meeting was scheduled — never use an empty string. If the text is not a meeting transcript or notes, do not produce any output.
Raw JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"action_items": {
"items": {
"additionalProperties": false,
"properties": {
"due_date": {
"minLength": 1,
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"type": [
"string",
"null"
]
},
"owner": {
"minLength": 1,
"type": "string"
},
"task": {
"minLength": 1,
"type": "string"
}
},
"required": [
"task",
"owner",
"due_date"
],
"type": "object"
},
"type": "array"
},
"decisions": {
"items": {
"minLength": 1,
"type": "string"
},
"type": "array"
},
"next_meeting": {
"minLength": 1,
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"type": [
"string",
"null"
]
}
},
"required": [
"action_items",
"decisions",
"next_meeting"
],
"title": "MeetingActionItems",
"type": "object"
}
Zod (TypeScript)
import { z } from "zod";
const ActionItemSchema = z
.object({
task: z.string().min(1),
owner: z.string().min(1),
due_date: z.string().min(1).regex(/^\d{4}-\d{2}-\d{2}$/).nullable(),
})
.strict();
export const MeetingActionItemsSchema = z
.object({
action_items: z.array(ActionItemSchema),
decisions: z.array(z.string().min(1)),
next_meeting: z.string().min(1).regex(/^\d{4}-\d{2}-\d{2}$/).nullable(),
})
.strict();
Pydantic v2 (Python)
from typing import Annotated
from pydantic import BaseModel, Field, field_validator
class ActionItem(BaseModel):
task: str = Field(..., min_length=1)
owner: str = Field(..., min_length=1)
due_date: str | None = Field(
..., min_length=1, pattern=r"^\d{4}-\d{2}-\d{2}$"
)
@field_validator("task", "owner", "due_date", mode="before")
@classmethod
def _strip_strings(cls, v):
if not isinstance(v, str):
return v
return v.strip()
class MeetingActionItems(BaseModel):
action_items: list[ActionItem] = Field(...)
decisions: list[Annotated[str, Field(min_length=1)]] = Field(...)
next_meeting: str | None = Field(
..., min_length=1, pattern=r"^\d{4}-\d{2}-\d{2}$"
)
@field_validator("next_meeting", mode="before")
@classmethod
def _strip_next_meeting(cls, v):
if not isinstance(v, str):
return v
return v.strip()
@field_validator("decisions", mode="before")
@classmethod
def _strip_decisions(cls, v):
if not isinstance(v, list):
return v
return [item.strip() if isinstance(item, str) else item for item in v]