Skip to content
Back to skills

Meeting Copilot

ASecurity

Real-time meeting assistant — monitors live transcription and cross-references against the project knowledge base

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentsgoshellbashreactawsgitapidocumentation

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add tatargabor/set-copilot --skill meeting-copilot --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Meeting Copilot?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Meeting Copilot
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tatargabor-meeting-copilot/badge)](https://www.skillsdirectory.com/skills/tatargabor-meeting-copilot)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: meeting-copilot
description: Real-time meeting assistant — monitors live transcription and cross-references against the project knowledge base
user_invocable: true
---

# Meeting Copilot Skill

Real-time meeting assistant that monitors a live transcript and cross-references what's being said against the project's knowledge base, specs, and past decisions. Powered by `set-copilot`.

The copilot is NOT a separate AI — it IS this Claude Code session. The `set-copilot` capture process handles audio + transcription; Claude Code handles analysis via a Monitor loop + filesystem access to your knowledge base. The only external dependency is Soniox for transcription.

Both halves are config, not code:
- **Knowledge** — `knowledge.sources` (dirs, files, or globs), `knowledge.decisions`, `knowledge.keywords`. The digest step reads those and produces three artifacts the copilot consumes.
- **Policy** — `copilot.alerts` (what to speak up about) and `copilot.instructions` (a project-owned markdown file of domain rules), rendered by `npx set-copilot prompt`.

## Usage

### `/meeting-copilot start` (or `start --lite` / `start --zero`)

**IMPORTANT: Execute ALL phases sequentially without stopping. Do NOT wait for user input between phases. Do NOT use forks or subagents — do everything inline.**

**Three modes:**
- **`start`** (default) — active lookup: pre-loads the digest, then uses Grep/Read on `knowledge.sources` during the meeting. Slower (~3-8s) but precise.
- **`start --lite`** — pre-loaded context: loads the enriched JSON at Phase 1, then **zero tool calls** during the meeting. Fast (<1s) but limited to pre-loaded knowledge.
- **`start --zero`** — zero-load: **skips Phase 1 entirely**. Works purely from what's already in the conversation context (CLAUDE.md, rules, memory). Fastest start, smallest footprint.

**Wall option (`start wall`).** Add the word `wall` (combinable with any mode, e.g. `start --lite wall`) to have the copilot **own the monitor wall**: it launches the wall itself in Phase 2b — scoped to the SAME runtime dir as the capture, on a per-session port, fake-feed off — and tears it down in `stop`. This is the supported way to run the wall; do NOT hand-start `set-copilot wall` in another terminal against a different dir (that split is what makes drawings and transcript point at different places). Without `wall`, no wall is started and Phase 5 simply skips emitting.

**Mirror option (`start wall mirror`).** Add the word `mirror` (only meaningful alongside `wall`) to **mirror your substantive chat onto the wall**. It is an opt-in on top of your primary voice: you keep talking in chat and each substantive line is *additionally* echoed to the wall.

Delivery is a **transcript follower** (`set-copilot mirror-follow`), NOT your own discipline — a field meeting proved a prompt-only mandate falls behind (the chat carried far more than the wall). It watches this session's transcript and emits **every** text block you write, as you write it: mid-turn blocks included, not just the turn's last message. You do not emit the mirror yourself.

It replaced a `Stop` hook on 2026-07-29, because a turn-boundary hook is late by construction *and* was measured one message behind: it read the transcript at turn end and delivered the message written 37 s earlier, while the one written 0.2 s before it never arrived at all.

To turn it on for the session, in Phase 2b (right after the wall is up) **start the follower, verify it is running, and only then create the opt-in marker**. Also export `COPILOT_MIRROR=1` on the Phase-1 `prompt` line so the policy shows a `## Mirroring` block:
```bash
# the follower goes in a Bash call with `run_in_background: true` — never a trailing `&`,
# which dies with the tool call's shell. It runs all session and stops with `set-copilot stop`.
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot mirror-follow
# then, in a SECOND call: the marker is what the follower gates on — write it ONLY if the check passes
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" \
  npx set-copilot doctor --mirror && : > "$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}/wall-mirror.enabled"
```
**Never write the marker without the check passing.** The follower is self-gating: an absent follower and a working-but-idle one look identical from the wall (both: empty wall, no error). A marker with no follower is an opt-in that can never fire — measured on 2026-07-28 with the hook-era equivalent, that produced a wall that stayed blank for a whole meeting with nothing reporting it anywhere.

If the check fails, **report the CLI's own message verbatim** (it names the missing piece and the command that starts it) and say plainly that mirroring is **not** enabled for this session. Do not claim it is on, and do not create the marker anyway; the rest of the session (wall, capture, narration) proceeds normally without mirroring.

Note what the gate does and does not cover: it checks **the follower being alive** — a strictly more direct check than the hook era's, which could only confirm a hook was *registered*, never that it fired. The other states `doctor --mirror` prints (the marker, a running wall, the last emission time) are reported, not required, because at enable time the marker is what you are about to write and the wall may legitimately still be coming up.

**What reaches the wall is decided by policy, not by you** (`copilot.mirror`, rendered into your `prompt` output): a length floor, the project's filler phrases, code-block handling, and a chunk budget that *divides* a long message across consecutive wall events rather than truncating it. Write the whole thing; use headings and markdown tables (both render as structure); fence anything whose readability depends on alignment. And note the floor of the mechanism: delivery is per **completed message**, not per token — the wall gets a block when you finish writing it.

**If the mirror looks dead, read `wall-mirror.log`** in the runtime dir before concluding anything. It records, per message, what the mirror decided (`emit` / `filler` / `short` / `dup` / `error` / `reset`). On 2026-07-29 a session concluded "the mirror silently stopped at 20:52" from a wall that had in fact delivered its last message at 20:57:40; the log exists so that mistake is not repeatable.

Off by default: without `mirror`, no marker exists, the hook is a no-op, and the chat-primary / wall-secondary separation stays.

#### Phase 0: Scope the runtime dir (do this in EVERY command below)

Every `set-copilot` command in this skill — `digest`, `prompt`, `path`, `capture`, `poll`,
`stop`, `status` — MUST be prefixed with the same runtime dir:

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}"
```

It is where the keyword index, the transcript, and the PID file live, and every command
reads it back from there. Scope it in one command and not another and they silently talk
past each other: a `capture` in the scoped dir with a `digest` in the default one finds no
keyword index, so no line ever gets a `topics` annotation and the copilot loses its routing.
Byte-identical, every time.

#### Phase 1: Knowledge Pre-load (max 2 minutes)

Regenerate the digest first (pulls fresh keywords/decisions from your configured sources into the runtime dir), then load the project's copilot policy — the alert categories, the engagement level, and any project instructions, all from `set-copilot.config.json`:
```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot digest
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot prompt
```

**The `prompt` output is your analysis policy for this session.** It defines which categories you may speak up about, what triggers each, which ones fire a desktop notification, and any domain rules the project wrote for you. It replaces the default taxonomy in Phase 4 — follow it, not your assumptions about what matters. Run it in every mode, including `--zero`: it is config, not knowledge, and costs one call.

**If the args include `mirror`** (see the Mirror option above), export `COPILOT_MIRROR=1` on the `prompt` line so the rendered policy carries the `## Mirroring` mandate:
```bash
COPILOT_MIRROR=1 SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot prompt
```

