# FIKO-AI — MİMARİ

Phase 1 (Go backend + Angular frontend + WebSocket + Ollama) için mimari özet.

---

## 1. Katmanlar

Bağımlılık yönü **tek yönlüdür**; üstteki katmanlar alttakilere bağımlıdır, tersi yoktur.

```
                    ┌──────────────────────────────┐
   HTTP / WS   ───► │  internal/api   (Fiber)      │  dış dünya
                    │  REST uçları + /ws bağlantısı│
                    └───────────┬──────────────────┘
                                │
              ┌─────────────────┼──────────────────┬───────────────┐
              ▼                 ▼                  ▼               ▼
      ┌──────────────┐  ┌──────────────┐   ┌──────────────┐  ┌─────────────┐
      │ internal/ws  │  │   agent      │   │  internal/   │  │ internal/tool│
      │ oturum,      │  │  orkestratör │   │  skill       │  │ Registry    │
      │ heartbeat,   │  │              │   │  Registry    │  │ Executor    │
      │ backpressure │  └──────┬───────┘   │ + arayüzler  │  │ Policy      │
      └──────┬───────┘         │           └──────┬───────┘  └──────┬──────┘
             │                 ▼                  │                 ▼
             │          ┌──────────────┐           │          ┌──────────────┐
             │          │    prompt    │           │          │ tool/builtin │
             │          │ sistem istemi│           │          │ fs.*, sys.*  │
             │          └──────────────┘           │          └──────────────┘
             │                                     ▼
             │                            ┌────────────────┐
             │                            │ internal/schema│  JSON Schema doğrulama
             │                            │ (bağımlılıksız)│
             │                            └────────────────┘
             ▼
      ┌─────────────────────────────────────────────────────────┐
      │ internal/llm (Provider/Embedder) → llm/ollama            │
      │ internal/event (Bus) · internal/audit · pkg/id          │
      └─────────────────────────────────────────────────────────┘
```

| Katman | Sorumluluk | Bilmediği şeyler |
|---|---|---|
| `api` | HTTP taşıma, CORS, doğrulama, hata eşleme, WS bağlantısı | LLM, depolama, izin kararı |
| `api/ws` | Bağlantı ömrü, okuma/yazma döngüsü, heartbeat, kuyruk, sahiplik | İş kuralları |
| `agent` | Mesajı kaydet → istem oluştur → LLM'e gönder → **tool döngüsü** → akıt → kaydet | Taşıma, depolama detayları |
| `conversation` | Konuşma/mesaj kuralları, sahiplik, otomatik başlık | HTTP, LLM |
| `skill` | Skill alan modeli, doğrulama, arama/embedding, **tool arayüzleri** | Tool'ın nasıl çalıştığı |
| `tool` | Tool tanımı, CRUD, **yürütme zinciri**, izin/risk kararı | HTTP, LLM |
| `tool/builtin` | Hazır tool uygulamaları (dosya/süreç) ve sandbox kuralları | Kayıt/izin kararı |
| `schema` | JSON Schema alt kümesi doğrulaması + varsayılan uygulama | Her şey (bağımlılığı yok) |
| `llm` | Sağlayıcı arayüzü (`GenerateStream`, `Embed`, `ListModels`) | Konuşma mantığı |
| `prompt` | Sistem istemi parçaları (modüler, test edilebilir) | Her şey |
| `event` | Process içi olay dağıtımı (yayıncıyı bloklamaz) | — |
| `audit` | Append-only denetim kaydı (yazma hatası akışı durdurmaz) | — |

### Paketler arası bağlar

`skill`, `agent` ve `tool` paketleri birbirini **bilmez**; yalnızca arayüz tanır
(`skill.ToolExecutor`/`ToolChecker`, `agent.ToolRunner`/`ToolCatalog`). Gerçek uygulamalar
`cmd/server/tools.go` bağlantısında (DI) takılır. Böylece her biri bağımsız geliştirilip test edilir.

`llm` tarafında `Provider` **değişmedi**; tool çağırma isteğe bağlı `llm.ToolProvider`
alt arayüzüdür (embedding ile aynı desen). Ollama uygular; desteklemeyen sağlayıcıda
ajan bugünkü gibi tek tur çalışır.

