Skip to content

Commit c0e1c53

Browse files
committed
Bağlı site ajan eylemleri kontratını yaz
Selene MCP tarafında bir şey yakaladı ve haklı: orbit-remote-mcp'de PRIVATE_OPERATIONS statik, yani Orbit'e yeni bir yazma ucu eklemek onu MCP'de kendiliğinden görünür yapmıyor. "API'ye eklerim, MCP otomatik görür" okuma için doğru, yazma için değildi. İş bölümü: MCP transport ve keşif katmanını Selene bağlayacak, ben Orbit ile site arasındaki kontratı kesinleştiriyorum. Belge onun sorduğu altı şeyi karşılıyor — katalog ucu ve şeması, şema bilgisinin nereden geldiği, çağrı ucu, gövde/idempotency yapısı, Orbit→site devretme biçimi ve Rota'nın ilk işlemleri. Taşıyıcı iki karar: Orbit sitelerin işlerini bilmiyor. Katalog siteden geliyor (statik bir JSON, sunucu gerekmiyor), Orbit yalnız yetkiyi tutuyor, imzalıyor ve taşıyor. Beşinci site geldiğinde Orbit'e kod girmemesinin sebebi bu — alternatifi Orbit'i her şeyi bilmek zorunda olan bir yere çevirirdi. Ajan hiçbir şey saklamıyor. ChatGPT Web gibi istemcilerde konuşmalar arasında kalıcı depo yok; ajanın elinde site anahtarı tutulmuyor, Orbit her çağrıda 60 saniyelik imzalı bir belge üretip kendisi kullanıyor. Bu aynı zamanda iptali tek yerde tutuyor: insan panelden kapattığında ortada yaşamaya devam eden bir anahtar kalmıyor.
1 parent 03ab02a commit c0e1c53

1 file changed

Lines changed: 219 additions & 0 deletions

File tree

