Skip to content

fix: surgically relocate only OpenCode-fingerprinted system entries#156

Closed
griffinmartin wants to merge 4 commits into
mainfrom
fix/surgical-system-relocation
Closed

fix: surgically relocate only OpenCode-fingerprinted system entries#156
griffinmartin wants to merge 4 commits into
mainfrom
fix/surgical-system-relocation

Conversation

@griffinmartin

@griffinmartin griffinmartin commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #154 — a two-step fix for OpenCode's OAuth system[] content-classifier rejection (#147) that preserves attention priority and prompt-cache stability for AGENTS.md, skills, and the safe portion of the env block.

The original surgical relocation in this PR (commit daa6425) worked correctly on synthetic separated system[] shapes, but did not deliver its stated outcome at runtime: OpenCode joins the provider prompt + every input.system entry + input.user.system into a single string before triggering experimental.chat.system.transform (see anomalyco/opencode packages/opencode/src/session/llm.ts:88-103). transformBody() then saw one giant blob carrying OpenCode fingerprints and relocated it wholesale into the first user message — taking AGENTS.md, skills, and the safe part of env with it. Credit @ihabwahbi for catching this in review.

The follow-up commit (832105d) closes that runtime gap.

What changed

src/transforms.ts

  • splitOpenCodeSystemBlob(text) — recovers the original segments from OpenCode's joined blob using stable upstream anchors:
    • env-block sentinel ^You are powered by the model named ... <env>...</env> (from upstream packages/opencode/src/session/system.ts environment())
    • <available_skills> wrapper for the skills block
    • Instructions from: ... headers (from upstream packages/opencode/src/session/instruction.ts system()) for AGENTS.md / CLAUDE.md
    • falls back to a single-entry passthrough on unfamiliar shapes — never worse than v1.4.8 bulk relocation
  • normalizeEnvBlock(env) — separates the OpenCode-only opener (You are powered by the model named ...) and Workspace root folder: ... line into a relocatable "branded extras" entry while reconstructing a Claude-Code-shaped <env> block (Working directory, Is directory a git repo, Platform, Today's date) that stays in system[]. Closes the env portion of known-limitation Configure npm publishing #1 from the original PR description.
  • Tightened OPENCODE_FEATURE_PATTERNS — the brand regex was previously /\bopencode\b/i, which false-positively matched lowercase path components like /Users/foo/dev/opencode-claude-auth/... (this very repo!) and /Users/foo/.cache/opencode/skills/.... Tightened to \bOpenCode\b (case-sensitive PascalCase), plus an explicit opencode\.ai URL pattern, plus the existing \banomalyco\b, Workspace root folder, and <directories> patterns. anthropic.txt and other branded prose use PascalCase consistently, so brand detection is unchanged in practice; what changes is that working-directory paths and skill plugin file:// URLs no longer trigger relocation.

src/index.ts

  • experimental.chat.system.transform now detects the joined-blob shape via looksLikeOpenCodeJoinedBlob() and, when found, runs splitOpenCodeSystemBlob() and replaces output.system with [identity, ...split-pieces]. Replacing the array (rather than mutating in place after index 0) also bypasses OpenCode's rejoin guard at llm.ts:107-111, which would otherwise fold our split entries back into a single string when system.length > 2 && system[0] === header.
  • For non-Anthropic providers and for already-separated system[] arrays the hook keeps the original behavior (only prepend identity).

src/transforms.test.ts

Three new describe blocks — looksLikeOpenCodeJoinedBlob, normalizeEnvBlock, splitOpenCodeSystemBlob — plus a runtime-shaped pipeline test (the gap):

runtime pipeline: joined blob -> hook split -> transformBody
  keeps AGENTS and safe env in system[]

This builds a realistic joined blob (anthropic-style provider prompt + the upstream env block template + a generic <available_skills> block + an Instructions from: …/CLAUDE.md block), runs it through the splitter and the body transform, and asserts:

  • AGENTS.md content remains in system[]
  • the safe (Workspace root folder-stripped) env remains in system[]
  • the generic skills block remains in system[]
  • the provider prompt and the OpenCode-only env opener + Workspace root folder: line are prefixed onto the first user message
  • nothing branded leaks back into system[]

Two existing brand-detection tests were updated to reflect PascalCase semantics, and an explicit "no false-positives on common path/URL patterns" test was added.

Behavior table at runtime (this PR vs v1.4.8 vs daa6425)

System content v1.4.7 v1.4.8 (#148) daa6425 (this PR pre-832105d) This PR (post-832105d)
Billing header stays stays stays stays
Identity prefix stays (after #98) stays stays (split out) stays (split out)
AGENTS.md / CLAUDE.md (no OpenCode mention) stays (400) relocated relocated (joined blob) stays
Generic skills block stays (400) relocated relocated (joined blob) stays
Claude-Code-shaped lines of env stays (400) relocated relocated (joined blob) stays (after normalization)
OpenCode-only env opener + Workspace root folder stays (400) relocated relocated relocated
OpenCode core agent prompt stays (400) relocated relocated relocated

Test plan

  • All 243 tests pass (was 227; +16 net for splitter, normalizer, blob-detector, pipeline test, brand-regex coverage)
  • pnpm run build clean
  • pnpm run lint — 0 warnings, 0 errors, formatting clean

Known limitations / follow-ups

  1. Env block still relocates today. Resolved in 832105d via normalizeEnvBlock.
  2. False positive on OpenCode mentions in user AGENTS.md. If a user's AGENTS.md prose contains the brand OpenCode (PascalCase), that entry gets relocated. Acceptable — content still reaches the model, just at user-message priority.
  3. Splitter is heuristic. If upstream OpenCode renames Workspace root folder, removes the </env> close tag, or restructures the joined order, the splitter falls back to passthrough and transformBody reverts to v1.4.8-style bulk relocation. Never worse than today.

Fixes #154

v1.4.8 (#148) worked around Anthropic's OAuth content validation by
bulk-relocating all non-core system entries to the first user message.
This caused a regression in long conversations (#154) because AGENTS.md,
environment metadata, and skills blocks lost system-level attention
priority and prompt-cache efficiency.

Replace bulk relocation with a feature-based detector that only moves
entries containing OpenCode fingerprints (\bopencode\b, anomalyco,
'Workspace root folder', <directories>), keeping all other system
content in system[] where it belongs.

Empirical probing against api.anthropic.com confirmed the classifier is
multi-feature rather than substring-based — AGENTS.md, skills, and
Claude Code-format env blocks all pass validation cleanly when kept in
system[].

Fixes #154
@griffinmartin
griffinmartin marked this pull request as draft April 9, 2026 04:58
@ihabwahbi

Copy link
Copy Markdown

I traced this end-to-end against the actual OpenCode runtime path and there is one important gap in the current approach.

#156 is directionally right, but in normal OpenCode sessions transformBody() usually does not receive separate system[] entries for AGENTS / skills / env.

Runtime path:

  • packages/opencode/src/session/prompt.ts builds system as separate pieces: env, skills, InstructionPrompt.system(), structured-output prompt.
  • packages/opencode/src/session/llm.ts:90-102 then immediately collapses provider prompt + input.system + input.user.system into a single joined string before experimental.chat.system.transform runs.
  • This plugin's hook in src/index.ts currently only prepends the Claude Code identity.
  • So transformBody() sees something much closer to [identity?, giant OpenCode block] than the separated shape used in the new tests.

That means the current isOpenCodeBrandedEntry() logic still relocates the whole combined block in the common case, because the combined text contains OpenCode fingerprints. In practice, AGENTS.md / skills / safe env content are still moved out of system[] and into the first user message.

I verified this locally before patching by feeding a runtime-shaped combined system blob through the current code path.

What worked locally

I added a small follow-up fix in experimental.chat.system.transform that splits the combined OpenCode system blob back into separate entries before transformBody() runs:

  • OpenCode core prompt stays isolated and can be relocated
  • env block is normalized into a Claude-Code-safe entry (Working directory, Is directory a git repo, Platform, Today's date)
  • OpenCode-only env extras like Workspace root folder / <directories> are preserved as separate branded entries so they relocate cleanly instead of poisoning the safe env block
  • skills block stays separate
  • Instructions from: ... entries stay separate
  • already-prefixed cases are handled too

With that in place, the runtime-shaped flow behaved the way this PR intends:

  • OpenCode-branded content relocated
  • AGENTS / skills / normalized env stayed in system[]
  • live Anthropic smoke test returned 200 (same machine/account that was previously hitting the extra-usage 400)

I also added runtime-shaped tests locally for:

  1. combined prompt splitting in the system hook
  2. already-prefixed combined prompt splitting
  3. end-to-end system.transform -> transformBody() preserving AGENTS / skills / safe env in system[]

So I think the missing piece here is not in src/transforms.ts anymore, but in src/index.ts: the plugin needs to reconstruct segmentation before the classifier-based relocation logic can actually deliver the regression fix in real sessions.

Happy to share the exact shape/tests if helpful, but the key point is: the current tests prove the surgical relocation works on synthetic separated system[] input, while OpenCode's real runtime shape is still mostly a single combined block.

@NamedIdentity

NamedIdentity commented Apr 24, 2026

Copy link
Copy Markdown

I posted a new issue #210 before figuring out this PR existed. Running a fork with this PR code now. oauth working fine so far.

I'd suggest merging this sooner rather than later. The plugins I'm using and developing have system prompts which when relocated degrades agent task performance. It also was making developing context management and state systems for OpenCode a lot more complicated.

OpenCode joins the provider prompt, input.system entries, and
input.user.system into a single string before triggering
experimental.chat.system.transform (anomalyco/opencode
packages/opencode/src/session/llm.ts:88-103). The earlier surgical
relocation logic then saw one giant blob carrying OpenCode fingerprints
and moved it wholesale into the first user message — taking AGENTS.md,
skills, and the safe portion of the env block with it. The unit tests
shipped with #156 used a synthetic separated system[] shape that the
runtime never produces, so the regression #154 was not actually fixed.

This change:

- Adds splitOpenCodeSystemBlob(text) to recover the original segments
  using stable upstream anchors (env-block sentinel, <available_skills>
  wrapper, 'Instructions from:' headers).
- Adds normalizeEnvBlock(env) to separate the OpenCode-only opener and
  'Workspace root folder' line into branded extras while reconstructing
  a Claude-Code-shaped <env> block that can stay in system[].
- Wires both into the system.transform hook in src/index.ts. Replacing
  output.system also bypasses OpenCode's rejoin guard at llm.ts:107-111.
- Tightens the brand regex to PascalCase \bOpenCode\b plus an explicit
  opencode.ai URL pattern, so working-directory paths like
  /Users/foo/dev/opencode-claude-auth and skill plugin file:// locations
  no longer false-positive as branded.
- Adds runtime-shaped tests: splitter, env normalizer, and an end-to-end
  pipeline test that asserts AGENTS.md, the safe env, and a generic
  skills block all survive in system[] after hook + transformBody.

Credit @ihabwahbi for surfacing the runtime/test mismatch in PR review.
@griffinmartin

Copy link
Copy Markdown
Owner Author

Confirmed and fixed in 832105d. Your trace was right end-to-end:

  • packages/opencode/src/session/llm.ts:88-103 (anomalyco/opencode dev branch) joins the provider prompt + every input.system entry + input.user.system into a single string before triggering the hook.
  • This plugin's experimental.chat.system.transform was only prepending the identity, so transformBody() saw [billing, identity, oneBigBlob]. The blob carried OpenCode fingerprints, surgical relocation matched once, and the entire blob — AGENTS.md and skills included — moved to the first user message. The unit tests passed because they used a synthetic separated system[] shape that the runtime never produces.
  • This repo's own src/transforms.ts already documented the concatenation behavior in the identity-split comment ("OpenCode then concatenates all system entries into a single text block"), but the new surgical-relocation logic ignored that.

Fix matches what you described:

  • splitOpenCodeSystemBlob(text) recovers segments using the env-block sentinel, <available_skills> wrapper, and Instructions from: headers, with a graceful single-entry passthrough on unfamiliar shapes.
  • normalizeEnvBlock(env) peels the OpenCode-only opener and Workspace root folder: line into a relocatable extras entry while reconstructing a Claude-Code-shaped <env> body that stays in system[]. Closes the env limitation in one shot.
  • The hook replaces output.system so OpenCode's rejoin guard at llm.ts:107-111 (system.length > 2 && system[0] === header) doesn't fold our split entries back together.
  • Brand regex tightened to \bOpenCode\b (PascalCase) plus an explicit opencode\.ai URL match. Working-directory paths like /Users/foo/dev/opencode-claude-auth and skill plugin file:// URLs no longer false-positive — that was eating the safe env in early test runs of the splitter.

Added a runtime-shaped pipeline test (runtime pipeline: joined blob -> hook split -> transformBody keeps AGENTS and safe env in system[]) that builds a realistic joined blob and walks it through the full hook → JSON.stringify → transformBody path.

Thanks for the catch — the unit-test-only validation was the real gap.

@griffinmartin

Copy link
Copy Markdown
Owner Author

Update: 832105d closes a runtime gap in the original commit on this PR (the surgical relocation worked on synthetic input but, because OpenCode pre-joins all system pieces into one string before the plugin hook runs, the joined blob still got relocated as a single unit at runtime — taking AGENTS.md and skills with it). The new commit adds a splitter + env-block normalization that recovers AGENTS.md, safe env, and generic skills back into system[] for full attention priority and prompt-cache stability.

If you've been running daa6425, please rebuild against 832105d and confirm — your plugin-development scenario is exactly the case the runtime-shape test was added to lock down. Will move on merge once CI passes.

@bvironn bvironn left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical Review — APPROVE ✅

Checked out branch, ran full suite (250/250 pass), lint (0 issues), build (clean). Traced splitter logic for edge cases.

What this PR does right:

  • Solves the runtime gap @ihabwahbi identified: OpenCode pre-joins system entries. splitOpenCodeSystemBlob() peels the blob back into pieces so transformBody classifies each independently.
  • isOpenCodeBrandedEntry() with tightened PascalCase matching eliminates path false positives.
  • Zero regression risk for non-OpenCode users. Graceful degradation for unrecognized shapes.
  • Excellent test coverage including runtime pipeline integration test.

Minor observation (not blocking):
INSTRUCTIONS_HEADER_RE at line 94 uses [/~] which won't match Windows absolute paths. Low severity. Removing the char class to just /Instructions from: / would make it platform-agnostic.

This fixes #154 and #210. Most mature and impactful open PR. Ship it.

@griffinmartin

Copy link
Copy Markdown
Owner Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a runtime regression (#154) where the v1.4.8 bulk-relocation workaround was defeating itself: OpenCode joins all system[] entries into a single blob before firing experimental.chat.system.transform, so the entire blob — including AGENTS.md, skills, and the safe portion of the env block — was being moved to the first user message. The fix adds a blob-splitter that recovers the original segments using stable upstream anchors, then runs per-entry brand detection so only OpenCode-fingerprinted content is relocated.

  • splitOpenCodeSystemBlob / normalizeEnvBlock — split the joined blob back into constituent pieces (provider prompt, branded env extras, safe env, skills, instruction blocks) and reconstructs a Claude-Code-shaped env entry that passes the content classifier without triggering it.
  • Tighter brand detectionOPENCODE_FEATURE_PATTERNS replaces the case-insensitive \\bopencode\\b with PascalCase \\bOpenCode\\b plus explicit opencode.ai and anomalyco entries to avoid false positives on lowercase path components.
  • Two gaps remain in src/transforms.ts: looksLikeOpenCodeJoinedBlob still uses the old case-insensitive pattern, and normalizeEnvBlock silently drops opener lines when ENV_OPENER_RE fails to match but ENV_CONTAINER_RE succeeds.

Confidence Score: 4/5

Safe to merge with the two edge-case caveats understood; the happy-path pipeline is well-tested and the fallback degrades rather than crashes.

looksLikeOpenCodeJoinedBlob retains the case-insensitive \bopencode\b pattern that the rest of this PR deliberately tightened, and normalizeEnvBlock silently drops opener lines when ENV_OPENER_RE misses while ENV_CONTAINER_RE still matches — content that v1.4.8 would have relocated simply disappears. Both gaps require an unusual input combination or an upstream OpenCode format change to trigger, but they contradict the never-worse-than-v1.4.8 invariant the PR relies on.

Files Needing Attention: src/transforms.ts — specifically looksLikeOpenCodeJoinedBlob (case-sensitivity inconsistency at line 218) and the ENV_OPENER_RE-miss path inside normalizeEnvBlock (silent data loss around line 828).

Important Files Changed

Filename Overview
src/transforms.ts Adds five new exports; contains a case-sensitivity inconsistency in looksLikeOpenCodeJoinedBlob vs OPENCODE_FEATURE_PATTERNS, and a silent data-loss path in normalizeEnvBlock when ENV_OPENER_RE fails to match.
src/index.ts Wires blob-detection and splitting into the experimental.chat.system.transform hook; logic is correct for the expected common case.
src/transforms.test.ts Adds 16 new tests covering looksLikeOpenCodeJoinedBlob, normalizeEnvBlock, splitOpenCodeSystemBlob, and the full runtime pipeline; correctly updates existing tests to reflect the new surgical-relocation semantics.

Comments Outside Diff (1)

  1. src/transforms.ts, line 828-855 (link)

    P2 Silent data loss when ENV_OPENER_RE doesn't match

    normalizeEnvBlock is called with the full env-block string captured by ENV_BLOCK_RE (which requires the "You are powered by the model named…" opener). If ENV_CONTAINER_RE matches (the <env>…</env> tags are present) but ENV_OPENER_RE fails — e.g. OpenCode changes the second line from "Here is some useful information…" to any other phrase — opener is set to "". The opener lines that precede <env> are never captured in opener, and they are not included in safe (which is built only from the <env> body). Those lines are simply dropped: they appear in neither safe nor branded, and nothing is passed through for them.

    In v1.4.8, the whole joined blob would be relocated to the first user message, so the model would still see the content. Here the text silently vanishes, which contradicts the stated invariant "never worse than v1.4.8 bulk relocation." A minimal guard — returning { safe: null, branded: envBlock } when ENV_OPENER_RE fails (mirroring the !containerMatch path) — would restore the v1.4.8 fallback for this case.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/transforms.ts
    Line: 828-855
    
    Comment:
    **Silent data loss when `ENV_OPENER_RE` doesn't match**
    
    `normalizeEnvBlock` is called with the full env-block string captured by `ENV_BLOCK_RE` (which requires the "You are powered by the model named…" opener). If `ENV_CONTAINER_RE` matches (the `<env>…</env>` tags are present) but `ENV_OPENER_RE` fails — e.g. OpenCode changes the second line from `"Here is some useful information…"` to any other phrase — `opener` is set to `""`. The opener lines that precede `<env>` are never captured in `opener`, and they are not included in `safe` (which is built only from the `<env>` body). Those lines are simply dropped: they appear in neither `safe` nor `branded`, and nothing is passed through for them.
    
    In v1.4.8, the whole joined blob would be relocated to the first user message, so the model would still see the content. Here the text silently vanishes, which contradicts the stated invariant "never worse than v1.4.8 bulk relocation." A minimal guard — returning `{ safe: null, branded: envBlock }` when `ENV_OPENER_RE` fails (mirroring the `!containerMatch` path) — would restore the v1.4.8 fallback for this case.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Cursor Fix in Claude Code Fix in Codex

Fix All in Cursor Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/transforms.ts:218-223
`looksLikeOpenCodeJoinedBlob` still uses the case-insensitive `/\bopencode\b/i` pattern that the rest of this PR explicitly replaces with the tighter PascalCase `/\bOpenCode\b/`. A system entry whose provider prompt begins with "You are powered by the model named …" and whose working directory contains a lowercase `opencode` path component (e.g. `/home/dev/opencode-project`) would satisfy both conditions here and incorrectly trigger the blob-splitter. Downstream `isOpenCodeBrandedEntry` would still classify the split pieces correctly (nothing would be wrongly relocated), but the unnecessary activation undermines the stated goal of the case-sensitivity tightening.

```suggestion
export function looksLikeOpenCodeJoinedBlob(text: string): boolean {
  return (
    /^You are powered by the model named/m.test(text) &&
    OPENCODE_FEATURE_PATTERNS.some((p) => p.test(text))
  )
}
```

### Issue 2 of 2
src/transforms.ts:828-855
**Silent data loss when `ENV_OPENER_RE` doesn't match**

`normalizeEnvBlock` is called with the full env-block string captured by `ENV_BLOCK_RE` (which requires the "You are powered by the model named…" opener). If `ENV_CONTAINER_RE` matches (the `<env>…</env>` tags are present) but `ENV_OPENER_RE` fails — e.g. OpenCode changes the second line from `"Here is some useful information…"` to any other phrase — `opener` is set to `""`. The opener lines that precede `<env>` are never captured in `opener`, and they are not included in `safe` (which is built only from the `<env>` body). Those lines are simply dropped: they appear in neither `safe` nor `branded`, and nothing is passed through for them.

In v1.4.8, the whole joined blob would be relocated to the first user message, so the model would still see the content. Here the text silently vanishes, which contradicts the stated invariant "never worse than v1.4.8 bulk relocation." A minimal guard — returning `{ safe: null, branded: envBlock }` when `ENV_OPENER_RE` fails (mirroring the `!containerMatch` path) — would restore the v1.4.8 fallback for this case.

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread src/transforms.ts
Comment on lines +218 to +223
export function looksLikeOpenCodeJoinedBlob(text: string): boolean {
return (
/^You are powered by the model named/m.test(text) &&
/\bopencode\b/i.test(text)
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 looksLikeOpenCodeJoinedBlob still uses the case-insensitive /\bopencode\b/i pattern that the rest of this PR explicitly replaces with the tighter PascalCase /\bOpenCode\b/. A system entry whose provider prompt begins with "You are powered by the model named …" and whose working directory contains a lowercase opencode path component (e.g. /home/dev/opencode-project) would satisfy both conditions here and incorrectly trigger the blob-splitter. Downstream isOpenCodeBrandedEntry would still classify the split pieces correctly (nothing would be wrongly relocated), but the unnecessary activation undermines the stated goal of the case-sensitivity tightening.

Suggested change
export function looksLikeOpenCodeJoinedBlob(text: string): boolean {
return (
/^You are powered by the model named/m.test(text) &&
/\bopencode\b/i.test(text)
)
}
export function looksLikeOpenCodeJoinedBlob(text: string): boolean {
return (
/^You are powered by the model named/m.test(text) &&
OPENCODE_FEATURE_PATTERNS.some((p) => p.test(text))
)
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/transforms.ts
Line: 218-223

Comment:
`looksLikeOpenCodeJoinedBlob` still uses the case-insensitive `/\bopencode\b/i` pattern that the rest of this PR explicitly replaces with the tighter PascalCase `/\bOpenCode\b/`. A system entry whose provider prompt begins with "You are powered by the model named …" and whose working directory contains a lowercase `opencode` path component (e.g. `/home/dev/opencode-project`) would satisfy both conditions here and incorrectly trigger the blob-splitter. Downstream `isOpenCodeBrandedEntry` would still classify the split pieces correctly (nothing would be wrongly relocated), but the unnecessary activation undermines the stated goal of the case-sensitivity tightening.

```suggestion
export function looksLikeOpenCodeJoinedBlob(text: string): boolean {
  return (
    /^You are powered by the model named/m.test(text) &&
    OPENCODE_FEATURE_PATTERNS.some((p) => p.test(text))
  )
}
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System prompt relocation in v1.4.8 degrades instruction-following in long conversations

4 participants