**Normal mode (`start`):** Read the digest, then remember you have Grep/Read access during the meeting:
```bash
cat "$(SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot path digest)"
```
You don't need to memorize everything — know WHAT EXISTS and WHERE. During the meeting, Grep/Read the configured `knowledge.sources` on demand.

**Lite mode (`start --lite`):** Load the enriched context JSON — this replaces ALL grep/read during the meeting:
```
Read the file printed by:  SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot path context
```
It contains: `decisions`, `deferred`, `cards` (per-entity quirks), `domainFaq`, `recentIncidents`. From here on, work exclusively from what's loaded.

**Zero mode (`start --zero`):** Skip Phase 1 entirely — no digest, no reads. Work from conversation context only.

**After loading (or skipping): immediately proceed to Phase 2. Do NOT stop.**

#### Phase 2: Start Capture

ONE ordinary Bash call (NOT `run_in_background`) — `--detach` starts the capture in its own
session and returns in about two seconds; the capture plays the rising tone by itself when the
mic is live, and self-stops after 4 hours (no separate timer or beep step):

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot capture --max-minutes 240 --detach
```

**Never start the capture (or the wall) as a `run_in_background` command.** The harness kills
background commands at its time limit — measured on a real client call (2026-10-01): the first
capture died after 30 minutes, the restarted one at exactly 2 hours, mid-sentence, with the wall,
and nothing reported it until the transcript ran out. A detached process is not the tool call's
child: it lives until `stop` / `wall-stop` (they find it by its PID file) or `--max-minutes`.
Its output goes to `capture.log` in the runtime dir; if it dies during start-up, the command
exits non-zero and prints the log's tail — report that, do not retry blindly.

`SET_COPILOT_DIR` scopes the transcript and the PID file to this Claude session and this
project, exactly as `/ds` does for dictation. Without it the capture lands in the shared
`/tmp/set-copilot`, where a reboot eats the meeting and a second Claude session is refused
outright ("a capture is already running"). **Keep it byte-identical in Phase 3 and in
`stop`** — that directory is how they find this capture.

**After starting: immediately proceed to Phase 2b (if `wall`) then Phase 3. Do NOT stop.**

#### Phase 2b: Start the wall (ONLY if `wall` was in the args)

ONE ordinary Bash call (NOT `run_in_background` — see Phase 2), detached. Derive a
per-session port so parallel sessions do not fight over one (the wall walks to the next free
port anyway if it is taken), and start it in the SAME scoped runtime dir, fake-feed off:

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}"
WALL_PORT=$(( 4180 + $(printf '%s' "${CLAUDE_CODE_SESSION_ID:-shared}" | cksum | cut -d' ' -f1) % 800 ))
SET_COPILOT_DIR="$SET_COPILOT_DIR" npx set-copilot wall --no-fake-feed --port "$WALL_PORT" --detach
```

