Files
synapbus/specs/009-attachments-threads/data-model.md
T
Algis DumbrisandClaude Opus 4.6 667b7a4c2e feat: file attachments and thread visibility (009-attachments-threads)
Web UI: paperclip button for file upload (images, PDFs, text), inline
attachment cards with file icon/name/size, image thumbnails with
fullscreen overlay, attachment display in thread panel.

Threads: always-visible reply count badges on messages, clickable to
open thread panel. reply_count and attachments enriched in all API
responses via batch queries.

MCP: attachments parameter on send_message tool, updated tool
descriptions for threading and attachment workflow guidance.

Backend: file type validation (allowlist), AttachmentLinker interface
to avoid circular deps, GetReplyCounts batch query, EnrichMessages
method on MessagingService.

Admin CLI: synapbus attachments backup/restore with tar.gz archives,
dedup-safe restore.

24 new test cases across 4 packages. All 24 test packages pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:36:21 +02:00

2.7 KiB

Data Model: Attachments & Threads Enhancement

Branch: 009-attachments-threads | Date: 2026-03-17

Entity Changes

Message (Modified)

Existing table messages — already has reply_to column via migration 007.

New computed field (not stored, calculated in queries):

  • reply_count (integer): COUNT of messages where reply_to = this message's ID

New field in API response (joined from attachments table):

  • attachments (array of Attachment): All attachments linked to this message via message_id FK

Attachment (Existing, No Schema Changes)

Table attachments — no changes needed. Existing schema:

Column Type Description
id INTEGER PK Auto-increment
hash TEXT NOT NULL SHA-256 content hash
original_filename TEXT NOT NULL Original upload filename
size INTEGER NOT NULL File size in bytes
mime_type TEXT NOT NULL Detected MIME type
message_id INTEGER FK Nullable, references messages(id)
uploaded_by TEXT NOT NULL Agent/user who uploaded
created_at TIMESTAMP Upload timestamp

Relationships

Message 1 ──── 0..* Attachment  (via attachment.message_id)
Message 1 ──── 0..* Message     (via message.reply_to → parent message.id)

Query Changes

Message List with Reply Count

SELECT m.*,
       COALESCE(rc.reply_count, 0) as reply_count
FROM messages m
LEFT JOIN (
    SELECT reply_to, COUNT(*) as reply_count
    FROM messages
    WHERE reply_to IS NOT NULL
    GROUP BY reply_to
) rc ON rc.reply_to = m.id
WHERE ...

Message with Attachments

SELECT a.id, a.hash, a.original_filename, a.size, a.mime_type, a.created_at
FROM attachments a
WHERE a.message_id = ?
ORDER BY a.created_at ASC

Thread Replies

Already exists in store.go:

SELECT ... FROM messages WHERE reply_to = ? ORDER BY created_at ASC

API Response Changes

Message Response (enriched)

{
  "id": 123,
  "body": "Hello",
  "from_agent": "research-bot",
  "reply_to": null,
  "reply_count": 3,
  "attachments": [
    {
      "hash": "abc123...",
      "original_filename": "report.pdf",
      "size": 1048576,
      "mime_type": "application/pdf",
      "is_image": false
    }
  ]
}

Send Message Request (enriched)

{
  "to": "agent-name",
  "body": "See attached report",
  "reply_to": 456,
  "attachments": ["abc123...", "def456..."]
}

File Storage (No Changes)

Content-addressable storage structure (existing):

{dataDir}/attachments/
├── ab/
│   └── cd/
│       └── abcd1234...  (full SHA-256 hash as filename)
├── ef/
│   └── 01/
│       └── ef012345...

Backup archive mirrors this structure in tar.gz format.