openoma 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. openoma-0.1.0/PKG-INFO +1009 -0
  2. openoma-0.1.0/README.md +1001 -0
  3. openoma-0.1.0/pyproject.toml +14 -0
  4. openoma-0.1.0/setup.cfg +4 -0
  5. openoma-0.1.0/src/openoma/__init__.py +62 -0
  6. openoma-0.1.0/src/openoma/adapter/__init__.py +4 -0
  7. openoma-0.1.0/src/openoma/adapter/base.py +82 -0
  8. openoma-0.1.0/src/openoma/adapter/protocol.py +42 -0
  9. openoma-0.1.0/src/openoma/builders/__init__.py +5 -0
  10. openoma-0.1.0/src/openoma/builders/contract.py +118 -0
  11. openoma-0.1.0/src/openoma/builders/flow.py +108 -0
  12. openoma-0.1.0/src/openoma/builders/work_block.py +78 -0
  13. openoma-0.1.0/src/openoma/core/__init__.py +29 -0
  14. openoma-0.1.0/src/openoma/core/condition.py +15 -0
  15. openoma-0.1.0/src/openoma/core/contract.py +40 -0
  16. openoma-0.1.0/src/openoma/core/flow.py +41 -0
  17. openoma-0.1.0/src/openoma/core/types.py +42 -0
  18. openoma-0.1.0/src/openoma/core/versioning.py +19 -0
  19. openoma-0.1.0/src/openoma/core/work_block.py +24 -0
  20. openoma-0.1.0/src/openoma/execution/__init__.py +27 -0
  21. openoma-0.1.0/src/openoma/execution/block_execution.py +50 -0
  22. openoma-0.1.0/src/openoma/execution/contract_execution.py +54 -0
  23. openoma-0.1.0/src/openoma/execution/events.py +48 -0
  24. openoma-0.1.0/src/openoma/execution/flow_execution.py +74 -0
  25. openoma-0.1.0/src/openoma/execution/types.py +34 -0
  26. openoma-0.1.0/src/openoma/store/__init__.py +4 -0
  27. openoma-0.1.0/src/openoma/store/memory.py +33 -0
  28. openoma-0.1.0/src/openoma/store/protocol.py +30 -0
  29. openoma-0.1.0/src/openoma/validation/__init__.py +29 -0
  30. openoma-0.1.0/src/openoma/validation/graph.py +70 -0
  31. openoma-0.1.0/src/openoma/validation/references.py +102 -0
  32. openoma-0.1.0/src/openoma.egg-info/PKG-INFO +1009 -0
  33. openoma-0.1.0/src/openoma.egg-info/SOURCES.txt +51 -0
  34. openoma-0.1.0/src/openoma.egg-info/dependency_links.txt +1 -0
  35. openoma-0.1.0/src/openoma.egg-info/requires.txt +1 -0
  36. openoma-0.1.0/src/openoma.egg-info/top_level.txt +1 -0
  37. openoma-0.1.0/tests/test_adapter.py +24 -0
  38. openoma-0.1.0/tests/test_block_execution.py +66 -0
  39. openoma-0.1.0/tests/test_builders.py +125 -0
  40. openoma-0.1.0/tests/test_condition.py +12 -0
  41. openoma-0.1.0/tests/test_contract.py +30 -0
  42. openoma-0.1.0/tests/test_contract_execution.py +41 -0
  43. openoma-0.1.0/tests/test_core_types.py +39 -0
  44. openoma-0.1.0/tests/test_events.py +24 -0
  45. openoma-0.1.0/tests/test_execution_types.py +22 -0
  46. openoma-0.1.0/tests/test_flow.py +43 -0
  47. openoma-0.1.0/tests/test_flow_execution.py +93 -0
  48. openoma-0.1.0/tests/test_smoke.py +49 -0
  49. openoma-0.1.0/tests/test_store.py +52 -0
  50. openoma-0.1.0/tests/test_validation_graph.py +90 -0
  51. openoma-0.1.0/tests/test_validation_references.py +96 -0
  52. openoma-0.1.0/tests/test_versioning.py +18 -0
  53. openoma-0.1.0/tests/test_work_block.py +31 -0