The wall writes `wall.pid` + `wall.url` into the runtime dir and keeps running on its own
(log: `wall.log`). Read the URL back and tell the user (the bound port may differ from
`WALL_PORT` on fallback), in a **separate** call:

```bash
sleep 1; cat "$(SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot path wall-url)"
```

Tell the user: "🖥 Wall: <url>" (the wall lives on `/wall`; `/` redirects there).

**If `mirror` was also in the args**, enable it here — start the follower, gate on it, then
write the marker. The order matters: the follower records its start position on first run, so
starting it BEFORE the marker is what keeps the session's earlier messages off the wall.

Two Bash calls. The first with `run_in_background: true` (it runs for the whole session):

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot mirror-follow
```
```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" \
  npx set-copilot doctor --mirror && : > "$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}/wall-mirror.enabled"
```

On success tell the user mirroring is on. On a non-zero exit, **do not write the marker**:
report the command's own message (it names the command that starts the follower) and state that
mirroring is off for this session — then continue. See the `mirror` option above for why an
ungated marker is worse than no marker.

**After starting: immediately proceed to Phase 3. Do NOT stop.**

#### Phase 3: Long-poll Monitor

Do NOT `tail -f` (per-line flood) and do NOT run the poll as a blocking foreground loop. The right shape: `npx set-copilot poll` (long-poll — returns one batch per reaction-worthy event or after ~60s) wrapped in a Monitor. Each non-empty round emits ONE notification you answer as normal chat text; empty rounds emit nothing.

Start the Monitor with `persistent: true` and `timeout_ms: 7200000`:
```bash
while :; do OUT=$(SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot poll 60); if [ -n "$OUT" ]; then printf '%s\n' "$OUT"; fi; case "$OUT" in *capture-dead*) exit 0;; esac; done
```

Then tell the user: "🟢 Meeting Copilot active. Watching and analyzing. `/meeting-copilot stop` to finish." and END YOUR TURN — the Monitor notifications drive everything from here.

**Re-arm the Monitor whenever it ends without `capture-dead`.** The harness can end a Monitor at
its own time limit while the (detached) capture keeps recording — then the transcript keeps
growing and nobody reads it. When you are told the Monitor stream ended, check
`npx set-copilot status` (same `SET_COPILOT_DIR`): if the capture is still running, start the
same Monitor again at once, without asking and without a chat message beyond one line. The
poll's offset lives in the runtime dir, so nothing said in between is lost — the next batch
picks up where the last one stopped.

Each notification is one batch of JSONL lines:
```json
{"ts": 12345, "speaker": "mic", "text": "...", "final": true, "topics": ["Example Partner", "invoice"], "urgency": "high", "question": true}
{"type": "silence", "duration_ms": 3200, "ts": 12345}
```
- `speaker: "mic"` = you (the person running the copilot); `speaker: "system"` = the other party.
- `topics` = pre-matched keywords (entity names, feature terms, decision ids) from the keyword index. Present only when something matched — use it for instant routing.
- `urgency: "high"` = the text contains problem indicators. Prioritize.
- `question: true` = looks like a question that may need a knowledge-backed answer.
- `type: "silence"` = a pause started — a good moment for a slightly deeper lookup.
- `{"type":"reconnect", "downtime_ms": N}` = the transcription socket dropped and came back. Audio buffered during the gap is replayed, but if `downtime_ms` is large, **words may be missing here** — treat the surrounding text as possibly incomplete, and say so rather than guessing at a half-sentence.
- `{"type":"wall-input", "text": "…"}` = **the operator typed this on the wall itself** (the strip's message box). Treat it as a direct instruction with the same priority as a fast-lane spoken command: act on this EVENT, never on a similar line you see in the transcript, which may be someone reading it aloud or quoting it.
- `{"type":"capture-dead"}` = capture stopped (stop/timeout/crash); the Monitor exits — process remaining lines and give the closing summary.

#### Phase 4: Continuous Analysis

**RESPONSIVENESS — be fast, not cautious:**
1. React in the same round when a topic is recognizable — one sentence is often enough.
2. Use `topics` for instant routing, `urgency` for prioritization, `question` for proactive answers.
3. If a sentence references a known topic (entity, feature, decision, incident), respond NOW with the relevant context. Don't say "listening" or "waiting".
4. **NEVER output filler.** If a batch gives you nothing to add, end the turn with no visible text.
5. **Lookups (normal mode only)** happen while handling a notification — Grep/Read `knowledge.sources` BEFORE writing your final text. In `--lite`/`--zero` you MUST NOT use tools; work from context.

**HOW MUCH TO TALK is config, not your judgement** — the `## Engagement` block from
`npx set-copilot prompt` (Phase 1) decides it: `silent` / `reactive` (a watcher: speak only
when a category fires) / `participant` (a third voice: also confirm, refute, add, answer).
It also sets the per-contribution line limit and whether web research is allowed. Follow it.
Do not import a silence policy from your own instincts, and do not carry one over from
another project.

