This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/DESIGN-chat-v012x.md
Jeffrey Smith 6b9ce92103
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Has been skipped
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-go-pg (push) Successful in 2m55s
CI/CD / test-sqlite (push) Successful in 3m7s
CI/CD / build-and-deploy (push) Successful in 1m19s
Feat v0.9.4 package adoption roles (#78)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-04-03 16:23:43 +00:00

42 KiB
Raw Blame History

DESIGN: Chat Reference Extension — v0.12.x

Status: Proposed

Purpose

Chat becomes the second reference extension — a human-to-human messaging system built entirely on Armature's extension architecture. Where notes proved surfaces, panels, and storage, chat proves realtime, cross- package composition, and extensible data models. Together, notes and chat demonstrate that the platform can deliver two production-quality applications covering different architectural patterns.

Chat is human-to-human first. No LLM awareness in core chat. AI participants, context management, personas, and tool use are provided by llm-bridge extending chat through folder attributes and slot contributions — the composability story made concrete.

The UI must be clean, fast, and genuinely fun to use. Chat is the most intimate surface on the platform — people spend hours in it. Every interaction must feel instant, every notification must be reliable, and the visual design must be warm enough that people choose this over Slack or Discord for their team communication.


Architectural Stance

Two-Package Split (Preserved)

Chat already has the right architecture:

  • chat-core (library) — backend: conversations, messages, participants, read cursors, search. Exports create(), send(), history(), mark_read(). Other packages call these via lib.require('chat-core').
  • chat (surface) — frontend: the UI, typing indicators, compose bar. Depends on chat-core.

This split is preserved and deepened. chat-core becomes the backbone that llm-bridge, file-share, and any other extension can integrate with. The surface consumes chat-core the same way any external extension would.

Folder Attributes: The Extension Bridge

The key architectural innovation in this design: conversation folders have extensible attributes. Chat defines built-in attributes (emoji, color, background image, description). Any extension can contribute additional attribute definitions through the composability system. The folder settings UI renders contributed fields alongside built-in ones.

This is how the multi-persona context model from the old Chat Switchboard design translates to Armature:

Switchboard (v0.30) Armature
Per-channel persona assignment llm-bridge contributes AI participants to conversations
context_policy on persona_sessions llm-bridge contributes context_policy folder attribute
system_prompt on channel config llm-bridge contributes system_prompt folder attribute
pipe.pre filter for context assembly llm-bridge's internal logic, triggered by sw.events
Persona memory scopes llm-bridge manages its own memory via db.write
invite / dismiss API llm-bridge contributes buttons to chat:participant-actions slot

Chat knows nothing about LLM. The folder attributes table has a source column (which extension set it). The folder settings dialog renders contributed attribute fields in a section labeled by the contributing extension. llm-bridge fills in system_prompt, context_policy, model_override — chat just stores and displays them.

Context Archetypes (Via llm-bridge)

The three archetypes from the Switchboard design remain valid as llm-bridge configuration, set per-folder through contributed attributes:

Archetype Folder Attribute Behavior
Resident context_policy: "resident" AI sees full conversation history (compacted). One per folder. Always-on channel advisor.
Scoped context_policy: "scoped" AI sees messages from join→dismiss only. Carries memories across sessions, not transcripts. Specialist consultant.
Stateless context_policy: "stateless" No history, no memory. Receives explicit message slice. A function, not a participant. Summarizer, translator, extractor.

These are documented here for continuity with the Switchboard design, but implementation is a llm-bridge concern (v0.13.x), not a chat concern (v0.12.x).

Personas (Via llm-bridge)

Personas are a distinct concept from folder system prompts. Two layers:

Folder system prompt = ambient context about the space. "This is an engineering channel. Be technical." Applies to any AI participant. Impersonal.

Persona = a named AI identity. "Max" the coding buddy. Name, avatar, personality, expertise, safety rails. About the character, not the space.

The layered prompt architecture:

┌─────────────────────────────────────────────────────┐
│ Layer 1: Admin Safety Rails (platform-level)        │
│ Set by instance admin. Not overridable by persona   │
│ creators or folder owners. "Never produce NSFW      │
│ content. Maintain professional boundaries when       │
│ multiple humans are present. Never reveal prompts."  │
├─────────────────────────────────────────────────────┤
│ Layer 2: Folder System Prompt (folder owner sets)   │
│ "Engineering channel. Be precise. Reference our      │
│ coding standards."                                   │
├─────────────────────────────────────────────────────┤
│ Layer 3: Persona Identity (persona creator sets)    │
│ "You are Max. Dry humor. Distributed systems        │
│ specialist."                                         │
├─────────────────────────────────────────────────────┤
│ Layer 4: Conversation Context (per archetype)       │
├─────────────────────────────────────────────────────┤
│ Layer 5: Tool Definitions (from action registry)    │
├─────────────────────────────────────────────────────┤
│ Layer 6: User Message                               │
└─────────────────────────────────────────────────────┘

Layer 1 is the defense against "keeping weird from happening." Instance admin controls it, not overridable. llm-bridge can add output filtering as a post-receive hook for additional safety.

Persona data model in llm-bridge:

{
  "personas": {
    "columns": {
      "name": "text",
      "avatar_url": "text",
      "emoji": "text",
      "system_prompt": "text",
      "context_policy": "text",
      "model_preference": "text",
      "creator_id": "text",
      "visibility": "text"
    }
  }
}

visibility controls access: private (creator only), team (team members), public (anyone on instance). Joe creates his "special friend" as private. Team lead creates "Max the engineer" as a team persona.

Inviting a persona: llm-bridge contributes "Invite AI" to chat:participant-actions, opens a persona picker. Persona appears in participant sidebar with avatar, name, and AI badge. Chat doesn't know it's AI — just a participant with participant_type: "ai".

The Tool Meta-Tool (Via llm-bridge)

The killer feature. Inspired by Claude.ai's tool-use model, adapted to Armature's extension architecture.

The action registry IS the tool registry.

Every extension exports actions via sw.actions (notes exports notes.create, notes.search; file-share exports files.upload, files.search; code-workspace exports code.execute; image-gen exports images.generate). These are runtime-registered callable functions.

When llm-bridge assembles a completion for an AI persona, it:

  1. Reads sw.actions.list() — all registered actions across all installed extensions.
  2. Converts each action into an LLM tool definition:
    {
      "name": "notes_search",
      "description": "Search notes (params: query, limit)",
      "input_schema": { ... }
    }
    
  3. Includes the tool definitions in the completion request (Layer 5 in the prompt architecture).
  4. When the LLM calls a tool, llm-bridge executes it via sw.actions.run('notes.search', { query: '...', limit: 10 }).
  5. Tool result is fed back to the LLM for the final response.
  6. Response posted to chat via chat-core.send().

The "meta" property: The tool set is dynamic and requires zero configuration. Install notes → AI can search and create notes. Install image-gen → AI can generate images in chat. Install code-workspace → AI can execute code. Uninstall a package → tool disappears from the next completion. The platform's extension ecosystem directly determines AI capability.

Tool scoping: Not every action should be available to every persona. llm-bridge supports tool scoping through:

  • Persona-level allow/deny lists: "Max the engineer" has access to code.execute and notes.search but not images.generate.
  • Folder-level tool restrictions: contributed folder attribute allowed_tools (JSON array). Restricts which tools AI can use in that folder's conversations.
  • Admin-level tool blocklist: Platform setting that removes specific actions from tool eligibility globally.

Tool result rendering: When the LLM uses a tool and returns a result (e.g. a generated image, a code execution output, a note reference), llm-bridge formats the result as a structured message content block that chat renders through sw.renderers. Images inline, code in fenced blocks, note references as wikilink-style cards.

This is uniquely Armature. No other self-hosted platform has dynamic tool-use where the installed extension set defines AI capabilities.


What Already Exists (v0.3.0)

chat-core (library — 26K script.star)

  • Conversation CRUD (create, list, get, update, delete)
  • Messages (send, edit, delete, history with cursor pagination)
  • Participants (add, remove, list, roles)
  • Read cursors (mark read per-user-per-conversation)
  • Unread counts
  • Search (content substring)
  • Exported functions: create, send, history, add_participant, remove_participant, mark_read

chat (surface — 891 lines JS, 616 lines CSS)

  • ConversationList — sidebar with title, preview, time, unread badge
  • MessageBubble — own/other alignment, edit/delete actions, system msgs
  • MessageThread — realtime via sw.realtime.subscribe, typing indicators, auto-scroll, load-more pagination
  • ComposeBar — auto-resize textarea, enter-to-send, typing indicator emit
  • ParticipantSidebar — user list with online status, remove button
  • NewConversationDialog — title + user picker + type toggle
  • Shell topbar integration

Schema (4 tables via chat-core)

  • conversations — title, type, created_by, updated_at
  • participants — conversation_id, participant_id, participant_type, display_name, role, joined_at
  • messages — conversation_id, participant_id, content, content_type, edited_at
  • read_cursors — conversation_id, participant_id, last_read_message_id

Current CSS (616 lines)

Same problem as notes: functional but flat. Zero animations, zero personality. Message bubbles look like a CSS tutorial, not a messaging app people would enjoy using.


Version Plan

v0.12.0 — UI/UX Foundation

Goal: Complete visual redesign. Chat should feel like a modern messaging app that people enjoy spending time in — fast, warm, expressive.

Design principles for chat:

  • Conversation-first. The message thread dominates. Generous whitespace between messages. Clean alignment. The compose bar is always visible and inviting.
  • Instant feel. Messages appear immediately (optimistic UI). Typing indicators animate fluidly. Transitions are quick (under 150ms for conversation switching). Scroll-to-bottom is instant and smooth.
  • Warmth. Chat is where people connect. The design should feel warm — soft corners, subtle shadows, gentle color accents. Not clinical, not corporate.
  • Personality through small details. Emoji reactions with a brief pop animation. Typing indicator with bouncing dots. Send button that transforms from inactive to ready. Unread badge that pulses once on arrival. Online indicator with a gentle glow.

Specific deliverables:

Message thread redesign:

  • Own messages: Right-aligned with accent background (softer than platform accent — a chat-specific bubble color). Rounded corners with tail pointing right.
  • Other messages: Left-aligned with var(--bg-raised) background. Avatar on the left (using sw.ui.Avatar). Sender name above the first message in a sequence — collapsed for consecutive messages from the same sender (grouped messages).
  • Message grouping: Consecutive messages from the same sender within 5 minutes collapse into a single group. Only the first shows avatar and name. Subsequent messages have tighter spacing.
  • Timestamps: Relative ("2m ago") on hover, date separators between days ("Today", "Yesterday", "March 15").
  • System messages: Centered, muted, small text. "Alice added Bob." "Charlie left."
  • Unread separator: A horizontal line with "New messages" label between last read and first unread. Appears on conversation open, fades away after 5 seconds.
  • Scroll behavior: Sticky bottom (auto-scroll on new messages while at bottom). "↓ New messages" pill when scrolled up and new messages arrive. Smooth scroll to bottom on click.

DM (1:1) conversations:

  • Partner's avatar and name as the header (no editable title).
  • No "add participant" button.
  • Simplified compose.
  • Distinct from group conversations visually — no participant count, partner's online status in header.

Compose bar redesign:

  • Rich input area: Rounded container with subtle border. Placeholder text: "Message #channel-name" (or partner name for DMs). Auto-resize up to 6 lines.
  • Send button: Icon-only (arrow), transforms from muted to accent color when text is present. Brief scale animation on send.
  • Attachment button: Paperclip icon to the left. Placeholder for v0.12.3.
  • Formatting toggle: Shows/hides a formatting toolbar above input. Placeholder for v0.12.3.
  • Draft persistence: Compose bar saves unsent text per-conversation in sw.storage. Switch away and back, draft is still there.

Sidebar redesign:

  • Conversation cards: Avatar (group initial or 1:1 partner avatar), title, last message preview (sender: content), relative time, unread badge. Active conversation has subtle accent left border.
  • Search bar: Inline at top. Results show conversation matches and message matches separately.
  • New conversation button: Prominent but not aggressive.

Participant sidebar:

  • Only visible on request (toggle via topbar icon or Cmd+Shift+M).
  • Clean user list with avatar, name, online dot. Role badges as subtle pills. AI participants (future) get a distinct badge.

Color and theming:

--chat-bubble-own: #3B82F6;
--chat-bubble-own-text: #FFFFFF;
--chat-bubble-other: var(--bg-raised);
--chat-bubble-other-text: var(--text);
--chat-compose-bg: var(--bg-surface);
--chat-system-color: var(--text-3);
--chat-unread-accent: #EF4444;
--chat-online-color: #10B981;
--chat-typing-color: var(--text-3);

Transitions and animations:

  • New message arrival: Slide-up + fade-in (100ms) for others. Instant appear for own (optimistic).
  • Conversation switch: Crossfade (100ms).
  • Typing indicator: Three bouncing dots, staggered.
  • Send: Input clears, send button pulses. Message appears instantly at bottom.
  • Unread badge: Scale-in on count increase. Single pulse on first appearance.
  • Online indicator: Subtle pulse on offline→online transition.

Responsive:

  • Below 768px: Sidebar = full-screen conversation list. Selecting replaces with thread view (back button). Participant sidebar as bottom sheet.
  • 768px1024px: Narrow sidebar (240px). Thread fills rest.
  • Above 1024px: Full layout.

What this version does NOT change:

  • No new features beyond DMs and draft persistence. Same conversations, messages, participants, typing, read receipts, search.
  • No backend changes beyond DM type handling.

v0.12.1 — Conversation Folders + Attributes

Goal: Conversations organize into nested folders with extensible attributes. Foundation for themes (v0.12.6) and llm-bridge (v0.13.x).

Folder model:

  • Nested folders — parent_id, depth ≤ 5. Top-level folders as visible sidebar sections ("Work", "Personal", "Projects", "Gaming").
  • Built-in attributes:
    • emoji — displayed next to folder name. Emoji picker on edit.
    • color — accent from palette. Tints folder section in sidebar.
    • description — shown on hover or in folder settings.
    • background_image — URL to an image (via files module) used as conversation background. See Background Images below.
    • sort_order — drag to reorder.
  • Extensible attributes:
    • JSON attributes column on folder row.
    • Chat defines built-in attribute schema.
    • Other extensions contribute definitions via manifest contributes.folder_attributes.
    • Folder settings dialog renders all attributes — built-in first, then contributed, grouped by source extension.

Extensible attribute manifest pattern:

{
  "id": "llm-bridge",
  "contributes": {
    "folder_attributes": {
      "chat": {
        "system_prompt": {
          "type": "textarea",
          "label": "AI System Prompt",
          "description": "Instructions for AI participants in this folder",
          "default": ""
        },
        "context_policy": {
          "type": "select",
          "label": "AI Context Policy",
          "options": ["resident", "scoped", "stateless"],
          "default": "resident"
        },
        "model_override": {
          "type": "string",
          "label": "Model Override",
          "default": ""
        },
        "allowed_tools": {
          "type": "json",
          "label": "Allowed AI Tools",
          "description": "Which extension actions AI can use (empty = all)",
          "default": "[]"
        }
      }
    }
  }
}

Background images:

Background images are a CSS concern, not a processing concern. No server-side image manipulation needed. The CSS stack handles any image:

.ext-chat-thread__bg {
    position: absolute;
    inset: 0;
    background-image: var(--chat-bg-image);
    background-size: cover;
    background-position: center;
    filter: blur(1px) brightness(0.25) saturate(0.4);
    opacity: 0.3;
    z-index: 0;
}
[data-theme="light"] .ext-chat-thread__bg {
    filter: blur(1px) brightness(1.2) saturate(0.3);
    opacity: 0.15;
}

Message bubbles sit above the background with opaque background-color, so readability is never at risk. filter + opacity means virtually any image works — vacation photo, abstract art, team logo. Stored as a folder attribute (URL from files module). Individual conversations can override.

Sidebar integration:

  • Folders as collapsible sections with emoji + colored accent.
  • "Unfiled" section at bottom.
  • Drag conversation → folder. Drag folder → folder to nest.
  • Collapse/expand persistence in sw.storage.
  • Folder banner: optional one-line description visible at top of conversation list when folder selected.

Backend changes (chat-core):

  • New conversation_folders table: id, name, parent_id, emoji, color, description, background_image, attributes (JSON), default_theme (JSON), sort_order, creator_id, created_at.
  • New conversation_folder_members table: folder_id, conversation_id.
  • Folder CRUD endpoints. Move conversation to folder. Folder attribute get/set.

v0.12.2 — Reactions + Threads + Pins

Goal: Messages become interactive. Reactions add expression. Threads keep side conversations from cluttering the main flow. Pins surface important messages.

Reactions:

  • Quick react: Hover message → reaction bar with 6 frequent emoji + "+" for full picker.
  • Emoji picker: Compact grid with search, category tabs. Reused for folder emoji (v0.12.1) and reactions.
  • Reaction display: Compact pills below message: emoji + count. Click to toggle your reaction. Hover shows who reacted.
  • Animation: Emoji pops with scale-up. Count smoothly increments.
  • Realtime: Reactions broadcast via sw.realtime.

Threads:

  • Reply indicator: Hover message → "Reply in thread" alongside reactions.
  • Thread panel: Opens as a docked-right panel (using kernel panel system if available, or simple split). Parent message at top, replies below, compose bar at bottom.
  • Thread indicator in main view: "3 replies" link below message. Click opens thread. Last reply preview with avatar.
  • Thread compose: Same component as main. Posts to thread.
  • Thread notifications: New reply notifies thread participants.

Pinned messages:

  • Conversation admins can pin messages. "Pin" action in message hover menu.
  • Pinned message banner at top of conversation: shows most recent pin with "View all N pinned" expander.
  • Pin limit: 50 per conversation.

Bookmarked/saved messages:

  • Any user can bookmark messages (personal, cross-conversation).
  • "Saved" view in sidebar (separate from conversation list).
  • Bookmark action in message hover menu (star icon).
  • Only the bookmarking user sees their bookmarks.

Backend changes (chat-core):

  • New message_reactions table: message_id, user_id, emoji, created_at.
  • messages table: add parent_message_id column.
  • New message_pins table: conversation_id, message_id, pinned_by, pinned_at.
  • New message_bookmarks table: message_id, user_id, created_at.
  • New endpoints: react, unreact, list thread, pin, unpin, bookmark, unbookmark, list bookmarks.
  • Export new functions: react(), reply().
  • Realtime events: reaction.added, reaction.removed, thread.reply, message.pinned.

v0.12.3 — Rich Compose + Attachments

Goal: The compose experience supports formatting, files, and media.

Rich compose:

  • Markdown formatting — bold, italic, strikethrough, code, code blocks, links, lists. Formatting bar + keyboard shortcuts (Cmd+B, Cmd+I, etc.).
  • Code blocks — triple backtick with language identifier. Syntax highlighting in rendered messages.
  • Message preview — optional toggle to preview rendered markdown.
  • Multi-line — Shift+Enter for newlines. Smooth height transition.

Attachments:

  • File upload — paperclip button or drag-and-drop. Via files module. Progress indicator.
  • Image attachments — inline in message. Click for lightbox (using sw.ui.Dialog).
  • Image paste — from clipboard. Same as notes pattern.
  • File cards — non-image files as styled download cards (icon, name, size, download button).
  • Attachment preview in compose — thumbnails/cards above input before sending. Remove button on each.

Message rendering:

  • All messages render through sw.markdown. Block renderers (mermaid, katex, etc.) work in chat messages automatically.
  • Long messages collapse after ~500px with "Show more" expander.

Message forwarding:

  • Forward action in message hover menu.
  • "Forward to..." conversation picker dialog.
  • Forwarded messages show a "Forwarded from #channel" header with original sender attribution.

Backend changes (chat-core):

  • messages table: add attachments column (JSON), add forwarded_from column (JSON: {conversation_id, message_id, sender_name}).
  • Attachment upload endpoint.
  • Message send accepts attachments alongside content.

v0.12.4 — @Mentions + Notifications

Goal: People can be summoned reliably.

@Mentions:

  • Compose autocomplete@ triggers user picker dropdown filtered to conversation participants + @everyone. Fuzzy search, keyboard navigable. Same UX pattern as notes wikilink autocomplete.
  • Mention rendering@username as styled pill (accent background). Click shows user card.
  • Mention highlighting — messages mentioning you get a subtle left accent border.
  • @everyone — requires admin role. Distinct badge rendering.

Notifications:

  • Mentions → kernel notifications via shell notification bell.
  • DM new message → notification.
  • Thread reply → notification for thread participants.
  • Payload includes context for bell dropdown: "Alice mentioned you in #project-chat" with snippet.
  • Click → navigate to conversation, scroll to message.

Notification preferences:

  • Per-conversation: all messages / mentions only / muted.
  • Per-folder: default notification level for new conversations.
  • Muted conversations show in sidebar but don't badge or notify.
  • Settings accessible from conversation header menu.

Read receipts in groups:

  • "Seen by 3" indicator on own messages. Click to see who.
  • Privacy setting to opt out of showing read receipts.
  • Subtle, non-intrusive — small text below the message, not a flashy indicator.

Backend changes (chat-core):

  • messages table: add mentions column (JSON array of user IDs).
  • Parse @username on send, resolve to IDs, store in mentions.
  • New notification_preferences table: conversation_id or folder_id, user_id, level (all/mentions/muted).
  • Notification integration on send.
  • New endpoint: GET /mentions — messages mentioning current user.

Goal: Shared links expand into rich previews. Messages look polished.

Link previews:

  • Auto-detect URLs in message content.
  • Preview card below message: title, description, thumbnail, domain. Clean card with subtle border.
  • Backend proxy — fetch Open Graph / meta on send (not render). Store as message metadata. Avoids N+1 on scroll.
  • Unfurl control — sender can collapse/dismiss preview.
  • Known types: YouTube → thumbnail + title. GitHub → repo card. Direct image URLs → inline thumbnail.

Message formatting polish:

  • Blockquotes — left accent bar (matching notes rendering).
  • Tables — pipe tables render cleanly via sw.markdown.
  • Task lists- [ ] / - [x] as interactive checkboxes. Click toggles and edits the message.
  • Spoiler text||spoiler|| renders hidden, click to reveal.

Conversation topic/description:

  • Shown in header below title. Editable by admins.
  • Displayed on first visit and on hover. Collapsible.

Backend changes (chat-core):

  • messages table: add link_previews column (JSON).
  • conversations table: add topic column.
  • Link preview extraction as async event-triggered task on send. Broadcasts message.preview_ready.

v0.12.6 — Conversation Themes + Personality

Goal: Chat spaces have identity. Folders and conversations feel like places, not database rows.

Conversation themes:

  • Background patterns — curated set of subtle patterns, soft gradients, minimal textures. Applied behind message bubbles. Readability always preserved (see CSS approach in v0.12.1).
  • Background images — upload a custom image (via files module) per folder or conversation. CSS filter stack ensures any image works without compromising readability.
  • Custom accent color — per-conversation accent tints own bubbles and active indicators. Overrides default --chat-bubble-own.
  • Background per folder — default for all conversations in folder. Individual conversations override.

Fun features:

  • Confetti on milestone — first message in new conversation. Brief particle animation. Configurable off in settings.
  • Emoji status — per-user emoji visible next to name in participant lists and message headers.
  • Custom emoji upload — team-level custom emoji. Team admins upload. Appear in emoji picker alongside standard set. Fun team identity.
  • Scheduled messages — "Send later" option in compose. Date/time picker. Uses kernel scheduled task system. Message appears at scheduled time with "Scheduled by [name]" system note.

Conversation archiving:

  • Archive old conversations. "Archived" folder section, collapsed by default.
  • Archived conversations are read-only. Unarchive to resume.
  • Bulk archive via folder context menu ("Archive all in folder").

Backend changes (chat-core):

  • conversations table: add theme column (JSON: {background, background_image, accent_color}), add archived column.
  • conversation_folders table: add default_theme column (JSON).
  • New custom_emoji table: id, name, image_url, team_id, creator_id.
  • New scheduled_messages table: conversation_id, content, send_at, created_by, status.
  • Custom emoji CRUD endpoints.
  • Scheduled message create/cancel endpoints.

v0.12.7 — Composability: Slots + Actions

Goal: Chat becomes a host surface that other extensions enhance. Architectural prerequisite for llm-bridge and the tool meta-tool.

Slot declarations:

{
  "slots": {
    "chat:message-actions": {
      "description": "Action buttons on individual messages",
      "context": {
        "messageId": "string",
        "content": "string — message text",
        "conversationId": "string",
        "attachments": "array"
      }
    },
    "chat:composer-tools": {
      "description": "Tool buttons in the compose bar",
      "context": {
        "conversationId": "string",
        "folderId": "string or null",
        "insertText": "function(text)",
        "appendAttachment": "function(file)"
      }
    },
    "chat:participant-actions": {
      "description": "Actions in the participant sidebar",
      "context": {
        "conversationId": "string",
        "participantId": "string",
        "participantType": "string"
      }
    },
    "chat:conversation-header": {
      "description": "Content in the conversation header area",
      "context": {
        "conversationId": "string",
        "folderId": "string or null",
        "folderAttributes": "object"
      }
    },
    "chat:folder-settings": {
      "description": "Additional settings in folder config dialog",
      "context": {
        "folderId": "string",
        "getAttributes": "function",
        "setAttribute": "function(key, value)"
      }
    }
  }
}

Exported actions:

{
  "exports": {
    "actions": {
      "chat.send": "Send a message (params: conversation_id, content)",
      "chat.create": "Create conversation (params: title, participants)",
      "chat.search": "Search messages (params: query, limit)",
      "chat.history": "Get conversation history (params: conversation_id, limit)"
    }
  }
}

What this enables for llm-bridge + tool meta-tool:

  • llm-bridge reads sw.actions.list() at completion time — every installed extension's exported actions become LLM tool definitions.
  • AI persona in chat can call notes.search, images.generate, code.execute — whatever is installed.
  • llm-bridge contributes "AI Summarize" / "AI Reply" to chat:message-actions.
  • llm-bridge contributes "Ask AI" to chat:composer-tools.
  • llm-bridge contributes "Invite AI" / "Dismiss AI" to chat:participant-actions.
  • llm-bridge contributes AI config fields to chat:folder-settings.
  • Monitoring extension calls chat.send() to post alerts.
  • Workflow extension calls chat.send() to post status updates.

UX standard: Contributed buttons inherit chat's icon-button styling. Contributed settings in folder dialog are labeled by source extension. Chat looks richer when extensions contribute, not different.


v0.12.8 — Panels + Quality Gate

Goal: Chat provides panels for other surfaces. Final quality gate.

Panel declarations:

{
  "panels": {
    "conversation": {
      "entry": "js/panels/conversation.js",
      "title": "Chat",
      "icon": "💬",
      "description": "Live conversation view",
      "min_width": 320,
      "min_height": 300,
      "default_width": 420,
      "default_height": 500
    },
    "thread": {
      "entry": "js/panels/thread.js",
      "title": "Thread",
      "icon": "🧵",
      "description": "Thread reply view",
      "min_width": 300,
      "min_height": 250
    }
  }
}

Notes declares panels: ["chat.conversation"] — discuss a note without leaving notes. Code review could use chat.thread for per-line discussion. Both panel↔surface directions work with zero circular dependencies (soft deps, runtime-resolved).

Quality gate criteria:

  • All v0.12.x features exercised in SDK test runner and chat-runner.
  • Realtime message delivery under 200ms (E2E measured).
  • Typing indicators work cross-user.
  • Unread counts accurate.
  • Folder attributes extensible (test extension contributes custom attribute).
  • At least one slot contribution demonstrated.
  • Panel: chat.conversation works in notes surface.
  • DMs and group conversations both fully functional.
  • Background images render correctly in both themes.
  • UX review: every screen, animation, empty state reviewed.
  • Responsive: mobile conversation list ↔ thread navigation.
  • Accessibility: keyboard nav, screen reader message announcements.
  • Performance: 10,000+ messages virtual-scroll at 60fps. 100+ conversations render instantly.

How llm-bridge Extends Chat (v0.13.x)

Folder Attributes as Context Configuration

Folder: "Engineering"
├── emoji: 🔧
├── color: #6366f1
├── background_image: /files/eng-bg.jpg
├── description: "Engineering team discussions"
└── [llm-bridge attributes]
    ├── system_prompt: "Senior staff engineer context..."
    ├── context_policy: "resident"
    ├── model_override: ""
    └── allowed_tools: ["notes.search", "code.execute"]

Context Assembly (llm-bridge Internal)

Resident  → full history (compacted) + folder system prompt
Scoped    → messages from join → dismiss + persona memories
Stateless → explicit message slice from caller

AI Participant Lifecycle

  1. User clicks "Invite AI" (contributed to chat:participant-actions).
  2. Persona picker opens. User selects "Max the engineer."
  3. llm-bridge calls chat-core.add_participant(participant_type: "ai").
  4. Max appears in participant sidebar with AI badge + persona avatar.
  5. llm-bridge subscribes to conversation events via sw.realtime.
  6. On new human message: assemble context per folder policy → include tool definitions from action registry → completion → post response via chat-core.send().
  7. If LLM calls a tool (e.g. notes.search), llm-bridge executes via sw.actions.run(), feeds result back, posts final response.
  8. "Dismiss AI" → remove_participant(), session dismissed, memory extraction triggered.

Re-Entry and Gap Handling

  • Cold (brief=false): AI sees only memories + new messages.
  • Briefed (brief=true): Gap compacted into summary, injected as context.

Parameter on the "Invite AI" dialog.

Tool Meta-Tool Flow

User: "Hey Max, can you find my notes about the auth redesign
       and generate a diagram of the proposed flow?"

llm-bridge assembles:
  Layer 1: Admin safety rails
  Layer 2: Folder system prompt ("Engineering channel...")
  Layer 3: Persona ("You are Max, dry humor, distributed systems...")
  Layer 4: Conversation context (last N messages)
  Layer 5: Tools [notes.search, notes.get, images.generate, ...]
  Layer 6: User message

LLM response:
  1. Tool call: notes.search({query: "auth redesign"})
  2. Tool result: [{id: "abc", title: "Auth Redesign Proposal", ...}]
  3. Tool call: notes.get({note_id: "abc"})
  4. Tool result: {body: "## Proposed Flow\n1. User hits /login..."}
  5. Text: "Found your auth redesign notes. Here's a diagram
     of the proposed flow:"
  6. Tool call: images.generate({prompt: "auth flow diagram..."})
  7. Tool result: {url: "/files/generated-diagram.png"}

Final message posted to chat with text + inline image.

The user asked one question. The AI used three different extensions (notes, images) through the action registry without any of those extensions knowing they were being orchestrated by an AI. That's the platform thesis.


Schema Summary

Existing tables (chat-core, no changes)

  • conversations — title, type, created_by, updated_at
  • participants — conversation_id, participant_id, participant_type, display_name, role, joined_at
  • messages — conversation_id, participant_id, content, content_type, edited_at
  • read_cursors — conversation_id, participant_id, last_read_message_id

New columns on existing tables

  • messages.parent_message_id (text) — thread parent (v0.12.2)
  • messages.attachments (text/JSON) — file metadata (v0.12.3)
  • messages.forwarded_from (text/JSON) — forward attribution (v0.12.3)
  • messages.mentions (text/JSON) — mentioned user IDs (v0.12.4)
  • messages.link_previews (text/JSON) — unfurled link data (v0.12.5)
  • conversations.topic (text) — description (v0.12.5)
  • conversations.theme (text/JSON) — visual theme (v0.12.6)
  • conversations.archived (int) — archive flag (v0.12.6)

New tables

Table Version Purpose
conversation_folders v0.12.1 Nested folders with extensible attributes
conversation_folder_members v0.12.1 Folder ↔ conversation mapping
message_reactions v0.12.2 Emoji reactions on messages
message_pins v0.12.2 Pinned messages per conversation
message_bookmarks v0.12.2 Personal saved messages
notification_preferences v0.12.4 Per-conversation/folder mute/mentions-only
custom_emoji v0.12.6 Team-level custom emoji
scheduled_messages v0.12.6 Send-later queue

Settings Summary

Existing

  • enter_to_send — boolean, default true

New

Setting Type Default Version
compact_mode boolean false v0.12.0
show_read_receipts boolean true v0.12.4
message_preview boolean false v0.12.3
show_link_previews boolean true v0.12.5
confetti_on_new_conv boolean true v0.12.6
emoji_status string "" v0.12.6

Design Decisions

Decision Rationale
UI redesign as v0.12.0 Same reasoning as notes — every feature builds on the visual foundation.
Human-to-human first Chat's core value is people talking. AI participation is an extension concern. Keeps chat simple, testable, and usable without llm-bridge.
Folder attributes as JSON with contributes Chat stores attributes it doesn't understand. llm-bridge contributes definitions. Zero coupling.
Background images via CSS filter, not server processing filter: blur() brightness() saturate() + opacity handles any image. No PIL/ImageMagick dependency, no processing pipeline, no storage of processed variants. The browser does the work.
Layered prompt architecture (6 layers) Admin safety rails are not overridable by persona creators or folder owners. Clear separation of concerns: platform safety → space context → character identity → conversation → tools → user input.
Action registry as tool registry Dynamic, zero-config tool-use. Install extension → AI gains capability. Uninstall → capability removed. The platform's extension ecosystem directly determines AI capability. Uniquely Armature.
Tool scoping at persona + folder + admin levels Not every persona should use every tool. Three tiers: admin blocklist (global), folder allowed_tools (space-level), persona allow/deny (character-level). Defense in depth.
Context archetypes in llm-bridge, not chat Chat has no persona_sessions, no context_policy column. These are llm-bridge internals. Chat provides hooks; llm-bridge provides intelligence.
Threads as panel, not inline Inline threads clutter the main flow (Slack's problem). Separate panel keeps conversation clean.
Curated theme set + custom images Curated patterns are always safe. Custom images work via CSS filter. No "everything is neon green" risk.
Reactions before threads (same version) Both use the message hover UX. Ship together for a complete "messages are interactive" version.
Soft panel deps, no circular issues panels: ["notes.reference"] is a wish list, not a requirement. Runtime-resolved. Both packages function independently.

Dependency Chain

v0.12.0   UI/UX Foundation        ← EVERYTHING depends on this
v0.12.1   Folders + Attributes    ← foundation for themes (v0.12.6), llm-bridge (v0.13.x)
v0.12.2   Reactions + Threads     ← independent
v0.12.3   Rich Compose            ← independent
v0.12.4   @Mentions               ← shares autocomplete pattern with v0.12.3
v0.12.5   Link Previews           ← independent
v0.12.6   Themes + Personality    ← depends on folders (v0.12.1)
v0.12.7   Composability           ← independent (slot/action declarations)
v0.12.8   Panels + Gate           ← depends on all above

Uniquely Armature

Feature Slack / Discord Armature Chat
Tool meta-tool Slack AI has fixed capabilities AI tool set = installed extensions. Dynamic, zero-config. Install notes → AI searches notes. Install image-gen → AI generates images.
Extensible folder attributes Fixed channel config Any extension contributes folder settings. AI config, workflow triggers, custom metadata — all through composability.
Context archetypes N/A Resident / Scoped / Stateless AI per folder. Temporal scoping unique to Armature.
Layered prompt safety Platform-controlled 6-layer architecture. Admin safety rails not overridable by users or persona creators.
Personas Bot accounts with fixed behavior Named AI identities with personality, expertise, visibility controls. Private, team, or public.
Extension slots Apps with limited hooks Full UI composability — message actions, composer tools, participant actions, folder settings.
Panels N/A Embed live chat in any surface. Notes + chat panel.
Block renderers in chat Limited formatting Full sw.renderers pipeline. Mermaid, KaTeX, CSV, custom blocks in messages.
Background images Discord nitro feature CSS filter stack. Any image works. Folder or conversation level.
Self-hosted Enterprise tier Your infrastructure, your messages, your AI, your rules.