Mission is an object-oriented Entity system. Every first-class domain object is represented by an authoritative Entity class, a schema module, and a contract module. This three-file architecture is canonical and must not be violated.
The older projection-oriented model is retired because it allowed broad derived payloads, surface views, transport payloads, and domain state to blur together. Mission now uses canonical Entity instances, schema-validated shapes, Entity contracts, and precise read models or events. The word Projection is not canonical vocabulary for Entity truth.
Decision
Every Entity follows this ownership rule:
<Entity>.ts -> Entity class, behavior, invariants, lifecycle, authority
<Entity>Schema.ts -> all Zod schemas and inferred TypeScript types
<Entity>Contract.ts -> EntityContract metadata that binds methods/events to schemas
The Entity class is the authoritative domain object. It owns identity, behavior, invariants, state transitions, lifecycle methods, process ownership when applicable, command availability, and the implementation of remote method targets. Entity instances are not passive records, DTO wrappers, state bags, projections, stores, controllers, or transport envelopes.
The abstract Entity base owns generic property hydration and schema-driven serialization for every concrete Entity instance. The Entity instance is the canonical live domain object after hydration. Generic Entity hydration parses input through the concrete Entity’s <Entity>Schema, applies schema-declared fields to the instance, materializes related Entity fields as related Entity instance properties when their schemas are registered in the Entity model catalogue, and keeps runtime capability properties outside the schema data contract. toData() serializes the current Entity instance back through <Entity>Schema for contracts, persistence, and surfaces. A stored data object, if an implementation keeps one, is only a rebuildable serialization snapshot/cache and must not become a second live truth.
Concrete Entity classes do not redeclare every schema field. The base/factory must apply all schema-declared fields at runtime. A concrete class may use TypeScript-only declarations such as declare id: string or declare recordingEntries: TerminalRecordingEntry[] for fields it reads or specializes, but those declarations are ergonomic typing aids rather than hydration requirements. Concrete Entity code must read and mutate instance properties directly instead of repeatedly calling toData() to access fields.
The schema module owns every serializable shape that belongs to the Entity boundary. A schema is any Zod schema, whether it describes the complete Entity instance, persisted storage, constructor input, method arguments, method responses, event payloads, transport messages, descriptors, snapshots, or acknowledgements. First-class Entity schemas are defined in <Entity>Schema.ts unless a submodule is deliberately used for a closed internal family and re-exported through the Entity schema boundary.
Entity boundary naming is singular by law. If <Entity>Schema already names the complete boundary-facing Entity shape, code must not introduce an alternate public alias such as <Entity>HydratedSchema, <Entity>RuntimeSchema, <Entity>ProjectionSchema, <Entity>ViewSchema, <Entity>SnapshotSchema, or matching Type aliases for that same Entity truth. A second public name means a second public concept and therefore requires distinct doctrine, distinct validation role, and distinct contract ownership. Otherwise the fields belong directly on <Entity>Schema and its mechanically inferred <Entity>Type.
The contract module owns no behavior. <Entity>Contract.ts declares the EntityContract object for the Entity and binds method names to argument schemas, result schemas, execution mode, event payload schemas, and UI metadata. It points at schemas owned by <Entity>Schema.ts and methods implemented by <Entity>.ts. It must not invent alternate argument names, duplicate schemas, perform behavior, normalize data, or become a router with domain decisions.
Public Entity event families are part of that same boundary ownership. If an event crosses the Entity boundary as a named public event, its payload schema belongs in <Entity>Schema.ts and its contract registration belongs in <Entity>Contract.ts. Registries, supervisors, adapters, workflow executors, and transport layers may emit or consume those events, but they must not become the owning definition site for public Entity event unions or payload shapes.
Entity inheritance and dependency direction are governed by ADR-0001.07. Generic Entity infrastructure is a parent abstraction and must remain independent of concrete Entity children, daemon registries, runtime services, adapters, and provider implementations. Concrete contract catalogues and daemon capability injection are daemon dispatch responsibilities, not base Entity responsibilities.
Entity Specification Workflow
Every general Entity creation or Entity modification task must use the entity-specification skill. Entity work includes changes to <Entity>.ts, <Entity>Schema.ts, <Entity>Contract.ts, Entity storage metadata, Entity command or event surfaces, Entity documentation, and ADR text that changes Entity architecture.
The skill is the required working procedure for keeping class, schema, contract, storage metadata, Entity documentation, and governing ADRs aligned. It is not limited to documentation generation. Before implementation begins, the Entity specification must settle ownership, canonical file boundaries, schema names, storage fields, method and event surfaces, invariants, storage metadata descriptions, and unresolved doctrine gaps.
When accepted ADRs and target Entity documentation intentionally lead current code, implementation must follow the accepted doctrine and mark current code as convergence evidence. Current code is not allowed to override the target specification in that mode.
Canonical Entity Schemas
For an Entity named <Entity>, these names have fixed meaning:
<Entity>Schemais the serializable shape of the complete hydrated Entity instance exposed by the Entity boundary. It is the mother schema for that Entity.<Entity>Typeis exactlyz.infer<typeof <Entity>Schema>. It is not hand-written, widened, aliased, or imported under another name.<Entity>StorageSchemais the persisted backend shape for the Entity. It contains only data that should be physically stored in the state store, filesystem, or future database.<Entity>StorageTypeis exactlyz.infer<typeof <Entity>StorageSchema>.<Entity>InputSchemais the validated input needed to create, register, or instantiate a new Entity when such input exists.<Entity>InputTypeis exactlyz.infer<typeof <Entity>InputSchema>.
When additional live boundary fields are needed, such as terminal attachment, adapter label, current turn title, or input-awaiting state, those fields extend <Entity>Schema itself unless doctrine defines a truly distinct boundary concept. They do not justify a second public Entity type family.
Method-specific schemas are named for the method or message they validate, such as <Entity>ReadInputSchema, <Entity>CommandInputSchema, <Entity>SendMessageInputSchema, or <Entity>ChangedSchema. They are still Zod schemas and therefore receive mechanically inferred Type exports by replacing only the final Schema suffix with Type.
If data is not backed by a Zod schema, it is not a first-class serializable Entity shape. Hand-written interfaces and type aliases may be used only for private non-serialized implementation details inside the owning module. They must not cross Entity, daemon, storage, transport, or surface boundaries.
Exported Zod schemas must name real Entity boundary concepts, not implementation conveniences. A schema module must not export a partial field bundle such as <Entity>PrimaryDataSchema, <Entity>BaseDataSchema, <Entity>CoreDataSchema, <Entity>CommonSchema, or <Entity>SharedSchema merely to avoid repeating identity, display, metadata, or other fields across <Entity>StorageSchema, <Entity>Schema, adapter construction, tests, or documentation. If those fields are stored, they belong directly on <Entity>StorageSchema; if they are exposed through the Entity boundary, they belong directly on <Entity>Schema; if they are daemon-only construction material, they remain private daemon implementation data rather than an Entity schema export. A narrower exported schema is valid only when it validates an independently named domain concept with its own boundary role, contract use, or storage relation.
Storage And Instance Shape
<Entity>Schema and <Entity>StorageSchema are intentionally different concepts.
<Entity>Schema describes the serializable Entity instance as exposed by the Entity boundary. It also defines which serializable fields generic Entity hydration applies to the live Entity instance and which fields toData() serializes back out. It may include persisted fields, derived read material, command descriptors, current process state, current transport state, related Entity data fields, or other state that is meaningful for a live Entity instance.
When the Entity boundary exposes first-class child Entities or relation-owned records as part of the complete Entity read, those children belong directly on <Entity>Schema. Do not create a parallel aggregate wrapper such as <Entity>IndexSchema, <Entity>ReadModelSchema, <Entity>ProjectionSchema, or another standalone composite file merely to bundle the owning Entity with child Entity arrays that are already part of the canonical hydrated Entity boundary. In that case, the owning <Entity>Schema itself is the hydrated aggregate.
<Entity>StorageSchema describes what is stored. It must not absorb derived UI material, command descriptors, transient transport details, or other fields merely because they appear in the hydrated Entity shape. If a field cannot or should not survive storage and recovery, it does not belong in storage. If a live field must survive recovery, it needs either a storage field or an explicit durable log/event record owned by the Entity.
In implementation terms, Entity<TData> means TData is the concrete Entity’s canonical serializable boundary data, not its persisted storage row by default. TStorage names are reserved for <Entity>StorageSchema output and storage adapter seams. Entity hydration consumes TData to populate instance properties; toData() reconstructs TData from current schema-declared instance properties. Persistence adapters and factories separately decide how <Entity>StorageSchema records are read, written, and hydrated into Entity instances.
Related Entity fields must not remain as duplicate mutable live data once they are materialized as related Entity instance properties. For example, TerminalSchema.recordingEntries serializes as TerminalRecordingEntryType[], while the live Terminal instance may hold recordingEntries: TerminalRecordingEntry[]. toData() serializes those related Entity objects back to their data shape. Runtime capability properties such as an AgentExecution-owned agentAdapter are excluded because they are not fields on <Entity>Schema.
For example, an AgentExecution Entity may expose current process and optional terminal transport state in AgentExecutionSchema, while AgentExecutionStorageSchema persists only the recoverable execution identity, scope, durable context, process reference, selected protocol, log references, and lifecycle fields that must survive daemon restart.
SurrealDB Schema Metadata
Entity storage schemas are also the canonical seam for future SurrealDB storage adapter provisioning. When an Entity is expected to be persisted through the Mission state store, its <Entity>StorageSchema should carry @flying-pillow/zod-surreal table and field metadata directly on the existing storage schema and storage fields. Do not create a parallel Surreal-only schema when the Entity storage schema already describes the persisted record.
The Entity schema module owns this metadata because it already owns the persisted shape. The storage adapter may compile that metadata into SurrealDB DEFINE TABLE, DEFINE FIELD, and DEFINE INDEX statements, but generated SurrealQL is adapter output, not the canonical schema source. Hand-maintained SurrealQL DDL must not become a second authority for Entity storage records.
Use these conventions for Entity storage schemas:
- Register the storage schema itself with
tablemetadata from@flying-pillow/zod-surreal. - Register persisted storage fields with
fieldmetadata from@flying-pillow/zod-surreal. - Use
descriptionmetadata for human-readable field and table documentation. The zod-surreal compiler may render that description as SurrealDBCOMMENTDDL becauseCOMMENTis SurrealQL syntax, but Entity schemas should not use acommentmetadata property. - Do not set
typeorstoragemetadata when zod-surreal can infer them from the Zod schema and the<Entity>StorageSchemashape. - Use explicit metadata only when it adds information the schema cannot infer, such as field names, references, relation/table kind, optional nested-field metadata, index declarations, analyzer declarations, assertions, defaults, sensitivity, searchability, or adapter-relevant descriptions.
- Define table-level indexes in the storage schema table metadata when they are part of the canonical storage/query seam for that Entity.
- Keep SurrealDB metadata storage-facing. It must not be used to smuggle domain behavior, command availability, workflow legality, UI presentation, or adapter-specific persistence decisions into schemas.
Shared identifier schemas belong in the Entity schema boundary. Use IdSchema for ordinary non-empty domain ids such as owner ids, agent ids, message ids, and journal ids. Use EntityIdSchema only for canonical Entity ids in table:uniqueId form. Entity-specific id schemas should be introduced only when the Entity has additional validation beyond the shared id contract.
Contract Discipline
Entity contracts are declarative. A contract method selects existing schemas; it does not define a parallel method model.
A contract method selects the narrowest existing canonical schema that already describes its argument or result. A read method that returns a full Entity returns <Entity>Schema; it must not introduce <Entity>ReadResponseSchema, <Entity>ReadResultSchema, <Entity>ResponseSchema, or another wrapper when the Entity schema is already the result. A method whose only argument is the target Entity identity uses EntityIdSchema directly as its argument schema; it must not introduce a method-specific locator or input object solely to repeat that id. A mutation that needs method-specific arguments uses an explicitly named input schema for those arguments. A mutation that returns an acknowledgement returns the explicitly named acknowledgement schema. A method that emits an event uses an explicitly named event payload schema. These schemas live in the Entity schema module and have inferred Type exports.
Concrete Entity method signatures should mirror those frozen contract shapes whenever the contract already identifies one precise argument type. If a contract method argument is EntityIdSchema, the corresponding concrete method should accept id: string and immediately validate it with EntityIdSchema.parse(...); it should not widen the signature back to unknown or wrap the id in a local { id } locator object. If a contract method argument is <Entity>InputSchema or another method-specific input schema, the concrete method should accept the corresponding inferred Type and validate that value at ingress. unknown belongs at generic transport or dispatch ingress, not as the default signature for concrete Entity behavior whose canonical argument is already frozen by the contract.
Entity classes may still expose an internal resolve(...) helper when behavior needs the hydrated Entity instance itself rather than the boundary-facing serialized data. That helper is not a second public Entity contract surface and must not invent a second locator concept. Its role is narrow: accept the same canonical identity argument shape as the read path, load the hydrated Entity instance through the Entity factory, attach required runtime capabilities, and return the object-oriented Entity instance so class or instance behavior can continue without duplicating hydration code.
Contracts must not contain:
- business logic.
- persistence logic.
- compatibility normalization.
- schema copies.
- ad hoc
z.object(...)definitions that belong in<Entity>Schema.ts. - owner-specific command wrappers for another Entity’s behavior.
- projection-specific result shapes when an existing Entity schema is the result.
The daemon may generically execute contracts, build command descriptors, validate method arguments, parse results, and publish events. The owning Entity class remains the only behavior authority.
The generic Entity layer may provide reusable invocation mechanics, but it must receive contract resolution and capabilities from its caller. A module is not generic Entity infrastructure if it imports every concrete Entity contract, constructs daemon registries, or contains post-command daemon behavior. Such modules belong in daemon-owned dispatch infrastructure.
Dependency Direction
The dependency direction for Entity architecture is one-way:
daemon / host / adapter layer
-> Entity contracts
-> concrete Entity classes
-> Entity schemas
-> generic Entity infrastructure
Generic Entity infrastructure must not point back upward into concrete Entity contracts, concrete Entity classes, daemon runtime modules, registries, adapters, terminal runtime modules, code intelligence services, or provider implementations. Dynamic imports are not an exception; they are still dependencies.
EntityExecutionContext is a narrow invocation envelope. The base Entity module may define only capabilities that are truly generic to Entity invocation, such as auth token and an Entity factory. Daemon-specific capabilities may be attached by daemon-owned dispatch code, but the base Entity module must not import daemon types or name daemon services as first-class base context fields.
Path-bearing context fields such as legacy surfacePath are convergence debt. Generic Entity invocation must not use a caller filesystem path as Entity identity or as a fallback owner reference. When a method needs a filesystem location, the owning domain must name that location precisely, such as workingDirectory, repositoryRootPath, missionWorktreeRoot, or Artifact root path, and must still address Repository and Mission Entities by repositoryId and missionId.
Retiring Projection Vocabulary
Projection is not canonical vocabulary for Entity truth. It hides too many different things: hydrated Entity data, storage data, Entity events, snapshots, view models, workflow-derived state, app pane state, and UI rendering material. New Entity work must not introduce Projection-named schemas, types, files, methods, transforms, or result shapes. When callers need complete Entity data, they use <Entity>Schema; when they need presentation-only data, the shape must be named as an app view, pane view, status view, timeline, snapshot, event, or other precise non-Entity-truth concept.
New code must use precise names:
Entityor<Entity>Schemafor complete hydrated Entity data exposed to callers.<Entity>StorageSchemafor persisted records.<Entity>InputSchemafor creation or instantiation input.Entity eventfor accepted daemon-published facts.System status snapshotor otherSnapshotSchemanames for true point-in-time read models.Open Mission app view,pane view,status view, or component-specific view model names for presentation-only data.Derived workflow statefor workflow-owned derivations.
Existing Projection names are convergence debt. They must be renamed or removed during the owning Entity refactor instead of being copied forward into Surreal storage, Entity contracts, daemon dispatch, or caller-facing Entity read shapes.
Implementation Rules
- Do make Entity classes thick, authoritative, and object-oriented.
- Do use the
entity-specificationskill for every general Entity creation or modification task. - Do put all Entity boundary Zod schemas in
<Entity>Schema.tsand infer all exported serializable types from those schemas. - Do make
<Entity>Schemathe complete serializable instance shape for the Entity. - Do make generic Entity hydration apply all
<Entity>Schemafields to the Entity instance and maketoData()serialize the current Entity instance back through<Entity>Schema. - Do materialize related Entity fields into related Entity instance properties when the hydrated schema field uses another registered Entity data schema.
- Do let concrete Entity classes declare only the schema fields they need TypeScript to understand; do not require child classes to redeclare every schema property.
- Do put owned child Entity arrays, linked Entity reads, and other canonical aggregate read material directly on
<Entity>Schemawhen they are part of the full Entity boundary. - Do make
<Entity>StorageSchemathe physical persisted shape and keep it narrower than the hydrated Entity shape when appropriate. - Do put persisted identity, display, and metadata fields directly on
<Entity>StorageSchemainstead of hiding them behind exported partial composition schemas. - Do register
<Entity>StorageSchemaand its persisted fields with zod-surrealtableandfieldmetadata when the Entity will be stored through the Mission state store. - Do make
<Entity>Contract.tsdeclarative metadata only. - Do route behavior through the Entity class named by the contract.
- Do make concrete Entity method signatures accept the exact schema-inferred argument type already selected by the contract when one precise argument type exists.
- Do use internal
resolve(...)helpers only to load hydrated Entity instances for behavior; keep them aligned with the same canonical identity shape as the corresponding read path. - Do keep generic Entity infrastructure independent of concrete Entity children and daemon runtime services.
- Do put concrete Entity contract catalogues, daemon registry injection, and post-command runtime behavior in daemon-owned dispatch modules.
- Do pass Entity-owned persisted records through registered Entity classes and storage schemas rather than raw storage drivers.
- Do use precise names for snapshots, events, inputs, storage records, method arguments, and view models.
- Do use
descriptionfor zod-surreal table and field documentation; generated SurrealQL may still use the SurrealDBCOMMENTkeyword. - Do not create behavior-owning controllers, executors, services, projections, stores, gateways, or adapters when the behavior belongs to one Entity class.
- Do not treat
<Entity>StorageSchemaas the Entity instance schema unless that concrete Entity truly exposes a storage row as its complete Entity instance shape. - Do not keep a related Entity data field and a materialized related Entity instance property as two independent mutable live truths.
- Do not require concrete Entity classes to hand-write getters, setters, or hydration code for every schema field.
- Do not introduce
Projection-named schemas, types, files, methods, transforms, or contract results for Entity data; use<Entity>Schemafor complete hydrated Entity data and a precise non-projection name for true presentation-only or point-in-time read models. - Do not hand-write exported serializable TypeScript types beside schemas.
- Do not alias schemas or schema-inferred types under alternate export names or import names.
- Do not alias schema-inferred types under different names.
- Do not widen concrete Entity method signatures to
unknownwhen the contract already freezes a single canonical argument type. - Do not introduce local locator wrappers such as
{ id }for a method whose only canonical argument isEntityIdSchema. - Do not create alternate public Entity type families such as
<Entity>HydratedType,<Entity>RuntimeType,<Entity>ProjectionType,<Entity>ViewType,<Entity>SnapshotType, or<Entity>ManagedTypewhen<Entity>Typealready names the complete Entity boundary shape. - Do not export partial composition schemas whose only purpose is sharing field groups between canonical schemas, adapter construction, tests, or documentation.
- Do not create a parallel aggregate schema file or
<Entity>IndexSchema/<Entity>ReadModelSchemawrapper when the shape is simply the canonical hydrated<Entity>Schemaplus owned child Entities. - Do not put ad hoc Zod objects in contracts when the shape belongs to the Entity schema module.
- Do not define public Entity event unions or event payload shapes in registries, supervisors, adapters, workflow executors, or transport helpers when those events belong to the Entity boundary.
- Do not import concrete child Entity contracts or classes from generic Entity infrastructure.
- Do not import daemon registries, runtime services, terminal runtime modules, code intelligence services, adapters, or provider implementations from generic Entity infrastructure.
- Do not hide daemon or runtime dependencies behind dynamic imports inside Entity classes.
- Do not add daemon-owned services to the base
EntityExecutionContexttype. - Do not create parallel Surreal-only schemas or hand-maintained SurrealQL DDL for Entity storage records when zod-surreal metadata can be attached to the canonical storage schema.
- Do not add redundant zod-surreal
typeorstoragemetadata when the compiler can infer it from the Zod schema and storage schema shape. - Do not persist command descriptors, view state, or presentation-only projection material as Entity storage data.
- Do not introduce owner-specific wrapper Entities or command vocabularies for another Entity’s methods.
- Do not use
Projectionas a synonym for Entity data, storage data, event payloads, snapshots, or UI state.
Consequences
- Entity architecture is unambiguous: class owns behavior, schema owns shapes, contract owns remote metadata.
- Future SurrealDB storage can map from
<Entity>StorageSchemawithout redefining Entity instance shape. - Open Mission and daemon transports share schema-backed payloads instead of copying shape declarations.
- Refactors remain local because changing an Entity shape means changing its schema module and inferred type exports.
- Derived views stay subordinate to Entity truth and cannot become accidental domain owners.
- AgentExecution and other process-owning Entities can expose live instance state without confusing that state with storage or terminal transport authority.