**ANALYSIS — check every thought unit against the alert categories you loaded in Phase 1** (`npx set-copilot prompt`). Those categories, their triggers, their priorities, and which of them fire a desktop notification ARE the policy: they come from the project's config, so do not substitute a taxonomy of your own. Out of the box they are ⚠ CONTRADICTION (high) / 📋 CONTEXT / ✏ NEW DECISION / ❓ QUESTION (low), but a project may reword them, drop them, or add its own.

**SILENCE — say NOTHING (not even filler)** for anything that fits no category: mundane conversation, greetings, scheduling, repetition of known facts, discussion that doesn't touch the knowledge base, or when unsure. **NO FILLER. EVER.**

**OUTPUT FORMAT** — alerts go into the chat as normal text; keep it SHORT (max 3 lines per alert). With the default categories:
```
⚠ CONTRADICTION: Cutting log deferred to phase 2 (DEC-002, manufacturing.md:100)
  Now being requested in scope. A scope change is needed if this moves in.

📋 CONTEXT: Documentation photos already implemented (logistics.md)
  Driver-app photo upload is in the spec.
```

#### Phase 5 (optional): Mirror analysis to the monitor wall

**Chat is your primary voice; the wall is a secondary artifact.** If a wall is running
(started by Phase 2b when the session began with `wall`, or already up for this runtime dir),
the wall must never be your ONLY output — a wall that doesn't visibly update looks broken, so
the chat carries the liveness and the interpretation (the `## Feedback` block from
`set-copilot prompt`). Emit with the SAME scoped `SET_COPILOT_DIR`, so the events reach the
wall this session owns.

