Internal Documentation internal
TalkIDE internal documentation

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:

SourceOwnsUpdatedCache scope
Plugin agent file agents/talkide-pm.mdIdentity, role, TalkIDE UI awareness, delegation rulesPlugin releaseShared across all users (max cache reuse)
Project file <project-path>/CLAUDE.mdProject meta + team roster (members + per-member briefing), active-user markerBE renders before each Claude spawnShared 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:

  1. User sends a message → BE spawns Claude (claude for first turn, claude --resume <id> for subsequent turns)
  2. The Claude process lives while Mara works (delegations, agent waits)
  3. When Mara finishes her turn (“user turn”), the process exits
  4. 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 rendered CLAUDE.md will 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.
  • teamBriefing is rendered as a blockquote (> ) — preserves user’s intent visually and stays readable in the LLM context.
  • Multi-line teamBriefing: if teamBriefing contains 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.url null handling: if project.url is null or blank, render - **URL**: — (em-dash, U+2014). Consistent with the empty teamBriefing semantics — signals “intentionally empty”, not a bug.
  • project.description null/blank handling: render - **Popis**: — (em-dash). Same rule as url. 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 defaults Kotlin / 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.yml př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:

  1. Porty jsou v .talkide/project.yml (strukturovaný YAML, čitelný přes yq).
  2. Plugin skripty čtou port výhradně z YAMLu, nikdy z argumentu.
  3. Porty jsou odvozeny z project.id jako 8090 + id / 5200 + id. Protože DB sequence pro project.id startuje na 1, 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:

  1. Built-in Claude Code system prompt — shared globally
  2. Plugin agents (talkide-pm.md etc.) — shared across all TalkIDE sessions
  3. Project meta + team roster (top of CLAUDE.md) — shared across spawns of this project
  4. Active-user marker (bottom of CLAUDE.md) — varies when active user changes
  5. 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.

Was this page helpful?

Thanks for the feedback.