### Agent ⇄ Skill bağı (Faz 5c — SKILL → TOOL → LLM)

```
kullanıcı mesajı
   └─ SkillRetriever.TopSkills()   ← semantik/hibrit arama (en çok 4, skor ≥ 0.35)
         └─ toolsOfSkills()        ← bu skill'lerin tool adları (tekrar elenir)
               └─ filterTools()     ← izinli tool listesinden yalnızca bunlar
                     └─ prompt.SkillSelector + tool çağırma döngüsü
```

- **Retrieval bir ÖNERİDİR, üretimi engellemez.** Boş sonuç, hata veya bağlanmamış retriever
  durumunda filtre uygulanmaz → tüm izinli tool'lar sunulur (eski davranış).
- **Aynı tool'a bağlı ikinci skill anlatılmaz** (prompt şişer, model kafası karışır).
- `plan()` katalog sorgusunu **bir kez** yapar; `generate()` ikinci kez sorgulamaz.
- Daraltma gerçekleştiğinde log yazar: `tool listesi skill'e göre daraltıldı … onceden=2 sonra=1`.
---

## 2. Bir mesajın yolculuğu

```
Kullanıcı yazar
   ├─► ChatStore.send()  ──► optimistic balon eklenir
   │        └─► WsService.send('text.message', {conversationId, payload})
   ▼  (WebSocket /ws)
ws.session.handle()
   ├─ geçersiz JSON / bilinmeyen tür → error (bağlantı açık kalır)
   ├─ payload yok → error
   └─ sem dolu → busy (retryable)
   ├─► Hub.sem (bağlantı başına eşzamanlılık sınırı)
   ▼  (ayrı goroutine — okuma döngüsü bloklanmaz)
agent.HandleText(ctx, in, emit)
   ├─ doğrulama: conversationId, boş mesaj, uzunluk
   ├─ konuşma başına tek aktif üretim (busy değilse reddet)
   ├─ Kullanıcı mesajını kaydet → ilk mesajda başlığı türet
   ├─ konuşma geçmişini oku (son N) → sistem istemi + mesajlar
   ├─ emit(conversation.state, THINKING)
   ├─ emit(assistant.thinking, messageId)
   ▼
llm.ToolProvider.GenerateStreamWithTools(ctx, req, toolSpecs, onDelta)
   (Ollama NDJSON akışı; ctx iptali HTTP bağlantısını kapatır)
   ├─ ilk delta'da → emit(conversation.state, SPEAKING)
   ├─ her delta   → emit(assistant.text.delta, messageId, delta)
   ├─ tool çağrısı YOK ──────────────────────► akış bitti (aşağıdaki sona)
   ▼  tool çağrısı VAR (en fazla MaxIterations tur)
├─ emit(tool.start, callId, name, input)
├─ tool.Executor.Execute()   ← yetki, şema, timeout, retry, audit HEPSİ BURADA
│     ├─ emit(tool.progress, callId, percent)
│     └─ emit(tool.result, callId, status, output|error)
├─ geçmişe ekle: "→ fiko.echo({…})" + "[fiko.echo tool sonucu] {…}"
└─ tekrar üretim (bir sonraki tur)
   ▼  akış bitti (ya da döngü sınırı → durduruldu bildirimi)
   ├─ KISMİ İÇERİĞİ KAYDET (iptal/hata durumunda da kaydedilir)
   ├─ emit(assistant.text.final, {content, status, model, tokens, latency})
   ├─ kesildiyse  → emit(assistant.interrupted, {partial, reason})
   ├─ hata varsa  → emit(error, {code, message, retryable})
   └─ emit(conversation.state, IDLE)
```

**Önemli:** `text.final` HER ZAMAN gönderilir. İstemci mesaj balonunu tek terminal olayla kapatır; `interrupted` ve `error` buna ek olarak gelir.

### Tool döngüsü kuralları (Faz 5)

