Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
42 KiB
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. Exportscreate(),send(),history(),mark_read(). Other packages call these vialib.require('chat-core').chat(surface) — frontend: the UI, typing indicators, compose bar. Depends onchat-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:
- Reads
sw.actions.list()— all registered actions across all installed extensions. - Converts each action into an LLM tool definition:
{ "name": "notes_search", "description": "Search notes (params: query, limit)", "input_schema": { ... } } - Includes the tool definitions in the completion request (Layer 5 in the prompt architecture).
- When the LLM calls a tool,
llm-bridgeexecutes it viasw.actions.run('notes.search', { query: '...', limit: 10 }). - Tool result is fed back to the LLM for the final response.
- 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.executeandnotes.searchbut notimages.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_atparticipants— conversation_id, participant_id, participant_type, display_name, role, joined_atmessages— conversation_id, participant_id, content, content_type, edited_atread_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 (usingsw.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.
- 768px–1024px: 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 (viafilesmodule) used as conversation background. See Background Images below.sort_order— drag to reorder.
- Extensible attributes:
- JSON
attributescolumn 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.
- JSON
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_folderstable: id, name, parent_id, emoji, color, description, background_image, attributes (JSON), default_theme (JSON), sort_order, creator_id, created_at. - New
conversation_folder_memberstable: 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_reactionstable: message_id, user_id, emoji, created_at. messagestable: addparent_message_idcolumn.- New
message_pinstable: conversation_id, message_id, pinned_by, pinned_at. - New
message_bookmarkstable: 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
filesmodule. 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):
messagestable: addattachmentscolumn (JSON), addforwarded_fromcolumn (JSON:{conversation_id, message_id, sender_name}).- Attachment upload endpoint.
- Message send accepts
attachmentsalongsidecontent.
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 —
@usernameas 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):
messagestable: addmentionscolumn (JSON array of user IDs).- Parse
@usernameon send, resolve to IDs, store inmentions. - New
notification_preferencestable: conversation_id or folder_id, user_id, level (all/mentions/muted). - Notification integration on send.
- New endpoint:
GET /mentions— messages mentioning current user.
v0.12.5 — Link Previews + Message Formatting
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):
messagestable: addlink_previewscolumn (JSON).conversationstable: addtopiccolumn.- 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
filesmodule) 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):
conversationstable: addthemecolumn (JSON:{background, background_image, accent_color}), addarchivedcolumn.conversation_folderstable: adddefault_themecolumn (JSON).- New
custom_emojitable: id, name, image_url, team_id, creator_id. - New
scheduled_messagestable: 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-bridgereadssw.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-bridgecontributes "AI Summarize" / "AI Reply" tochat:message-actions.llm-bridgecontributes "Ask AI" tochat:composer-tools.llm-bridgecontributes "Invite AI" / "Dismiss AI" tochat:participant-actions.llm-bridgecontributes AI config fields tochat: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
- User clicks "Invite AI" (contributed to
chat:participant-actions). - Persona picker opens. User selects "Max the engineer."
llm-bridgecallschat-core.add_participant(participant_type: "ai").- Max appears in participant sidebar with AI badge + persona avatar.
llm-bridgesubscribes to conversation events viasw.realtime.- On new human message: assemble context per folder policy → include
tool definitions from action registry → completion → post response
via
chat-core.send(). - If LLM calls a tool (e.g.
notes.search),llm-bridgeexecutes viasw.actions.run(), feeds result back, posts final response. - "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_atparticipants— conversation_id, participant_id, participant_type, display_name, role, joined_atmessages— conversation_id, participant_id, content, content_type, edited_atread_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. |