DEV Community

Cover image for Kenapa osFoundry mengirim schema record di setiap halaman yang di-list Codex?
BBT for BBT Indonesia

Posted on

Kenapa osFoundry mengirim schema record di setiap halaman yang di-list Codex?

Berikut tool record channel yang didapat koneksi Codex CLI dari osFoundry, seperti yang di-list oleh endpoint MCP publiknya. Tool ini diambil dengan tools/list tanpa autentikasi pada 2026-11-09 dan dipangkas ke field yang relevan di sini:

{"name": "list_record_items",
 "description": "List the schema and a bounded page of item previews from one granted record channel. Use read_record_item for the complete values.",
 "inputSchema": {"required": ["record_channel_id"],
   "properties": {"record_channel_id": {"type": "string"}, "before_id": {"type": "string"},
                  "limit": {"type": "integer", "minimum": 1, "maximum": 50}}},
 "annotations": {"readOnlyHint": true}}

{"name": "read_record_item",
 "description": "Read one item by ID from a granted record channel and return its current version.",
 "inputSchema": {"required": ["record_channel_id", "item_id"]}}

{"name": "edit_record_item",
 "description": "Update validated fields in one record item. Supply the version from read_record_item; concurrent changes are rejected.",
 "inputSchema": {"required": ["record_channel_id", "item_id", "values", "expected_version", "idempotency_key"],
   "properties": {"values": {"type": "object"}}},
 "annotations": {"readOnlyHint": false, "destructiveHint": true},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:write"]}]}
Enter fullscreen mode Exit fullscreen mode

Perhatikan values. Isinya {"type": "object"}, tidak lebih. Schema tool tidak bisa memberi tahu key apa saja yang dimiliki sebuah channel, mana yang field Select, atau apa yang boleh diisi ke Points, karena semua itu beda per channel dan berubah saat seseorang mengedit tab Schema. Satu-satunya tempat Codex bisa mengetahuinya adalah baris pertama deskripsi tool list: schema ikut kembali bersama halaman. Sisa tulisan ini mengukur apa yang didapat dari situ, pada sebuah rebuild, beserta dua pilihan lain di empat tool yang sama: cursor before_id alih-alih offset, dan event log untuk kembali lagi nanti.

Apa itu nilai yang valid?

Tipe field-nya tetap. packages/shared/src/types/record.ts di frontend osFoundry mendefinisikan RecordChannelFieldType sebagai 16 tipe: text, number, date, boolean, email, phone, url, select, multi_select, currency, percentage, rating, relation, file, json, dan formula. Setiap field membawa is_required, is_unique, dan config khusus per tipe: max_length untuk text, min/max untuk number, max_stars untuk rating, options untuk dua tipe select, expression untuk formula.

Select adalah yang paling sering menjebak agent. Sebuah opsi berbentuk {value, label, color}. Yang disimpan adalah value, sedangkan label adalah yang dilihat orang. RecordDataGrid.tsx me-render sel select dengan options.find((o) => o.value === params.value) dan fallback ke String(params.value) di chip yang pudar kalau tidak ada yang cocok. Jadi kalau pengecekan yang longgar membiarkan "In progress" masuk ke field yang value-nya in_progress, grid akan menampilkan sesuatu yang kelihatan benar padahal salah: tanpa warna, dan tidak terlihat oleh apa pun yang mengelompokkan atau memfilter berdasarkan value. Model yang hanya pernah melihat label di grid, atau pesan dari seseorang, akan mengirim label. Harus ada yang menolak.

Seperti apa seharusnya penolakan itu?

API record osFoundry membalas panggilan yang gagal dengan {ok: false, error: {message}}. Tool record milik aplikasi desktop untuk agent di dalam aplikasi (src-tauri/src/record_tools.rs) meneruskan pesan itu ke model apa adanya, dan komentarnya menjelaskan alasannya: pesan API "names the field that failed validation, which is usually enough for a self-correcting retry." Unit test-nya memakai values.status is not a valid option sebagai contoh.

"Usually enough" itu bisa diukur. Rebuild ini mencoba empat tingkat detail dalam sebuah penolakan:

  • terse: {"error": "validation_failed"}
  • field: key mana yang gagal
  • values: key, alasan, dan nilai opsi yang diizinkan atau rentangnya
  • labels: sama, ditambah label setiap opsi

Ini tingkat "values", persis dari hasil run:

{"error": "validation_failed", "fields": [
  {"key": "status", "message": "values.status must be one of the option values, got \"In progress\"",
   "allowed": ["new", "triaged", "in_progress", "fixed", "wont_fix"]},
  {"key": "owner", "message": "values.owner must be one of the option values, got \"Aiko\"",
   "allowed": ["aiko", "mika", "ren"]},
  {"key": "points", "message": "values.points must be between 0 and 13, got 20", "min": 0, "max": 13},
  {"key": "due", "message": "values.due must be a date as YYYY-MM-DD, got \"Friday\""},
  {"key": "age_days", "message": "values.age_days is a formula field and is computed, not written"}]}
Enter fullscreen mode Exit fullscreen mode

String-string ini milik rebuild. Hanya penamaan values.<key> yang mengikuti osFoundry.

Rebuild-nya

Tidak ada binary Codex yang dijalankan di sini. record_store.py mengimplementasikan list_record_items, read_record_item, edit_record_item, dan poll_resource_events di atas sqlite in-memory, dengan nama tool, nama argumen, dan batas yang sama seperti versi live. Tipe field dan key config diambil dari record.ts. Panjang preview (120 karakter), bentuk hasil, dan semua aturan validasi adalah buatan rebuild sendiri. Sebuah edit berjalan di dalam BEGIN IMMEDIATE: lookup idempotensi, perbandingan versi, validasi, merge, kenaikan versi, append event, commit. Masalah apa pun akan me-rollback seluruh edit.

triage.py adalah Codex yang di-script. Ia bekerja pada bug tracker dengan 11 field (Contoh: ticket, title, description, status, owner, severity, points, area, due, reporter, age_days), 200 item New per run, seed 1–5. Setiap item mendapat niat triage dalam kata-kata seseorang: "In progress", "Aiko", "S2 Major" atau cuma "major", points dari {1, 2, 3, 5, 8, 13, 20}, due "Friday" atau tanggal ISO, satu area. Agent bisa mengirim kata-kata itu apa adanya, atau memetakannya dulu lewat schema dengan mencocokkan label ke value. Saat ditolak, ia memperbaiki apa yang bisa diperbaiki berdasarkan penolakan, membuang yang tidak bisa, dan menandai field itu untuk ditangani seseorang. Ia tidak pernah memotong angka ke batas. Python 3.9.6, Apple M1.

Apakah schema di dalam halaman berpengaruh?

1,000 item, masing-masing dengan 6 field yang ingin diisi:

Agent membaca schema Isi penolakan Field tersimpan (dari 6,000) Tersisa untuk manusia Panggilan edit per item
tidak tidak ada 1,363 (22.7%) 4,637 7.00
tidak field mana 1,363 (22.7%) 4,637 2.00
tidak field, alasan, nilai yang diizinkan 5,361 (89.3%) 639 2.00
tidak field, alasan, nilai dan label 5,857 (97.6%) 143 2.00
ya tidak ada 5,857 (97.6%) 143 1.86
ya field mana 5,857 (97.6%) 143 1.14
ya nilai, atau nilai dan label 5,857 (97.6%) 143 1.14

97.6% adalah batas atasnya. Sisa 143 adalah item di mana orangnya bilang 20 points padahal field-nya berhenti di 13. Item seperti itu memang harus ditolak, dan memang ditolak di setiap baris.

Agent yang membaca schema mencapai batas atas apa pun isi penolakannya. Edit pertamanya hanya gagal untuk 143 points yang di luar rentang. Tanpa schema, setiap edit pertama gagal: status, owner, severity, dan area berisi label, dan separuh tanggal due berisi "Friday". Dari situ, pemulihan sepenuhnya bergantung pada error-nya. "Field mana" tidak berguna tanpa nilai yang diizinkan: agent tahu status salah tapi tidak tahu yang benar, jadi ia membuangnya. Nilai yang diizinkan tanpa label mencapai 89.3%. 639 yang meleset adalah "Won't fix" lawan wont_fix dan "major" lawan s2. Keduanya tidak bisa dicocokkan dari daftar nilai. Dengan label di penolakan, agent tanpa schema bisa menyusul, dengan satu panggilan tambahan per item. Error singkat tanpa schema membuat agent memecah setiap edit menjadi enam edit satu field, 7 panggilan per item, dan tetap saja yang tersimpan kurang dari seperempat.

Jadi schema adalah separuh dari desain validasi. Pengecekan di server menghentikan nilai yang salah. Schema yang ikut di setiap halaman adalah yang membuat percobaan pertama bisa langsung benar. Biayanya 1,705 byte per halaman di channel ini, sekitar 10% dari halaman 16.7 KiB berisi 50 preview.

Kenapa preview, dan kenapa maksimal 50?

Satu halaman berisi 50 preview berukuran 16.7 KiB. Membaca 50 item itu secara penuh memakan 124.6 KiB, karena deskripsi di sini panjangnya 300–4,000 karakter. Untuk triage, preview sudah cukup untuk memilih apa yang perlu dibuka: status, ticket, awal judul. Codex membuka item yang akan ia ubah. Halaman yang dibatasi juga mencegah channel besar membanjiri context. Deskripsi tool menyebut "bounded" dan schema-nya menyebut maximum: 50.

Kenapa before_id dan bukan offset?

Orang terus membuat bug baru selagi Codex membaca. Rebuild ini mem-page 600 item, 50 per halaman, dari yang terbaru, dan mengubah channel setelah setiap halaman. Lima run:

Perubahan setelah tiap halaman Paging Halaman Pembacaan item Terbaca dua kali Tidak pernah terlihat
2 bug baru dibuat offset 65 3,120 120 0
2 bug baru dibuat before_id 60 3,000 0 0
2 item yang sudah dibaca dihapus offset 60 2,890 0 110
2 item yang sudah dibaca dihapus before_id 60 3,000 0 0

Item baru di bagian atas mendorong semuanya ke bawah, jadi halaman offset saling tumpang tindih dan Codex men-triage beberapa item dua kali. Menghapus sesuatu yang sudah dibaca menggeser semuanya ke atas, jadi halaman offset berikutnya mulai setelah item yang belum dibaca siapa pun. 110 bug yang ada dari awal sampai akhir tidak pernah terlihat. Cursor menunjuk item terakhir yang dibaca, dan insert atau delete di tempat lain tidak bisa menggesernya. Item yang dibuat selama proses berjalan tidak muncul di halaman mana pun. Untuk itulah event log ada.

Bagaimana Codex menemukan apa yang berubah sejak proses terakhir?

Setelah satu proses atas 400 item, orang mengedit 30 item, membuat 12, dan menghapus 3. Dua cara untuk menemukan 45 itu:

Strategi Panggilan Data yang dikembalikan Ditemukan
list ulang semua halaman, bandingkan versi 9.0 135.8 KiB 45 dari 45
poll_resource_events dari sequence yang disimpan 1.0 4.6 KiB 45 dari 45

Script-nya memakai assert untuk memastikan keduanya menemukan tepat 45. Biaya polling sebesar apa yang berubah. Biaya list ulang sebesar ukuran channel, setiap kali. State yang perlu disimpan antar-run hanya satu integer, sequence terakhir.

Lalu pengecekan versinya?

Singkat saja, karena cara kerjanya sama seperti untuk Notes dan knowledge base. Dalam 1,000 edit di mana seseorang mengubah 1 dari 8 item di antara read dan write milik Codex, versi dari hasil read menghasilkan 131 konflik dan menimpa 0 perubahan orang tersebut. Membaca ulang versi tepat sebelum menulis menghasilkan 0 konflik dan menimpa semua 131. Setelah konflik, Codex yang di-script membaca ulang, melihat item-nya sudah in progress, dan hanya menambahkan severity.

Berapa biaya validasi?

2,000 edit valid berisi enam field, tiga run: median 72.8–75.1 µs per edit dengan validasi, 41.9–42.4 µs dengan validate() diganti stub. Selisihnya adalah query schema dan pengecekan per field di dalam transaksi. Dibandingkan satu giliran model yang butuh beberapa detik, ini tidak terasa.

Kalau kamu memasangnya sendiri

  • Kembalikan schema bersama listing, di setiap halaman, bukan lewat panggilan terpisah yang bisa dilewati agent. Selisih yang terukur adalah satu edit yang ditolak per item.
  • Masukkan nilai opsi yang diizinkan beserta label-nya ke dalam penolakan. Nama field saja tidak membuat agent bisa memperbaiki apa pun.
  • Tolak seluruh edit kalau ada satu field yang gagal, dan sebutkan field mana saja. Penyimpanan sebagian yang melaporkan sukses menyembunyikan field yang salah diisi agent.
  • Jangan pernah mengonversi label atau memotong angka di server. "major" dan "20 points" adalah pertanyaan untuk manusia, bukan untuk matcher.
  • Lakukan paging dengan cursor pada ID yang stabil, dan berikan agent sequence event untuk run berikutnya.
  • Jauhkan perubahan schema, create, dan delete dari tool agent sampai kamu benar-benar membutuhkannya. Validasi hanya berarti kalau agent tidak bisa memperlebar field-nya.

Definisi singkat osFoundry tentang protokol di baliknya ada di entri glosarium MCP. Layar record channel tempat schema berasal ada di halaman Records.

Top comments (0)