# Camino Camino is the proposed database/object runtime for the next direction of Quixos. This is a working design note, not a spec. For status tracking across implementation passes, see `CAMINO_LEDGER.md`. ## Core Idea Camino is a live object graph database for Quixos-style programs. Instead of "packages with state contexts", the system becomes: - classes define schemas, methods, subscriptions, and UI affordances - instances are durable stateful objects - packages provide executable behavior for methods/channels/functions - objects and edges form a graph - state is live, subscribable, inspectable, and eventually collaborative The Smalltalk-ish version: - browse objects - inspect state - send messages - watch updates - edit schemas - compose programs out of durable live instances The old state context ID maps roughly to the new Camino object instance ID. A "program" becomes a root object instance of a root class, with references/edges to lower-level objects. ## Quixos Execution Philosophy Build the general-purpose path first. Quixos should prefer the Turing-complete package/runtime mechanism as the canonical execution substrate. Higher-level conveniences such as lambda-style functions, step-function-like orchestration, declarative queries, GraphQL-ish views, UI adapters, or schema-editor shortcuts should compile down to, wrap, or cleanly eject back to package-backed execution. The core question for a new feature is: ```text Can this be represented as ordinary package code with explicit schema, objects, operations, and provenance? ``` If not, the shortcut is probably too magical for the kernel. This means: - method calls are package-backed, not built into Camino - derived state is maintained or resolved by packages - declarative query/view systems are later adapters over package-backed materialization - package refs and resolved package artifacts are important provenance - "easy" special forms should have an escape hatch to general package code CRDT reconciliation is the main known exception. CRDT fields are specialized state machinery because their merge semantics are part of their storage type, not arbitrary application execution. ## V0 Kernel Keep the first implementation small: - class schema - object instance - typed field values - named edges - object subscriptions - method call -> package execution - operation provenance - schema migration placeholder Explicitly defer: - global graph query language - query subscriptions - privacy/sharing model - private fields/write permissions - root-scope GC details - jujutsu backend - cutover-stale redesign - package-slice optimizer - declarative adapter packages - full derived state execution - full React schema editor ## Object Model An object instance has: - global hard-to-guess ID - class ID - schema version - field values - outgoing/incoming edges - operation/provenance history - subscriptions - optional UI components/views later Useful object envelope: ```ts ObjectInstance { id classId schemaVersion fields edges createdAt updatedAt provenanceLog? } ``` The object envelope is Camino's center of gravity. CRDTs, blobs, mounts, packages, indexes, and UI should plug into it rather than define it. ## Classes A class defines: - fields - edge types - methods/functions - async callbacks/channels - constructor/default initialization - schema migrations - optional conflict-resolution hooks later - optional React component exports later Methods are backed by packages today. Later, individual functions/channels may be backed by smaller slice packages or lambda-style execution units. Constructor functions are schema-declared package functions. In v0 Camino first creates the base object, then invokes the constructor through orch. Constructors must be idempotent and tolerate retry. If the constructor fails today, the base object may remain; transactional constructors/rollback are an explicit future requirement. ## Schema Interfaces Camino schema interfaces are globally named contracts over object value surfaces. They are not protobuf inheritance. A `.camino.proto` message can be marked with `option (camino.interface)` and fields can declare interface requirements such as `required`, `readable`, `writable`, `watchable`, and a concrete type or type parameter. Classes declare `option (camino.implements)` with a namespaced interface ID and optional type bindings. Schema validation checks that the class has the required fields and operations. For stored fields, Camino's built-in storage supplies read/watch/write semantics. For custom fields, validation checks the declared operation service, using `Get` as the value type and `WatchStart`/`WatchStop` as the watchability contract. The first built-in interface is `quixos.react.ReactComponent`: ```text props: watchable readable value surface of type Props source: readable string ``` Generated TypeScript refs should carry implemented-interface information, e.g. `TaskCardRef = ObjectRef<...> & ReactComponentRef`. `source` is static-final for the initial React component path: it is attached to the schema/package implementation, not mutable per instance. The current Todo React runtime serves the compiled JavaScript module from its package build artifact. This is intentionally a v0 pull model. Later we may replace the string with a blob/artifact ref, package output ref, or static cache strategy, and may add parameterized static-final fields. Do not add mutable static state. ## Fields Fields have declared semantics, not just storage representation. More precisely, a field is a named value surface on an object. It may be a stored variable, but it does not have to be. Likely axes: - representation: scalar, json, blob, doc, ref, edge-set, mount - conflict behavior: replace, preserve_conflicts, crdt - implementation: built-in stored value, built-in CRDT, package-backed get/set, static-final package value, external substrate, later optimized built-in strategies - indexing: none, value index, inverse edge index, custom later - lifecycle: owned, borrowed/global link, external/mounted Do not make every object field a deep CRDT. Instead, make CRDT document primitives easy and default-feeling where they fit. Good CRDT field candidates: - rich text - collaborative docs - canvas-ish data - comments - shared lists/maps where generic merge is meaningful - counters/sets/maps when their CRDT semantics fit the domain Suspicious CRDT field candidates: - finite state machine status - money/accounting state - locks/leases/ownership - package boot lifecycle - migration status - external process handles - "exactly one active X" - multi-field invariants For invariant-heavy state, prefer method-backed mutation and class/package logic. ## Value Surfaces The cleaner model is operation-based: ```text field named value surface on an object may support get(params?) -> value may support set(params?, value, extra_args?) -> ack/result may support watch(params?) -> value updates signal/channel named event surface on an object may support subscribe(params?) -> events does not promise a retained current value ``` Stored Camino fields get these operations from Camino itself: ```text stored title get = read current stored value set = write through field conflict/storage policy watch = object/field op subscription ``` CRDT fields are built-in value surfaces whose setter is a merge operation: ```text crdt notes get = read current/materialized document state set = merge CRDT update watch = CRDT/document update subscription ``` Package-backed/custom fields use marker field types and point at a service that declares their typed operations: ```proto camino.CustomField chest_contents = 1 [ (camino.field_ops) = { implementation: SERVICE service: ".quixos.minecraft.MinecraftWorldChestContents" } ]; ``` For stored and built-in fields, use normal protobuf field types where they make the schema easier to read: ```proto string title = 1; camino.CrdtRichText notes = 2; ``` For custom/served fields, the marker type says "this is a value surface whose operations are served elsewhere." The service's `Get` return type is the value type. This avoids duplicating a value type in both the field declaration and the operation declaration. This is an intentional bifurcation: ```text stored/built-in field protobuf field type is the value type custom/served field marker field type points to a service service Get return type is the value type ``` The current v0 schema still says `LAZY resolver`; conceptually this should move toward `camino.CustomField` plus `implementation: SERVICE`. Parameterized field accessors are allowed. A field name can represent a family of values indexed by input parameters rather than a single stored variable: ```text MinecraftWorld.chest_contents({ x, y, z }) MinecraftWorld.player_location({ player_id }) TaskList.filtered_list({ status, tag, text }) ``` This is still a field because the result is value-like: it has a type, can be read, may be watched, may sometimes be written through a declared setter, and can appear in UI/query composition. Signals/channels are parallel but event-like: ```proto message MinecraftWorld { option (camino.channel) = { name: "player_events" implementation: SERVICE service: ".quixos.minecraft.MinecraftWorldPlayerEvents" }; } service MinecraftWorldPlayerEvents { rpc Subscribe(PlayerEventsQuery) returns (camino.StreamStartResult); rpc Unsubscribe(camino.StreamStopRequest) returns (camino.StreamStopResult); } ``` Older sketches may use direct function refs: ```proto option (camino.channel) = { name: "activity" subscribe: { symbol: "watchActivity" } input_type: "quixos.todo.ActivityFilter" }; ``` Prefer the service form because protobuf validates the operation payload types. Signals do not need a current value. Examples: - task activity events - build log lines - chat messages - Minecraft player joined/left - payment webhook events - UI gesture streams The old Quixos `on-channel-subscribe` idea maps to two schema hooks: ```text field.watch(params?) channel.subscribe(params?) ``` Both are demand-driven. When a client watches a field or subscribes to a channel, Camino records demand and asks orch to activate the package-backed handler for that object, slot, and parameter set. When demand drops to zero, Camino/orch should close the watch activation and let the package tear down its internal subscriptions. Streams may become a separate runtime-level resource later: ```text signal/channel = schema-level event surface stream = concrete runtime subscription ID with cursor/replay/backpressure ``` Do not introduce stream semantics until we have real pressure for cursors, backpressure, replay, fanout, or retention. ### Operation Services Use protobuf `service`/`rpc` declarations as a typed operation IDL, not as a promise that Camino exposes those operations directly as gRPC services. Canonical pattern: ```proto message MinecraftWorld { option (camino.class) = { id: { namespace: "quixos.minecraft" name: "MinecraftWorld" version: "1" } }; camino.CustomField chest_contents = 1 [ (camino.field_ops) = { implementation: SERVICE service: ".quixos.minecraft.MinecraftWorldChestContents" } ]; } message ChestQuery { int32 x = 1; int32 y = 2; int32 z = 3; } message ChestContents { repeated string item_ids = 1; } service MinecraftWorldChestContents { rpc Get(ChestQuery) returns (ChestContents) { option (camino.impl) = { package_namespace: "quixos.minecraft" package_name: "minecraft-runtime" symbol: "getChestContents" }; } rpc WatchStart(ChestQuery) returns (camino.WatchStartResult) { option (camino.impl) = { package_namespace: "quixos.minecraft" package_name: "minecraft-runtime" symbol: "startChestContentsWatch" }; } rpc WatchStop(camino.WatchStopRequest) returns (camino.WatchStopResult) { option (camino.impl) = { package_namespace: "quixos.minecraft" package_name: "minecraft-runtime" symbol: "stopChestContentsWatch" }; } } ``` Use one service per field or channel/signal surface. Standard method names are: ```text field service Get Set WatchStart WatchStop channel/signal service Subscribe Unsubscribe ``` The class field points to the operation service. Avoid service-level backrefs to the class/field as the primary binding; the object schema remains the source of truth for which fields/channels exist. Implementation refs live on the service RPCs, not on the class field. This keeps the object schema service-shaped: the field says which typed operation surface it uses, and each operation says which package symbol handles it. Package runtimes may expose a single operation object and let generated glue map standard RPC names onto methods of that object. In that case the implementation ref should identify both the exported symbol and the operation method: ```proto service TaskDisplayLabel { rpc Get(google.protobuf.Empty) returns (google.protobuf.StringValue) { option (camino.impl) = { package_namespace: "quixos.todo" package_name: "todo-runtime" symbol: "displayLabel" operation: "get" }; } rpc WatchStart(google.protobuf.Empty) returns (camino.WatchStartResult) { option (camino.impl) = { package_namespace: "quixos.todo" package_name: "todo-runtime" symbol: "displayLabel" operation: "watchStart" }; } rpc WatchStop(camino.WatchStopRequest) returns (camino.WatchStopResult) { option (camino.impl) = { package_namespace: "quixos.todo" package_name: "todo-runtime" symbol: "displayLabel" operation: "watchStop" }; } } ``` For package authors, `derivedWithDeps` is package-side sugar for producing such an operation object. The schema does not say "derived with deps"; it only asks for `Get`, `WatchStart`, and `WatchStop`. The runtime helper can synthesize watch operations by dependency-tracking the `get` function: ```ts export const displayLabel = derivedWithDeps({ async get(ctx) { const status = await ctx.object.status.get(); const priority = await ctx.object.priority.get(); const title = await ctx.object.title.get(); return `${status} ${priority} ${title}`; }, }); ``` Manual implementations use the same schema shape and export the same operation surface directly: ```ts export const chestContents = fieldOps({ async get(ctx, coords) { return readChest(ctx, coords); }, async watchStart(ctx, coords, sink) { return watchChest(ctx, coords, sink); }, async watchStop(ctx, handle) { await handle.close(); }, }); ``` Generated TypeScript should make this obvious enough that agents can see which operations are required. A later pass may use `satisfies` or generated operation interfaces to make the `get`/`watchStart`/`watchStop` contract explicit in package source. Open concern: if GraphQL-like adapters and derived read provenance become the main reactive surface, explicit watch operations may become lower-level runtime plumbing rather than the API most package authors use directly. Return values may eventually need provenance metadata: which object fields, parameterized fields, channels, or adapter subqueries contributed to this value. A client that watches the returned value could then implicitly register upstream watchers from that provenance graph, similar in spirit to automatic differentiation tracking a calculation graph. This may attach to watch IDs or derived evaluation IDs. Do not over-design this yet; keep `WatchStart`/`WatchStop` as the concrete operation surface for v0 while leaving room for provenance-driven reactivity to replace some manual watcher declarations later. For non-parameterized fields, use `google.protobuf.Empty` as the input type: ```proto service TaskComputedLabel { rpc Get(google.protobuf.Empty) returns (google.protobuf.StringValue); } ``` Conceptually, `Empty` is the singleton parameter set. Parameter sets must be serializable and canonicalizable because they become part of read/watch/cache identity: ```text field operation key = object_id + field_name + canonical_param_bytes ``` Canonical parameter bytes are semantic identity, not display formatting. Two parameter objects with the same protobuf/JSON value but different map key order must produce the same key. V0 can canonicalize by lowering the Camino `Value` map to normalized JSON with sorted object keys, then encoding that canonical string. Later, when typed protobuf field APIs move into Camino, the preferred form should be deterministic protobuf bytes for the operation input message. This key should eventually be reused by: - `GetField` and `WatchField` dedupe - package-backed watch demand leases - derived/adaptor cache entries - provenance/debug dependency records - cycle and fanout accounting - parameterized UI props subscriptions `protoc` validates message types used by `rpc` input/output, which avoids stringly `input_type`/`output_type` metadata. Custom options still contain a string service name because protobuf options do not have a native `ServiceDescriptorRef` value type. A Camino validator must resolve that service name against the descriptor set. Validator/linter rules should include: - `field_ops.service` resolves to exactly one service. - field service methods are limited to `Get`, `Set`, `WatchStart`, `WatchStop`. - channel service methods are limited to `Subscribe`, `Unsubscribe`. - custom field services define at least one useful operation. - `WatchStart` input matches `Get` input unless an explicit exception is added. - `Set` exists only for writable custom fields. - rpc `camino.impl` refs resolve to package descriptor exports. - descriptor capability kind matches the operation kind. - package/runtime codegen agrees with the service method signatures. Scalar returns may use standard protobuf wrappers. Camino and generated code can manually unwrap these: ```text google.protobuf.StringValue -> string google.protobuf.BoolValue -> bool google.protobuf.Int64Value -> int64 ... ``` Otherwise, the RPC return message is the value type. ### Custom Setters Computed/custom fields may declare setters. By default they are not writable, but a setter is useful when a write to a value-like surface needs domain logic instead of a blind register update. Example: `MinecraftWorld.player_location(player_id)` can be read from a running server, watched while a player moves, and set by external code to request a teleport. The setter can reconcile contested writes: ```text server says player moved to A external controller requests teleport to B setter decides whether B overrides A based on package/server causal state ``` This is the general-purpose path. If a custom setter pattern becomes common and optimizable, Camino can later grow a built-in storage/merge strategy for it, just as CRDT fields are built-in merge strategies today. ## Cache Semantics For v0, package-backed computed/custom reads should behave as if cache is always disabled. Reads should be side-effect-free where possible. Later cache policy can become explicit: ```text none ttl(duration) stale_while_revalidate(duration) retain_latest(priority?) package_invalidated ``` Possible future cache controls: - TTL/freshness metadata - stale/error/unavailable states - package-sent invalidation/bust signal - retention hints such as LRU priority or "cheap to recompute" - provenance for the package/function/input that produced a cached value Do not let cache become invisible state. A client or inspector should be able to tell whether a value is fresh, stale, unresolved, unavailable, or failed. ## Conflict Semantics Use a small set of conflict strategies for v0: ```text replace preserve_conflicts crdt ``` `replace` means concurrent writes are allowed and one value becomes canonical by deterministic operation ordering. This is LWW-ish, but should not depend on wall clock time. Use for low-stakes scalars/preferences where losing a concurrent write is acceptable. `preserve_conflicts` means concurrent writes do not get silently erased. Normal reads can expose a canonical value, but conflict-aware code can inspect competing values and resolve them. This is the Camino-friendly name for multi-value register behavior. It is a good default for human-authored scalar fields and single refs where overwrite loss would be surprising. `crdt` means the field's own CRDT type defines merge behavior. Use for CRDT text, rich text, json docs, lists, maps, sets, counters, etc. Private/method-only/package-only mutation is orthogonal to conflict strategy and is deferred. Possible future axes: ```text commit mode: async | sync_authoritative mutation policy: direct | method | package_only ``` ## CRDT Documents CRDTs should be first-class doc-type primitives, not necessarily the merge logic backing every field. Automerge is the preferred first backend unless implementation work reveals a strong reason not to use it. It supports collaborative text, rich text marks and block markers, lists, maps, counters, and JSON-like documents. Prefer explicit CRDT field types: - CrdtText - CrdtRichText - CrdtJson - CrdtList - CrdtMap - CrdtSet - CrdtCounter Avoid leaking too much CRDT taxonomy such as `g_counter` into app schemas unless there is a concrete reason. ### Structured Documents Do not make a whole structured app object one giant CRDT JSON document by default. Use "CRDT islands": - Camino object graph owns durable structure, identity, lifecycle, edges, methods, and migrations. - CRDT fields own collaborative local content inside an object. Example: for a Google Forms-like app, prefer: ```text Form title: CrdtText description: CrdtRichText questions: ordered edge list -> Question Question prompt: CrdtRichText helpText: CrdtRichText kind: replace/preserve enum options: ordered edges -> Option, or a CRDT list if that is enough Option label: CrdtText stable value/id ``` Inside a `conflict: crdt` field, the nested values are governed by that CRDT field type's rules. The policy stops at the field boundary. ## Edges And Refs Refs and edges are the same underlying graph concept. A ref-looking field in a class schema is a declaration site for a canonical edge type, not proof that the edge is owned by that class forever. A simple ref can lower to a named edge with cardinality: ```ts field assignee: Ref ``` lowers to something like: ```ts edge Assignee { from Task.assignee optionalOne to User.assigned_tasks many } ``` A richer edge can have its own fields: ```ts edge Membership { from User.memberships manyUnique to Org.members manyUnique role: Role joinedAt: Time } ``` Metadata-free edges and metadata-bearing edges should use the same storage/subscription/indexing model. Schema controls: - edge type identity - endpoint projection names - endpoint class/interface constraints - per-endpoint cardinality - uniqueness - indexing Edge identity and cardinality matter. It should be possible to express whether there can be more than one edge of a given type between the same source/target. The current implementation supports `optional_one`, `exactly_one`, `many`, `many_unique`, `many_ordered`, and `many_unique_ordered`. It checks cardinality on both endpoints, but only has one stored ordinal, so edges that are ordered on both endpoints are rejected for now. Inline proto syntax is still preferred over a separate edge-schema file for ergonomics. The inline declaration should lower to a canonical edge type record with two endpoint projections: ```proto camino.Ref for_object = 3 [ (camino.edge) = { id: { namespace: "quixos.todo" name: "TaskReactComponent" version: "1" } this_endpoint: { interface: { namespace: "quixos.react" name: "ReactComponent" version: "1" } projection: "for_object" cardinality: EXACTLY_ONE } other_endpoint: { class: { namespace: "quixos.todo" name: "Task" version: "1" } projection: "admin_components" cardinality: MANY_UNIQUE_ORDERED indexed: true } } ]; ``` This lets a schema add an extension projection to another schema without forking that other schema. For example, `TodoReactComponent` can define `for_object`, while `Task` can still show an `admin_components` projection in the admin UI by registry discovery. Future edge work: - a first-class registry/query API instead of clients scanning all class schemas - anonymous or autogenerated edge IDs that reconcile to stable canonical edge identities - per-endpoint ordinal storage if ordered:ordered edges become necessary - edge interfaces/tags for semantic discovery, such as admin panel component edges, without baking every projection name into clients ## Document-Local Refs CRDT documents should not splice raw global UUIDs into content without Camino knowing they are references. Use document-local aliases plus a sidecar. Doc content contains a small local alias: ```json { "type": "mention", "ref": "r17" } ``` The sidecar maps aliases to Camino refs/edges: ```json { "r17": { "decl": "note_mentions", "target": "camino://Task/018f..." } } ``` The schema maps the local declaration to a real namespaced/versioned type. Distinguish: - soft ref: backlink/index/search/rendering reference; no ownership/lifecycle implication - edge: proper Camino edge projected from the sidecar; participates in graph semantics Prefer alias-per-occurrence. Multiple aliases may target the same object. Benefits: - docs stay portable/readable - UUIDs are not invisible substrings - indexing has one canonical place to inspect - references can have types - references can be validated - references can be remapped during clone/import - references can be marked dangling when targets are deleted For now, avoid heavy sugar here. Raw strings like `"Mentions"` are too loose for indexing/migration/graph semantics. Use structured namespaced/versioned refs in the canonical schema IR. ## Schema Authoring Use protobuf as the initial authoring syntax. Do not build a custom parser for v0. Pipeline: ```text .camino.proto files -> protoc parses them -> FileDescriptorSet -> Camino compiler reads descriptors + custom options -> ClassSchema IR -> runtime/storage/codegen ``` Protobuf gives: - parser - descriptors/reflection - field numbers - generated types - cross-language method payloads - wire-compatible evolution discipline But protobuf descriptors alone are not Camino's full schema, because Camino also needs: - field conflict/storage policy - CRDT/doc kind - edge declarations - edge cardinality - inverse associations - method declarations - package execution bindings - subscriptions/channels - migration functions - document-local ref declarations - mounted external state - UI/component exports later So the canonical internal schema should be Camino's own IR, likely itself encoded as protobuf messages. Sketch: ```proto message ClassSchema { string id = 1; uint32 version = 2; repeated FieldSchema fields = 3; repeated EdgeSchema edges = 4; repeated MethodSchema methods = 5; repeated Migration migrations = 6; } message FieldSchema { string name = 1; TypeRef type = 2; StorageKind storage = 3; ConflictStrategy conflict = 4; } message EdgeSchema { string name = 1; TypeRef from_class = 2; TypeRef to_class = 3; Cardinality cardinality = 4; repeated FieldSchema fields = 5; } ``` In v0, write `.proto` files with Camino custom options and lower them to `ClassSchema`. Later, a visual editor or nicer schema syntax can also emit the same IR. ## Namespaced And Versioned References Use structured refs for semantic identity. Avoid bare strings for anything that matters to indexing, migrations, execution, or graph semantics. Useful general shape: ```proto message SymbolRef { string namespace = 1; string name = 2; string version = 3; optional string hash = 4; } ``` Use this shape, or a close relative, for: - class IDs - edge type IDs - doc ref type IDs - method function IDs - migration function IDs - CRDT doc kinds if needed - component export IDs later Local strings are still fine for local names: - field name - method name - alias ID like `r17` - schema-local ref declaration like `note_mentions` But local names should lower to structured refs in the canonical schema IR. ## Migrations Every class has schema versions and migrations. For now, migrations can be named function hooks, not a declarative migration language: ```proto message Task { option (camino.class) = { version: 3 migrations: [ { from: 1, to: 2, function: ... }, { from: 2, to: 3, function: ... } ] }; } ``` The function ref should be structured and resolve to a package/function/symbol. Future migration specs may handle common cases declaratively: - rename field - split field - field -> edge - edge -> field - default new field - materialize derived value - start new CRDT epoch For ordinary fields, migration transforms current materialized values. For edges, migration transforms relation records. For CRDT docs, migration probably needs epoching or materialized migration: - read current accessible state - produce new doc/state version - future writes target new version - offline stale devices may need repair/import/rejection This is acceptable for now. CRDT migration is known-risk, not a blocker. ## Packages And Function Refs Packages stop being the conceptual center. Schemas/classes are the semantic center; packages become execution providers. A class schema declares executable slots and points each slot at structured Quixos code refs: ```text Task.assign -> quixos.todo/task-runtime#task.assign Document.renderPreview -> quixos.docs/document-ui#document.render_preview MinecraftWorld.start -> quixos.minecraft/minecraft-server#world.start ``` Function refs should be structured, not just strings: ```proto package quixos; message FunctionRef { string package_namespace = 1; string package_name = 2; string symbol = 3; optional string version_ref = 4; } ``` `version_ref` might be a VCS hash, a flake hash, or a content-addressed package artifact ref. Important distinction: - declared ref: what the schema slot asks for - resolved ref: exact package artifact that was actually executed For migrations especially, provenance should record the exact resolved ref, not only the declared one. Package descriptors are Quixos runnable capability manifests. They prove that a code ref exists, what kind of slot it can satisfy, how to run it, and what build or runtime protocol it needs. They do not own the class contract. For functions, descriptor capabilities should at least distinguish methods, field resolvers, and migrations so schema validation can catch a symbol that exists but is wired into the wrong kind of slot. For v0, a schema's executable slots should generally point into one package lineage for that class. If a workflow coordinates many object types, model that as a coordinator class with refs/edges to other objects, not as one package implementing unrelated classes. Eventually dependencies may be declared at function/channel/field granularity, but the schema remains the place where slot refs are declared. ## Orchestrator And Object Activation Camino should request work, not process lifecycle. The normal Camino-to-orchestrator path is: ```text InvokeFunction(functionRef, objectId, input) WatchFunction(functionRef, objectId, input) ``` The orchestrator decides whether that work requires starting a package process, reusing one, attaching an object, keeping state warm, or shutting something down later. Camino should not need to know whether a package is already running. Object activation is the replacement for old state-context opening, but it is an orchestrator-internal lifecycle model rather than a normal Camino API call: ```text old: open state context(package, stateContextId) new: invoke/watch/render/follow object -> orch activates package for object ``` Following, watching, rendering, resolving a lazy field, invoking a method, or otherwise using an object ref can create demand for object-scoped package code. The package may receive an object id, object snapshot, invocation input, and an object-scoped working directory similar to today's state-context directories. Activation has an explicit end. The exact policy is not settled, but v0 should assume: ```text demand/refcount + idle timeout + explicit close ``` When demand drops to zero, orch can start an idle timer, ask the package to detach/close, and eventually kill it if it leaks side effects or memory. Some objects may declare package-backed sidecars or "babysitters" later: code that is expected to run while the object is active because the object represents external/mounted state, a UI surface, a server process, or another side-effectful runtime. This should still lower to function/watch/render work plus orch activation, not a separate execution substrate. Open questions: - exact close/deactivation semantics - how object working directories are named and migrated - whether activation state is ever durable or only orch-local - how to represent "keep warm" policy without making it a second runtime model - how packages report readiness, freshness, and graceful shutdown progress ## Method Calls Make method calls explicit operations. Avoid "random code mutates JSON" as the main model. Method call record should include: - method name - target object - args - package/function - actor/provenance - result or error - state changes caused Open semantic question: should objects be actor-like? Current leaning: each object should at least conceptually serialize its own method calls. This makes invariant-heavy logic easier. Multi-object consistency can remain explicit/heavier later. Direct field writes can still exist for simple data. ## Subscriptions Initial subscription model can stay object-oriented: - subscribe to object - maybe subscribe to field/deep doc - maybe subscribe to edge set Query subscriptions can wait. They are useful but not needed for step one. Be careful with deep graph subscriptions. It is easy to accidentally pull in a large object graph without explicit scope boundaries. ## Derived State And Adapters Derived state should not reintroduce old Quixos "values" as a separate ontology. If something is subscribable, debuggable, referenceable, migratable, or worth showing in an inspector, it should usually have Camino object identity. The default pattern: ```text source objects -> watched by package process -> package writes or resolves derived object/field -> UI/other packages subscribe to that object/field ``` Distinguish: - authoritative objects: source of truth for user/system state - materialized derived objects: package-maintained outputs with provenance - custom/computed fields: resolved only when read or watched - external-backed objects/fields: canonical state lives inside package-owned substrate such as a Minecraft server, filesystem, Postgres, or process memory Parameterized computed fields handle cases where there may be infinitely many possible values to ask for. Example: ```text MinecraftWorld level/process handle ChestView world -> MinecraftWorld x/y/z coordinates contents: custom field resolved by minecraft-runtime#getChestContents ``` `ChestView.contents({ x, y, z })` may not be computed until something reads or watches that parameter set. On watch, Camino should ask the package for a watcher. When no watchers/read leases remain, the resolver can detach. Non-stored fields need explicit freshness/error semantics: ```text unresolved | resolving | fresh | stale | error | unavailable ``` That metadata should make it clear whether an empty value is real, stale, uncomputed, or blocked by a resolver failure. Declarative tools such as GraphQL-ish state mapping should come later as shortcuts that generate or configure package-backed derived objects/fields. They must cleanly fall back to package code. ### GraphQL-ish Adapters GraphQL-like adapters should start life as ordinary packages, not Camino kernel features. A future built-in query engine can be an optimized replacement for the same pattern. Sketch: ```text TaskList.filtered_list(filters) -> TaskList package resolves/constructs a GraphQLAdapter for this TaskList -> GraphQLAdapter reads TaskList(ID).items[*].type/status/title/etc. -> adapter subscribes to the subfields needed for the query -> adapter publishes filtered list updates ``` The adapter may be: - constructed by the owning object's constructor when all inputs are known - constructed lazily when a parameterized field is first read/watched - reused across subscribers with the same object/parameter set - owned by a normal Camino object if it needs identity, state, or inspection Watching a field like `TaskList.filtered_list(filters)` may therefore activate the TaskList package first, so it can decide how to construct or route through the adapter. This is acceptable: it keeps the declarative shortcut ejectable to ordinary package code. Evaluation graphs can contain cycles: ```text field A depends on adapter B adapter B reads field C field C indirectly watches field A ``` V0 should not try to prove all cycles impossible declaratively. It should protect the runtime: - track evaluation stack/object/field/params during reads - fail or warn on direct cycles - impose depth and fanout limits - record dependency edges for debugging - expose cycle warnings in the admin inspector - avoid hidden eager evaluation of large graph scopes ### Transparent Writes Through Adapters Some adapters can forward writes to underlying state. A derived field does not have to be read-only if its schema declares a setter. Example: a query result may expose a CRDT document field that is merely passed through from an underlying object. If the adapter can prove the path is a simple projection, a write to the derived surface can be forwarded to the original CRDT field instead of becoming a separate mutation API. This gives adapters a "transparent" feel: ```text write TaskList.filtered_list(filters)[3].notes -> adapter recognizes notes as pass-through -> forwards CRDT update to Task.notes ``` Do this carefully. A transparent write is only valid when the adapter can unambiguously map the write target back to an underlying object/field and when the target field's setter/merge policy accepts that write. Otherwise the write must fail or route through an explicit method/custom setter. ## Provenance Track operation provenance early: - actor - package/function - logical timestamp/op ID - schema version - causal dependencies - correlation/cause ID - exact resolved package/function ref where applicable This helps debugging, replay, migrations, future permissions, and agent-written programs. ### Package Runtime Identity Package code must not self-assert actor identity by sending arbitrary `actor` fields or package headers to Camino. Orch owns runtime activation and should mint short-lived capability tokens for package runtime processes. Package runtime helpers attach those tokens when calling Camino. Camino verifies the token with a shared secret or future stronger mechanism and derives package provenance from the signed claims. V0 implementation uses `CAMINO_RUNTIME_AUTH_SECRET` as the shared signing secret. The dev start scripts for Camino and orch create/read the same local secret under `quixos-instance/.var` when the env var is not already set. Orch mints `CAMINO_RUNTIME_AUTH_TOKEN` for spawned package processes, and the TypeScript package runtime helper attaches it to Camino RPCs. If orch has a secret, spawned package processes also receive `CAMINO_RUNTIME_AUTH_REQUIRED=1` so helper startup fails if the token is missing. V0 token claims are intentionally small: ```text package_namespace package_name descriptor_version runtime_key issued_at ``` The token proves only "this request came through a runtime process spawned for this package descriptor." It is not yet a user permission model and does not solve package sandboxing. A compromised package can still use its own package token, but it cannot claim to be a different package without orch's signing secret. Camino rejects invalid runtime-token headers. Requests with no runtime token are still accepted as `local-rpc` in v0 so CLI/dev tooling can mutate objects before we have a real user/session auth layer. That means this is runtime provenance enforcement, not full write authorization. Later this should grow into: - invocation/watch correlation IDs - exact function ref and schema slot provenance - expiry and rotation - per-object/field capability scope where useful - a user/session auth path distinct from package runtime auth - Camino-side rejection for writes whose caller identity cannot be proven - audit records that distinguish user intent from package-side effects ## Mounted/External State Some fields may represent external or mounted state: - filesystem-like state - Postgres instance - Minecraft server world - process-owned runtime data - large blob/doc store These should not pretend to be ordinary CRDT fields. They are object fields with special storage/activation semantics. ## Possible V0 Protocol Tiny first protocol, exposed through Connect RPC rather than REST: ```text createObject(classId, initialFields) getObject(id) setField(id, field, value) addEdge(from, type, to, fields?) removeEdge(edgeId) callMethod(id, method, args) subscribeObject(id) migrateObject(id, targetSchemaVersion) ``` If this protocol feels good, the rest has a place to attach. Plain HTTP should be reserved for health checks and future metrics. The Camino RPC shape should also inform future orchestrator/package RPC cleanup so Quixos does not accumulate several unrelated control protocols. ## Running Example: Todo Keep the fixture small. No users, no permissions, no saved query/list views. Classes: ```text TodoApp root object for one todo program instance owns projects stores app-level settings later Project collection of tasks can be archived owns task ordering Task main work item title, notes, status, due date, priority can link to tags, comments, subtasks Comment discussion/note object attached to a task no author for now useful CRDT doc example Tag reusable label object linked to many tasks ``` Graph: ```text TodoApp -[HasProject]-> Project Project -[HasTask]-> Task Task -[HasSubtask]-> Task Task -[TaggedWith]-> Tag Task -[HasComment]-> Comment ``` Sketch: ```proto message TodoApp { option (camino.class) = { version: 1 }; string name = 1 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; repeated Ref projects = 2 [ (camino.edge) = { name: "HasProject" to: "Project" cardinality: MANY_ORDERED inverse: "apps" } ]; } ``` ```proto message Project { option (camino.class) = { version: 1 }; string name = 1 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; string description = 2 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; bool archived = 3 [ (camino.field) = { conflict: REPLACE } ]; repeated Ref tasks = 4 [ (camino.edge) = { name: "HasTask" to: "Task" cardinality: MANY_ORDERED inverse: "project" } ]; } ``` ```proto message Task { option (camino.class) = { version: 1 methods: [ { name: "complete", function: ... }, { name: "reopen", function: ... }, { name: "archive", function: ... } ] }; string title = 1 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; CrdtRichText notes = 2 [ (camino.field) = { conflict: CRDT } ]; TaskStatus status = 3 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; int64 due_at = 4 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; Priority priority = 5 [ (camino.field) = { conflict: REPLACE } ]; repeated Ref tags = 6 [ (camino.edge) = { name: "TaggedWith" to: "Tag" cardinality: MANY_UNIQUE inverse: "tasks" } ]; repeated Ref comments = 7 [ (camino.edge) = { name: "HasComment" to: "Comment" cardinality: MANY_ORDERED inverse: "task" } ]; repeated Ref subtasks = 8 [ (camino.edge) = { name: "HasSubtask" to: "Task" cardinality: MANY_ORDERED inverse: "parentTasks" } ]; } ``` ```proto message Comment { option (camino.class) = { version: 1 }; CrdtRichText body = 1 [ (camino.field) = { conflict: CRDT } ]; int64 created_at = 2 [ (camino.field) = { conflict: REPLACE } ]; } ``` ```proto message Tag { option (camino.class) = { version: 1 }; string name = 1 [ (camino.field) = { conflict: PRESERVE_CONFLICTS } ]; string color = 2 [ (camino.field) = { conflict: REPLACE } ]; } ``` This fixture tests: - root object - object creation - ordered edges - unique edges - self-referential edges - CRDT doc fields - preserve-conflicts scalar fields - method-backed mutations - inverse associations - schema migration later ## One-Sentence Version Camino is a live, schema-aware object graph database where durable instances have typed fields and typed edges, packages provide behavior for class methods and channels, CRDT document primitives are available where they fit, and the whole system is inspectable, subscribable, migratable, and eventually editable through generated React/admin views.