1. **Sonsuz döngü yok.** `CHAT_MAX_AGENT_ITERATIONS` (varsayılan 4, tavan 8) aşılırsa döngü durur, neden kullanıcıya söylenir.
2. **AYNI araç + AYNI girdi ikinci kez çalıştırılmaz.** Metin tabanlı tool geçmişi (native `tool` rolü olmayan taşınabilir biçim) modelin aynı çağrıyı tekrarlamasına yol açıyordu; canlı testte 4 turda aynı `fiko.echo` çalıştı (28 sn). Önlemler üç katmanlı: (a) sistem istemine `prompt.ToolUsage()` kuralları, (b) çağrının geçmişe yazılması, (c) sunucuda `runState.duplicate` ile tekrarın **yeniden çalıştırılmaması**. Tekrar yan etkili olurdu (aynı dosyayı iki kez yazmak/silmek).
3. **Modele yalnızca yetkili tool sunulur** (`agentCatalog`: etkin + çalıştırılabilir + kullanıcı politikasına uygun). Reddedilen çağrı 403 döner ve döngüyü bozar; hiç sunmamak daha doğrusudur.
4. **Tool hatası döngüyü kırmaz**: hata metni modele geri beslenir, model alternatif dener.
5. **Çalıştırma kaydı kalıcıdır** (`conversation.Message.ToolRuns`): giriş/çıktı 2 kB ile sınırlanır, cevap başına en çok 16 kayıt. Arayüzde rozet olarak görünür, sayfa yenilendiğinde de gelir.
6. Olay çıktısı **tur başına** saklanır (`agent.begin(…, emit)`): eşzamanlı iki konuşmada tool olayları doğru oturuma gider.

---

## 3. Kesintiler ve bağlantı koptuğunda

| Durum | Davranış |
|---|---|
| Kullanıcı iptal (`conversation.cancel`) | Aktif üretim iptal edilir, kısmi içerik `interrupted` olarak kaydedilir |
| **Sadece sahibi iptal edebilir** | Başka bağlantının başlattığı konuşma sessizce yoksayılır (olay gönderilmez) |
| Bağlantı koptu | O bağlantının üretimleri iptal edilir, sebep `disconnect` |
| Sunucu kapanıyor | `agent.Close` → `hub.Close` → `http.Shutdown` (aşağıya bak) |
| Tarayıcı bağlantısı koptu | `WsService` üstel backoff ile yeniden bağlanır; kuyruktaki mesajlar gönderilir |
| Yeniden bağlanmada delta kaçarsa | `text.final` içeriği authoritative → balon onunla tamamlanır |

### Kapanış sırası (testle sabitlendi)
---

## 4. Depolama

`conversation.Repository` arayüzü tek gerçek:

```go
CreateConversation / GetConversation / ListConversations
UpdateTitle / DeleteConversation / AppendMessage / Messages
```

- **Phase 1:** `MemoryRepo` (bellek içi, süreç kapanınca veri kaybolur)
- **Aktif:** `storage/mongostore.Repo` (MongoDB) — **aynı arayüzü** uygular; üst kod değişmemiştir. `conversation.RepositoryContract` her iki uygulamayı da aynı testten geçirir; sözleşme değişirse depo değişir, **değil sözleşme**.

Depo seçimi `cmd/server.main` içindeki `openRepository` ile yapılır: Mongo bağlanabilirse kalıcı, bağlanamazsa **uyarıyla** bellek içi düşüş. Sır log'a yazılmaz (`redactURI`: `mongodb://***@host/...`).

Mesajlar ayrı koleksiyonda tutulur; `Conversation` yalnızca API yanıtında mesajları doldurur. Konuşma şemasında Skill/Tool/Context alanları baştan yer alır ki sonraki fazlarda migrasyon gerekmesin.

---

## 5. Protokol

`internal/protocol/protocol.go` ve `frontend/src/app/models/protocol.ts` **birebir eşleşmelidir**. Protokolü değiştirirsen iki tarafı birlikte güncelle.

- Zarf (istemci → sunucu): `{ type, id?, conversationId?, payload? }`
- Zarf (sunucu → istemci): `{ type, conversationId?, replyTo?, at, payload? }`

> **Dikkat:** Backend olayları JSON olarak yollar, bu yüzden istemcide payload çalışma anında düz nesnedir. Backend testlerinde de JSON'a geçen payload için tipli erişim (`o.Payload.(T)`) **panic** verir — `payloadAs[T]` kullanılır.

REST sözleşmesi ve örnekler: [`API.md`](API.md).

---

## 6. Alınan kararlar