**Narration — the live commentary lane (mechanics).** When narration is enabled (a `## Narration`
block is present in `set-copilot prompt`), keep the wall's `narráció` box alive: once per reaction
batch, and once on each `silence` event, `wall-emit` ONE substantive line to it **directly** — no
fork, `zone:"both"` (a single line has nothing to compose). The line reaches the wall through the
server-side redactor; mark internal detail `[belső]` or leave it out:

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-emit '{"category":"narráció","zone":"both","text":"<one substantive line>"}'
```

- **Cadence: at most one line per batch.** Not one per transcript token, and never gated on an alert
  firing. If a batch holds nothing substantive to add, emit NOTHING — the box keeps its previous line.
  **NO FILLER**: never "listening"/"waiting", never a bare echo of the raw transcript.
- **Separate from the alert output.** Narration does NOT replace the ⚠/📋/✏/❓ alerts — they are
  independent channels. An alert still goes to chat exactly as before (alerts are chat-only now — no
  wall box subscribes to them); narration is the running commentary in the wall box. Neither suppresses
  the other.
- **What to say comes from the policy, not this file.** The `## Narration` block owns the substance and
  verbosity; this file owns only the emit + the cadence. If no `## Narration` block is present (narration
  disabled), skip this lane entirely.

**The pinned box — reference, not flow (mechanics).** The public wall has a `kitűzött` box that the
scrolling stream can never displace. It is where the agenda, the open questions, the recorded decisions
and the tasks live, so they are still on screen when the conversation has moved past them (measured: the
scrolling log buried the open-questions list and the operator went hunting for it mid-meeting).

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-emit '{"category":"kitűzött","zone":"both","text":"**Nyitott kérdések**\n- …\n- …"}'
```

- **Always emit the WHOLE block.** This is a `latest` box: a new event REPLACES the content, it does not
  merge into it. Sending only the line that changed silently deletes every other point. This is the one
  way the region is easy to use wrongly.
- **Cadence: occasionally, not continuously.** It is reference content, not a second narration lane —
  update it when the reference actually changed (a question got answered, a decision was made, a task
  landed), not once per batch. If nothing changed, emit nothing; the box keeps what it has.
- **Redaction applies here as everywhere.** It goes through the same server-side `ingest`, so mark an
  internal detail `[belső]` or leave it out.
- Formatting works: it is a `text` render, so bullet lists and **bold** read as structure on the wall.

**Mirroring the chat onto the wall.** When mirroring is on (the `wall-mirror.enabled` marker exists, i.e.
the session started with `mirror`), the **transcript follower does the mirroring for you** — it emits every
text block you write as a `tükör` event, as you write it, mid-turn included. **Do NOT `wall-emit` the mirror
yourself** — that would double it. Your only job is to keep your chat substantive (the follower mirrors
whatever you actually say, subject to the project's filler policy), to use structure — headings and markdown
tables render as structure, and a long message is split across consecutive wall events rather than
truncated — and to remember redaction still applies to the public variant: keep an internal detail off the
public wall by marking it `[belső]`. If mirroring is off (no marker), there is nothing to do here.

**Switching the wall layout at runtime.** The active layout of a live window can be changed mid-session,
no restart — e.g. into the wide `chat-wide` layout (big chat box on the left half, visuals on the right)
when mirroring is on, and back to `third-two-thirds` afterwards. It is geometry only: the boxes keep their
content and state.

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-layout /wall chat-wide
```

Do it on an explicit request ("switch to the wide chat layout", "make the chat bigger"), not on your own —
a layout is the operator's choice. An unknown route or layout id is dropped with a warning, never blanking
the wall.

**Showing a live web page.** An iframe (`webpage` payload) only works for pages that permit
embedding; news sites, Google, banks, etc. send `X-Frame-Options` / `CSP frame-ancestors` and
render blank. For those, screenshot instead — it works for ANY page:

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-shot <url> [--caption "<text>"]
```

It renders the page with headless Chromium and puts the image on the wall. Prefer `webpage`
for embed-friendly URLs (instant, live); reach for `wall-shot` when the page blocks framing or
a static snapshot is all that is wanted. (Needs Chromium/Chrome installed.)

**The categories, the payload shapes, and when a visual is warranted are NOT in this file.** They
come from the `## Drawing the wall` block of `set-copilot prompt`, which you loaded in Phase 1.
That is deliberate: a producer inherits that block for free (below), so it is paid for once per
session instead of being restated on every drawing. This file owns only the mechanics:

**Spawn a fork to draw.** Don't draw inline — you'd stop talking while you did. Spawn a fork of
yourself (`subagent_type: "fork"`) whose prompt is ONLY the mandate:

> Draw the `<category>` for what we just discussed: <one line of what to show>.
> Emit with `SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-emit '<json>'`, then stop.

