Foundation / Agentic Patterns
Collaboration Patterns
Realtime patterns for shared agentic surfaces — showing who is present, where they are pointing, what they are changing, and how conflicts are resolved. Every component is built to make human–agent co-editing legible and trustworthy.
--ds-color-agency) marks agent identity and authority. Blue (--ds-color-temporal) marks active presence. Gold (--ds-color-validation) marks conflict and human review gates. These three are the entire collaboration color vocabulary.Component 1 of 4
PresenceAvatarStack
Agent-aware presence row. Humans render as circles, agents as squares. Executing agents pulse with an agency-red ring; active participants show a temporal-blue dot. Ghost participants (recently left) fade to 38% opacity.
Default (size=28)
Large (size=36)
Compact (size=20, maxVisible=3)
import { PresenceAvatarStack } from "@/components/patterns/PresenceAvatarStack";
const participants = [
{ id: "1", type: "agent", name: "Treasury", role: "ORCHESTRATOR", state: "executing" },
{ id: "2", type: "agent", name: "Risk", role: "EXECUTOR", state: "active" },
{ id: "3", type: "human", name: "Faramarz", state: "active", color: "#3D5AFE" },
{ id: "4", type: "human", name: "Sarah", state: "idle" },
{ id: "5", type: "human", name: "James", state: "ghost" },
];
<PresenceAvatarStack
participants={participants}
maxVisible={5}
size={28}
overlap={8}
showTooltip
/>| Prop | Type | Default | Description |
|---|---|---|---|
| participants | PresenceParticipant[] | — | All participants to display. |
| maxVisible | number | 5 | Max avatars before +N overflow chip. |
| size | number | 28 | Avatar size in px. |
| overlap | number | 8 | Overlap between adjacent avatars in px. |
| showTooltip | boolean | true | Show name+role on hover. |
Agent running — agency red pulse ring
Currently focused — temporal blue dot
Present, not interacting — muted dot
Recently left — 38% opacity, no dot
Component 2 of 4
CollaborativeCursor
Ghost cursor overlay with identity badge. Placed inside a position: relative container. Human cursors use a standard arrow shape. Agent cursors use a bracket form — visually distinct from human pointers.
import { CollaborativeCursor } from "@/components/patterns/CollaborativeCursor";
// Inside a position:relative container:
<div style={{ position: "relative", width: "100%", height: 400 }}>
{/* Human cursor */}
<CollaborativeCursor
id="user-1"
type="human"
name="Sarah Chen"
x={25} // % of container width
y={40} // % of container height
color="#00897B"
/>
{/* Agent cursor — executing */}
<CollaborativeCursor
id="agent-1"
type="agent"
name="Treasury Agent"
role="ORCHESTRATOR"
x={60}
y={55}
executing
/>
{/* Ghost — recently left */}
<CollaborativeCursor id="user-2" type="human" name="James" x={10} y={80} ghost />
</div>| Prop | Type | Default | Description |
|---|---|---|---|
| type | "human" | "agent" | — | Determines cursor shape. |
| name | string | — | Participant name in identity label. |
| role | string | — | Agent role label (agents only). |
| x | number | — | X as % of container width (0–100). |
| y | number | — | Y as % of container height (0–100). |
| color | string | #3D5AFE | Hex color for human cursor. Agents always use --ds-color-agency. |
| executing | boolean | false | Elevated opacity + EXECUTING label (agents only). |
| ghost | boolean | false | Fade to 25% — participant recently left. |
Component 3 of 4
LiveChangeIndicator
Diff highlight with attribution. Three modes — inline (underline), field (left border), block (full tint). Every change must be attributed to a named human or agent. State drives color: pending (blue/red), review (gold), accepted (green), rejected (faded).
Mode: inline
Mode: field
Updated from market data feed at 14:32:07 UTC
Mode: block
Based on updated Basel IV guidelines, the capital adequacy ratio for the EUR book has been recalculated from 12.4% to 13.1%. This requires a position reduction of €8.2M by EOD.
import { LiveChangeIndicator } from "@/components/patterns/LiveChangeIndicator";
// Inline — wraps changed text
<p>
Notional:{" "}
<LiveChangeIndicator
mode="inline"
state="review"
author={{ type: "agent", name: "Treasury", role: "EXECUTOR" }}
>
€42.5M
</LiveChangeIndicator>
</p>
// Field — wraps a form field row
<LiveChangeIndicator
mode="field"
state="review"
author={{ type: "agent", name: "Risk Agent", role: "REVIEWER" }}
description="Updated from market data feed"
onAccept={() => acceptChange()}
onReject={() => rejectChange()}
>
<VaRLimitField value={2850000} />
</LiveChangeIndicator>
// Block — wraps a content block
<LiveChangeIndicator
mode="block"
state="pending"
author={{ type: "agent", name: "Compliance", role: "ORCHESTRATOR" }}
>
<RegulatoryUpdateCard />
</LiveChangeIndicator>| Prop | Type | Default | Description |
|---|---|---|---|
| mode | "inline" | "field" | "block" | "inline" | Visual presentation mode. |
| state | ChangeState | "pending" | pending | review | accepted | rejected. |
| author | ChangeAuthor | — | { type: 'human'|'agent', name, role? } |
| description | string | — | Short change description (field/block modes). |
| onAccept | () => void | — | Accept callback — shows Accept button when provided. |
| onReject | () => void | — | Reject callback — shows Reject button when provided. |
Component 4 of 4
ConflictResolutionBanner
Agent conflict surface. When two participants (agents or human + agent) produce diverging values the system cannot auto-resolve, this component surfaces both versions for human arbitration. Conflict is gold — it is a decision gate, not an error.
Derived from Bloomberg forward curve + Basel spread adjustment
Manual correction based on desk trading sheet at 15:00 CET
import { ConflictResolutionBanner } from "@/components/patterns/ConflictResolutionBanner";
const [state, setState] = useState<ConflictResolutionState>("unresolved");
<ConflictResolutionBanner
fieldLabel="EUR/USD Forward Rate"
description="Maturity: 2026-06-30"
state={state}
versionA={{
id: "a",
label: "Version A",
authorName: "Treasury Agent",
authorType: "agent",
authorRole: "ORCHESTRATOR",
value: "1.0842",
confidence: 0.91,
reasoning: "Bloomberg forward curve + Basel spread",
}}
versionB={{
id: "b",
label: "Version B",
authorName: "Sarah Chen",
authorType: "human",
value: "1.0856",
reasoning: "Desk trading sheet at 15:00 CET",
}}
onAcceptA={() => setState("resolved_a")}
onAcceptB={() => setState("resolved_b")}
onMerge={() => setState("resolved_manual")}
/>| Prop | Type | Default | Description |
|---|---|---|---|
| fieldLabel | string | — | Short label for the field/block in conflict. |
| description | string | — | Description of the conflict context. |
| versionA / versionB | ConflictVersion | — | { id, label, authorName, authorType, authorRole?, value, confidence?, reasoning? } |
| state | ConflictResolutionState | "unresolved" | unresolved | resolved_a | resolved_b | resolved_manual. |
| onAcceptA / onAcceptB | () => void | — | Accept callbacks — show Accept buttons when provided. |
| onMerge | () => void | — | Manual merge — shows Merge Manually option. |
--ds-color-validation). Conflict is not an error — it is a decision gate. Red (agency) is reserved for agent identity markers. Green (--ds-color-outcome-positive) signals resolved state.Anti-Patterns
What NOT to do
Do
✓ Use circular avatars for humans, square for agents — entity type must be visually distinct.
✓ Show EXECUTING state agents with agency-red rings, not temporal blue.
✓ Attribute every live change to a named participant.
✓ Use gold for conflict banners — conflicts are decisions, not failures.
✓ Ghost stale cursors (opacity 25%) rather than removing immediately.
Don't
✗ Don't show agents with human-style circular avatars — this obscures entity type.
✗ Don't use red for conflict banners — red means error or agent authority.
✗ Don't show anonymous diffs — every change needs attribution.
✗ Don't auto-resolve agent conflicts — always surface to human.
✗ Don't use CollaborativeCursor outside a position:relative container.
Related