| Karar | Gerekçe |
|---|---|
| **Mesaj gönderimi WS üzerinden, REST'ten değil** | Akış doğal; REST'e gönderme yolu delta üretmez |
| **Sunucu ping/pong atar, istemci de kendi ping'ini yollar** | Tarayıcı JS API'si pong'u otomatik cevaplar, bize göstermez; ölü bağlantıyı ancak uygulama düzeyi ping ile yakalarız |
---

## 7. Test stratejisi

| Katman | Yaklaşım | Neden |
|---|---|---|
| `agent` | Sahte `llm.Provider` (akış fonksiyonu) | Kontrollü senaryo: iptal, timeout, boş yanıt |
| `ws` | Kanal tabanlı sahte `Conn` | Gerçek soket yok; heartbeat/backpressure ölçülebiliyor |
| `api` | `app.Test()` + **gerçek `net.Listen`** (WS el sıkışma) | `app.Test` fasthttp'ye bağlanmaz; WS el sıkışması ancak gerçek bağlantıda doğrulanır |
| `chat.store` | Sahte `WebSocket` (`vi.stubGlobal`) + `HttpTestingController` | Delta akışı ve REST zinciri birlikte |
| `mongostore` | **Gerçek MongoDB** (`RepositoryContract` + ek testler) | Depo sözleşmesi ancak gerçek veritabanında anlamlıdır; URI yoksa test **atlanır** |
| Uçtan uca | `tools/ws-e2e.mjs` + `tools/bench-rt.mjs` | Katmanların birleştiği tek gerçek sınır; `bench-rt` gecikme ölçer |

Mongo testleri kendi veritabanını (`fiko_test_<unixnano>`) kullanır ve `t.Cleanup` ile düşürür; testler birbirinin verisini görmez, sıra bağımsızdır.

**Kural:** Eşzamanlı (zamanlamaya bağlı) testlerden sonra `-count=5` ile kararlılık doğrulanır. "Bir olayın tetiklenmesine" dayanan testlerde akış **bitmemelidir** (`neverEndingStream`), yoksa test yük altında zaman aşımına uğrar.

---

## 8. Sonraki fazlar için yerleşik noktalar

| Faz | Nerede büyüyecek |
|---|---|
| 2 — Context manager | ✅ `agent.buildMessages()` + `internal/ctxmgr` (geçmiş kesme) |
| 3 — Skill Registry / RAG | ✅ `prompt.Builder.With(...)`, `llm.Provider.Embed()`, `skill/search.go` |
| 4 — Tool Registry | ✅ `internal/tool` (Registry/Executor/Policy) + `/api/tools`, `/api/permissions` |
| 4b — Tool arayüzü | ✅ `features/tools/` (panel + izin kutusu), `core/tool.store.ts`; `allowed=false` tool'da Çalıştır kilitli |
| 5 — Agent Engine | ✅ `agent.generate()` tool döngüsü; execution tracking `Message.ToolRuns` |
| 6 — STT / VAD | ✅ `internal/stt` + `voice.*` olayları (motor bağımsız arayüz) |
| 7 — TTS / barge-in | ✅ `internal/tts` (motor bağımsız `Synthesizer` + markdown temizleme + cümle bölme), `tts.*` olayları, oturum bazlı sesli mod, barge-in |

Protokol tipleri geleceğin fazları için önceden yer ayırdı; bu olaylar şimdilik `default:` dalında yok sayılıyor.

---

## 9. Tool zinciri (Faz 4)

```
POST /api/tools/:id/execute  (ya da  POST /api/skills/:id/execute)
        │
        ▼
  Executor.Execute ──► Registry.Resolve        404  (tool yok / uygulaması yok → 501)
        │             durum kontrolü            409  (kapalı)
        ▼
  schema.ApplyDefaults + Validate               400  (alan bazlı hata)
        ▼
  Policy.Authorize                              403  (eksik izin / risk kapısı)
        │            (varsayılan: yalnızca execute)
        ▼
  context.WithTimeout(tool.TimeoutMs)           504  (zaman aşımı) / 499 (iptal)
        ▼
  handler(ctx, tool, input)                     502  (tool hatası; panik de buraya sarılır)
        │        └─ hata Retryable() ise ve retry.maxAttempts>0 ise TEKRAR
        ▼
  audit.Record(tool.execute) + event.ToolExecuted
```

