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

> Source: <https://dev.to/bbt-id/kenapa-osfoundry-mengirim-schema-record-di-setiap-halaman-yang-di-list-codex-27fm>
> Published: 2026-11-11 10:11:25+00:00

Berikut tool record channel yang didapat koneksi Codex CLI dari [osFoundry](https://osfoundry.io/id), 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"]}]}
```

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.

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.

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:

`{"error": "validation_failed"}`
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"}]}
```

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

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.

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.

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`.

`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.

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.

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.

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.

Definisi singkat osFoundry tentang protokol di baliknya ada di [entri glosarium MCP](https://osfoundry.io/id/glossary/mcp). Layar record channel tempat schema berasal ada di [halaman Records](https://osfoundry.io/id/platform/records-list).
