|
1
|
+ |
# The delegation target seam
|
|
2
|
+ |
|
|
3
|
+ |
**Date:** 2026-08-25
|
|
4
|
+ |
**Issue:** [#37](https://openagents.com/OpenAgentsInc/openagents.com/issues/37), *Integrate cloud computers with Work, chat, text, voice, and API*
|
|
5
|
+ |
**Source:** `docs/2026-08-22-cloud-computer-scale-architecture-audit.md`
|
|
6
|
+ |
**Status:** The seam in section 4 shipped with this document. Sections 5 and 6 are the decisions it defers.
|
|
7
|
+ |
|
|
8
|
+ |
Issue #37 asks for five integrations: Work, chat, text, voice, and the
|
|
9
|
+ |
authenticated API. This document argues they are one integration and four
|
|
10
|
+ |
renderings, names the part they share, and records what shipped.
|
|
11
|
+ |
|
|
12
|
+ |
---
|
|
13
|
+ |
|
|
14
|
+ |
## 1. "Cloud computer" is not a noun this codebase gets
|
|
15
|
+ |
|
|
16
|
+ |
`docs/taxonomy.md` already settled the word, and #37 collides with it:
|
|
17
|
+ |
|
|
18
|
+ |
> **computer** — someone's own machine, connected or absent, never provisioned
|
|
19
|
+ |
> **box** — a sandbox VM this application provisions, caps, and reclaims
|
|
20
|
+ |
|
|
21
|
+ |
So an OpenAgents-managed cloud computer cannot be called a computer here. It is
|
|
22
|
+ |
a **Box**, and #37's "customer-connected computer" is a **Computer**. The
|
|
23
|
+ |
delegation target kind is already `box` or `computer`, the kind already travels
|
|
24
|
+ |
in the identifier (`box:{uuid}`, `computer:{uuid}`), and authority is already
|
|
25
|
+ |
scoped per kind.
|
|
26
|
+ |
|
|
27
|
+ |
Adding a third kind named `cloud_computer` would create exactly the collision
|
|
28
|
+ |
the taxonomy exists to prevent: three nouns, two of which are provisioned, one
|
|
29
|
+ |
of which is not, and a reader who cannot tell from the name which failure a
|
|
30
|
+ |
missing one is. A Box that is gone was reclaimed and you can provision another.
|
|
31
|
+ |
A Computer that is gone is somebody's laptop.
|
|
32
|
+ |
|
|
33
|
+ |
**Decision.** #37 adds no target kind. It makes the two that exist addressable,
|
|
34
|
+ |
observable, and advertisable through one seam.
|
|
35
|
+ |
|
|
36
|
+ |
---
|
|
37
|
+ |
|
|
38
|
+ |
## 2. What exists today
|
|
39
|
+ |
|
|
40
|
+ |
Measured by direct reading, not by documentation.
|
|
41
|
+ |
|
|
42
|
+ |
**Real, in this repository:**
|
|
43
|
+ |
|
|
44
|
+ |
| Thing | Where | What it does |
|
|
45
|
+ |
| --- | --- | --- |
|
|
46
|
+ |
| Delegation facade | `OpenAgents.Delegations` | One `start` / `get` / `cancel` / `inventory` across both kinds, at `/api/v1/conversations/{id}/delegations`. Stores nothing; derives everything. IDENTITY-009. |
|
|
47
|
+ |
| Box substrate | `OpenAgents.Box`, `OpenAgents.BoxRuns` | Rents VMs from the Box Public API at `ascii.dev`. Ledgers `conversation_boxes`, `box_runs`, `box_fanout_items`. |
|
|
48
|
+ |
| Computer substrate | `OpenAgents.Machines`, `OpenAgents.Computer`, `OpenAgents.ComputerAgentJobs` | Pairing, credential, live WebSocket control, durable ACP delegations as `work_jobs` rows. |
|
|
49
|
+ |
| Capacity and matching | `OpenAgents.Capacity` | Runtime classes `standard`, `strong`, `batch`, `connected`, each with an isolation, egress, and data location. Published at `/api/v1/capacity` and `/api/v1/capacity/matches`. Issue #76. |
|
|
50
|
+ |
|
|
51
|
+ |
**Documented, and not real anywhere:**
|
|
52
|
+ |
|
|
53
|
+ |
- The logical-computer record, the runtime-lease record, and the command
|
|
54
|
+ |
journal with `may_have_started`. None exists in this repository.
|
|
55
|
+ |
- The provider-neutral control-plane `Req` client #37 asks for. **There is no
|
|
56
|
+ |
endpoint for it to call.** In the `OpenAgentsInc/openagents` monorepo,
|
|
57
|
+ |
`crates/openagents-cloud-contract/src/cloud_computer_v1.rs` and the 34-file
|
|
58
|
+ |
`packages/khala-sync-server/src/cloud-computer-*.ts` set are real, tested
|
|
59
|
+ |
code that no binary imports and no route exposes; there are no fixtures for
|
|
60
|
+ |
`cloud_computer.v1` and no OpenAPI, protobuf, or JSON schema describing any
|
|
61
|
+ |
cloud-computer request or response. The one machine-readable artifact,
|
|
62
|
+ |
`schema/cloud_computer.v1.schema.json`, describes a record, not an endpoint.
|
|
63
|
+ |
|
|
64
|
+ |
That last finding decides the shape of this work. A `Req` client written today
|
|
65
|
+ |
would be a client for something that does not answer.
|
|
66
|
+ |
|
|
67
|
+ |
---
|
|
68
|
+ |
|
|
69
|
+ |
## 3. The seam, and why five surfaces share exactly one
|
|
70
|
+ |
|
|
71
|
+ |
Work, chat, text, voice, and the API differ in who is asking and how the answer
|
|
72
|
+ |
is drawn. They ask the same three questions of a target:
|
|
73
|
+ |
|
|
74
|
+ |
1. **How is it addressed?** By the kind-prefixed reference the facade already
|
|
75
|
+ |
parses. Settled, and nothing here mints a second identifier.
|
|
76
|
+ |
2. **What is it doing?** Today, four vocabularies answer this and none of them
|
|
77
|
+ |
agree. `conversation_boxes.state` has ten values; `box_runs.state` has eight;
|
|
78
|
+ |
`work_jobs.status` has seven; a Computer has `status` plus a live
|
|
79
|
+ |
reachability read. A surface that wants to say "this computer is busy" must
|
|
80
|
+ |
know all four.
|
|
81
|
+ |
3. **What may this caller do to it?** Nothing answered this at all. Every
|
|
82
|
+ |
surface had to re-derive it from kind, state, and scopes.
|
|
83
|
+ |
|
|
84
|
+ |
The third question is the one that earns a module. Reach is scoped per kind
|
|
85
|
+ |
(IDENTITY-009), so a surface that decides for itself whether a caller may start
|
|
86
|
+ |
work on a target is a surface that can widen reach past the substrate. Five
|
|
87
|
+ |
surfaces means five chances to get that wrong, and four of them would be
|
|
88
|
+ |
written by someone reading the fifth.
|
|
89
|
+ |
|
|
90
|
+ |
Before this change there were also three divergent projections of "what can I
|
|
91
|
+ |
delegate to": the `computer_list` tool's own result schema, which cannot see a
|
|
92
|
+ |
Box at all; the API's `openagents.delegation_targets.v1`; and the chat panel's
|
|
93
|
+ |
fleet projection. Three answers to one question.
|
|
94
|
+ |
|
|
95
|
+ |
---
|
|
96
|
+ |
|
|
97
|
+ |
## 4. What shipped
|
|
98
|
+ |
|
|
99
|
+ |
`OpenAgents.Delegations.Target` — pure, no database, no process — computes five
|
|
100
|
+ |
fields for any target, and two for any delegation.
|
|
101
|
+ |
|
|
102
|
+ |
| Field | Meaning |
|
|
103
|
+ |
| --- | --- |
|
|
104
|
+ |
| `custody` | `openagents_managed` or `customer_premises`, **derived from the target kind alone**. |
|
|
105
|
+ |
| `runtime_class` | The `OpenAgents.Capacity.Catalog` class, or `nil` when none describes the kind. |
|
|
106
|
+ |
| `lifecycle` | One word from the seven-state enum of `openagents.cloud_computer.v1`. |
|
|
107
|
+ |
| `capabilities` | The operations this caller may exercise now. `["start"]` or `[]`. |
|
|
108
|
+ |
| `unavailable_reason` | The lifecycle when the target is not startable, `not_authorized` when authority is what is missing, `nil` when nothing is. |
|
|
109
|
+ |
|
|
110
|
+ |
A delegation carries `lifecycle` from a six-word vocabulary and
|
|
111
|
+ |
`capabilities` of `["cancel"]` or `[]`.
|
|
112
|
+ |
|
|
113
|
+ |
Three decisions inside it are worth stating.
|
|
114
|
+ |
|
|
115
|
+ |
**Custody is structural.** It reads the target kind and nothing else. It cannot
|
|
116
|
+ |
be reached from the route that received the request, from whether the caller
|
|
117
|
+ |
typed or spoke, or from anything about the asking. This is what makes #37's
|
|
118
|
+ |
acceptance criterion — *never infer customer-owned versus OpenAgents-managed
|
|
119
|
+ |
from text versus voice or from the route* — a property of the type rather than
|
|
120
|
+ |
a rule five surfaces must remember.
|
|
121
|
+ |
|
|
122
|
+ |
**The lifecycle vocabulary is borrowed, not invented.** The seven words are the
|
|
123
|
+ |
`state` enum of `openagents.cloud_computer.v1` verbatim. The Elixir projection
|
|
124
|
+ |
maps onto the contract that already exists upstream, so a target described here
|
|
125
|
+ |
keeps its word if a control plane ever starts producing one. The substrate's own
|
|
126
|
+ |
state travels beside it untouched, because the substrate is still the authority
|
|
127
|
+ |
and this is a projection of it.
|
|
128
|
+ |
|
|
129
|
+ |
**A Box claims no runtime class.** `Capacity.Catalog`'s `standard` class asserts
|
|
130
|
+ |
`policy_broker` egress and `managed_standard` isolation. A Box is rented from a
|
|
131
|
+ |
third party and nothing in this repository establishes its egress posture, so
|
|
132
|
+ |
calling it `standard` would publish a containment claim no test backs.
|
|
133
|
+ |
`runtime_class("box")` returns `nil` until that evidence exists. A Computer is
|
|
134
|
+ |
`connected` by construction, and `Capacity.Connected` already counts machines as
|
|
135
|
+ |
that class.
|
|
136
|
+ |
|
|
137
|
+ |
The tests derive their inputs from the substrate schemas rather than listing
|
|
138
|
+ |
them: every value of `ConversationBox.states/0`, `Run.states/0`, and
|
|
139
|
+ |
`Job.statuses/0` must map into the vocabulary, and a new state fails the suite
|
|
140
|
+ |
the day it lands instead of silently reading as `failed` on five surfaces. The
|
|
141
|
+ |
authority tests assert the negative case for every kind and lifecycle pair, so
|
|
142
|
+ |
a capability cannot leak by omission.
|
|
143
|
+ |
|
|
144
|
+ |
Both consumers now read it: `Delegations.inventory/2` for the API, and
|
|
145
|
+ |
`Delegations.projection/2` for the chat panel, including the fan-out queue —
|
|
146
|
+ |
which is the only thing in this application that reaches the contract's
|
|
147
|
+ |
`queued` state today.
|
|
148
|
+ |
|
|
149
|
+ |
---
|
|
150
|
+ |
|
|
151
|
+ |
## 5. Why the remaining surfaces are a rendering, not four integrations
|
|
152
|
+ |
|
|
153
|
+ |
**Chat and #38.** #38 wants each logical computer shown with a stable label and
|
|
154
|
+ |
one of `cold | queued | starting | active | stopping | failed | destroyed`, the
|
|
155
|
+ |
reason a computer is queued, and owner controls "only when the current
|
|
156
|
+ |
capability allows them". Those are `lifecycle`, `unavailable_reason`, and
|
|
157
|
+ |
`capabilities` on a projection the chat panel already receives. #38 is a
|
|
158
|
+ |
template and a set of tests over fields that now exist.
|
|
159
|
+ |
|
|
160
|
+ |
**Text and voice.** Both already reach the same `OpenAgents.Work` path;
|
|
161
|
+ |
`work_jobs.surface` is `text` or `voice` and nothing else about the two differs.
|
|
162
|
+ |
Because custody is derived from the target kind, the equivalence #37 demands —
|
|
163
|
+ |
that a text and a voice request produce the same target, authority, and custody
|
|
164
|
+ |
— holds by construction rather than by a test that watches for drift.
|
|
165
|
+ |
|
|
166
|
+ |
**The API.** Already served. The seam is additive on
|
|
167
|
+ |
`openagents.delegation_targets.v1`.
|
|
168
|
+ |
|
|
169
|
+ |
**Model tools.** A tool renders the same document. It is deliberately not
|
|
170
|
+ |
shipped here: the tool catalog is at a **zero base** by policy
|
|
171
|
+ |
(`docs/2026-08-23-agent-tools-zero-base.md`), seven read-only tools ship, and
|
|
172
|
+ |
none of the five computer tools is among them. Re-admission runs through that
|
|
173
|
+ |
document's six criteria and is a change to both `config/config.exs` and
|
|
174
|
+ |
`shipped_catalog_test.exs`. #37's line about adding a model tool predates that
|
|
175
|
+ |
policy. The seam is built so re-admitting one is a config line and a rendering;
|
|
176
|
+ |
the admission decision belongs to section 6 of that document, not to this issue.
|
|
177
|
+ |
|
|
178
|
+ |
**Work.** `Capacity.match/2` returns a runtime *class* and never a thing you can
|
|
179
|
+ |
address; `Delegations` returns addressable targets and, until now, no class.
|
|
180
|
+ |
A surface that wanted to choose a target had to bridge those itself. The
|
|
181
|
+ |
descriptor is that bridge's near half. The far half is section 6.
|
|
182
|
+ |
|
|
183
|
+ |
---
|
|
184
|
+ |
|
|
185
|
+ |
## 6. What this does not do
|
|
186
|
+ |
|
|
187
|
+ |
Named plainly so nothing here reads as more than it is.
|
|
188
|
+ |
|
|
189
|
+ |
- **No control-plane client.** There is no endpoint. Section 2.
|
|
190
|
+ |
- **No logical-computer or runtime-lease record**, no generation fence, no
|
|
191
|
+ |
command journal, and so no `may_have_started`. #37's recovery criterion is
|
|
192
|
+ |
unmet and stays unmet until those exist upstream.
|
|
193
|
+ |
- **No Box capacity evidence.** `OpenAgents.Capacity` counts connected
|
|
194
|
+ |
computers and broker-reported managed classes. It cannot see a Box — the one
|
|
195
|
+ |
OpenAgents-managed compute this application actually provisions. Closing that
|
|
196
|
+ |
loop means classifying the Box's isolation and egress, which is the decision
|
|
197
|
+ |
`runtime_class("box") == nil` is holding open.
|
|
198
|
+ |
- **No new operations.** `start` and `cancel` are what the facade serves, so
|
|
199
|
+ |
they are what it advertises. #37's `attach`, `stop`, `checkpoint`, `restore`,
|
|
200
|
+ |
`fork`, and `destroy` are not capabilities this seam can honestly offer, and
|
|
201
|
+ |
a capability list that names operations no route serves is worse than none.
|
|
202
|
+ |
A Box can in fact be stopped, but only through `Fleet.stop/2` by label, on a
|
|
203
|
+ |
path the facade does not own.
|