Her adım bağımsız test edilmiştir; zincirin sırası `internal/tool` testlerinde "hiçbir handler çalışmamalı" varsayımıyla sabitlenmiştir.
| **Delta kaybetmektense bağlantıyı kapat** (dolu kuyruk) | Yarım yanıt, hiç yanıttan iyidir; istemci yeniden bağlanıp geçmişi okur |
| **Kısmi içerik de kaydedilir** | Üretim iptal edilse bile bilgi kaybolmaz; `interrupted` durumu ile saklanır |
| **Sahiplik sunucuda denetlenir** (404 sızdırmaz) | Başkasının konuşmasının varlığı bile sızdırılmaz |
| **WS olayları Go tipleri üretir, JSON ile taşınır** | İç katmanlar tip güvenliği kazansın; taşıma katmanı JSON'a indirir |
| **Frontend'de göreli adres + proxy** | Geliştirmede CORS tamamen ortadan kalkar |
| **Olay listesi tek yerde (`applyEvent`)** | Yeni olay eklemek tek noktada; test kapsamı belli |
| **Executor zinciri sabit sırada** (çöz → doğrula → yetki → çalıştır) | Doğrulama yetkiden önce: kullanıcı izni olmadan bile hata mesajı yalnızca kendi girdisini anlatır. Reddedilen tool hiçbir yan etki bırakmaz |
| **Varsayılan izin yalnızca `execute`** | Spec: "AI kendisine sınırsız yetki vermemeli". `read/write/delete/system` ve high/critical risk kullanıcıdan **açıkça** istenir |
| **Her tool çalıştırıldığında `execute` gerekir** (tool bildirmese bile) | Aksi hâlde "izin yok" tool'ları risksiz görünür |
| **Builtin tool'lar silinemez, yalnızca kapatılır** | Kodla gelen kaydın silinmesi bir sonraki açılışta sessizce geri gelirdi |
| **Açılışta builtin tanımı ezilmez** | Kullanıcının düzenlemesi kod güncellemesiyle kaybolmaz; yalnızca eksikse oluşturulur |
| **Kapatılan tool otomatik açılmaz** | Kapatmak bir kullanıcı kararıdır; açılışta geri açılmaz |
| **Retry yalnızca `Retryable()` diyen hatalarda** | Kalıcı hata 4 kez denendiğinde yalnızca zarar verir; süreç çalıştırma gibi yan etkili tool'lar `maxAttempts=0` |
| **Her tool çalıştırması denetim kaydına girer** (reddedilenler de) | "Kimin neyi denediği" sorusu cevaplanabilir olmalı |
| **Dosya tool'ları kök altına sandbox'lanır** | `..` kaçışı ve mutlak yol reddedilir; tool'lar sistemin geri kalanına erişemez |
| **`sys.exec` varsayılan kapalı + izin listesi + kabuksuz** | İki bağımsız kapı (env + izin) ve shell injection'a kapalı argüman geçişi |

```
1. agent.Close(ctx)   → süren üretimleri iptal et (kısmi cevaplar kaydedilsin)
2. hub.Close(ctx)      → o "interrupted" olaylarını gönder, bağlantıları boşalt
3. api.Shutdown(ctx)   → yeni istek alma, açık HTTP bağlantılarını bitir
```

Sıra tersine çevrilirse istemci "kaybolmuş" bir üretim görür ve kısmi cevap kaybolur. Bir bileşen hata verse de zincir kırılmaz (hata loglanır, kalan kapanır).

---

## 10. Kimlik modeli (üyelik)

Üç ayrı kavram vardır ve **birbirine karıştırılmaz**:

| Kavram | Soru | Nerede |
|---|---|---|
| **Kimlik** (authentication) | Bu istek kimden geliyor? | `internal/user` (parola, oturum) + `api/middleware.go` (`resolveUserID`) |
| **Rol** | Sistem genelinde ne yapabilir? (`admin` / `user`) | `user.Role`, `api/session.go` (`isAdmin`, `adminOnly`) |
| **Araç yetkisi** | Hangi tool'u çalıştırabilir? (`read/write/execute/delete/system` + risk kapıları) | `tool.GrantStore.PolicyFor(userID)` — **üye bazlı, kimlikten bağımsız** |