‎docs/baglisite-ajan-eylemleri.md‎

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Bağlı sitelerde ajan eylemleri — kontrat
2+
3+
Durum: **tasarım kesinleşti, uygulama sürüyor.** Bu belge Orbit ile MCP köprüsü
4+
arasındaki sözleşmedir. MCP tarafını Selene kuruyor; buradaki hiçbir şey
5+
`orbit-remote-mcp` deposuna dokunmadan değişmemeli.
6+
7+
## Neyi çözüyor
8+
9+
İnsan, Orbit hesabıyla girdiği bir siteyi panelden ajanına açıyor. Ajan o
10+
sitede **insanın adına** iş yapıyor — ayrı ajan hesabı, ayrı ajan listesi
11+
oluşmuyor. Orbit'in kendisi istisna: orada ajanlar kendi adına davranır ve o
12+
yol `mcp_authorization_grants` üzerinden yürür, buraya karışmaz.
13+
14+
Tasarımın taşıyıcı kısıtı: **Orbit sitelerin işlerini bilmez.** "Listeye anime
15+
ekle" Orbit'te tanımlı değildir. Orbit yetkiyi tutar, imzalar ve taşır; işlemin
16+
ne olduğunu site söyler. Beşinci site eklendiğinde Orbit'e kod girmemesinin
17+
sebebi bu.
18+
19+
İkinci kısıt: **ajan hiçbir şey saklamaz.** ChatGPT Web gibi istemcilerde
20+
konuşmalar arasında kalıcı depo yok. Ajanın elinde site anahtarı tutulmaz;
21+
Orbit'e bağlı olması yeter, site anahtarını Orbit üretip kendisi kullanır.
22+
23+
## 1. Katalog — ajan ne yapabileceğini nereden öğrenir
24+
25+
```
26+
GET /v1/me/connected-sites/actions
27+
Authorization: Bearer <ajan credential>
28+
```
29+
30+
Ajan kimliğiyle çağrılır. Yanıt, ajanın insanının **ajan erişimi açık** olan
31+
bağlı sitelerini ve o sitelerin bildirdiği işlemleri taşır. Erişimi kapalı
32+
siteler listede hiç görünmez — kapalı bir kapıyı katalogda göstermek, ajanı
33+
kesin reddedilecek bir isteğe davet etmek olurdu.
34+
35+
```json
36+
{
37+
"sites": [
38+
{
39+
"grantId": "grant_01H…",
40+
"clientId": "orbit-equinox-rota",
41+
"label": "Equinox Rota",
42+
"siteUrl": "https://anime.sametbasbug.dev",
43+
"catalogFetchedAt": 1756000000000,
44+
"operations": [
45+
{
46+
"operationId": "rota.listeyeEkle",
47+
"summary": "İnsanın listesine anime ekler veya durumunu günceller.",
48+
"idempotent": true,
49+
"input": {
50+
"type": "object",
51+
"required": ["animeId", "durum"],
52+
"additionalProperties": false,
53+
"properties": {
54+
"animeId": { "type": "string", "maxLength": 300 },
55+
"durum": { "enum": ["IZLIYOR", "BITTI", "PLANLI", "BIRAKTI"] },
56+
"ilerleme": { "type": "integer", "minimum": 0, "maximum": 100000 },
57+
"puan": { "type": "integer", "minimum": 1, "maximum": 10 }
58+
}
59+
},
60+
"output": {
61+
"type": "object",
62+
"properties": { "animeId": { "type": "string" }, "durum": { "type": "string" } }
63+
}
64+
}
65+
]
66+
}
67+
]
68+
}
69+
```
70+
71+
### `describe` neden ayrı bir uç değil
72+
73+
Şema kataloğun içinde geliyor. Ayrı bir `describe` ucu her işlem için ek bir
74+
gidiş-dönüş demekti ve ajanın elinde saklayacak yer olmadığı için o gidiş-dönüş
75+
her seferinde tekrarlanırdı. Katalog küçük ve zaten site başına filtreli.
76+
77+
`input` ve `output` **JSON Schema draft 2020-12** alt kümesidir: `type`,
78+
`required`, `properties`, `enum`, `additionalProperties`, `minimum`, `maximum`,
79+
`maxLength`, `items`. Bu listeyi genişletmek MCP tarafını kırabilir; genişlerse
80+
burada ilan edilir.
81+
82+
## 2. Çağrı — ajan işi nasıl yaptırır
83+
84+
```
85+
POST /v1/me/connected-sites/{grantId}/actions
86+
Authorization: Bearer <ajan credential>
87+
Content-Type: application/json
88+
89+
{
90+
"operationId": "rota.listeyeEkle",
91+
"input": { "animeId": "kitsu:1376", "durum": "IZLIYOR" },
92+
"idempotencyKey": "<yazdıran tarafın ürettiği, ≤128 basılabilir karakter>"
93+
}
94+
```
95+
96+
`grantId` yolda, çünkü yetki kontrolünün baktığı satır o. Gövdede `clientId`
97+
taşımak, aynı bilgiyi iki yerden alıp uyuşmazlığını kontrol etmek olurdu.
98+
99+
`idempotencyKey` **zorunlu**. Bu bir yazma işlemi ve ajan tarafı yeniden
100+
denemeye yatkın; anahtarsız bir tekrar, listeye ikinci kez yazmak demek.
101+
Biçim Orbit'in başka yerlerindekiyle aynı: 1–128 basılabilir ASCII.
102+
103+
### Yanıt
104+
105+
```json
106+
{
107+
"action": {
108+
"operationId": "rota.listeyeEkle",
109+
"status": "applied",
110+
"output": { "animeId": "kitsu:1376", "durum": "IZLIYOR" }
111+
}
112+
}
113+
```
114+
115+
`status`: `applied` (uygulandı) veya `replayed` (aynı `idempotencyKey` ile daha
116+
önce uygulanmıştı; `output` ilk çalışmanınkidir).
117+
118+
### Hatalar
119+
120+
| kod | durum | anlamı |
121+
|---|---|---|
122+
| `connected_site_not_found` | 404 | İzin yok ya da başkasına ait. |
123+
| `agent_access_closed` | 403 | İnsan bu site için ajan erişimini açmamış ya da kapatmış. |
124+
| `unknown_site_operation` | 400 | `operationId` sitenin kataloğunda yok. |
125+
| `invalid_site_operation_input` | 400 | `input`, işlemin şemasına uymuyor. |
126+
| `site_catalog_unavailable` | 503 | Sitenin katalog dosyası okunamadı. |
127+
| `site_action_failed` | 502 | Site isteği reddetti ya da erişilemedi; `details.siteStatus` taşınır. |
128+
| `idempotency_conflict` | 409 | Aynı anahtar farklı bir gövdeyle kullanıldı. |
129+
130+
Girdi doğrulaması **Orbit'te** yapılır, siteye geçmeden. Site kendi kontrolünü
131+
ayrıca yapar — iki kat, çünkü Orbit şemayı siteden okur ve okuduğu şey eskimiş
132+
olabilir.
133+
134+
## 3. Site kendini nasıl bildirir
135+
136+
`oauth_clients` satırına `actions_url` alanı eklenir. Site oraya statik bir
137+
JSON koyar; sunucu gerekmez, GitHub Pages'te duran bir dosya yeter.
138+
139+
```json
140+
{
141+
"version": 1,
142+
"actionsEndpoint": "https://rslltclzdfzwaxqtozmq.supabase.co/functions/v1/orbit-eylem",
143+
"operations": [ /* katalogdaki `operations` ile aynı biçim */ ]
144+
}
145+
```
146+
147+
Orbit bu dosyayı okur ve **10 dakika** önbellekte tutar; `catalogFetchedAt` o
148+
anı verir. Yeni bir işlem eklendiğinde Orbit'e kod girmez, en geç 10 dakika
149+
sonra katalogda görünür.
150+
151+
`actionsEndpoint` `https` olmak ve `oauth_clients.site_url` ile aynı kayıtlı
152+
siteye ait olmak zorundadır; katalog dosyası Orbit'i keyfi bir adrese istek
153+
atmaya ikna edemez.
154+
155+
## 4. Orbit → site: devretme belgesi
156+
157+
Orbit siteye şunu gönderir:
158+
159+
```
160+
POST <actionsEndpoint>
161+
Authorization: Bearer <eylem belgesi>
162+
Content-Type: application/json
163+
Idempotency-Key: <ajanın verdiği anahtar>
164+
165+
{ "operationId": "rota.listeyeEkle", "input": { … } }
166+
```
167+
168+
Eylem belgesi **ES256 imzalı JWT**'dir ve Orbit'in mevcut site anahtarlarıyla
169+
imzalanır; site onu `/.well-known/jwks.json` üzerinden doğrular. Siteyle
170+
paylaşılan kalıcı bir sır **yoktur** — sızacak bir şey olmasın diye.
171+
172+
```json
173+
{
174+
"iss": "https://orbit.sametbasbug.dev",
175+
"aud": "orbit-equinox-rota",
176+
"sub": "<pairwise subject — sitenin o insan için tanıdığı kimlik>",
177+
"act": { "sub": "agent:<agentId>", "handle": "<ajan handle>" },
178+
"scope": "site.actions",
179+
"operation": "rota.listeyeEkle",
180+
"jti": "<tek kullanımlık>",
181+
"iat": 1756000000,
182+
"exp": 1756000060
183+
}
184+
```
185+
186+
- `sub` sitenin giriş sırasında tanıdığı **aynı** pairwise kimliktir. Site
187+
kullanıcıyı kendi kaydında bununla bulur; yeni bir eşleme kurmasına gerek yok.
188+
- `act` (RFC 8693 "actor") işi yapanın ajan olduğunu söyler. İş insanın
189+
adınadır ama **kimin yaptığı kaybolmaz**; site isterse kaydeder.
190+
- `operation` belgeye gömülüdür: bir işlem için alınmış belge, gövdesi
191+
değiştirilerek başka bir işlem için kullanılamaz.
192+
- Ömür **60 saniye**. Ajan saklamaz, Orbit her çağrıda yeniden üretir.
193+
194+
Site şunları doğrulamak zorundadır: imza, `iss`, `aud` (kendi clientId'si),
195+
`exp`, ve `operation` ile gövdedeki `operationId`'nin eşleştiği.
196+
197+
## 5. Rota'nın ilk işlemleri
198+
199+
| operationId | ne yapar |
200+
|---|---|
201+
| `rota.listeyeEkle` | Listeye ekler ya da var olan kaydın durumunu/ilerlemesini/puanını günceller. |
202+
| `rota.listeyiOku` | İnsanın listesini döndürür. |
203+
204+
İkisi de `personal_list_entries` üzerinde ve **insanın satırlarında** çalışır.
205+
Ajanın kendi listesi yoktur.
206+
207+
## Kararlar ve gerekçeleri
208+
209+
**Neden ajan siteye doğrudan konuşmuyor.** Konuşsaydı elinde bir site anahtarı
210+
tutması gerekirdi; saklama yeri olmayan istemcilerde bu çalışmaz. Ayrıca iptal
211+
tek yerde kalmaz — insan Orbit'te kapatır ama ajanın elindeki anahtar yaşamaya
212+
devam ederdi.
213+
214+
**Neden izin `oauth_client_grants` satırının içinde.** Site bağlantısı
215+
kesildiğinde ajan erişimi de düşmeli ve bunun ayrı bir temizlik adımı
216+
gerektirmemesi lazım; unutulan temizlik açık kalan kapıdır.
217+
218+
**Neden katalog siteden geliyor.** Alternatifi, her site özelliği için Orbit'e
219+
kod eklemekti. Beş sitede Orbit her şeyi bilmek zorunda olan bir yere dönerdi.

0 commit comments

Comments
 (0)