openoma-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,1009 @@
1
+ Metadata-Version: 2.4
2
+ Name: openoma
3
+ Version: 0.1.0
4
+ Summary: Opera Autonoma is designed to be a versatile and powerful tool for automating various tasks and processes. It provides a user-friendly interface and a wide range of features to help users streamline their workflows and increase productivity.
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pydantic>=2.12.5
8
+
9
+ # Openoma (Opera Autonoma) Specification
10
+
11
+
12
+ > **I vibe-coded this library via Claude Opus 4.6**
13
+ ---
14
+ > **Open Operational Model Architecture** — A declarative framework for modeling,
15
+ > composing, and tracking operational processes in organizations.
16
+
17
+ **Version:** 0.1.0-draft
18
+ **Status:** Draft
19
+
20
+ ---
21
+
22
+ ## 1. Philosophy
23
+
24
+ Openoma separates **what work is** from **how work is done**.
25
+
26
+ The framework is built on three principles:
27
+
28
+ 1. **Declarative over imperative.** Every entity — blocks, flows, contracts,
29
+ conditions, assessments — describes *what* should happen, never *how* to
30
+ execute it. Execution is a separate concern.
31
+ 2. **Composability.** Small units compose into larger units. Work blocks compose
32
+ into flows. Flows compose into contracts. Assessment itself is a flow.
33
+ 3. **Identity and immutability.** Every entity has a UUID. Every mutation
34
+ produces a new version. Executions reference a specific version, so the
35
+ definition of work never shifts under a running process.
36
+
37
+ ---
38
+
39
+ ## 2. Core Module — `openoma.core`
40
+
41
+ The core module defines the logical model of operational processes. It contains
42
+ no execution semantics.
43
+
44
+ ### 2.1 WorkBlock
45
+
46
+ A **WorkBlock** is the atomic unit of operational work. It declares what inputs
47
+ are needed and what outputs will be produced — like a single entry from a team's
48
+ runbook.
49
+
50
+ A WorkBlock is **reusable**: it can appear in many flows because it describes
51
+ *what* needs to be done, not a specific instance of doing it.
52
+
53
+ | Field | Type | Description |
54
+ |-------------------|-------------------------------|-----------------------------------------------------------------------------|
55
+ | `id` | `UUID` | Stable identity across versions. |
56
+ | `version` | `int` | Monotonically increasing. `(id, version)` is globally unique. |
57
+ | `name` | `str` | Human-readable name. |
58
+ | `description` | `str` | Natural-language description of the work to be performed. |
59
+ | `inputs` | `list[PortDescriptor]` | Declared input ports. |
60
+ | `outputs` | `list[PortDescriptor]` | Declared output ports. |
61
+ | `execution_hints` | `list[str]` | Optional capability tags (e.g. `"sql-access"`, `"api-gateway"`). Never names a specific executor. |
62
+ | `metadata` | `dict` | Arbitrary key-value pairs (tags, labels, ownership). |
63
+
64
+ #### PortDescriptor
65
+
66
+ A **PortDescriptor** names and describes a single input or output of a WorkBlock.
67
+
68
+ | Field | Type | Description |
69
+ |---------------|----------------|---------------------------------------------------------------|
70
+ | `name` | `str` | Identifier unique within the block's input or output list. |
71
+ | `description` | `str` | Natural-language description. |
72
+ | `required` | `bool` | Whether this port must be supplied / will always be produced. |
73
+ | `schema` | `dict \| None` | Optional. A JSON Schema document describing the expected shape of data on this port. The framework does not enforce it; consumers and adapters may validate against it. |
74
+ | `metadata` | `dict` | Optional. Hints for consumers (e.g. expected media type). |
75
+
76
+ > **Design decision — execution hints.** WorkBlocks may carry *capability hints*
77
+ > that describe what tooling or access is needed, but they never name a specific
78
+ > executor. The binding of a block to an executor is the responsibility of the
79
+ > Execution module. This keeps the core model clean while still allowing
80
+ > execution planners to filter and match blocks to capable executors.
81
+
82
+ ---
83
+
84
+ ### 2.2 Condition
85
+
86
+ A **Condition** is a **value object** embedded in an edge. It governs traversal
87
+ between nodes in a flow. Conditions are declarative — the framework stores them
88
+ but never evaluates them. Evaluation is the responsibility of an external
89
+ execution engine.
90
+
91
+ Conditions are not first-class entities. They have no UUID and no version
92
+ lineage. They exist only as part of the edge that contains them. If the same
93
+ logical condition is needed on multiple edges, it is duplicated (or the
94
+ consuming application maintains a condition template library outside Openoma).
95
+
96
+ | Field | Type | Description |
97
+ |---------------|-----------------|--------------------------------------------------------------------|
98
+ | `description` | `str` | Natural-language description (prompt-like). Always present. |
99
+ | `predicate` | `dict \| None` | Optional structured predicate (e.g. JSONLogic, CEL expression, or a simple expression tree). |
100
+ | `metadata` | `dict` | Optional. |
101
+
102
+ A Condition with only a `description` is evaluated by a human or an LLM agent.
103
+ A Condition with a `predicate` can be evaluated programmatically. Both can
104
+ coexist — the description serves as documentation, the predicate as machine
105
+ logic.
106
+
107
+ ---
108
+
109
+ ### 2.3 Flow
110
+
111
+ A **Flow** is a directed graph of nodes connected by edges with optional
112
+ conditions. It is the primary composition mechanism in Openoma.
113
+
114
+ Key properties:
115
+
116
+ - A flow is **not necessarily fully connected**. It may contain multiple
117
+ disconnected subgraphs representing independent work streams.
118
+ - A flow's nodes reference **WorkBlocks**.
119
+ - A flow can appear in multiple contracts.
120
+
121
+ #### 2.3.1 NodeReference
122
+
123
+ A **NodeReference** is a *slot* within a flow that points to a WorkBlock. The
124
+ same WorkBlock can be referenced multiple times within the same flow (e.g. two
125
+ parallel lanes doing the same type of work with different executors). Each slot
126
+ has its own UUID so that executions, edges, and conditions can address it
127
+ unambiguously.
128
+
129
+ | Field | Type | Description |
130
+ |------------------|----------------------------|------------------------------------------------------------------|
131
+ | `id` | `UUID` | Unique slot identifier within this flow. |
132
+ | `target_id` | `UUID` | The referenced WorkBlock ID. |
133
+ | `target_version` | `int` | The pinned version of the target. |
134
+ | `alias` | `str \| None` | Optional human label (e.g. `"primary-review"`, `"backup-review"`). |
135
+ | `metadata` | `dict` | Optional. |
136
+
137
+ > **Design decision — same block, two slots.** When the same task must be
138
+ > performed in parallel (e.g. by different teams), the flow creates two
139
+ > NodeReferences pointing to the same WorkBlock. The core model is identical;
140
+ > differentiation happens in the Execution module where each slot is assigned
141
+ > to a different executor.
142
+
143
+ #### 2.3.2 Edge
144
+
145
+ An **Edge** connects two nodes in the flow's directed graph.
146
+
147
+ | Field | Type | Description |
148
+ |-----------------|------------------------|---------------------------------------------------------------|
149
+ | `source_id` | `UUID \| None` | Source NodeReference ID. `None` marks an **entry edge** — the node is a starting point. |
150
+ | `target_id` | `UUID` | Target NodeReference ID. |
151
+ | `condition` | `Condition \| None` | Traversal condition. `None` means unconditional. |
152
+ | `port_mappings` | `list[PortMapping]` | How outputs of the source node map to inputs of the target node. Empty list if no explicit mapping (adapter uses convention). |
153
+
154
+ - Entry edges (`source_id = None`) designate starting nodes of the flow or of
155
+ a disconnected subgraph. Port mappings on entry edges describe how external
156
+ inputs to the flow are wired to the entry node's input ports.
157
+ - Terminal nodes are nodes with no outgoing edges.
158
+
159
+ #### PortMapping
160
+
161
+ A **PortMapping** connects an output port on the source node to an input port
162
+ on the target node.
163
+
164
+ | Field | Type | Description |
165
+ |---------------|--------|-------------------------------------------------------|
166
+ | `source_port` | `str` | Name of an output port on the source node. |
167
+ | `target_port` | `str` | Name of an input port on the target node. |
168
+
169
+ Port mappings are optional. When omitted, adapters may apply convention-based
170
+ wiring (e.g. matching port names). When present, they make data flow explicit
171
+ and portable across adapters.
172
+
173
+ #### 2.3.3 Flow Entity
174
+
175
+ | Field | Type | Description |
176
+ |--------------------|--------------------------|--------------------------------------------------------------------|
177
+ | `id` | `UUID` | Stable identity. |
178
+ | `version` | `int` | Monotonically increasing. |
179
+ | `name` | `str` | Human-readable name. |
180
+ | `description` | `str` | Natural-language description. |
181
+ | `nodes` | `list[NodeReference]` | All slots in this flow. |
182
+ | `edges` | `list[Edge]` | Directed edges between slots, with optional conditions. |
183
+ | `expected_outcome` | `Any \| None` | Opaque blob. Declared upfront: what this flow is meant to achieve. |
184
+ | `metadata` | `dict` | Optional. |
185
+
186
+ > **Interpreted outcome.** A flow's *interpreted outcome* is not stored in the
187
+ > core model. It is derived at execution time from the actual outputs of its
188
+ > terminal nodes. The core model only stores the *expected* outcome as a
189
+ > declarative goal.
190
+
191
+ ---
192
+
193
+ ### 2.4 Contract
194
+
195
+ A **Contract** is the highest-level organizational unit. It models an
196
+ organizational commitment — a mission, an SLA, a project charter — with two
197
+ sides:
198
+
199
+ | Side | Contains |
200
+ |-------------------|----------------------------------------------------------------------------|
201
+ | **Work side** | Workflows (FlowReferences) and/or sub-contracts (ContractReferences) that carry out the work. |
202
+ | **Outcome side** | Required outcomes (declarative goals) and assessment flows that evaluate whether goals are met. |
203
+
204
+ #### 2.4.1 RequiredOutcome
205
+
206
+ A **RequiredOutcome** is a named, opaque declaration of what the contract must
207
+ achieve.
208
+
209
+ | Field | Type | Description |
210
+ |---------------|----------|----------------------------------------------------|
211
+ | `id` | `UUID` | Identity. |
212
+ | `name` | `str` | Human label (e.g. `"99.9% uptime"`, `"Q3 revenue target"`). |
213
+ | `description` | `str` | Natural-language specification of the outcome. |
214
+ | `metadata` | `dict` | Optional. |
215
+
216
+ #### 2.4.2 Assessment Flows
217
+
218
+ Assessment is modeled as a **regular Flow** with one constraint:
219
+
220
+ > **An assessment flow must have exactly one terminal node.** The output of that
221
+ > terminal node is the assessment result for the required outcome it evaluates.
222
+
223
+ Assessment flows are first-class flows. They can contain WorkBlocks that pull
224
+ data from test executions, compare against required outcomes, compute scores,
225
+ and produce a final verdict. Because they are flows, they themselves can be
226
+ executed, tracked, versioned, and even assessed by higher-level contracts.
227
+
228
+ The Contract links each RequiredOutcome to one or more assessment flows. The
229
+ assessment flows may consume:
230
+
231
+ - Outputs from the contract's **work-side flows** (the work being assessed).
232
+ - Outputs from dedicated **test flows** (flows specifically designed to probe
233
+ outcomes, e.g. integration tests, audits, KPI computations).
234
+ - Assessment results from **sub-contracts**.
235
+
236
+ #### 2.4.3 ContractReference
237
+
238
+ | Field | Type | Description |
239
+ |--------------------|----------------|--------------------------------------|
240
+ | `contract_id` | `UUID` | Referenced sub-contract. |
241
+ | `contract_version` | `int` | Pinned version. |
242
+ | `alias` | `str \| None` | Optional human label. |
243
+ | `metadata` | `dict` | Optional. |
244
+
245
+ #### 2.4.4 AssessmentBinding
246
+
247
+ An **AssessmentBinding** links a RequiredOutcome to its assessment mechanism.
248
+
249
+ | Field | Type | Description |
250
+ |-----------------------|--------------------------|----------------------------------------------------------------|
251
+ | `required_outcome_id` | `UUID` | The outcome being assessed. |
252
+ | `assessment_flow_id` | `UUID` | The flow that performs the assessment. |
253
+ | `assessment_flow_version` | `int` | Pinned version. |
254
+ | `test_flow_refs` | `list[FlowReference]` | Flows whose execution outputs are supplied as inputs to the assessment flow's entry nodes. The adapter is responsible for wiring test flow terminal-node outputs to assessment flow entry-node inputs. |
255
+ | `metadata` | `dict` | Optional. |
256
+
257
+ #### 2.4.5 Contract Entity
258
+
259
+ | Field | Type | Description |
260
+ |------------------------|-----------------------------|-------------------------------------------------------|
261
+ | `id` | `UUID` | Stable identity. |
262
+ | `version` | `int` | Monotonically increasing. |
263
+ | `name` | `str` | Human-readable name. |
264
+ | `description` | `str` | Natural-language description. |
265
+ | `work_flows` | `list[FlowReference]` | Operational workflows on the work side. |
266
+ | `sub_contracts` | `list[ContractReference]` | Nested contracts on the work side. |
267
+ | `required_outcomes` | `list[RequiredOutcome]` | Declared goals on the outcome side. |
268
+ | `assessment_bindings` | `list[AssessmentBinding]` | Links outcomes to assessment flows and test flows. |
269
+ | `metadata` | `dict` | Optional. |
270
+
271
+ #### FlowReference
272
+
273
+ Used anywhere a flow is referenced by another entity.
274
+
275
+ | Field | Type | Description |
276
+ |----------------|----------------|---------------------------|
277
+ | `flow_id` | `UUID` | Referenced flow. |
278
+ | `flow_version` | `int` | Pinned version. |
279
+ | `alias` | `str \| None` | Optional human label to distinguish multiple references to the same flow. |
280
+ | `metadata` | `dict` | Optional. |
281
+
282
+ ---
283
+
284
+ ### 2.5 Versioning Model
285
+
286
+ All primary entities (WorkBlock, Flow, Contract) follow the same versioning
287
+ scheme:
288
+
289
+ - **`id`** (`UUID`) is the stable identity of the entity across all versions.
290
+ - **`version`** (`int`) starts at `1` and increments with each mutation.
291
+ - The pair `(id, version)` is globally unique and immutable once created.
292
+ - All references to an entity pin a specific version (`target_id` +
293
+ `target_version`, `flow_id` + `flow_version`, etc.).
294
+ - Executions always reference a pinned version, ensuring the definition of work
295
+ cannot change under a running process.
296
+ - Creating a new version does **not** invalidate existing versions. Old versions
297
+ remain accessible for auditing and for in-flight executions.
298
+
299
+ ---
300
+
301
+ ### 2.6 Entity Relationship Summary
302
+
303
+ ```
304
+ Contract
305
+ ├── work_flows ──────→ Flow (via FlowReference)
306
+ ├── sub_contracts ───→ Contract (via ContractReference)
307
+ ├── required_outcomes: [RequiredOutcome]
308
+ └── assessment_bindings
309
+ ├── required_outcome_id → RequiredOutcome
310
+ ├── assessment_flow_id → Flow (assessment flow, 1 terminal node)
311
+ └── test_flow_refs ───→ [Flow] (via FlowReference)
312
+
313
+ Flow
314
+ ├── nodes: [NodeReference]
315
+ │ └── target_id → WorkBlock
316
+ └── edges: [Edge]
317
+ ├── source_id → NodeReference (or None for entry)
318
+ ├── target_id → NodeReference
319
+ ├── condition → Condition (value object, optional)
320
+ └── port_mappings → [PortMapping] (source_port → target_port)
321
+
322
+ WorkBlock
323
+ ├── inputs: [PortDescriptor] (each with optional schema)
324
+ ├── outputs: [PortDescriptor] (each with optional schema)
325
+ └── execution_hints: [str]
326
+
327
+ Execution Interfaces
328
+ ├── EventStore (protocol) ← storage backends implement
329
+ └── ExecutionAdapter (protocol, uses EventStore) ← orchestration engines implement
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 3. Execution Module — `openoma.execution`
335
+
336
+ The execution module records **how** work defined in the core module was carried
337
+ out. It is strictly separate: the core model is a blueprint; the execution model
338
+ is the as-built record.
339
+
340
+ The execution module never modifies core entities. It only references them.
341
+
342
+ ### 3.1 Event-Sourced Execution Log
343
+
344
+ All execution state is derived from an **append-only event log**. Execution
345
+ entities (BlockExecution, FlowExecution, ContractExecution) are projections
346
+ of the event stream — they are materialized views, not mutable records.
347
+
348
+ This design is motivated by a key principle from modern agent infrastructure:
349
+ **the session must outlive any single executor.** If an agent crashes, a human
350
+ goes off-shift, or an orchestration engine is swapped, the event log remains
351
+ the durable source of truth. A new executor can resume from the last event
352
+ without data loss.
353
+
354
+ #### ExecutionEvent
355
+
356
+ Every state change in the execution module is recorded as an immutable event.
357
+
358
+ | Field | Type | Description |
359
+ |--------------------|--------------------------|-------------------------------------------------------------------|
360
+ | `id` | `UUID` | Unique event identity. |
361
+ | `timestamp` | `datetime` | When the event was recorded. |
362
+ | `execution_id` | `UUID` | The BlockExecution, FlowExecution, or ContractExecution this event belongs to. |
363
+ | `event_type` | `ExecutionEventType` | The type of state transition (see below). |
364
+ | `executor` | `ExecutorInfo \| None` | The executor active at the time of this event (if applicable). |
365
+ | `payload` | `Any` | Event-specific data (opaque blob). |
366
+ | `metadata` | `dict` | Optional. |
367
+
368
+ #### ExecutionEventType
369
+
370
+ | Event Type | Description |
371
+ |-----------------------|-----------------------------------------------------------------------|
372
+ | `created` | Execution entity was initialized. |
373
+ | `executor_assigned` | An executor was assigned or reassigned. |
374
+ | `executor_released` | An executor released the execution (crash, handoff, shift end). |
375
+ | `started` | Work began. |
376
+ | `progress` | Intermediate progress update (opaque payload). |
377
+ | `outcome_produced` | An output was produced (payload contains the outcome blob). |
378
+ | `completed` | Execution finished successfully. |
379
+ | `failed` | Execution finished with an error. |
380
+ | `skipped` | Execution was deliberately skipped. |
381
+ | `cancelled` | Execution was aborted. |
382
+
383
+ #### ExecutionState (derived)
384
+
385
+ The current state of any execution entity is **derived** by replaying its event
386
+ stream. The canonical states are:
387
+
388
+ | State | Description |
389
+ |---------------|-------------------------------------------------------|
390
+ | `pending` | `created` event exists, no `started` event yet. |
391
+ | `in_progress` | `started` event exists, no terminal event yet. |
392
+ | `completed` | `completed` event exists. |
393
+ | `failed` | `failed` event exists. |
394
+ | `skipped` | `skipped` event exists. |
395
+ | `cancelled` | `cancelled` event exists. |
396
+
397
+ > **Resumability.** Because state is derived from events, any executor can
398
+ > pick up an execution by reading the event log. The `executor_assigned` and
399
+ > `executor_released` events make handoffs explicit. An orchestration engine
400
+ > that crashes simply stops emitting events; a replacement reads the log up
401
+ > to the last event and resumes.
402
+
403
+ ### 3.2 ExecutorInfo
404
+
405
+ Executor identity is recorded **per event**, not per execution, because
406
+ executors can change over the lifetime of an execution (crash recovery, shift
407
+ handoff, escalation from agent to human).
408
+
409
+ | Field | Type | Description |
410
+ |--------------|---------------------------------|--------------------------------------------------|
411
+ | `type` | `"human" \| "agent" \| "system"` | Category of executor. |
412
+ | `identifier` | `str` | Unique identifier (user ID, agent name, system URI). |
413
+ | `metadata` | `dict` | Optional (team, role, model version, etc.). |
414
+
415
+ > **Executor continuity.** The execution log naturally records the full
416
+ > lineage of who worked on what. If agent A starts a block, crashes, and
417
+ > agent B resumes, the event stream contains:
418
+ > `executor_assigned(A)` → `started` → `executor_released(A)` →
419
+ > `executor_assigned(B)` → `completed`. No information is lost.
420
+
421
+ ### 3.3 BlockExecution
422
+
423
+ A **BlockExecution** is a projection over the event stream for a single
424
+ WorkBlock execution within a specific flow context.
425
+
426
+ | Field | Type | Description |
427
+ |--------------------|--------------------------|-------------------------------------------------------------------|
428
+ | `id` | `UUID` | Unique execution identity. |
429
+ | `node_reference_id`| `UUID` | The NodeReference (slot) in the flow that was executed. |
430
+ | `work_block_id` | `UUID` | The WorkBlock that was executed. |
431
+ | `work_block_version` | `int` | Pinned version. |
432
+ | `state` | `ExecutionState` | Derived from event stream. |
433
+ | `events` | `list[ExecutionEvent]` | The ordered event log for this execution. |
434
+
435
+ Inputs, outcomes, executor assignments, timing, and attempt counts are all
436
+ read from the event stream rather than stored as top-level fields.
437
+
438
+ ### 3.4 FlowExecution
439
+
440
+ A **FlowExecution** records the execution of a flow as a whole. Its state is
441
+ **derived** from its block executions — it is not set independently.
442
+
443
+ | Field | Type | Description |
444
+ |---------------------|-----------------------------|-----------------------------------------------------------------|
445
+ | `id` | `UUID` | Unique execution identity. |
446
+ | `flow_id` | `UUID` | The flow being executed. |
447
+ | `flow_version` | `int` | Pinned version. |
448
+ | `block_executions` | `list[UUID]` | BlockExecution IDs. **Many-to-many**: a BlockExecution can belong to multiple FlowExecutions. |
449
+ | `state` | `ExecutionState` | Derived from constituent block executions. |
450
+ | `events` | `list[ExecutionEvent]` | Flow-level events (e.g. flow started, flow completed). |
451
+
452
+ > **Shared execution outcomes.** A single BlockExecution (with its UUID and
453
+ > outcome) can be referenced by multiple FlowExecutions if the work it
454
+ > represents satisfies requirements in all of them. This avoids redundant
455
+ > execution of identical work.
456
+
457
+ #### FlowExecution State Derivation
458
+
459
+ A FlowExecution's state is derived deterministically from its block executions:
460
+
461
+ | Rule | Condition | Derived State |
462
+ |------|-----------|---------------|
463
+ | 1 | Any block is `failed`. | `failed` |
464
+ | 2 | Any block is `cancelled`. | `cancelled` |
465
+ | 3 | All **terminal nodes** are `completed` or `skipped`. | `completed` |
466
+ | 4 | Any block is `in_progress` or `pending` (and no rule 1–2 applies). | `in_progress` |
467
+ | 5 | All blocks are `pending`. | `pending` |
468
+
469
+ Rules are evaluated in priority order (1 is highest). A terminal node is a
470
+ node with no outgoing edges in the flow definition. Skipped terminal nodes
471
+ count as satisfied — they represent branches that were legitimately not taken.
472
+
473
+ ### 3.5 ContractExecution
474
+
475
+ A **ContractExecution** records the execution state of a contract.
476
+
477
+ | Field | Type | Description |
478
+ |----------------------------|------------------------------|--------------------------------------------------------------|
479
+ | `id` | `UUID` | Unique execution identity. |
480
+ | `contract_id` | `UUID` | The contract being executed. |
481
+ | `contract_version` | `int` | Pinned version. |
482
+ | `flow_executions` | `list[UUID]` | FlowExecution IDs for work-side flows. **Many-to-many**: a FlowExecution can belong to multiple ContractExecutions. |
483
+ | `sub_contract_executions` | `list[UUID]` | ContractExecution IDs for sub-contracts. |
484
+ | `assessment_executions` | `list[AssessmentResult]` | Assessment flow execution results. |
485
+ | `state` | `ExecutionState` | Derived from flow and sub-contract executions. |
486
+ | `events` | `list[ExecutionEvent]` | Contract-level events. |
487
+
488
+ #### ContractExecution State Derivation
489
+
490
+ A ContractExecution's state is derived from its flow executions and
491
+ sub-contract executions:
492
+
493
+ | Rule | Condition | Derived State |
494
+ |------|-----------|---------------|
495
+ | 1 | Any flow execution or sub-contract execution is `failed`. | `failed` |
496
+ | 2 | Any flow execution or sub-contract execution is `cancelled`. | `cancelled` |
497
+ | 3 | All flow executions and sub-contract executions are `completed` or `skipped`. | `completed` |
498
+ | 4 | Any is `in_progress` or `pending` (and no rule 1–2 applies). | `in_progress` |
499
+ | 5 | All are `pending`. | `pending` |
500
+
501
+ Assessment flow executions are **not** factored into the contract's work-side
502
+ state derivation. They run independently and their results populate the
503
+ `assessment_executions` list. A contract can be `completed` (all work done)
504
+ but have failing assessments — this is an important distinction between
505
+ "work finished" and "goals met."
506
+
507
+ #### AssessmentResult
508
+
509
+ Links an assessment flow's execution to the required outcome it evaluated.
510
+
511
+ | Field | Type | Description |
512
+ |----------------------------|------------|----------------------------------------------------------|
513
+ | `required_outcome_id` | `UUID` | The outcome being assessed. |
514
+ | `assessment_flow_execution_id` | `UUID` | The FlowExecution of the assessment flow. |
515
+ | `result` | `Any` | The terminal node's output — the assessment verdict. |
516
+
517
+ ---
518
+
519
+ ## 4. Execution Interfaces — `openoma.adapter` and `openoma.store`
520
+
521
+ Openoma defines the **what** (core) and the **record** (execution). Two
522
+ interfaces connect it to the outside world:
523
+
524
+ - **Execution Adapter** — drives work (orchestration engines implement this).
525
+ - **Event Store** — persists and retrieves the event log (storage backends
526
+ implement this).
527
+
528
+ These are deliberately separate concerns. An orchestration engine (CrewAI,
529
+ Managed Agents, Temporal, a human task board) should not need to implement
530
+ storage. A storage backend (Kafka, EventStoreDB, a NoSQL collection, an
531
+ in-memory test store) should not need to know how work is executed.
532
+
533
+ ### 4.1 Event Store Protocol
534
+
535
+ ```python
536
+ class EventStore(Protocol):
537
+ """Interface for persisting and retrieving the execution event log."""
538
+
539
+ def append(self, event: ExecutionEvent) -> None:
540
+ """Append an event to the log. Immutable once appended."""
541
+ ...
542
+
543
+ def get_events(
544
+ self,
545
+ execution_id: UUID,
546
+ after: datetime | None = None,
547
+ ) -> list[ExecutionEvent]:
548
+ """Retrieve events for an execution, optionally after a timestamp.
549
+
550
+ Returns events in chronological order.
551
+ """
552
+ ...
553
+
554
+ def get_latest_event(self, execution_id: UUID) -> ExecutionEvent | None:
555
+ """Retrieve the most recent event for an execution.
556
+
557
+ Used for quick state checks and resumption without replaying
558
+ the full log.
559
+ """
560
+ ...
561
+ ```
562
+
563
+ Openoma ships an `InMemoryEventStore` for testing and prototyping. Production
564
+ deployments implement the protocol with their storage backend of choice.
565
+
566
+ ### 4.2 Execution Adapter Protocol
567
+
568
+ ```python
569
+ class ExecutionAdapter(Protocol):
570
+ """Interface that any orchestration engine implements to drive Openoma."""
571
+
572
+ def execute_block(
573
+ self,
574
+ block: WorkBlock,
575
+ node_ref: NodeReference,
576
+ inputs: Any,
577
+ store: EventStore,
578
+ ) -> UUID:
579
+ """Begin execution of a work block.
580
+
581
+ The adapter is responsible for:
582
+ - Selecting an appropriate executor based on execution_hints.
583
+ - Emitting ExecutionEvents via `store.append()`.
584
+ - Returning the BlockExecution UUID.
585
+
586
+ The adapter must NOT modify the WorkBlock or NodeReference.
587
+ """
588
+ ...
589
+
590
+ def resume_block(
591
+ self,
592
+ block_execution_id: UUID,
593
+ store: EventStore,
594
+ ) -> None:
595
+ """Resume a block execution from the last event in its log.
596
+
597
+ Called when an executor crashes or is replaced. The adapter reads
598
+ the event stream via `store.get_events()`, determines the last
599
+ known state, and continues.
600
+ """
601
+ ...
602
+
603
+ def evaluate_condition(
604
+ self,
605
+ condition: Condition,
606
+ context: Any,
607
+ ) -> bool:
608
+ """Evaluate a traversal condition given the current execution context.
609
+
610
+ For conditions with a structured predicate, the adapter may evaluate
611
+ it directly. For natural-language conditions, it may delegate to an
612
+ LLM, a human, or any other mechanism.
613
+ """
614
+ ...
615
+ ```
616
+
617
+ ### 4.3 Adapter and Store Semantics
618
+
619
+ - **The adapter drives; the store records.** The adapter decides *when* to
620
+ emit events. The store decides *where* events are persisted. Neither knows
621
+ the other's implementation.
622
+ - **The store is injected into the adapter.** Adapter methods receive the
623
+ store as a parameter. This allows the same adapter to work against different
624
+ storage backends without modification.
625
+ - **Multiple adapters can coexist.** A human-task adapter for manual work, an
626
+ agent adapter for AI-driven blocks, a CI/CD adapter for automated pipelines
627
+ — all writing to the same event store.
628
+ - The adapter is responsible for **executor selection**. It reads the
629
+ WorkBlock's `execution_hints` and matches them against available executors.
630
+ - The adapter is responsible for **condition evaluation**. It reads the
631
+ Condition's `description` and/or `predicate` and returns a boolean.
632
+
633
+ > **Analogy.** In Anthropic's Managed Agents architecture, the harness calls
634
+ > hands via `execute(name, input) → string` and records to a durable session
635
+ > log. In Openoma, the Execution Adapter is the harness interface, and the
636
+ > Event Store is the session log interface. Both are abstract — any
637
+ > orchestration engine implements the adapter, any storage backend implements
638
+ > the store.
639
+
640
+ ---
641
+
642
+ ## 5. Design Principles
643
+
644
+ ### 5.1 Separation of Definition and Execution
645
+
646
+ The core module (`openoma.core`) and the execution module (`openoma.execution`)
647
+ are strictly separated. The core module defines the topology and intent of
648
+ work. The execution module records what actually happened. A third-party
649
+ orchestration engine reads the core model, decides how to execute it, and
650
+ writes results into the execution model via the Execution Adapter.
651
+
652
+ ### 5.2 Event Sourcing
653
+
654
+ Execution state is never mutated in place. Every state transition is an
655
+ immutable event appended to a log. Current state is derived by replaying the
656
+ log. This guarantees:
657
+
658
+ - **Resumability.** Any executor can resume from the last event after a crash.
659
+ - **Auditability.** The complete history of who did what and when is preserved.
660
+ - **Executor independence.** The log outlives any single executor or
661
+ orchestration engine.
662
+
663
+ ### 5.3 Self-Similar Assessment
664
+
665
+ Assessment is not a special mechanism — it is a regular flow with the constraint
666
+ of having exactly one terminal node. This means:
667
+
668
+ - Assessment flows can be versioned, executed, and tracked like any other flow.
669
+ - Assessment flows can themselves be assessed by higher-level contracts.
670
+ - The entire framework is recursively composable: contracts assess sub-contracts
671
+ whose assessments are themselves flows.
672
+
673
+ ### 5.4 Graph Topology Freedom
674
+
675
+ Flows are directed graphs with no requirement for full connectivity. This
676
+ supports real-world operational structures where independent work streams
677
+ coexist within the same logical boundary without artificial sequencing.
678
+
679
+ ### 5.5 Many-to-Many Execution Sharing
680
+
681
+ A BlockExecution can be claimed by multiple FlowExecutions, and a
682
+ FlowExecution can be claimed by multiple ContractExecutions. This models
683
+ reality: a single piece of completed work often satisfies obligations in
684
+ multiple contexts.
685
+
686
+ ### 5.6 Opaque Outcomes
687
+
688
+ Outcomes (inputs, outputs, assessment verdicts) are opaque blobs. The framework
689
+ stores and routes them but does not inspect their contents. Schema enforcement,
690
+ if desired, is the responsibility of the execution engine or a validation layer
691
+ built on top of Openoma.
692
+
693
+ ### 5.7 Orchestration Agnosticism
694
+
695
+ Openoma makes no assumptions about the orchestration engine. It does not
696
+ assume agents, does not assume humans, does not assume any particular
697
+ scheduling model. The Execution Adapter and Event Store protocols are the only
698
+ contracts between Openoma and the outside world. This ensures the framework
699
+ remains a stable substrate as orchestration tools evolve — today's harness
700
+ assumptions become tomorrow's dead weight, but the data model persists.
701
+
702
+ ### 5.8 Flows as Templates, Executions as Instances
703
+
704
+ A Flow in the core model is a **template** — a reusable definition of work
705
+ structure. Each time a flow is run, a new **FlowExecution** is created in the
706
+ execution module. The same flow can produce many FlowExecutions (e.g. a sprint
707
+ cycle flow runs every two weeks).
708
+
709
+ Contracts reference flows (via FlowReference), not flow executions. A contract
710
+ does not model recurrence — it declares which flows constitute its work side.
711
+ Recurrence, scheduling, and triggering are the orchestration engine's concern.
712
+ Each run of a contract's workflows produces a new set of FlowExecutions, which
713
+ are grouped under a **ContractExecution**.
714
+
715
+ This means:
716
+ - The same Contract can have many ContractExecutions (one per evaluation
717
+ period, milestone, or triggered run).
718
+ - The same Flow can have many FlowExecutions across different
719
+ ContractExecutions or even the same one (e.g. a "daily health check" flow
720
+ executed multiple times within a single contract period).
721
+ - Openoma tracks every execution instance independently — the full history is
722
+ in the event log.
723
+
724
+ ---
725
+
726
+ ## 6. Illustrative Example
727
+
728
+ > An organization modeled end-to-end with Openoma.
729
+
730
+ ### The Mission (Contract)
731
+
732
+ ```
733
+ Contract: "Q3 Product Launch"
734
+ ├── required_outcomes:
735
+ │ ├── "Feature X shipped to production"
736
+ │ └── "Customer satisfaction ≥ 4.5/5"
737
+ ├── work_flows:
738
+ │ ├── Flow: "Engineering Sprint Cycle"
739
+ │ └── Flow: "QA Validation Pipeline"
740
+ ├── sub_contracts:
741
+ │ └── Contract: "Infrastructure SLA"
742
+ │ ├── required_outcomes: ["99.9% uptime"]
743
+ │ ├── work_flows: [Flow: "Infra BAU"]
744
+ │ └── assessment_bindings: [→ Flow: "Uptime Monitor Assessment"]
745
+ └── assessment_bindings:
746
+ ├── "Feature X shipped" → assessment Flow: "Release Verification"
747
+ │ └── test_flows: [Flow: "Smoke Test Suite"]
748
+ └── "Satisfaction ≥ 4.5" → assessment Flow: "CSAT Evaluator"
749
+ └── test_flows: [Flow: "Survey Collection"]
750
+ ```
751
+
752
+ ### A Workflow (Flow)
753
+
754
+ ```
755
+ Flow: "Engineering Sprint Cycle"
756
+ ├── nodes:
757
+ │ ├── [slot-A] → WorkBlock: "Ticket Triage"
758
+ │ ├── [slot-B] → WorkBlock: "Implementation"
759
+ │ ├── [slot-C] → WorkBlock: "Code Review"
760
+ │ └── [slot-D] → WorkBlock: "Implementation" ← same block, second slot
761
+ ├── edges:
762
+ │ ├── None → slot-A (entry)
763
+ │ ├── slot-A → slot-B [condition: "ticket is approved"]
764
+ │ ├── slot-A → slot-D [condition: "ticket is fast-track"]
765
+ │ ├── slot-B → slot-C (unconditional)
766
+ │ └── slot-D → slot-C (unconditional)
767
+ └── expected_outcome: "All sprint tickets implemented and reviewed"
768
+ ```
769
+
770
+ Note that `slot-B` and `slot-D` reference the same `WorkBlock: "Implementation"`
771
+ but represent two parallel lanes. At execution time, they may be assigned to
772
+ different engineers or agent systems.
773
+
774
+ ### Daily Work (WorkBlock)
775
+
776
+ ```
777
+ WorkBlock: "Code Review"
778
+ ├── inputs:
779
+ │ ├── name: "pull_request_url", description: "URL of the PR to review"
780
+ │ └── name: "review_checklist", description: "Criteria for approval", required: false
781
+ ├── outputs:
782
+ │ ├── name: "verdict", description: "approved | changes_requested | rejected"
783
+ │ └── name: "comments", description: "Review comments"
784
+ └── execution_hints: ["github-access", "codebase-read"]
785
+ ```
786
+
787
+ ### Assessment (Assessment Flow)
788
+
789
+ ```
790
+ Flow: "Release Verification" [assessment flow — 1 terminal node]
791
+ ├── nodes:
792
+ │ ├── [slot-1] → WorkBlock: "Check Deployment Status"
793
+ │ ├── [slot-2] → WorkBlock: "Run Smoke Tests"
794
+ │ └── [slot-3] → WorkBlock: "Produce Release Verdict" ← terminal
795
+ ├── edges:
796
+ │ ├── None → slot-1
797
+ │ ├── slot-1 → slot-2
798
+ │ └── slot-2 → slot-3
799
+ └── expected_outcome: "Binary pass/fail on release readiness"
800
+ ```
801
+
802
+ The terminal node (`slot-3`) produces the assessment result that the contract
803
+ uses to evaluate whether `"Feature X shipped to production"` is satisfied.
804
+
805
+ ---
806
+
807
+ ## 7. Module Boundary
808
+
809
+ ```
810
+ openoma/
811
+ ├── core/ # Core model — definitions only
812
+ │ ├── work_block.py
813
+ │ ├── condition.py
814
+ │ ├── flow.py
815
+ │ ├── contract.py
816
+ │ ├── types.py # PortDescriptor, PortMapping, FlowReference, ContractReference, etc.
817
+ │ └── versioning.py # Version management utilities
818
+ ├── execution/ # Execution model — event-sourced records
819
+ │ ├── events.py # ExecutionEvent, ExecutionEventType
820
+ │ ├── block_execution.py
821
+ │ ├── flow_execution.py
822
+ │ ├── contract_execution.py
823
+ │ └── types.py # ExecutorInfo, ExecutionState, AssessmentResult, etc.
824
+ ├── store/ # Event Store protocol + built-in implementations
825
+ │ ├── protocol.py # EventStore abstract interface
826
+ │ └── memory.py # InMemoryEventStore (for testing / prototyping)
827
+ ├── adapter/ # Execution Adapter protocol
828
+ │ ├── protocol.py # ExecutionAdapter abstract interface
829
+ │ └── base.py # Optional base class with common event-emitting logic
830
+ ├── validation/ # Invariant validators
831
+ │ ├── graph.py # Terminal node checks, edge validity
832
+ │ └── references.py # Version pin validation, reference integrity
833
+ ├── builders/ # Fluent builder APIs for constructing entities
834
+ │ ├── work_block.py
835
+ │ ├── flow.py
836
+ │ └── contract.py
837
+ └── __init__.py
838
+ ```
839
+
840
+ ---
841
+
842
+ ## 8. Constraints and Invariants
843
+
844
+ 1. **Assessment terminal constraint.** Any flow used as an assessment flow in
845
+ an AssessmentBinding must have exactly one terminal node (a node with no
846
+ outgoing edges).
847
+
848
+ 2. **Version pinning.** All cross-entity references must pin a specific
849
+ version. Unpinned references are not permitted.
850
+
851
+ 3. **No circular sub-contract inclusion.** A contract may not include itself
852
+ (directly or transitively) as a sub-contract.
853
+
854
+ 4. **NodeReference uniqueness.** Within a flow, each NodeReference has a unique
855
+ `id`, even if multiple references point to the same target entity.
856
+
857
+ 5. **Edge validity.** Every `source_id` and `target_id` in an edge must
858
+ reference a NodeReference that exists in the same flow's `nodes` list.
859
+
860
+ 6. **Event append-only.** Execution events are immutable once appended. Events
861
+ cannot be deleted or modified. State is always derived by replaying the log.
862
+
863
+ 7. **Terminal state finality.** Once an execution's derived state reaches a
864
+ terminal state (`completed`, `failed`, `cancelled`), no further
865
+ non-administrative events may be appended. A new execution must be created
866
+ for retries.
867
+
868
+ 8. **Adapter writes via store.** The Execution Adapter emits events
869
+ exclusively through the Event Store's `append()` method. The adapter
870
+ never writes directly to the underlying persistence layer.
871
+
872
+ 9. **Port mapping validity.** Every `source_port` in a PortMapping must
873
+ reference an output port that exists on the source node's WorkBlock.
874
+ Every `target_port` must reference an input port on the target node's
875
+ WorkBlock.
876
+
877
+ ---
878
+
879
+ ## 9. Out of Scope (for this framework)
880
+
881
+ - **Orchestration / scheduling.** How flows get executed (task queues, agent
882
+ routing, human assignment) is the domain of an orchestration engine that
883
+ implements the Execution Adapter and drives Openoma models.
884
+ - **Harness implementation.** The specific agent loop, context engineering,
885
+ prompt management, and model selection are the orchestration engine's
886
+ concern. Openoma only defines the adapter interface.
887
+ - **Schema enforcement.** PortDescriptor may carry an optional `schema` field,
888
+ but the framework does not validate data against it. Enforcement is the
889
+ responsibility of adapters or validation layers.
890
+ - **Production persistence.** Openoma ships an in-memory EventStore for
891
+ testing. Production storage backends (NoSQL, EventStoreDB, Kafka, etc.)
892
+ implement the EventStore protocol externally.
893
+ - **Access control.** Authorization over who can view/edit/execute entities is
894
+ external.
895
+
896
+ ---
897
+
898
+ ## 10. Open Questions
899
+
900
+ 1. **Condition expression language.** Should the spec mandate a specific
901
+ structured predicate format (JSONLogic, CEL, custom AST), or leave it as an
902
+ opaque dict for now? Current decision: opaque dict, with a recommended
903
+ format to be specified in a future RFC.
904
+
905
+ 2. **Execution lineage.** Should the execution model track causal
906
+ relationships between block executions (e.g. "this block was triggered
907
+ because that block completed")? This could be a `triggered_by` field on the
908
+ `started` event's payload, enabling full provenance graphs.
909
+
910
+ 3. **Event compaction.** For long-running contracts, the event log may grow
911
+ very large. Should the spec define a compaction/snapshotting mechanism
912
+ that preserves auditability while bounding read costs?
913
+
914
+ ---
915
+
916
+ ## 11. What Ships in the Package
917
+
918
+ When a consumer installs Openoma (`pip install openoma`), they get the
919
+ following:
920
+
921
+ ### 11.1 Data Models
922
+
923
+ Immutable, serializable data classes (Pydantic or dataclasses) for every entity
924
+ in Sections 2 and 3:
925
+
926
+ - **Core:** `WorkBlock`, `PortDescriptor`, `PortMapping`, `Condition`,
927
+ `NodeReference`, `Edge`, `Flow`, `Contract`, `RequiredOutcome`,
928
+ `AssessmentBinding`, `FlowReference`, `ContractReference`.
929
+ - **Execution:** `ExecutionEvent`, `ExecutionEventType`, `ExecutorInfo`,
930
+ `ExecutionState`, `BlockExecution`, `FlowExecution`, `ContractExecution`,
931
+ `AssessmentResult`.
932
+
933
+ All models support serialization to/from JSON-compatible dicts (ready for
934
+ GraphQL resolvers, NoSQL documents, or API payloads).
935
+
936
+ ### 11.2 Validators
937
+
938
+ Functions that enforce the invariants from Section 8:
939
+
940
+ - `validate_flow(flow)` — checks edge validity, NodeReference uniqueness,
941
+ and assessment terminal constraint.
942
+ - `validate_contract(contract)` — checks cycle detection (no circular
943
+ sub-contracts), version pin integrity, and assessment binding validity.
944
+ - `validate_port_mappings(flow)` — checks that all port mappings reference
945
+ ports that exist on the source and target nodes.
946
+
947
+ Validators raise structured errors with paths to the offending entity.
948
+
949
+ ### 11.3 State Derivation
950
+
951
+ Pure functions that compute execution state from event streams:
952
+
953
+ - `derive_block_state(events) → ExecutionState`
954
+ - `derive_flow_state(block_executions) → ExecutionState`
955
+ - `derive_contract_state(flow_executions, sub_contract_executions) → ExecutionState`
956
+
957
+ These implement the rules from Sections 3.4 and 3.5.
958
+
959
+ ### 11.4 Protocols
960
+
961
+ - `EventStore` protocol (Section 4.1) — for storage backends.
962
+ - `ExecutionAdapter` protocol (Section 4.2) — for orchestration engines.
963
+
964
+ ### 11.5 Built-in Implementations
965
+
966
+ - `InMemoryEventStore` — a simple in-memory EventStore for testing,
967
+ prototyping, and local development. Not suitable for production.
968
+
969
+ ### 11.6 Builder APIs
970
+
971
+ Fluent builder classes for constructing entities without error-prone dict
972
+ assembly:
973
+
974
+ ```python
975
+ from openoma.builders import FlowBuilder, WorkBlockBuilder
976
+
977
+ block = (
978
+ WorkBlockBuilder("Code Review")
979
+ .input("pull_request_url", "URL of the PR to review")
980
+ .input("review_checklist", "Criteria for approval", required=False)
981
+ .output("verdict", "approved | changes_requested | rejected")
982
+ .output("comments", "Review comments")
983
+ .hint("github-access")
984
+ .build()
985
+ )
986
+
987
+ flow = (
988
+ FlowBuilder("Sprint Cycle")
989
+ .add_block(block, alias="review")
990
+ .add_block(impl_block, alias="impl-primary")
991
+ .add_block(impl_block, alias="impl-fast-track") # same block, two slots
992
+ .edge("impl-primary", "review")
993
+ .edge("impl-fast-track", "review")
994
+ .entry("impl-primary", condition=Condition(description="ticket is approved"))
995
+ .entry("impl-fast-track", condition=Condition(description="ticket is fast-track"))
996
+ .build() # runs validate_flow() automatically
997
+ )
998
+ ```
999
+
1000
+ ### 11.7 What Openoma Does NOT Ship
1001
+
1002
+ - **No orchestration engine.** No scheduler, no task queue, no agent loop.
1003
+ - **No production storage.** No database drivers, no Kafka integration.
1004
+ - **No UI.** No dashboard, no visualization.
1005
+ - **No network layer.** No REST/GraphQL server. (A separate `openoma-graphql`
1006
+ package may be created for this.)
1007
+
1008
+ Openoma is a **data model + validation + protocol** library. It is the stable
1009
+ kernel that other systems build on top of.