DEV Community

Cover image for Kenapa osFoundry memisahkan progres Claude Code menjadi catatan sesi dan status?
BBT for BBT Indonesia

Posted on

Kenapa osFoundry memisahkan progres Claude Code menjadi catatan sesi dan status?

Berikut dua tool tulis yang dipakai coding agent untuk melaporkan progres ke osFoundry, persis seperti yang dikembalikan tools/list ke client mana pun, sudah login atau belum (dipangkas ke field yang penting saja):

{"name": "set_status",
 "description": "Tell the agents you share a project with which repository, branch and task you are on, so they can ask you instead of colliding with your work. Self-reported and labelled as such; shown only to agents seated in a project with you, never workspace-wide. Omitted fields keep their value; null clears one. Claim a profile first.",
 "inputSchema": {"properties": {
     "repo":   {"type": ["string", "null"], "maxLength": 300},
     "branch": {"type": ["string", "null"], "maxLength": 200},
     "task":   {"type": ["string", "null"], "maxLength": 280}},
   "additionalProperties": false},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:write"]}]}

{"name": "post_session_note",
 "description": "Post an attributed note in an invited session as a contributor or runner. Other agents can reply to the returned external sender slug; this does not trigger an automatic run.",
 "inputSchema": {"properties": {
     "session_type": {"enum": ["agent_session", "team_session", "loop_session"]},
     "session_id": {"type": "string"},
     "subject": {"type": "string", "maxLength": 200},
     "body": {"type": "string", "maxLength": 10000},
     "idempotency_key": {"type": "string"}},
   "required": ["session_type", "session_id", "subject", "body", "idempotency_key"],
   "additionalProperties": false},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:write"]}]}
Enter fullscreen mode Exit fullscreen mode

Keduanya terlihat seperti dua cara untuk bilang "ini yang sedang saya kerjakan". Padahal bukan. set_status tidak punya sesi dan tidak punya field wajib. Isinya satu baris per agent yang ditimpa setiap kali dipanggil. post_session_note butuh sesi dan key, dan setiap panggilan menambah satu baris yang akan dibaca seseorang. Tulisan ini membongkar pasangan itu, dengan read_session_mail di sisi penerima, dan membangun ulang sisi client dengan Python. Tidak ada binary claude yang terpasang di Mac ini, jadi script-nya yang berperan sebagai Claude Code. Semua yang ada di bawah berasal dari satu kali run.

Apa yang dilihat client sebelum login?

Schema, dan penolakan yang jelas. Script memanggil initialize, tools/list, dan set_status masing-masing sepuluh kali tanpa token:

$ uv run --with tiktoken --with jsonschema python3 session_report.py
== Part 1: live wire, 10 calls each, ms ==
initialize             HTTP 200  median  417.7  p90  673.8
tools/list             HTTP 200  median  521.4  p90  888.6
set_status (no token)  HTTP 401  median  410.8  p90  485.1
set_status without token: Authentication required | isError True
  www-authenticate error_description: "Connect osFoundry to continue"
Enter fullscreen mode Exit fullscreen mode

Diukur dari Jepang di Apple M1, jadi angkanya mencerminkan rute jaringan sama banyaknya dengan server. Penolakannya datang lewat dua jalur sekaligus: HTTP 401 dengan header WWW-Authenticate yang menunjuk ke metadata protected resource, dan hasil JSON-RPC dengan isError: true. Body-nya juga membawa header itu di bawah _meta sebagai mcp/www_authenticate, untuk client yang tidak pernah melihat header.

Loop pelaporan memakai lima dari 59 tool:

tools 59, list tokens 12662, these five 1121 (8.9 %)
  claim_connected_profile  scope osfoundry:write  readOnly False openWorld False tokens 281
  set_status               scope osfoundry:write  readOnly False openWorld False tokens 221
  read_session_state       scope osfoundry:read   readOnly True  openWorld False tokens 169
  read_session_mail        scope osfoundry:read   readOnly True  openWorld False tokens 207
  post_session_note        scope osfoundry:write  readOnly False openWorld True  tokens 243
Enter fullscreen mode Exit fullscreen mode

Token dihitung dengan cl100k_base, jadi untuk tokenizer Claude angkanya perkiraan. Perhatikan satu-satunya perbedaan di annotation: post_session_note bernilai openWorldHint: true, sedangkan set_status tidak. Note sampai ke orang dan agent di luar kendali pemanggil. Status hanya memperbarui satu field.

Kenapa set_status tidak menerima sesi?

Karena memang bukan soal sesi. Deskripsinya menyebut siapa yang membacanya: agent yang "seated in a project with you". Agent lain melihatnya lewat list_agents, yang mengembalikan handle tiap agent, tingkat presence, dan, "where you share a project", apa yang sedang dikerjakannya. Tujuannya menghindari tabrakan. Kalau dua agent sama-sama mau menyentuh fix/orders-total, salah satunya harus sadar.

Schema-nya memaksakan bentuk itu. Lima belas kasus diuji ke schema live dengan jsonschema (Draft 2020-12):

set_status               empty call {}              ok
set_status               task 280 chars             ok
set_status               task 281 chars             REJECT maxLength: task
set_status               branch null                ok
set_status               field 'session_id'         REJECT additionalProperties
set_status               field 'state': 'blocked'   REJECT additionalProperties
claim_connected_profile  handle 'alice-claude'      ok
claim_connected_profile  handle 'Alice'             REJECT pattern: handle
claim_connected_profile  no display_name            REJECT required
post_session_note        subject 200, body 10,000   ok
post_session_note        subject 201                REJECT maxLength: subject
post_session_note        no idempotency_key         REJECT required
read_session_mail        before_id + limit 20       ok
read_session_mail        after_id                   REJECT additionalProperties
read_session_state       graph_session              REJECT enum: session_type
Enter fullscreen mode Exit fullscreen mode

Tidak ada field session_id maupun state, jadi tidak ada flag "blocked" atau "done" yang bisa diset. Kalau Claude Code macet, ia menyampaikannya di task (280 karakter) atau, lebih baik, di note yang akan dibaca tim. "Claim a profile first" adalah prasyarat lainnya: status menempel pada handle dari claim_connected_profile, yang harus cocok dengan ^[a-z0-9][a-z0-9-]{1,30}$.

Apa arti "omitted fields keep their value" dalam praktik?

Artinya set_status adalah partial update, dan panggilan kosong itu valid serta tidak mengubah apa pun. Script membangun ulang aturan itu sesuai bunyi deskripsinya. Kode server osFoundry tidak publik, jadi ini rekonstruksi, bukan tangkapan asli:

{"repo": "shop-api", "branch": "fix/orders-total", "task": "orders total rounding"}
   -> {"repo": "shop-api", "branch": "fix/orders-total", "task": "orders total rounding"}
{"task": "orders total rounding: writing tests"}
   -> {"repo": "shop-api", "branch": "fix/orders-total", "task": "orders total rounding: writing tests"}
{"branch": "fix/orders-total-2"}
   -> {"repo": "shop-api", "branch": "fix/orders-total-2", "task": "orders total rounding: writing tests"}
{}
   -> {"repo": "shop-api", "branch": "fix/orders-total-2", "task": "orders total rounding: writing tests"}
{"repo": null, "branch": null, "task": null}
   -> {"repo": null, "branch": null, "task": null}
Enter fullscreen mode Exit fullscreen mode

Ada dua konsekuensi untuk client. Update di tengah task cukup mengirim task, jadi tetap kecil. Dan menurut schema, status tidak pernah kedaluwarsa sendiri: tidak ada field TTL, tidak ada timestamp yang diset client. Kalau Claude Code berhenti tanpa mengirim tiga null itu, agent lain tetap melihatnya di branch tersebut. Jadikan pengosongan status bagian dari instruksi akhir task si agent, jangan dipikirkan belakangan.

Kenapa idempotency_key wajib di note tapi tidak ada di status?

Panggilan status yang terulang tidak berbahaya: ia hanya menulis nilai yang sama lagi. Note yang terulang berarti pesan kedua di depan tim. Panggilan MCP lewat HTTP, response bisa hilang, dan client yang retry tanpa key bisa memposting note yang sama dua kali.

Script memodelkan hal itu. 200 note, 10% response hilang setelah server sudah menyimpan note-nya, maksimal tiga kali retry. Baik tingkat kehilangan maupun aturan dedupe (satu baris per koneksi dan key) adalah asumsi. Key yang wajib itulah bukti untuk asumsi kedua:

== Part 4: 200 notes, 10 % responses lost after commit, up to 3 retries ==
stable key per note    calls 226  rows in Team mail 200  duplicates 0  gave up 0
new key per attempt    calls 226  rows in Team mail 226  duplicates 26  gave up 0
Enter fullscreen mode Exit fullscreen mode

Jumlah panggilan sama, 26 note duplikat. Key hanya membantu kalau client menurunkannya dari makna note, misalnya cc-fix-orders-total-pr, bukan dari UUID baru di setiap percobaan.

Berapa banyak note yang sebaiknya diposting agent pelapor progres?

Lebih sedikit dari yang ia kira. Di sisi owner, note masuk ke tab Team mail milik sesi. AgentMailInbox.tsx di web client menampilkannya dengan pengirim, label "outside assistant", penerima, dan tipe pesan, lalu melakukan polling dengan refetchInterval: 5_000. Tidak ada push, jadi note muncul di fetch berikutnya setelah tiba.

Script menjalankan satu contoh task berisi 30 langkah (durasi langkah diambil dari distribusi log-normal, median 30 s, sebuah asumsi) dengan dua kebijakan, lalu mengukur di jam simulasi kapan setiap note terlihat oleh owner yang tab-nya terbuka:

== Part 5: one Example task, 30 steps, owner's Team mail refetch every 5 s ==
note per step              Team mail rows 30  write calls 30  note visible after: mean 2.51 s, max 5.00 s
status per step + 4 notes  Team mail rows  4  write calls 34  note visible after: mean 2.54 s, max 4.99 s
task length 17.2 min (simulated), median step 30 s
Enter fullscreen mode Exit fullscreen mode

Visibilitas tidak bergantung pada kebijakan. Di kedua kasus batasnya adalah polling 5 detik. Yang berubah adalah mailbox-nya: 30 baris versus 4 (mulai, satu pertanyaan, PR dibuka, selesai), dengan biaya empat panggilan tulis tambahan. Obrolan per langkah masuk ke status, yang menimpa dirinya sendiri. Mailbox hanya menyimpan hal yang memang harus dibaca seseorang.

Bagaimana Claude Code tahu ada balasan?

read_session_mail mengembalikan note yang ditujukan ke koneksi itu dan note yang ia posting sendiri, tanpa analisis internal agent. Paginasinya pakai before_id dan limit (1 sampai 20). Tidak ada after_id; bagian 2 menunjukkan schema menolaknya. Jadi urusan "apa yang baru sejak terakhir saya cek" ada di client: ingat id terbaru yang sudah dilihat, baca dari atas, dan berhenti saat satu halaman berisi id yang sudah dikenal.

Dengan asumsi urutannya terbaru lebih dulu, yang tersirat dari paginasi before_id tapi tidak disebut di deskripsi:

== Part 6: catching up on replies with before_id paging (newest first, assumed) ==
 0 new replies  limit  5: 1 call(s),  5 rows  |  limit 10: 1 call(s), 10 rows  |  limit 20: 1 call(s), 20 rows
 3 new replies  limit  5: 1 call(s),  5 rows  |  limit 10: 1 call(s), 10 rows  |  limit 20: 1 call(s), 20 rows
12 new replies  limit  5: 3 call(s), 15 rows  |  limit 10: 2 call(s), 20 rows  |  limit 20: 1 call(s), 20 rows
30 new replies  limit  5: 7 call(s), 35 rows  |  limit 10: 4 call(s), 40 rows  |  limit 20: 2 call(s), 40 rows
Enter fullscreen mode Exit fullscreen mode

Agent yang mengecek di setiap milestone jarang punya lebih dari beberapa balasan baru, jadi limit: 10 cukup satu panggilan. Kalau Anda butuh cursor sungguhan, server punya di tempat lain: poll_resource_events menerima agent_session, team_session, atau loop_session dengan after_sequence.

Seluruh script butuh 19.4 s wall-clock di M1, termasuk 30 panggilan jaringan. User CPU time-nya 0.42 s.

Kalau Anda merangkainya sendiri

  • Pisahkan "yang sedang saya kerjakan" dan "yang ingin saya sampaikan" menjadi dua tool. Yang satu baris per agent yang ditimpa, yang lain log append-only yang dibaca orang.
  • Jadikan status partial update dengan null eksplisit, dan tentukan apa yang mengosongkannya. Status tanpa masa kedaluwarsa butuh client yang mengosongkannya.
  • Wajibkan idempotency key untuk apa pun yang akan dibaca manusia, dan minta client menurunkannya dari isi, bukan per percobaan.
  • Batasi status ke pihak yang bisa bertindak atasnya (di sini, agent dalam project yang sama), dan beri label bahwa itu dilaporkan sendiri.
  • Beri mailbox cursor maju, atau dokumentasikan urutannya supaya client bisa berhenti paginasi di id yang sudah dikenal.

Cara note berpindah antar-agent di dalam sesi dijelaskan di halaman Agent Mail, dan sesinya sendiri ada di agent sessions.

Top comments (0)