This document defines the complete context Mara (the TalkIDE Product Manager AI) has available at the start of every Claude CLI session. The context comes from two sources with different lifecycles:
| Source | Owns | Updated | Cache scope |
|---|---|---|---|
Plugin agent file agents/talkide-pm.md | Identity, role, TalkIDE UI awareness, delegation rules | Plugin release | Shared across all users (max cache reuse) |
Project file <project-path>/CLAUDE.md | Project meta + team roster (members + per-member briefing), active-user marker | BE renders before each Claude spawn | Shared across spawns of the same project, breaks at active-user change |
Privacy principle — what may go into CLAUDE.md
Anything written into CLAUDE.md is shared with Anthropic as part of
the LLM context (their infrastructure, logs, retention windows, prompt
cache). Therefore:
Only data for which the user gave explicit, purpose-bound consent may go into CLAUDE.md.
Currently allowed:
- ✅
salutation— user explicitly provided this for “address me as X” - ✅
teamBriefing— user explicitly wrote this as instructions for the team
Currently forbidden:
- ❌
email— no consent for sharing with LLM - ❌
displayName/ full name — no consent for sharing with LLM - ❌
phone, address, any other PII
When adding new fields to the team roster, first verify the user gave explicit consent for that field to go to the LLM. If unsure, leave it out and ask.
Lifecycle
The TalkIDE BE topology:
- User sends a message → BE spawns Claude (
claudefor first turn,claude --resume <id>for subsequent turns) - The Claude process lives while Mara works (delegations, agent waits)
- When Mara finishes her turn (“user turn”), the process exits
- Next user message → BE spawns again with
--resume
Effectively, <project>/CLAUDE.md is re-read on every user turn
because each turn = new process spawn. The only stale window is within
a single Mara turn (typically minutes), where any change to user data
won’t be visible until the next user message.
Today vs. tomorrow
Today: each project has exactly one user. The team roster contains one member, who is always the active user.
Tomorrow: multi-user collaboration on a single project. The team roster contains N members; the active-user marker tells Mara who is currently writing. The format below already supports this — no breaking change needed when collaboration is enabled.
BE responsibility — render <project>/CLAUDE.md
The BE MUST render <project-path>/CLAUDE.md before every Claude spawn
(both initial spawn and --resume). The rendered content is purely a
deterministic projection of stable database state.
Sync contract — IMPORTANT: The runtime source of truth is the Kotlin renderer
talkide-be/src/main/kotlin/.../conversation/domain/MaraContextRenderer.kt. The template below is the human-readable contract, not the runtime source — BE does not read this file at runtime. When changing one (e.g. tweaking the welcome instruction), the same commit MUST update the other, otherwise renderedCLAUDE.mdwill diverge from the spec. A unit test (MaraContextRendererTestCases) pins the expected output byte-for-byte, so a Kotlin-side change without updating the test will fail CI — but spec drift is detected only by humans reading this file.
Template
# Projekt
- **Název**: {{project.name}}
- **URL**: {{project.url}}
- **Popis**: {{project.description | default: "—"}}
- **Backend**: {{project.backendLanguage}} / {{project.backendFramework}}
- **Frontend**: {{project.frontendLanguage}} / {{project.frontendFramework}}
# Welcome instrukce
**Pseudo-Mara již uživatele přivítala** v UI před vstupem do workspace.
Ty jsi reálná Mara a zahajuješ první konverzaci v tomto projektu.
Pravidla pro tvou první zprávu:
- **NEZDRAV.** Žádné "Ahoj", "Hi", "Vítej", "Hello", "Zdravím", oslovení
jménem v úvodní větě, ani parafráze typu "Tak jdeme na to". Pseudo-Mara
to už udělala — opakování působí roboticky.
- **Stav projektu = greenfield.** Toto je úplně nový, prázdný projekt.
Aktuálně **neexistuje žádný kód, žádný use case, žádný design, žádné
entity, žádný dokument**. Jsme na startu od nuly. **Nepředpokládej**,
že cokoli z popisu projektu už existuje, je rozpracované nebo "stačí
doladit". Popis projektu je *záměr*, ne *aktuální stav*.
- **Využij popis projektu** výše jako kontext — chápeš, co chce uživatel
stavět, a tomu přizpůsob konkrétní formulaci. Ale výchozí bod je
prázdné plátno.
- **Nabídni 2-3 vhodné úvodní směry** pro greenfield projekt. Typicky:
společně sepsat specifikaci (co aplikace dělá, kdo jsou uživatelé,
klíčové scénáře), identifikovat první use case (nejmenší smysluplný
kus s hodnotou), navrhnout doménový model (klíčové entity a vztahy),
ujasnit business cíle a omezení. Vyber 2-3 nejvhodnější podle popisu
projektu — nenabízej všechny.
- **Tón:** krátká, věcná první věta. Žádné prázdné fráze ("Na čem
dneska zapracujeme?" předpokládá kontext, který zatím není). Spíš
něco jako "Než začneme stavět, pojďme se domluvit, co to má vlastně
být." — a hned přejdi k nabídce směrů.
# Tým
{{#each team-member ordered by createdAt asc}}
## Oslovuj jako "{{member.salutation}}"
Briefing pro tým:
> {{member.teamBriefing | default: "—"}}
{{/each}}
# Aktuálně píše
{{activeMember.salutation}}
Notes:
- Roster ordering MUST be deterministic (e.g. by
createdAt asc) so identical team state produces byte-identical output. teamBriefingis rendered as a blockquote (>) — preserves user’s intent visually and stays readable in the LLM context.- Multi-line
teamBriefing: ifteamBriefingcontains newline characters, prefix each line with>separately, not just the first. Reason: a valid Markdown blockquote must have>on every line — otherwise the LLM sees a quoted first line followed by raw continuation (broken syntax + confusing semantics). - Empty briefing renders as
> —(em-dash). Mara is instructed to treat this as “no specific instructions, use sensible defaults.” project.urlnull handling: ifproject.urlisnullor blank, render- **URL**: —(em-dash, U+2014). Consistent with the emptyteamBriefingsemantics — signals “intentionally empty”, not a bug.project.descriptionnull/blank handling: render- **Popis**: —(em-dash). Same rule asurl. The description is collected during the conversational create flow (see UC-03002) and is used by Mara as situational anchor for her opening question.- Tech-stack fields (
backendLanguage,backendFramework,frontendLanguage,frontendFramework) are NOT NULL in the DB with defaultsKotlin / Spring Boot / TypeScript / Vue.js. They render unconditionally, never fall back to em-dash. - Welcome instruction is a static block — identical across all projects and renders. It does not vary with DB state and therefore does not break the prompt cache. Its purpose is to establish three things in Mara’s first turn: (1) explicit ban on greeting (pseudo-Mara already did it), (2) explicit greenfield state (zero code / UCs / design / entities exist), (3) a small set of suitable opening directions (spec sketch, first UC, domain model, business goals).
- The “Aktuálně píše” section names the active user by salutation (the same identifier used in the roster). For single-user projects today, this is always the only roster member.
Infrastruktura a porty — pointer na .talkide/project.yml
Sync contract: Tato sekce je sémantický překlad odpovídající části
MaraContextRenderer.kt. Runtime source of truth je Kotlin renderer — tahle dokumentace je pro lidi. Při každé změně Kotlin strany aktualizuj i tuto sekci (a naopak).
Aktuální chování (po per-project architecture refactoru): Renderer nevykresluje konkrétní hodnoty portů ani URL do CLAUDE.md. Místo toho vykresluje krátký statický pointer:
# Infrastructure
See `.talkide/project.yml` for ports, URLs, and configuration.
Tento blok je:
- Statický — identický napříč všemi projekty, nenarušuje prompt cache (žádné per-projektové hodnoty, které by ho rozbíjely při každém renderu).
- Učí Maru kde hledat — pokud potřebuje znát port nebo URL, ví že má číst
.talkide/project.ymlpřes Read/Bash, ne hádat z textu CLAUDE.md.
Proč tato změna proběhla: Předchozí verze rendereru zapisovala konkrétní porty
přímo do CLAUDE.md jako textovou instrukci (“BE port: 8097, neuhybej na 9090”).
V praxi to selhalo — Mařin devops agent na příkaz kill-port.sh 9090 sundal
samotnou TalkIDE platformu (incident SIGKILL pekarna-u-jelena vs. TalkIDE).
Textová instrukce neměla dostatečnou autoritu vůči shell příkazu. Strukturální
řešení viz per-project-architecture.md:
- Porty jsou v
.talkide/project.yml(strukturovaný YAML, čitelný přesyq). - Plugin skripty čtou port výhradně z YAMLu, nikdy z argumentu.
- Porty jsou odvozeny z
project.idjako8090 + id/5200 + id. Protože DB sequence proproject.idstartuje na1, nejnižší BE port je 8091 a FE 5201 — kolize s platformními porty 9090/5200 je strukturálně nemožná.
Pravidlo při obsazeném portu (zachováno v plugin agent dokumentaci, ne v CLAUDE.md): Pokud je vypočtený port obsazený jiným procesem, agent se MUSÍ zeptat uživatele — nesmí tiše přepnout na jiný port. Automatická změna portu by mohla vyvolat konflikty v konfiguraci a skrytou regresi.
CRITICAL — determinism for prompt cache
The render MUST be deterministic: identical inputs (same Project + team state + active user) MUST produce byte-identical output. This enables Anthropic’s prompt cache to hit across spawns of the same project.
Forbidden in the rendered output:
- ❌ Timestamps (
now(), “lastUpdated: 2026-05-02 14:32:11”) - ❌ Random IDs / nonces / counters (“turn #N”, “session message #N”)
- ❌ Non-deterministic sort orders (must use stable key like
createdAt) - ❌ Anything else that changes when underlying data hasn’t
Allowed:
- ✅ Pure projection of project meta + ordered team member projection (salutation, teamBriefing) + active-user salutation
If the project + team data + active user are unchanged, two consecutive
renders MUST be byte-identical (you can diff them — empty output expected).
Cache layout (for context)
Vrstvy v context window seřazené od nejstabilnějšího:
- Built-in Claude Code system prompt — shared globally
- Plugin agents (
talkide-pm.mdetc.) — shared across all TalkIDE sessions - Project meta + team roster (top of
CLAUDE.md) — shared across spawns of this project - Active-user marker (bottom of
CLAUDE.md) — varies when active user changes - Conversation history — varies per session
Cache rebreak při změně active-user je jen pár tokenů (krátká sekce na konci CLAUDE.md), tedy minimální token cost při střídání pisatelů v multi-user collab.
Plugin responsibility — agent file
agents/talkide-pm.md contains:
- Mara’s identity (“You are Mara, the PM in the TalkIDE platform…”)
- TalkIDE Workspace Awareness (don’t send preview URLs, use File Explorer references, don’t paste stack traces, etc.)
- Routing rules for
teamBriefing(Mara extracts relevant pieces per agent) - All existing PM rules (delegation table, no source code reading, etc.)
This file is part of the plugin and changes only with plugin releases.
Other agents — what they see
Other team agents (analyst, BE dev, FE dev, reviewer, devops) DO NOT see
<project>/CLAUDE.md. Mara is the single router for user-specific
context. When she delegates, she puts only the relevant slice of the
active user’s teamBriefing into the delegation prompt field. Rationale:
- Token efficiency — don’t broadcast irrelevant context
- Focus — each agent stays on their lane
- Audit — Mara owns the decision of what each agent needed to know
Other consumers of project files
<project-path>/CLAUDE.md is not the only file in the project root.
TalkIDE backend services maintain their own worker session state and artifacts (currently
.project-config.yml — see project-config.md).
These BE-managed files are NOT read by Mara or any AI agent — they
are internal BE state. The privacy boundary is strict: anything in
CLAUDE.md goes to the LLM; anything in BE sidecars stays inside
TalkIDE infrastructure.
When adding a new file to the project root, decide on the boundary
explicitly: does Mara need to read it (CLAUDE.md or new file Mara
will read), or is it BE-only (sidecar)?
Active user routing rules (multi-user)
Mara primarily routes the briefing of the active user (the one who sent the current message). Briefings of other team members are secondary context — Mara may consider them as defaults/fallbacks, but the active user’s preferences win in case of conflict.
Example: Bob’s briefing says “use Vue Router 4”; Anna’s says “I prefer Pinia for state”. Anna writes a message about state management → Mara uses Anna’s preference. Bob writes about routing → Mara uses Bob’s. Anna writes about routing (Bob has no opinion on routing) → Mara uses Bob’s “use Vue Router 4” as a sensible default.
Thanks for the feedback.