tunaFlow adalah klien desktop yang mengorkestrasi agen CLI seperti Claude, Codex, Gemini, dan OpenCode dalam satu aplikasi. Kami belum merilis karena ada masalah besar dan kecil (?). Ini adalah bagian dari proyek pengujian yang menyenangkan bagi saya. Namun, saya ingin menulis artikel yang menggali keraguan yang muncul selama proses pengembangan. Saya berharap itu membantu setidaknya 0.1 untuk orang-orang yang mengembangkan AI dengan maksud yang sama di tempat lain. Jika ada sanggahan terhadap isi artikel ini, semua kata-kata Anda benar. 😁 😁 😁
Apa yang Perlu Diberitahu kepada Agen
Ketika menggunakan hanya satu agen, semuanya relatif sederhana. Cukup meneruskan percakapan sebelumnya secara tepat, dan sedikit demi sedikit akan berfungsi. Namun, ketika beberapa agen mulai berkolaborasi dalam satu proyek, situasinya berbeda. Karena setiap agen hanya dapat melihat percakapan mereka sendiri dalam sesi mereka.
> Apa, berapa banyak, dan kapan harus diberitahu kepada setiap agen?
Untuk obrolan umum, cukup dengan meneruskan pesan sebelumnya. Namun, dalam orkestrasi multi-agen, itu saja tidak cukup. Sebenarnya ada cukup banyak informasi yang perlu diteruskan.
- Informasi proyek — nama, jalur, stack teknologi
- Rencana kerja saat ini — Plan, daftar subtask, fase saat ini
- Pernyataan agen lain — konten diskusi Roundtable (RT), hasil review sebelumnya
- Hasil pencarian basis kode — kode terkait yang ditemukan dengan rawq (mesin pencari kode)
- Memori jangka panjang — ringkasan percakapan terkompresi dari sesi sebelumnya
- Dokumen keterampilan — aturan penggunaan framework/library
- Identity — penugasan peran seperti "Anda adalah Architect. Hanya buat rencana"
Anda harus merakit ini dengan tepat setiap kali permintaan dan memasukkannya ke dalam prompt. Di tunaFlow, kami menyebut hasil perakitan ini ContextPack. (Bukan nama yang saya buat! Serius!)
Struktur ContextPack
Setiap permintaan agen di tunaFlow melewati satu fungsi.
build_normalized_prompt_with_budget(
conn, // Koneksi DB
conversation_id, // Percakapan saat ini
prompt, // Input pengguna
project_path, // Jalur proyek
active_skills, // Daftar keterampilan yang diaktifkan
cross_session_ids, // ID sesi terkait
persona_fragment, // Penugasan peran ("Architect", "Developer", dll)
context_mode, // Lite / Standard / Full
budget_cap, // Total anggaran konteks (jumlah karakter)
)
Baik Claude, Codex, maupun Gemini, semuanya merakit prompt melalui fungsi yang sama. Ini umum untuk 4 mesin.
Bagian-Bagian yang Dirakit
Kira-kira dibagi seperti ini dalam lapisan.
┌─────────────────────────────────────────────────┐
│ Jalur proyek │
│ Aturan platform (PLATFORM_TIER0) │ ← Selalu disertakan
│ Dokumen peran agen (Architect/Developer/...) │
│ Identity + Persona │
│ Metadata peserta percakapan │
├─────────────────────────────────────────────────┤
│ Percakapan terbaru (jendela berbasis anggaran) │ ← Maksimal dalam anggaran
│ + jaminan pesan terakhir per-agen │
│ + pemenggalan hasil alat (pesan lama) │
├─────────────────────────────────────────────────┤
│ Plan / Findings / Artifacts │ ← Mode Standard+
│ Retrieval (chunk percakapan masa lalu, FTS5+pencarian vektor)
│ Compressed memory (ringkasan per topik) │
├─────────────────────────────────────────────────┤
│ Skills / rawq / code-review-graph │ ← Mode Full atau kondisional
│ Konteks lintas-sesi │
│ Thread inheritance (warisan Branch) │
└─────────────────────────────────────────────────┘
Inti dari semuanya adalah bukan "ada banyak hal yang bisa dimasukkan", melainkan "hanya informasi yang diperlukan untuk permintaan ini yang harus dimasukkan dengan densitas yang tepat".
Desain Inti: Perakitan Berubah 3-Mode
Jika Anda memasukkan semua bagian setiap saat, token akan cepat habis. Jadi kami menyesuaikan menjadi 3 tingkat tergantung situasi.
Mode | Cakupan yang disertakan | Ukuran perkiraan | Waktu penggunaan |
|---|
Lite | Identity dasar + percakapan terbaru 4k | ~6k | pertanyaan singkat, model lokal |
Standard | + Plan/Findings/Retrieval | ~15k | pekerjaan umum |
Full | + Skills/rawq/cross-session | ~25k | pekerjaan kompleks, permintaan eksplisit |
// Mode ditentukan secara otomatis.
fn determine_context_mode(data: &ContextData) -> (ContextMode, &str) {
// Jika pengguna menentukan secara eksplisit
if let Some(override_mode) = &data.context_mode_override {
return (parse_mode(override_mode), "user-override");
}
// Jika percakapan 12 giliran atau lebih, gunakan Standard
if data.current_messages.len() >= 12 {
return (ContextMode::Standard, "long-conversation");
}
// Jika ada Plan, gunakan Standard
if data.plan_section.is_some() {
return (ContextMode::Standard, "has-plan");
}
// Default: Lite
(ContextMode::Lite, "default")
}
Memasukkan Full untuk pertanyaan singkat adalah pemborosan, dan sebaliknya, memasukkan hanya Lite untuk pekerjaan panjang akan kekurangan konteks. Pada akhirnya, yang penting adalah bukan memberikan semua informasi sebanyak mungkin, melainkan mencocokkan densitas informasi yang diperlukan saat ini.
Alokasi Anggaran Dinamis
Rasio yang ditempati setiap bagian dalam anggaran total (default 60.000 karakter) tidak diperbaiki. Kami menghitungnya secara dinamis sambil mempertimbangkan ukuran konten dan bobot.
let budget_alloc = allocate_budgets(total_budget, &[
SectionBudget { name: "plan", weight: 1.0, min: 500, max: 4000 },
SectionBudget { name: "plan-doc", weight: 2.0, min: 1000, max: 6000 },
SectionBudget { name: "findings", weight: 1.0, min: 500, max: 3000 },
SectionBudget { name: "skills", weight: 1.0, min: 500, max: 3000 },
SectionBudget { name: "rawq", weight: 0.8, min: 500, max: 3000 },
SectionBudget { name: "retrieval", weight: 1.2, min: 500, max: 5000 },
SectionBudget { name: "compressed", weight: 1.0, min: 500, max: 4000 },
SectionBudget { name: "cross", weight: 0.6, min: 300, max: 3000 },
]);
Sebagai contoh, jika dokumen Plan panjang, anggaran Plan mengambil lebih banyak, dan sebagai imbalannya, anggaran skills atau rawq berkurang. Bukan membagi dengan rasio tetap, melainkan struktur di mana anggaran bergerak menurut volume dan pentingnya konten yang akan dimasukkan sekarang.
Ini lebih penting daripada yang terlihat. Dalam pekerjaan nyata, tidak selalu informasi yang sama yang penting. Beberapa permintaan memiliki rencana sebagai inti, beberapa memiliki hasil pencarian kode, dan beberapa memiliki pencarian percakapan masa lalu lebih penting.
Riwayat Percakapan: Tidak Bisa Memasukkan Semuanya, Tidak Bisa Memotongnya karena Konteks Terputus
Ketika percakapan menjadi panjang, tidak mungkin memasukkan semua pesan sebelumnya. Namun, jika Anda hanya memotongnya secara kasar, alur akan terputus. Jadi tunaFlow menggunakan tiga strategi.
1. Jendela dinamis berbasis anggaran
Isi dari pesan terbaru secara terbalik, dan berhenti ketika anggaran habis.
// Isi anggaran secara terbalik dari pesan terbaru
for (i, msg) in messages.iter().enumerate().rev() {
let msg_cost = role.len() + content.len().min(max_per_msg) + 40;
if msg_cost <= char_budget {
trimmed.push(msg);
char_budget -= msg_cost;
} else if must_include.contains(&i) {
// Pesan ini harus disertakan meskipun melebihi anggaran
trimmed.push(msg);
}
}
Terlihat sederhana, tetapi operasi dasar ini benar. Konteks terbaru adalah yang paling penting.
Jaminan pesan terakhir per-agen
Masalah yang lebih penting dalam multi-agen adalah pesan terakhir setiap agen tidak boleh dipotong.
Sebagai contoh, jika Alice mengungkapkan pendapat penting tiga giliran yang lalu, tetapi pesan itu dipotong karena anggaran, agen berikutnya tidak akan tahu posisi Alice dan akan berdiskusi tanpa informasi itu. Maka, meskipun secara formal multi-agen, sebenarnya sama seperti monolog paralel.
Jadi kami memastikan pesan terakhir setiap agen selalu disertakan.
// Kumpulkan indeks pesan terakhir setiap agen
let mut agent_last_idx: HashMap = HashMap::new();
for (i, msg) in messages.iter().enumerate() {
if msg.role == "assistant" {
agent_last_idx.insert(msg.persona.clone(), i);
}
}
// Indeks ini harus selalu disertakan meskipun melebihi anggaran
let must_include: HashSet = agent_last_idx.values().collect();
Tanpa jaminan ini, multi-agen mudah kehilangan konteks.
Memori terkompresi (kompresi per topik)
Setelah 12 giliran atau lebih, pesan lama tidak dibawa seperti apa adanya, tetapi kami membuat ringkasan per topik dengan LLM.
## Memori percakapan terkompresi
### Topik: Keputusan desain API
Alice(claude): Perbandingan REST vs GraphQL, menyajikan dasar pemilihan REST
Bob(codex): Setuju, namun merekomendasikan WebSocket untuk subscription
### Topik: Metode autentikasi
Alice(claude): Mengusulkan JWT + refresh token
Charlie(gemini): Merekomendasikan menambahkan OAuth2 PKCE
Asli tetap disimpan di DB. Anda dapat mencarinya lagi kapan saja jika diperlukan. Hanya ringkasan yang dikirim dalam prompt.
Dengan kata lain, itu bukan membuang percakapan lama sepenuhnya, melainkan menyimpan teks asli dan mengirimkan yang terkompresi.
Engine Parity: Semua mesin harus menerima konteks yang sama
Salah satu prinsip penting tunaFlow adalah 4-engine parity. Baik Claude, Codex, Gemini, maupun OpenCode, untuk pertanyaan yang sama, mereka harus menerima kualitas konteks yang sebaik mungkin sama.
Jika perbedaan muncul antar mesin, itu mudah menjadi "model ini menerima lebih banyak informasi" bukan "model ini lebih baik". Itu bukan perbandingan.
Perbedaannya hanya satu.
- Claude: Pisahkan system prompt (--append-system-prompt-file)
- Non-Claude: Gabungkan semua konteks inline dalam satu prompt
// Claude: Pisahkan system prompt
let system_prompt = format!("{}\n\n{}", context_sections, platform_rules);
let user_prompt = user_input;
// Non-Claude: Gabungkan semuanya
let prompt = format!("{}\n\n---\n\n{}", context_sections, user_input);
Artinya, meskipun format pengiriman berbeda, informasi yang dimasukkan tetap dijaga sesuai/sama. Jadi ketika Anda mengganti agen, Anda bisa menginterpretasi perbedaan hasil.
Masalah yang Muncul di RT (Roundtable): Token membengkak N kali
Roundtable menghabiskan banyak token dari struktur.
Jika 3 orang berdiskusi 2 putaran, total 6 panggilan agen terjadi. Jika ContextPack terlampir di setiap panggilan, perhitungannya kira-kira seperti ini.
6 permintaan × ~15k (mode Standard) = ~90k karakter ≈ ~30k token
Bahkan dengan paket berbayar seperti Claude Pro, hanya beberapa kali menjalankan ini akan cepat mengurangi batas harian. Ada ironi bahwa semakin Anda menggunakan multi-agen secara aktif, semakin berkurang margin percakapan tunggal dalam multi-agen orchestrator.
Respons saat ini: RtContextCache
Jadi di RT, kami tidak membangun ContextPack baru setiap panggilan, tetapi membangun sekali saat awal putaran dan menyimpannya di cache.
struct RtContextCache {
auto_context: Option, // Untuk mesin komersial (Claude, Codex, Gemini)
lite_context: Option, // Untuk mesin lokal (Ollama, OpenCode)
}
Bahkan jika 3 orang dijalankan berturut-turut dalam putaran yang sama, kueri DB terkait ContextPack hanya terjadi 1-2 kali. Bukan N kali, tetapi 1 kali.
Namun ini hanyalah pengurangan biaya kueri. Jumlah teks yang benar-benar masuk ke prompt masih N kali. Dengan kata lain, cache menyelamatkan DB tetapi tidak menghilangkan biaya token itu sendiri.
Arah ke depan: Dari Push ke Pull
Saat ini ContextPack pada dasarnya adalah model Push.
"Agen mungkin membutuhkannya, jadi mari kita masukkan terlebih dahulu."
Saat ini:
[identity + project + plan + skills + rawq + memory + cross-session]
→ Kirim semua di setiap permintaan
→ 7 dari 10 kali tidak menggunakan skills/rawq/memory tetapi tetap sertakan
Masalahnya bukan hanya biaya token. Masalah yang lebih besar adalah kebisingan.
Ketika 10, 15 bagian masuk sekaligus, dari sudut pandang agen, "jadi apa yang harus saya prioritaskan untuk dilihat sekarang?" menjadi kabur. Pada akhirnya, rasio sinyal terhadap kebisingan menjadi rendah. Memberikan lebih banyak tidak selalu lebih baik.
tunaFlow sudah memiliki sistem penanda tool-request. Ketika agen memasukkan penanda seperti ini dalam respons,
tunaFlow mendeteksinya dan secara otomatis mengirimkan hasil pencarian sebagai follow-up.
Terutama context-hub (pencarian dokumen library) sudah dikonversi ke cara Pull ini. Bagian yang tersisa (skills, memory, cross-session) dapat dipindahkan ke pola yang sama.
Tujuannya adalah Push kecil + Pull selektif.
Tier 0 (selalu): identity + dasar proyek ~1.5k
Tier 1 (kondisional): plan + findings + rawq (saat pertanyaan kode) ~2~4k
Tier 2 (Pull): skills, memory, cross-session → agen meminta saat diperlukan
Namun tunaFlow bukan berbasis SDK tetapi berbasis CLI subprocess. Jadi 1 Pull berarti mulai ulang proses + 1 input baru. Bukan struktur yang menyelesaikan tool call secara alami dalam run yang sama.
Artinya, jika Pull hanya 1-2 kali, itu bisa menguntungkan, tetapi jika terulang 2 kali atau lebih, sebenarnya bisa lebih mahal daripada Push. Jadi kesimpulannya bukan Push murni atau Pull murni. Hybrid lagi yang menjadi jawabannya. (Format acara... hybrid lagi...😩)
Ringkasan
ContextPack pada akhirnya adalah status kemajuan tunaFlow saat ini tentang "apa yang akan diberitahu kepada agen".
Keputusan Desain | Alasan |
|---|
Pemrosesan mesin 4 umum dengan fungsi tunggal | Mempertahankan kualitas informasi yang sama ketika mengganti mesin |
Perakitan berubah 3-mode | Mempertahankan densitas informasi yang sesuai situasi dan mengurangi pemborosan token |
Alokasi anggaran dinamis | Karena pentingnya setiap bagian berbeda setiap saat |
Jaminan pesan terakhir per-agen | Untuk mencegah kehilangan konteks dalam diskusi multi-agen |
RtContextCache | Mengurangi biaya kueri DB dalam putaran RT |
Pull tool-request (sedang berlangsung) | Mengurangi kebisingan Push dan meningkatkan efisiensi token |
Dalam batasan berbasis CLI subprocess, pada akhirnya ada satu prinsip yang ingin kami pertahankan.
"Ketika agen nyaman, kualitas hasil meningkat."
Bukan tentang memberikan banyak konteks, tetapi memberikan yang diperlukan dengan densitas yang tepat yang lebih penting sebagai kesimpulan menengah. ContextPack saat ini sedang mencari titik keseimbangan itu!
Referensi
Dokumen Teknis Terkait
- Anthropic — Contextual Retrieval (2024.09): Menambahkan awalan konteks dokumen ke chunk mengurangi tingkat kegagalan pencarian sebesar 49%. Dirujuk dalam desain Tier 2 Pull ContextPack.
https://www.anthropic.com/news/contextual-retrieval
- Jina AI — Late Chunking (2024): Memproses seluruh dokumen dengan model embedding long-context kemudian pooling per chunk. NDCG@10 pada benchmark BeIR +5~15%.
https://jina.ai/news/late-chunking-in-long-context-embedding-models
- Dense X Retrieval (Chen et al., 2024): Chunking berbasis Proposition dengan recall@5 +12~17%. Setiap retrieval unit bersifat self-contained + atomic.
https://arxiv.org/abs/2312.06648
- RAPTOR (Sarthi et al., 2024, Stanford): Clustering rekursif + ringkasan untuk indexing berlapis. Akurasi NarrativeQA +20%.
https://arxiv.org/abs/2401.18059
- ColBERT v2 (Santhanam et al., 2022): Representasi multi-vektor per token dengan MRR@10 0.397 (BM25: 0.187).
https://arxiv.org/abs/2112.01488
Dokumen Internal tunaFlow
- docs/ideas/contextPackTieringIdea.md — Desain hybrid 3-Tier ContextPack + context vektor bersama + sqlite-vec + kualitas chunk
- docs/reference/multiAgentContextStrategy.md — Strategi 3-layer konteks multi-agen (metadata peserta + jendela dinamis + jaminan per-agen)
- docs/ideas/insightWorkflowIdea.md — Alur kerja Insight penyimpanan file laporan + UX promosi Plan (agen akses ContextPack 0 token)
Alat Terkait
- rawq — Mesin pencarian kode berbasis Rust. Embedding snowflake-arctic-embed-s (384 dimensi), mode daemon resident. Menangani pencarian kode + pembuatan embedding vektor di tunaFlow.
- context-hub (chub) — CLI pencarian dokumen library/framework. Di tunaFlow sudah selesai konversi ke cara Pull tool-request.