Scaffold project skeleton with DESIGN.md and supporting docs
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Go
|
||||||
|
/go/bin/
|
||||||
|
/go/pkg/
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
/vendor/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Mattermost plugin
|
||||||
|
*.tar.gz
|
||||||
|
plugin.tar.gz
|
||||||
|
/server/plugin
|
||||||
|
/server/dist
|
||||||
|
|
||||||
|
# IDE / editors
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# OS
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
/tmp/
|
||||||
|
/build/
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Changes
|
||||||
|
|
||||||
|
## v0.1.0 — 2026-06-16
|
||||||
|
|
||||||
|
- Project scaffolded: DESIGN.md, PROJECT.md, CHANGES.md, DECISIONS.md,
|
||||||
|
README.md, .gitignore
|
||||||
|
- No functional code yet — this is the pre-implementation skeleton
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Design Decisions
|
||||||
|
|
||||||
|
This file records architectural choices and the alternatives considered.
|
||||||
|
New entries are added when a design decision is resolved (not when options
|
||||||
|
are brainstormed). Unresolved questions live in DESIGN.md §10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Mattermost Plugin vs Bot Account
|
||||||
|
|
||||||
|
**Context:** AI assistants in Mattermost can be implemented as plugins
|
||||||
|
(running inside the Mattermost server process) or as bot accounts
|
||||||
|
(independent services using the REST API and WebSocket).
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
1. **Plugin** — hooks into slash commands, message hooks, KV store, System
|
||||||
|
Console config UI, and the plugin lifecycle. No separate process to
|
||||||
|
manage. Deployment is a single `.tar.gz` upload.
|
||||||
|
2. **Bot account** — separate service, more language freedom, but requires
|
||||||
|
managing OAuth tokens, WebSocket reconnection, a separate process with
|
||||||
|
its own lifecycle, and the bot account itself in Mattermost.
|
||||||
|
|
||||||
|
**Outcome:** Plugin chosen for v0.1.0 because:
|
||||||
|
- Single deployment artifact
|
||||||
|
- Built-in config UI via System Console
|
||||||
|
- No bot account to create and maintain
|
||||||
|
- KV store for persistence without a database
|
||||||
|
- Simpler for the target audience (self-hosted teams)
|
||||||
|
|
||||||
|
**Trade-off:** Plugin runs inside the Mattermost process — bugs can affect
|
||||||
|
server stability. Mitigated by standard isolation practices (panic recovery,
|
||||||
|
goroutine lifecycle management).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Ollama
|
||||||
|
|
||||||
|
**Context:** Backend LLM provider for the plugin.
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
1. **Ollama** — Single binary, simple REST API, streaming, model management,
|
||||||
|
self-hosted (data never leaves the infrastructure). Community Edition
|
||||||
|
friendly.
|
||||||
|
2. **OpenAI / Anthropic API** — SaaS, API key required, data leaves the
|
||||||
|
network, ongoing API costs. Not aligned with self-hosted Mattermost
|
||||||
|
ethos.
|
||||||
|
3. **Local inference (llama.cpp, etc.)** — More complex to set up and
|
||||||
|
manage. Ollama wraps this with a clean API.
|
||||||
|
|
||||||
|
**Outcome:** Ollama. It matches the self-hosted, no-data-leaves paradigm of
|
||||||
|
Mattermost Community Edition and provides the simplest API surface for a
|
||||||
|
plugin to consume.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Per-Channel Per-User Context (Proposed)
|
||||||
|
|
||||||
|
**Context:** When a user sends `/ai <prompt>`, should the plugin remember
|
||||||
|
previous exchanges?
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
1. **Per-user global** — Context follows the user across channels. Simple
|
||||||
|
but confusing: context from a #general question leaks into #dev.
|
||||||
|
2. **Per-channel per-user** — Each user gets separate context in each
|
||||||
|
channel. Natural mapping: different channels are different topics.
|
||||||
|
3. **No context** — Stateless. Simplest but every prompt is isolated;
|
||||||
|
users can't have a conversation.
|
||||||
|
|
||||||
|
**Outcome (proposed):** Per-channel per-user. Marked as D2 in DESIGN.md —
|
||||||
|
not final until v0.1.0 implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This file follows the template from the `project-docs` skill.*
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
# Design: Mattermore — Mattermost AI Chat Agent for Ollama
|
||||||
|
|
||||||
|
> **Status:** Draft / Pre-implementation
|
||||||
|
> **Last updated:** 2026-06-16
|
||||||
|
> **Assumed direction:** AI chat agent plugin connecting to a remote Ollama instance.
|
||||||
|
> **Open questions are marked** `TODO(design):` throughout. This is a living template — change
|
||||||
|
> assumptions as the product direction firms up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Purpose & Scope
|
||||||
|
|
||||||
|
### 1.1 What It Is
|
||||||
|
|
||||||
|
Mattermore is a Mattermost (Community Edition) plugin that lets users chat
|
||||||
|
with remote LLMs through an Ollama backend. Users invoke the plugin via a
|
||||||
|
slash command (e.g. `/ai`) and receive responses — either ephemeral or as
|
||||||
|
thread replies — streamed directly into the channel.
|
||||||
|
|
||||||
|
### 1.2 What It Is Not (Explicit Out-of-Scope)
|
||||||
|
|
||||||
|
| Out of scope | Rationale |
|
||||||
|
|---|---|
|
||||||
|
| Hosting its own LLM inference | Ollama is the sole backend; the plugin is a thin proxy |
|
||||||
|
| Multi-backend abstraction (OpenAI, Anthropic, etc.) | If needed later, abstract behind an `LLMProvider` interface — not now |
|
||||||
|
| Training / fine-tuning | Ollama handles that |
|
||||||
|
| Native webapp UI beyond slash commands | `TODO(design):` decide if a channel header button or RHS panel adds value |
|
||||||
|
| Bot accounts | Pure plugin hooks — no separate bot user to manage |
|
||||||
|
|
||||||
|
### 1.3 Target Audience
|
||||||
|
|
||||||
|
- Teams self-hosting Mattermost who want AI assistant access without data
|
||||||
|
leaving their infrastructure
|
||||||
|
- Users comfortable with slash commands
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architecture Overview
|
||||||
|
|
||||||
|
### 2.1 Component Diagram (text)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mattermost Server (Community Edition) │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Mattermore Plugin │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────────────────┐ │ │
|
||||||
|
│ │ │ SlashCommand │──▶│ Plugin (Go) │ │ │
|
||||||
|
│ │ │ /ai │ │ │ │ │
|
||||||
|
│ │ └─────────────┘ │ ┌───────────────────┐ │ │ │
|
||||||
|
│ │ │ │ OllamaClient │──┼───┼───┼──▶ Ollama API
|
||||||
|
│ │ ┌─────────────┐ │ └───────────────────┘ │ │ │ (remote)
|
||||||
|
│ │ │ Config (KV) │ │ ┌───────────────────┐ │ │ │
|
||||||
|
│ │ └─────────────┘ │ │ ConversationStore │ │ │ │
|
||||||
|
│ │ │ └───────────────────┘ │ │ │
|
||||||
|
│ │ ┌─────────────┐ │ ┌───────────────────┐ │ │ │
|
||||||
|
│ │ │ Webapp (?) │ │ │ RateLimiter │ │ │ │
|
||||||
|
│ │ └─────────────┘ │ └───────────────────┘ │ │ │
|
||||||
|
│ └──────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Request Flow (happy path)
|
||||||
|
|
||||||
|
```
|
||||||
|
User types "/ai write a release note for v2.1"
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Mattermost parses slash command → routes to Mattermore plugin
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Plugin.OnConfigurationChange() → reads config (Ollama URL, model, system prompt)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Plugin.ExecuteCommand() → parses args, strips command prefix
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RateLimiter.Allow(userId) → 200 or 429
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ConversationStore.GetSession(userId, channelId) → previous messages for context
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
OllamaClient.ChatCompletion(messages, stream=true, model=defaultModel)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ollama returns ndjson stream → plugin writes ephemeral post with live updates
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Post created → user sees response in channel (ephemeral or thread reply)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
ConversationStore.Append(userId, channelId, userMsg, assistantMsg)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Error Flow
|
||||||
|
|
||||||
|
| Failure point | Behaviour |
|
||||||
|
|---|---|
|
||||||
|
| Ollama unreachable | Ephemeral post: "Ollama at <url> unreachable. Check configuration." |
|
||||||
|
| Rate limit hit | Ephemeral post: "Rate limit exceeded. Try again in N seconds." |
|
||||||
|
| Invalid model name | Ephemeral post: "Model 'xyz' not found on Ollama server." |
|
||||||
|
| Timeout (>30 s) | Abort stream, post partial response + "Response truncated (timeout)." |
|
||||||
|
| Input too long | Reject with context window limit message; suggest `/ai new` to reset. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Plugin Manifest & Identity
|
||||||
|
|
||||||
|
### 3.1 `plugin.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "com.forkless.mattermore",
|
||||||
|
"name": "Mattermore",
|
||||||
|
"description": "AI chat agent powered by Ollama.",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"server": {
|
||||||
|
"executables": {
|
||||||
|
"linux-amd64": "server/dist/plugin-linux-amd64",
|
||||||
|
"linux-arm64": "server/dist/plugin-linux-arm64",
|
||||||
|
"darwin-amd64": "server/dist/plugin-darwin-amd64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webapp": {},
|
||||||
|
"settings_schema": {
|
||||||
|
"header": "Configure the connection to your Ollama instance.",
|
||||||
|
"footer": "",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"key": "OllamaURL",
|
||||||
|
"display_name": "Ollama Server URL",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Base URL of the Ollama API, e.g. http://10.0.0.5:11434",
|
||||||
|
"placeholder": "http://localhost:11434",
|
||||||
|
"default": "http://localhost:11434"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "DefaultModel",
|
||||||
|
"display_name": "Default Model",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Model name to use when none is specified in the command.",
|
||||||
|
"placeholder": "llama3.2",
|
||||||
|
"default": "llama3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SystemPrompt",
|
||||||
|
"display_name": "System Prompt",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "System prompt prepended to every conversation.",
|
||||||
|
"placeholder": "You are a helpful assistant.",
|
||||||
|
"default": "You are a helpful assistant."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MaxTokens",
|
||||||
|
"display_name": "Max Response Tokens",
|
||||||
|
"type": "number",
|
||||||
|
"help_text": "Maximum tokens per response (0 = model default).",
|
||||||
|
"default": 2048
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "RateLimitPerMinute",
|
||||||
|
"display_name": "Rate Limit (requests/minute/user)",
|
||||||
|
"type": "number",
|
||||||
|
"help_text": "Maximum requests per minute per user. 0 = unlimited.",
|
||||||
|
"default": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "AllowedUserIDs",
|
||||||
|
"display_name": "Allowed User IDs (comma-separated)",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Restrict to specific user IDs. Empty = all users.",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`TODO(design):` Should `AllowedUserIDs` be a team/channel whitelist instead?
|
||||||
|
For now, keep user-level granularity — easy to widen later.
|
||||||
|
|
||||||
|
### 3.2 Versioning
|
||||||
|
|
||||||
|
Follow `release-workflow` skill: `v0.1.0`, `v0.2.0`, etc. Bump PATCH for
|
||||||
|
bugs, MINOR for features. MAJOR after 1.0 for breaking config schema changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Server-Side Components
|
||||||
|
|
||||||
|
### 4.1 Plugin Lifecycle Hooks
|
||||||
|
|
||||||
|
| Hook | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `OnConfigurationChange()` | Re-read System Console settings, validate OllamaURL, rebuild clients |
|
||||||
|
| `ExecuteCommand()` | Intercept `/ai` and subcommands (`/ai new`, `/ai model ...`) |
|
||||||
|
| `OnActivate()` | Register slash command in `OnConfigurationChange()` — idempotent |
|
||||||
|
| `OnDeactivate()` | Clean up goroutines, close idle HTTP connections |
|
||||||
|
|
||||||
|
### 4.2 Slash Command: `/ai`
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
|
||||||
|
```
|
||||||
|
/ai <prompt>
|
||||||
|
— Send a prompt using the default model (current session context).
|
||||||
|
/ai model <name> <prompt>
|
||||||
|
— Send a prompt using a specific model.
|
||||||
|
/ai new
|
||||||
|
— Reset conversation context for the current user+channel.
|
||||||
|
/ai model list
|
||||||
|
— List available models from the Ollama server (ephemeral).
|
||||||
|
/ai help
|
||||||
|
— Show usage help.
|
||||||
|
```
|
||||||
|
|
||||||
|
`TODO(design):` Should `/ai` reply ephemerally (only the caller sees it) or
|
||||||
|
as a thread post (visible to the channel)? **Proposed:** ephemeral for
|
||||||
|
privacy, with an opt-in `--public` flag to post to thread.
|
||||||
|
|
||||||
|
### 4.3 OllamaClient
|
||||||
|
|
||||||
|
Interface (in `server/ollama/client.go`):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Client interface {
|
||||||
|
ChatCompletion(ctx context.Context, req *ChatRequest) (<-chan ChatStreamEvent, error)
|
||||||
|
ListModels(ctx context.Context) ([]Model, error)
|
||||||
|
Ping(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatRequest struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []ChatMessage `json:"messages"`
|
||||||
|
Stream bool `json:"stream"`
|
||||||
|
Options map[string]any `json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatStreamEvent struct {
|
||||||
|
Token string // delta content
|
||||||
|
Done bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`TODO(design):` Should we use Ollama's `/api/chat` (chat format) or
|
||||||
|
`/api/generate` (raw prompt)? `/api/chat` is preferred — it maps cleanly to
|
||||||
|
the conversational UX and supports the messages array natively.
|
||||||
|
|
||||||
|
### 4.4 ConversationStore
|
||||||
|
|
||||||
|
Store session context in Mattermost's KV store (plugin's `API.KVSet` /
|
||||||
|
`KVGet`).
|
||||||
|
|
||||||
|
| Key pattern | Value | TTL |
|
||||||
|
|---|---|---|
|
||||||
|
| `ctx_{userID}_{channelID}` | `[]Message` (last N turns) | 1 hour |
|
||||||
|
| `ctx_{userID}_{channelID}_model` | Model name override | 1 hour |
|
||||||
|
|
||||||
|
`TODO(design):`
|
||||||
|
- How many turns to keep? **Proposed:** last 10 messages (5 user + 5 assistant).
|
||||||
|
- TTL of 1 hour — resets on activity. Configurable?
|
||||||
|
- Should context be channel-scoped or user-global? Channel-scoped means a
|
||||||
|
user gets different context in different channels.
|
||||||
|
|
||||||
|
### 4.5 RateLimiter
|
||||||
|
|
||||||
|
Token-bucket per user. Configurable requests/minute from System Console.
|
||||||
|
Resets on config change. Stored in memory, not KV (ephemeral state).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Ollama Integration
|
||||||
|
|
||||||
|
### 5.1 API Mapping
|
||||||
|
|
||||||
|
| Plugin Action | Ollama Endpoint | Method |
|
||||||
|
|---|---|---|
|
||||||
|
| Chat completion (streaming) | `/api/chat` | POST |
|
||||||
|
| List models | `/api/tags` | GET |
|
||||||
|
| Health check | `/api/tags` (or HEAD `/`) | GET |
|
||||||
|
| Pull model | `/api/pull` | POST |
|
||||||
|
|
||||||
|
`TODO(design):` Should the plugin auto-pull a model if it's not present?
|
||||||
|
Risk: user mistypes name → long pull. **Proposed:** return error with list of
|
||||||
|
available models instead.
|
||||||
|
|
||||||
|
### 5.2 Streaming Strategy
|
||||||
|
|
||||||
|
1. Send POST to `/api/chat` with `"stream": true`.
|
||||||
|
2. Read ndjson response body line by line (each line is `{"message":{"role":"assistant","content":"..."},"done":false}`).
|
||||||
|
3. On each `done:false` event, update an ephemeral post via
|
||||||
|
`API.UpdateEphemeralPost()` with accumulated content.
|
||||||
|
4. On `done:true`, create the final ephemeral (or thread) post with full
|
||||||
|
content, usage stats (token count, duration).
|
||||||
|
|
||||||
|
`TODO(design):` Post-update on every token may be too chatty. **Proposed:**
|
||||||
|
batch updates every ~200 ms or every 3 tokens, whichever comes first.
|
||||||
|
|
||||||
|
### 5.3 Context Window Management
|
||||||
|
|
||||||
|
- Token counting: use Ollama's `num_ctx` parameter or a Go tokenizer
|
||||||
|
(tiktoken-go).
|
||||||
|
- If input exceeds configured limit (default: 4096 tokens), trim oldest
|
||||||
|
messages until within limit, or reject if a single message overflows.
|
||||||
|
- Display a warning: "Context window trimmed (oldest messages removed)."
|
||||||
|
|
||||||
|
`TODO(design):` Should the user be able to set `num_ctx` per-request?
|
||||||
|
`/ai --context 8192 ...`
|
||||||
|
|
||||||
|
### 5.4 Model Selection Precedence
|
||||||
|
|
||||||
|
1. Per-request: `/ai model gemma3:12b write a poem`
|
||||||
|
2. Session override: `/ai model gemma3:12b` (stored in KV)
|
||||||
|
3. Default from System Console: `DefaultModel`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Configuration
|
||||||
|
|
||||||
|
### 6.1 System Console Settings
|
||||||
|
|
||||||
|
Defined in `plugin.json` `settings_schema` (see §3.1). The System Console
|
||||||
|
provides the UI; the plugin reads via `OnConfigurationChange()`.
|
||||||
|
|
||||||
|
### 6.2 Dynamic Config Reload
|
||||||
|
|
||||||
|
`OnConfigurationChange()` is called on every save. The plugin must:
|
||||||
|
1. Re-read the config struct from `API.GetConfig()` / `API.GetPluginConfig()`.
|
||||||
|
2. Validate `OllamaURL` (parse as URL, reject non-HTTP(S) schemes).
|
||||||
|
3. If URL changed, create a new `http.Client` (with configurable timeout).
|
||||||
|
4. If `RateLimitPerMinute` changed, rebuild the token buckets.
|
||||||
|
5. Log the change at debug level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Data Flow — Detailed
|
||||||
|
|
||||||
|
### 7.1 Full Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
Step User / System Component
|
||||||
|
──── ────────────────────────────── ──────────────────
|
||||||
|
1 User types "/ai explain TCP" Mattermost webapp
|
||||||
|
2 Mattermost routes to plugin mm-server
|
||||||
|
3 Plugin.ExecuteCommand() parses args Mattermore plugin
|
||||||
|
3a If "/ai help" → show help text
|
||||||
|
3b If "/ai new" → KVStore.Delete(ctx)
|
||||||
|
3c If "/ai model …" → override model KVStore.Set(model)
|
||||||
|
3d If "/ai model list" → OllamaClient.ListModels()
|
||||||
|
4 RateLimiter.Allow(userId) RateLimiter
|
||||||
|
5 ConversationStore.GetSession(...) ConversationStore
|
||||||
|
6 Build []ChatMessage (system + history + new prompt)
|
||||||
|
7 OllamaClient.ChatCompletion(...) OllamaClient
|
||||||
|
8 Ollama streams ndjson tokens Ollama (remote)
|
||||||
|
9 Plugin accumulates tokens, updates Mattermore plugin
|
||||||
|
ephemeral post every ~200ms
|
||||||
|
10 On done:true → create final post Mattermore plugin
|
||||||
|
11 ConversationStore.Append(...) ConversationStore
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Thread Participation
|
||||||
|
|
||||||
|
`TODO(design):` If the user replies in a thread where the bot posted, should
|
||||||
|
the plugin pick it up? This requires a `MessageHasBeenPosted` hook and
|
||||||
|
thread detection — adds complexity. **Scope:** defer to v0.2+.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Security Model
|
||||||
|
|
||||||
|
### 8.1 Input Sanitization
|
||||||
|
|
||||||
|
- Strip control characters / zero-width Unicode from prompts before sending
|
||||||
|
to Ollama.
|
||||||
|
- Limit prompt length to 4096 characters (configurable via System Console).
|
||||||
|
- `TODO(design):` Should we block prompts that look like prompt injections?
|
||||||
|
("Ignore previous instructions...") Hard to do reliably — document risk
|
||||||
|
instead.
|
||||||
|
|
||||||
|
### 8.2 Output Safety
|
||||||
|
|
||||||
|
- Cap response tokens in the Ollama request (`options.num_predict`).
|
||||||
|
- `TODO(design):` Add a "Report this response" feedback mechanism? Defer.
|
||||||
|
|
||||||
|
### 8.3 Network Security
|
||||||
|
|
||||||
|
- `OllamaURL` must start with `http://` or `https://` — validate at config
|
||||||
|
load.
|
||||||
|
- Reject internal IP ranges unless explicitly enabled by a
|
||||||
|
`AllowPrivateNetworks` toggle (default: off for production).
|
||||||
|
- TLS verify enabled by default; optional `SkipTLSVerify` toggle (logged as
|
||||||
|
a warning).
|
||||||
|
|
||||||
|
### 8.4 Authorization
|
||||||
|
|
||||||
|
- If `AllowedUserIDs` is non-empty, reject requests from other users with a
|
||||||
|
"not authorized" ephemeral message.
|
||||||
|
- All plugin API calls are already scoped to authenticated Mattermost users.
|
||||||
|
|
||||||
|
### 8.5 Rate Limiting
|
||||||
|
|
||||||
|
- Per-user token bucket, replenish rate set via System Console.
|
||||||
|
- Default: 10 requests/minute. 0 = unlimited (logged as a warning).
|
||||||
|
- Burst: 1 (enforce smooth spacing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Observability
|
||||||
|
|
||||||
|
### 9.1 Logging
|
||||||
|
|
||||||
|
| Level | When |
|
||||||
|
|---|---|
|
||||||
|
| `ERROR` | Ollama unreachable, config validation failure, KV store error |
|
||||||
|
| `WARN` | Rate limit hit, context window trimmed, TLS skipped |
|
||||||
|
| `INFO` | Plugin activated/deactivated, config changed, first request |
|
||||||
|
| `DEBUG` | Every request/response round-trip (with userID, model, token count, latency) |
|
||||||
|
|
||||||
|
### 9.2 Metrics (future)
|
||||||
|
|
||||||
|
`TODO(design):` Expose Prometheus-style counters via a `/metrics` endpoint
|
||||||
|
if the Mattermost server has plugin metrics support:
|
||||||
|
- `mattermore_requests_total{user,model,status}`
|
||||||
|
- `mattermore_latency_seconds`
|
||||||
|
- `mattermore_tokens_total{direction="input|output"}`
|
||||||
|
- `mattermore_rate_limit_hits_total`
|
||||||
|
|
||||||
|
### 9.3 Error Reporting
|
||||||
|
|
||||||
|
Ephemeral posts with user-facing messages (not raw Go errors). Internal
|
||||||
|
errors logged at `ERROR` with stack trace.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Open Design Decisions
|
||||||
|
|
||||||
|
These are the unresolved questions that need answers before v0.1.0 ships.
|
||||||
|
|
||||||
|
| # | Question | Proposed | Alternatives | Decided? |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| D1 | Reply style | Ephemeral by default, `--public` flag for thread | Always ephemeral, always thread, always channel | ❌ |
|
||||||
|
| D2 | Context scope | Per-channel per-user | Per-user global, per-channel only, no context | ❌ |
|
||||||
|
| D3 | Context turns | Last 5 exchanges (10 messages) | Last N tokens, everything in TTL, sliding window | ❌ |
|
||||||
|
| D4 | Webapp UI | None (slash commands only) | Channel header button, RHS panel, settings page | ❌ |
|
||||||
|
| D5 | Model governance | All models available | Admin-restricted model list, least-privilege Ollama API key | ❌ |
|
||||||
|
| D6 | Thread replies | Defer to v0.2 | Auto-reply in thread when user replies to bot post | ❌ |
|
||||||
|
| D7 | `num_ctx` per request | Via `--context N` flag | System Console default only | ❌ |
|
||||||
|
| D8 | Auto-pull models | No (error + list available) | Yes (convenient but slow) | ❌ |
|
||||||
|
| D9 | Prompt injection guard | Defer (document risk) | Regex blocklist, LLM-as-judge pre-filter | ❌ |
|
||||||
|
| D10 | Metrics endpoint | Defer | `/metrics` via plugin HTTP handler | ❌ |
|
||||||
|
|
||||||
|
Each decision should be recorded in `DECISIONS.md` once resolved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Deployment & Lifecycle
|
||||||
|
|
||||||
|
### 11.1 Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make dist
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces `dist/com.forkless.mattermore-0.1.0.tar.gz` containing the server
|
||||||
|
binaries and `plugin.json`.
|
||||||
|
|
||||||
|
### 11.2 Upload
|
||||||
|
|
||||||
|
Via Mattermost System Console → Plugins → Upload Plugin, or via REST API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST $MM_URL/api/v4/plugins \
|
||||||
|
-H "Authorization: Bearer $MM_TOKEN" \
|
||||||
|
-F "plugin=@dist/com.forkless.mattermore-0.1.0.tar.gz"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.3 Upgrade
|
||||||
|
|
||||||
|
1. Upload new `.tar.gz` — Mattermost replaces the plugin binaries.
|
||||||
|
2. Plugin's `OnConfigurationChange()` fires (config preserved).
|
||||||
|
3. If the KV schema changed, a migration in `OnActivate()` handles it.
|
||||||
|
|
||||||
|
### 11.4 Rollback
|
||||||
|
|
||||||
|
Re-upload the previous version's `.tar.gz`. No data migration needed if the
|
||||||
|
KV key schema hasn't changed. If it has, restore from backup or
|
||||||
|
`DECISIONS.md` records the migration path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Future Considerations (v0.2+)
|
||||||
|
|
||||||
|
- **Multi-modal inputs** — Ollama supports images via `/api/chat` with
|
||||||
|
`images` field. Could allow `/ai` with attached image.
|
||||||
|
- **Multi-model routing** — Different models for different channel categories
|
||||||
|
(e.g. code-review channel uses codellama).
|
||||||
|
- **Tool calling** — Ollama supports tool definitions. Could let the
|
||||||
|
plugin fetch data from external APIs (weather, Jira, etc.).
|
||||||
|
- **Bot account mode** — A separate `mattermost-bot` skill / mode for
|
||||||
|
always-on presence in channels vs. on-demand slash commands.
|
||||||
|
- **Streaming into message attachments** — Render structured data (tables,
|
||||||
|
JSON, code blocks) with better formatting.
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
# Project: Mattermore
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Mattermore is a Mattermost (Community Edition) plugin that connects chat to
|
||||||
|
remote LLMs via Ollama. Users invoke it with a `/ai` slash command and
|
||||||
|
receive streamed AI responses directly in the channel.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
mattermore/
|
||||||
|
├── DESIGN.md — Functional design document
|
||||||
|
├── DECISIONS.md — Design rationale log
|
||||||
|
├── PROJECT.md — This file
|
||||||
|
├── CHANGES.md — Per-release changelog
|
||||||
|
├── README.md — User-facing docs
|
||||||
|
├── plugin.json — Mattermost plugin manifest
|
||||||
|
├── Makefile — Build, lint, dist targets
|
||||||
|
├── .gitignore — Go + plugin ignores
|
||||||
|
├── server/
|
||||||
|
│ ├── main.go — Plugin entry point (OnActivate, ExecuteCommand)
|
||||||
|
│ ├── plugin.go — Core plugin struct and hooks
|
||||||
|
│ ├── configuration.go — Config reading and validation
|
||||||
|
│ ├── command.go — Slash command parsing and dispatch
|
||||||
|
│ ├── ollama/
|
||||||
|
│ │ ├── client.go — Ollama API client (ChatCompletion, ListModels, Ping)
|
||||||
|
│ │ └── client_test.go — Unit tests (mocked HTTP server)
|
||||||
|
│ ├── store/
|
||||||
|
│ │ ├── conversation.go — KV-backed conversation storage
|
||||||
|
│ │ └── conversation_test.go
|
||||||
|
│ └── rate/
|
||||||
|
│ └── limiter.go — Token-bucket rate limiter
|
||||||
|
└── webapp/ (future — optional)
|
||||||
|
└── .placeholder
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `plugin.json` | Manifest: id, name, version, settings schema |
|
||||||
|
| `server/main.go` | `OnActivate()`, `OnDeactivate()`, `ExecuteCommand()` |
|
||||||
|
| `server/ollama/client.go` | HTTP client for Ollama REST API |
|
||||||
|
| `server/store/conversation.go` | Context persistence via Mattermost KV store |
|
||||||
|
| `DESIGN.md` | Full functional design with open decisions |
|
||||||
|
| `DECISIONS.md` | Record of architectural decisions and their rationale |
|
||||||
|
|
||||||
|
## External Dependencies
|
||||||
|
|
||||||
|
- **Go ≥ 1.21** (toolchain)
|
||||||
|
- **Mattermost Server ≥ v8.x** (plugin API)
|
||||||
|
- **Ollama** (remote inference server)
|
||||||
|
- Go modules (see `go.mod`):
|
||||||
|
- `github.com/mattermost/mattermost-server/v6` — Plugin SDK
|
||||||
|
- Standard library only for Ollama HTTP client (no external AI SDKs)
|
||||||
|
|
||||||
|
## Release Cadence
|
||||||
|
|
||||||
|
- Tags: `v0.x.y`
|
||||||
|
- CI builds on tag (see `.gitea/workflows/ci.yml`)
|
||||||
|
- Plugin bundle published as Gitea release artifact
|
||||||
|
- Follows `release-workflow` skill: draft → user test → publish
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- `DESIGN.md` — functional design (living document)
|
||||||
|
- `DECISIONS.md` — why key decisions were made
|
||||||
|
- `CHANGES.md` — user-facing changelog
|
||||||
|
- `README.md` — quick start, config reference, build instructions
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Mattermore
|
||||||
|
|
||||||
|
**AI chat agent for Mattermost — powered by Ollama.**
|
||||||
|
|
||||||
|
Ask questions, draft content, and get AI assistance directly in your
|
||||||
|
Mattermost channels using `/ai` slash commands. All inference runs on your
|
||||||
|
own Ollama server — no data leaves your infrastructure.
|
||||||
|
|
||||||
|
> **Status:** Pre-implementation. Design is in `DESIGN.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- `/ai <prompt>` — Chat with the default model
|
||||||
|
- `/ai model <name> <prompt>` — Use a specific model
|
||||||
|
- `/ai new` — Reset conversation context
|
||||||
|
- `/ai model list` — List available models
|
||||||
|
- `/ai help` — Show usage help
|
||||||
|
- Configurable Ollama endpoint, model, system prompt, rate limits
|
||||||
|
- Streaming responses updated live in ephemeral posts
|
||||||
|
- Per-channel per-user conversation context (configurable retention)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Mattermost Server** v8.x (Community Edition)
|
||||||
|
- **Ollama** running and reachable from the Mattermost server
|
||||||
|
- **Go** ≥ 1.21 (for building)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://gitea.forkless.com/forkless/mattermore.git
|
||||||
|
cd mattermore
|
||||||
|
|
||||||
|
# Build the plugin
|
||||||
|
make dist
|
||||||
|
|
||||||
|
# Upload via Mattermost System Console → Plugins → Upload Plugin
|
||||||
|
# Or via API:
|
||||||
|
curl -X POST $MM_URL/api/v4/plugins \
|
||||||
|
-H "Authorization: Bearer $MM_TOKEN" \
|
||||||
|
-F "plugin=@dist/com.forkless.mattermore-0.1.0.tar.gz"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configure via Mattermost **System Console → Plugins → Mattermore**:
|
||||||
|
|
||||||
|
| Setting | Description | Default |
|
||||||
|
|---|---|---|
|
||||||
|
| Ollama Server URL | Base URL of the Ollama API | `http://localhost:11434` |
|
||||||
|
| Default Model | Model used when none specified | `llama3.2` |
|
||||||
|
| System Prompt | Prepended to every conversation | `You are a helpful assistant.` |
|
||||||
|
| Max Response Tokens | Max tokens per response (0 = model default) | `2048` |
|
||||||
|
| Rate Limit | Requests/minute/user (0 = unlimited) | `10` |
|
||||||
|
| Allowed User IDs | Comma-separated user IDs (empty = all users) | (empty) |
|
||||||
|
|
||||||
|
## Build Targets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make dist # Build all platform binaries and package plugin
|
||||||
|
make check # Lint + test
|
||||||
|
make clean # Remove build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| File | What it covers |
|
||||||
|
|---|---|
|
||||||
|
| `DESIGN.md` | Functional design, architecture, data flow, open decisions |
|
||||||
|
| `PROJECT.md` | Project structure, key files, dependencies |
|
||||||
|
| `DECISIONS.md` | Rationale behind architectural choices |
|
||||||
|
| `CHANGES.md` | Per-release changelog |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT (to be confirmed — see `LICENSE` once added).
|
||||||
|
|||||||
Reference in New Issue
Block a user