The fork inherits this entire conversation — that inheritance IS its grounding, which is why the
mandate can be one line and why it knows what matters. It draws, emits, and exits.

**Mark the box pending BEFORE the fork (wall-pending-indicator).** A fork-based draw takes 16–62 s; a
silent "working" box is indistinguishable from a dead wall. So immediately before you spawn the fork,
emit a lightweight `pending` marker on the target category, so the box shows a ⏳ placeholder at once —
the real visual the fork emits replaces it, and a `ttlMs` clears it if the draw dies:

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-emit '{"kind":"pending","category":"<category>","label":"rajzolom: <egy sor>"}'
```

The pending marker defaults to `zone:"both"` — the visible "drawing…" placeholder IS the point: a
box that silently sits empty for a minute reads as a dead wall (measured on a cold start). Keep the
label short, mechanical, and content-free (`rajzolom: <egy sor>` describes the work, not the content);
the server scrubs a public-reaching label and drops the marker entirely when scrubbing does not clear
it. A `zone:"private"` marker is still honored for a project that runs its own private view. A direct
(non-fork) `wall-emit` needs no pending; the placeholder only earns its keep in front of the seconds a
fork costs.

**Spawn a fork only when there is something to compose.** If the content is already settled in this
conversation, `wall-emit` it directly: measured on a live session, a fork costs 16–62 s and 47–76k
tokens per drawing (a fork that reads source files is the slow end), against ~1 s for a direct emit.
A fork earns its cost by reading source or working something out — never by retyping what you
already know.

Rules that keep this cheap and correct:

- **One fork, one category.** Give each fork a single category. If two need updating, spawn two
  forks in the same message — they run concurrently and neither blocks the other.
- **Never pass a `model` override to a producer fork.** A fork always runs on your model and the
  override is ignored; asking for a cheaper tier silently does nothing.
- **Spawn on need, never to wait.** No idling forks polling for work, and never spawn one merely
  to keep a cache warm — that is pure waste.
- **YOU echo, not the fork.** After spawning, write ONE short chat line saying what you understood
  and are having drawn. The fork's output does not reach the chat, so if you don't say it, nothing
  does.
- **Ambiguity is a chat question, not a wall fact.** If the structure or numbers are ambiguous
  (e.g. values given only relatively), state your assumption in chat or ask — never hand a guess
  to a fork to render as established fact. A wall carries authority; don't lend it to a guess.
- **Never invent numbers that weren't said.**

For a one-line text note you may skip the fork and `wall-emit` directly — there is nothing to
compose, so a fork would only add latency.

**Prepared, not published — the `silence` window as a preparation window (predictive-staging).** A
fork-based draw is slow (16–62 s); by the time it lands the conversation has moved on. So use a `silence`
event not only for a deeper lookup but for a short (one- to two-step) extrapolation of where the
conversation is heading, and **pre-draw the likely-next visual into private staging** — so the expensive
draw is already done when the topic arrives. This is preparation, not prediction-on-the-wall:

- **Stage privately.** The staging fork emits the predicted visual to the `előrejelzés` category with
  `zone:"private"` and `staged:true` — with one public wall that displays NOWHERE until it is promoted,
  which is exactly what makes preparation free of publication risk. There is no private staging view to
  watch: `npx set-copilot wall-staged` is how you ask what you have prepared. Keep the mandate one line
  and DON'T read source for a prediction (the expensive branch) unless the mandate genuinely needs it; a
  guess is not worth a 60k-token source read.
- **A prediction is a guess; the wall carries authority.** NEVER emit a prediction to a `both`/`public`
  zone. There is no confidence threshold that makes an unspoken guess safe to publish — a confident wrong
  prediction is the worst case. The zone model is the guarantee: staged = private, full stop.
- **Promote when it actually arrives.** When the conversation reaches the predicted topic, lift the
  already-prepared visual to the public wall with a cheap promote (no re-draw) — on a single confirmation,
  or a clear rule that the topic was reached:

  ```bash
  SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-emit '{"kind":"promote","category":"előrejelzés","visual":"<staged id>","zone":"public"}'
  ```

  The server lifts the existing visual through the same redaction as any public event. Promotion is never
  automatic for an unspoken prediction — it is your explicit act.
- **Let a stale guess go.** An unpromoted prediction expires on its own (the server releases it). The
  expiry is now INVISIBLE — it was private-only, and no private view ships — so do not wait to *see* a
  guess expire; ask `wall-staged` when in doubt. The conversation turned elsewhere, and a stale guess
  must not be promoted later out of context. Don't fight the expiry — draw a fresh prediction if the
  topic recurs.

A malformed event is dropped with a warning, never crashing capture — so mirror freely and move
on. If no wall is running, skip the emitting — but the chat-feedback rules above (direct address,
ambiguity) still apply.

### `/meeting-copilot stop`

**With mirroring on, write your closing summary FIRST, in its own turn, and stop only after it.**
This is the one ordering no mechanism can fix for you: `stop` and `wall-stop` each drain the
mirror before tearing anything down, so whatever you have already written gets out — but a
summary written *after* the wall is gone has nothing left to reach. That is exactly how the
most valuable message of the 2026-07-29 session never appeared on the wall.

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot stop
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot wall-stop
```
Stop both: the capture, and the wall this session started (`wall-stop` is a no-op if none was
started, and only ever stops the wall in THIS runtime dir — never another project's). The
Monitor exits on its own (the in-flight poll returns `{"type":"capture-dead"}`). Both commands
first drain and then stop the mirror follower, reporting anything they could **not** deliver —
if you see that line, say so rather than reporting a clean stop.

`stop` archives the live meeting transcript exactly once (no `--print` — that would replay the
whole transcript into the session as if freshly spoken) and prints **three** paths:

```
[set-copilot] Transcript saved: …/transcript-<stamp>.jsonl   ← archive of record
[set-copilot] Readable:         …/transcript-<stamp>.md      ← for reading / note-taking
[set-copilot] Structured:       …/transcript-<stamp>-stitched.jsonl  ← for tools
```

Capture all three lines. The raw `.jsonl` is **fragments**, not sentences: the capture flushes on
its own timers, so a single utterance is cut across many lines — sometimes mid-word — and with two
channels those fragments interleave. The `.md` is that put back together. A client fact was once
lost exactly here, by a note-taking step reading the raw file because it was the one at hand.

**So: any post-meeting step — notes, knowledge extraction, summarization — reads the `.md`.** Point
to the raw `.jsonl` only as the archive. (Only the two older lines appear if `transcript.stitchOnStop`
is off, or if nothing was said.)

**If the project configured `copilot.handoverCommand`**, `stop` runs it after the archive and the
derived artifacts — that is where a project lifts the transcript out of the gitignored runtime dir
into its own inputs. **If it configured none**, `stop` copies the raw `.jsonl`, the `.md` and the
`-stitched.jsonl` into `docs/inputs/copilot/` (`<date>-meeting-part<N>-<sha8>.*`), skips it when the
same content is already in the repo, and prints `Saved to the repo:` lines and a `To commit:` line —
nothing is committed for you; include those paths in the summary. A `Not saved to the repo (…)`
line instead means the recording was **kept back** on purpose — a public-host remote stands the
built-in target down; report it as kept, and do not retry or hand-copy. Either way the hand-off cannot fail
the stop: a failure is printed and the archive stays intact, so if you see that line, report it
rather than claiming a clean hand-off. The paths reach a command as `SET_COPILOT_TRANSCRIPT` / `_MD` /
`_JSONL`, with `SET_COPILOT_KIND=meeting`; the one thing it cannot know is what the meeting was
*about*, so name the topic on the stop line when the project's command wants one:

```bash
COPILOT_HANDOVER_SLUG="<short-topic-slug>" \
  SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot stop
```

Without this seam a project has to fork this skill to hand its own transcript on — and a forked
skill drifts silently from the one the package ships (measured 2026-08-09: ~15 KB of this file that
never reached a session).

Then report a summary: meeting duration, alert counts by type (⚠/📋/✏/❓), any new decisions
detected, and the **readable transcript path** (naming the raw archive alongside it), so a
post-meeting step can find the full transcript.

### `/meeting-copilot status`

```bash
SET_COPILOT_DIR="$PWD/.set/copilot/${CLAUDE_CODE_SESSION_ID:-shared}" npx set-copilot status
```

## Prerequisites

- `set-copilot` installed (`npx set-copilot init`) and `SONIOX_API_KEY` in `.env`.
- `knowledge.sources` configured in `set-copilot.config.json` (for the cross-referencing; dictation needs none).
- Linux: `parec` + `notify-send`. macOS: `sox` + `osascript` (built in).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.