-
An LLM just ran into We don't seem to have docs to teach LLMs to make components for orchestration contexts. I tried to get an LLM to write such docs; it's taking a lot longer than I hoped. I'll share what I've got so far... |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments
-
Using the Exo APIThe The example below defines an exo that greets a nickname and lets a caller update it. const GreeterI = M.interface('Greeter', {
greet: M.call().optional(M.string()).returns(M.string()),
setNick: M.call(M.string()).returns(),
});
/** @param {Zone} zone */
export const prepareGreeter = zone =>
zone.exoClass('Greeter', GreeterI, nick => ({ nick }), {
greet(greeting = 'Hello') {
return `${greeting}, ${this.state.nick}`;
},
setNick(nick) {
this.state.nick = nick;
},
}); The interface definition ensures callers must supply valid arguments: bobGreeter.greet(123) results in: Error {
message: 'In "greet" method of (Greeter): arg 0?: number 123 - Must be a string',
} The The This design is well-suited to developers coming from strongly typed or RPC-driven frameworks: the behavior is precise, testable, and enforced at the method boundary. Creating Multiple InstancesSo far we've defined how to construct an exo, but not how to make more than one. In most frameworks, you'd instantiate a class or call a factory function. With This is where the import { makeHeapZone } from '@agoric/zone';
import { prepareGreeter } from './greeter.js';
test('Greeter exoClass', t => {
const zone = makeHeapZone();
const makeGreeter = prepareGreeter(zone);
const bobGreeter = makeGreeter('Bob');
{
const actual = bobGreeter.greet();
t.is(actual, 'Hello, Bob');
}
});
const greeterA = makeGreeter('Alice');
const greeterB = makeGreeter('Bob');
// Each greeter holds its own state
E(greeterA).greet(); // "Hello, Alice"
E(greeterB).greet(); // "Hello, Bob" Notes on Kind BehaviorKinds—whether created via
Key/Value Store with a Mutable StoreIn contrast to the immutable Below is a minimal key/value store with separate reader and writer facets. Only the writer can mutate the store. import { M } from '@endo/patterns';
const StoreKitI = {
reader: M.interface('ReaderFacet', {
get: M.call(M.string()).returns(M.or(M.string(), M.undefined())),
}),
writer: M.interface('WriterFacet', {
put: M.call(M.string(), M.string()).returns(),
delete: M.call(M.string()).returns(),
}),
};
export const prepareStoreKit = zone =>
zone.exoClassKit(
'StoreKit',
StoreKitI,
() => ({
store: zone.detached().mapStore('kvStore', {
keyShape: M.string(),
valueShape: M.string(),
}),
}),
{
reader: {
get(key) {
return this.state.store.get(key);
},
},
writer: {
put(key, value) {
this.state.store.init(key, value);
},
delete(key) {
this.state.store.delete(key);
},
},
},
); This pattern is especially useful when the state evolves over time and must remain durable between contract invocations. Greeter Kit with Faceted AuthorityIn the previous example, a single object allowed both reading and updating the nickname. Here, we split those responsibilities across two separate objects—called facets—within a single exo kit. Each facet has its own interface and only exposes the methods relevant to its role:
This separation supports the principle of least authority: a caller with access to one facet cannot perform actions intended only for another. There’s also a structural difference in how method definitions are organized. With const GreeterOnlyI = M.interface('Greeter', {
greet: M.call().optional(M.string()).returns(M.string()),
setNick: M.call(M.string()).returns(),
});
const GreeterAdminI = M.interface('Greeter', {
setNick: M.call(M.string()).returns(),
});
/** @param {Zone} zone */
export const prepareGreeterKit = zone =>
zone.exoClassKit(
'GreeterKit',
{ greeter: GreeterOnlyI, admin: GreeterAdminI },
nick => ({ nick }),
{
greeter: {
greet(greeting = 'Hello') {
return `${greeting}, ${this.state.nick}`;
},
},
admin: {
setNick(nick) {
this.state.nick = nick;
},
},
},
); One additional difference to note is how the methods are structured. In |
Beta Was this translation helpful? Give feedback.
-
Outline: Everything About Exos, Durable Objects, and Upgrade
1. IntroductionIf you’ve already built the greeter example, you’ve defined a kind—an exo with persistent state and clearly defined behavior. This guide builds on that foundation. Exos are how the Agoric platform represents persistent, remotable, and upgradeable objects. They are created via The exo system is key to structuring contracts that survive upgrades while enforcing clear separation of authority. 📚 Source: Zoe Contract Upgrade Guide, SwingSet Virtual Objects 2. Kinds and ZonesIn practice, a kind is a function that makes durable objects. Each object has a stable identity, its own persistent state, and a fixed behavior surface defined once in advance. A zone is where kinds live. You get one from your contract environment, and use it to define exos with durable state. Subzones can be used to isolate namespaces. Here’s a familiar example: const prepareGreeter = zone =>
zone.exoClass('Greeter', GreeterI, nick => ({ nick }), {
greet(greeting = 'Hello') {
return `${greeting}, ${this.state.nick}`;
},
}); You call 🔍 Terminology: 📚 Source: SwingSet Virtual Objects 3. State ManagementEvery instance of a kind gets its own // ✅ OK:
this.state.nick = 'Bob';
// ❌ NOT OK:
this.state.nick = { x: 1 };
this.state.nick.x = 2; // runtime error!
// ❌ Also NOT OK:
this.state.list = harden([]);
this.state.list.push('item'); // runtime error! Rules:
To manage evolving or nested state, consider using a store (see next section). 📚 Source: Zoe Upgrade Guide – Kinds 4. Stores (Mutable Internal State)If you want to track internal data that must be updated deeply (e.g., a growing list or map), use a store. Use () => ({
table: zone.detached().mapStore('table', {
keyShape: M.string(),
valueShape: M.string(),
}),
}) A store is the sanctioned way to encapsulate durable internal data that changes over time. It supports operations like
📚 Source: Patterns from 5. Facets and KitsEach object created by
Here's a basic example that builds on the greeter concept: const prepareGreeterKit = zone => {
const GreeterKitI = {
reader: M.interface('ReaderFacet', {
greet: M.call().returns(M.string()),
}),
admin: M.interface('AdminFacet', {
setNick: M.call(M.string()).returns(M.undefined()),
}),
};
return zone.exoClassKit(
'GreeterKit',
GreeterKitI,
nick => ({ nick }),
{
reader: {
greet() {
return `Hello, ${this.state.nick}`;
},
},
admin: {
setNick(newNick) {
this.state.nick = newNick;
},
},
}
);
}; When you call the maker returned by Note that in this structure, the method definitions are grouped under the facet keys. Each method implementation still uses 📚 Source: patterns from 6. Baggage and Tag CollisionsThe tag (first argument to To avoid this, use subzones: const subZone = zone.subZone('kv');
const prepareStore = subZone.exoClass(...); This ensures unique namespaces and prevents kind identity clashes. 📚 Source: Zoe Upgrade Guide 7. Contract Upgrade ConstraintsWhen a contract is upgraded, all durable kinds must preserve their state shape and behavior contracts. SwingSet restores each object by re-running the kind definition (via Upgrade constraints:
📌 You may refactor code and reorder logic, but kinds must remain compatible at the interface and state level. 📚 Source: Zoe Contract Upgrade Guide 8. Common Pitfalls and Debugging Clues🔸 Deep mutation this.state.foo = harden({ x: 1 });
this.state.foo.x = 2; // ❌ runtime error Always reassign the outer object ( 🔸 Array updates this.state.list = harden([]);
this.state.list.push('x'); // ❌ also fails Use stores if you need mutable collections. 🔸 Facet confusion If your method isn’t showing up, check which facet it’s in. 🔸 Baggage collisions Reusing the same zone or tag across upgrades without a subZone will cause identity mismatches. 📌 Use 9. Further Reading and References |
Beta Was this translation helpful? Give feedback.
-
AI Code Generation: Context and ConstraintsModern AI code generation tools—such as GitHub Copilot, ChatGPT, and Claude—are trained primarily on high-volume, mainstream open-source codebases. As a result, they perform best in ecosystems with strong conventions and broad representation in training data. These include:
These environments provide:
Why Agoric Patterns Are DifferentIn contrast, the Agoric ecosystem—particularly the
This means that:
The audience for these docs isn’t expecting autocomplete or boilerplate—they’re teaching patterns to the AI, not retrieving them. Intended AudienceThe primary audience is:
They are not looking for tutorials on syntax—but they do value:
These docs aim to enable AI tools to assist usefully, by first showing them what correctness looks like. |
Beta Was this translation helpful? Give feedback.
-
see also: |
Beta Was this translation helpful? Give feedback.
Outline: Everything About Exos, Durable Objects, and Upgrade
1. Introduction
If you’ve already built the greeter example, you’ve defined a kind—an exo with persistent state and clearly defined behavior. This guide builds on that foundation.
Exos are how the Agoric platform represents persistent, remotable, and upgradeable objects. They are created via
zone.exoClass()
orzone.exoClassKit()
, which define kinds—reusable blueprints for creating durable object instances with in…