Rol, araç yetkisini vermez: yönetici de kendi politikasıyla sınırlıdır. Rol yalnızca **kimin politika değiştirebileceğini** belirler.

### Kimlik çözümleme (`resolveUserID`)

```
istek
  ├─ token var mı? (Bearer → fiko_session çerezi → yalnızca /ws'de ?token=)
  │     geçerli  → kimlik = oturumdaki üye, rol = üyenin rolü, viaSession = true   ← GERÇEK kimlik
  │     geçersiz → açık uç değilse 401 (sessizce devam ETMEZ)
  ├─ üyelik açık + açık uç (health/models/system/auth/OPTIONS) → anonim geçer
  ├─ FIKO_API_KEY eşleşti → kimlik = DEFAULT_USER_ID, rol = admin
  ├─ üyelik KAPALI → X-Fiko-User (veya varsayılan kullanıcı)
  └─ aksi → 401 «giriş gerekli»
```

- **`X-Fiko-User` üyelik açıkken dikkate alınmaz.** İstemci «ben şuyum» diyerek kimlik seçemez; kullanıcı adını sunucu belirler.
- `authorize`: `viaSession=true` ise paylaşılan anahtar ayrıca istenmez (üye zaten parolayla doğrulandı).
- Sahiplik her yerde `currentUser(c)` ile denetlenir; başkasının verisi `404` döner (varlığı sızmaz).

### Güvenlik kararları

| Karar | Gerekçe |
|---|---|
| bcrypt parola; yanıt/log'da özet yok (`UserView`, `json:"-"`) | Sızan bir yanıt hash'i ifşa etmesin |
| Oturum token'ı sunucuda **özet** olarak saklanır | Depo ele geçse token kullanılamaz |
| Giriş hatasında **tek mesaj** + «kullanıcı yok» yolunda da bcrypt çalışır | Hesap varlığı ne mesajdan ne zamandan sızar |
| Kullanıcı adı normalleştirilir (küçük harf, kırpma) | `Ahmet` ve `ahmet` iki hesap olmasın |
| `?token=` yalnızca `/ws` yolunda | URL'deki token erişim günlüğüne sızar; WS'te başka çare yok (tarayıcı başlık koyamaz) |
| Yönetim uçları üyeye **404** (403 değil) | Uçun varlığı bile sızmasın |
| `PUT /api/permissions` üyelik açıkken yalnızca yönetici | Aksi hâlde her üye kendine `delete/system` verirdi |
| Hiç hesap yokken açılan **ilk hesap** yönetici | Bootstrap parolası `.env`'e yazılmadan yönetici olmazdı |
| Üyelik kapalıyken herkes `admin` sayılır | Tek kullanıcılı geliştirme akışı bozulmasın |

### Üye sayfası (`/uyeler/:id`)

Her üyenin kendi sayfası vardır; **araç yetkisi ve yapay zekânın o üyeyle davranışı aynı sayfada, tek «Kaydet» ile** yönetilir.
Davranış, **yalnızca o üyeye atanmış** (`assignedUserIds == [üye]`) kişisel profil olarak saklanır (`frontend/src/app/core/member.logic.ts`);
sunucu modeli (profil + atama) değişmedi. Birden çok kişiye atanmış ortak profiller üye sayfasında bilgi olarak görünür ve kişisel davranışa **eklenir**.
Davranış alanları tamamen boşaltılıp kaydedilirse kişisel profil silinir.

### Bilinen sınırlar

- **Oturumlar bellekte** (`user.Service.sessions`): sunucu yeniden başlayınca herkes yeniden giriş yapar. Kalıcı oturum için depo gerekir.
- Oturum ömrü `FIKO_SESSION_TTL` ile ayarlanır (boş/geçersiz → 30 gün). `FIKO_BOOTSTRAP_PASSWORD` doluysa açılışta `FIKO_BOOTSTRAP_USER` adlı yönetici oluşturulur (varsa dokunulmaz).
- Oturum tek bir sunucu sürecine bağlıdır: bellekte tutulduğu için birden fazla örnek çalıştırılırsa örnekler birbirinin oturumunu tanımaz.
