Mara (the AI PM) communicates with users primarily through free-form text. When she needs a
structured answer — a choice between options, multiple selections, or an open text input —
she emits a fenced markdown block tagged talkide-prompt. The frontend detects this block,
suppresses the raw JSON display, and renders an interactive card instead. Use of the tag is
opt-in: if Mara does not need structured input, she simply writes plain text.
Protocol
Mara emits the block as part of her regular chat message stream. It may appear anywhere in
the message (start, middle, or end). A single message must not contain more than one
talkide-prompt block.
Fenced block syntax:
```talkide-prompt
{ ... }
```
The frontend parser MUST:
- Detect the opening fence
```talkide-promptand read until the closing```. - Parse the content as JSON.
- Render an interactive prompt card in place of the fenced block (never show the raw JSON).
- If JSON parsing fails, render the block as a plain code block (graceful degradation).
Any text outside the fenced block is rendered as normal markdown.
JSON Schema
{
"prompts": Prompt[] // required, 1..N items
}
Prompt {
"id": string // required — unique within the block, used in the response
"type": "choice"
| "multi-choice"
| "text" // required
"question": string // required — label shown above the input
"options": string[] // required for choice and multi-choice; absent for text
"suggestions": string[] // optional, text type only; absent or empty = no chips shown
}
Constraints:
| Field | Rule |
|---|---|
prompts | At least 1 item |
id | Non-empty string, unique within the block |
question | Non-empty string |
options | Required for choice and multi-choice; at least 1 item |
suggestions | Allowed only for text; each item is a non-empty string |
Prompt Types
choice — single select
Fields: id, type, question, options.
{
"id": "backend",
"type": "choice",
"question": "Which backend framework?",
"options": ["Spring Boot", "Express", "FastAPI"]
}
FE renders: radio button group. Exactly one option can be selected. Submit is disabled until a selection is made.
Response format: the label of the selected option.
- Which backend framework?: Spring Boot
multi-choice — multi select
Fields: id, type, question, options.
{
"id": "auth",
"type": "multi-choice",
"question": "Which auth methods should be supported?",
"options": ["Email / password", "Google", "GitHub", "Magic link"]
}
FE renders: checkbox group. Zero or more options can be checked. Submit is always enabled (zero selections is a valid answer meaning “none”).
Response format: comma-separated list of selected labels, or (none) when nothing is
selected.
- Which auth methods should be supported?: Email / password, Google
text — open input
Fields: id, type, question. Optional: suggestions.
{
"id": "project_name",
"type": "text",
"question": "Working name for the project?",
"suggestions": ["Bakery App", "PekárnaOnline", "BreadShop"]
}
FE renders: a text input. When suggestions is present and non-empty, suggestion chips are
displayed above the input. Clicking a chip copies its value into the input (the user can
still edit it). Submit is disabled when the field is empty.
Response format: the text the user typed (or the chip value if unchanged after click).
- Working name for the project?: Bakery in the Forest
User Response Format
After the user clicks Submit, the frontend sends a plain text message to Mara. The message contains one line per prompt, in the order they appeared in the block:
- <question>: <answer>
For multi-choice with multiple selections, answers are comma-separated on a single line.
Mara treats this message as a regular user turn; no special envelope is needed — the format
is self-explanatory from context.
Full example response for a 4-prompt block:
- Which backend framework?: Spring Boot
- Database?: PostgreSQL
- Which auth methods should be supported?: Email / password, Google
- Working name for the project?: Bakery in the Forest
Edge Cases
Mandatory vs. optional answers
choice: mandatory — Submit disabled until one option is selected.multi-choice: optional — zero selections is valid; Submit always enabled.text: mandatory — Submit disabled while input is empty.
Single-prompt vs. multi-prompt card
Both are rendered as a single card with one Submit button. A single-prompt block produces a card with one input; a multi-prompt block stacks inputs vertically in declaration order. The card title is omitted when there is only one prompt; for multi-prompt, the card may display a neutral title such as “Answer Mara’s questions”.
User does not submit
Not handled for now. The card remains visible and interactive. There is no timeout or automatic dismissal.
Unknown type value
The frontend MUST skip the unknown prompt and log a warning. It MUST still render and submit the remaining valid prompts.
Malformed JSON
The frontend renders the entire block as a plain code block. The conversation can continue normally.
Examples
Example 1 — tech stack selection (mixed choice types)
Mara emits:
```talkide-prompt
{
"prompts": [
{
"id": "backend",
"type": "choice",
"question": "Which backend framework?",
"options": ["Spring Boot", "Express", "FastAPI"]
},
{
"id": "db",
"type": "choice",
"question": "Database?",
"options": ["PostgreSQL", "MySQL", "MongoDB"]
},
{
"id": "auth",
"type": "multi-choice",
"question": "Auth methods?",
"options": ["Email / password", "Google", "GitHub"]
},
{
"id": "name",
"type": "text",
"question": "Working project name?",
"suggestions": ["Bakery App", "PekárnaOnline"]
}
]
}
```
User submits, frontend sends:
- Which backend framework?: Spring Boot
- Database?: PostgreSQL
- Auth methods?: Email / password, Google
- Working project name?: Bakery in the Forest
Example 2 — single choice
Mara emits:
```talkide-prompt
{
"prompts": [
{
"id": "hosting",
"type": "choice",
"question": "Preferred hosting region?",
"options": ["Europe (Frankfurt)", "US East", "US West"]
}
]
}
```
User response:
- Preferred hosting region?: Europe (Frankfurt)
Example 3 — open text without suggestions
Mara emits:
```talkide-prompt
{
"prompts": [
{
"id": "description",
"type": "text",
"question": "Describe the main purpose of the app in one sentence."
}
]
}
```
User response:
- Describe the main purpose of the app in one sentence.: Customers can reserve tables and order food before arriving at the restaurant.
Example 4 — multi-choice with zero selections
Mara emits:
```talkide-prompt
{
"prompts": [
{
"id": "integrations",
"type": "multi-choice",
"question": "Which third-party integrations are needed?",
"options": ["Stripe", "Mailchimp", "Slack", "Google Sheets"]
}
]
}
```
User selects nothing and submits. Frontend sends:
- Which third-party integrations are needed?: (none)
Feedback
The brief was thorough and the key decisions were already locked — that made writing
straightforward. Two things that would have helped: (1) explicit rule for what happens when
multi-choice has zero selections — I defaulted to (none) but a brief mention would have
confirmed it; (2) whether the card title for multi-prompt blocks is determined by Mara (e.g.,
a top-level "title" field in the JSON) or always auto-generated by the FE — I assumed FE
generates it, which should be confirmed before implementation.
Thanks for the feedback.