stateous 0.2.2__tar.gz → 0.3.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 (196) hide show
  1. {stateous-0.2.2 → stateous-0.3.0}/.gitignore +2 -0
  2. {stateous-0.2.2 → stateous-0.3.0}/PKG-INFO +1 -1
  3. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/agents.md +2 -2
  4. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/context-notes-memory.md +1 -1
  5. stateous-0.3.0/docs/concepts/data.md +115 -0
  6. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/guards.md +11 -3
  7. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/model.md +6 -48
  8. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/parallel.md +4 -2
  9. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/pause-resume-cancel.md +4 -0
  10. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/reliability.md +1 -1
  11. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/tasks-and-routines.md +3 -3
  12. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/two-sides.md +1 -1
  13. {stateous-0.2.2 → stateous-0.3.0}/docs/getting-started.md +94 -25
  14. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/steer-tokens.md +8 -2
  15. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/write-a-routine.md +8 -2
  16. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/write-a-workflow.md +7 -3
  17. {stateous-0.2.2 → stateous-0.3.0}/docs/index.md +10 -7
  18. {stateous-0.2.2 → stateous-0.3.0}/docs/inside/data.md +16 -12
  19. stateous-0.3.0/docs/inside/words.md +39 -0
  20. {stateous-0.2.2 → stateous-0.3.0}/docs/inside/workflows.md +2 -2
  21. {stateous-0.2.2 → stateous-0.3.0}/docs/reference/cli.md +4 -2
  22. {stateous-0.2.2 → stateous-0.3.0}/docs/reference/files.md +1 -1
  23. {stateous-0.2.2 → stateous-0.3.0}/docs/reference/http.md +1 -1
  24. {stateous-0.2.2 → stateous-0.3.0}/docs/reference/python.md +6 -0
  25. {stateous-0.2.2 → stateous-0.3.0}/mkdocs.yml +2 -0
  26. {stateous-0.2.2 → stateous-0.3.0}/pyproject.toml +19 -1
  27. {stateous-0.2.2 → stateous-0.3.0}/scripts/check.py +1 -1
  28. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/__init__.py +3 -0
  29. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/starter.py +21 -24
  30. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/tokens.py +23 -3
  31. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/codegen.py +2 -0
  32. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/compile.py +13 -4
  33. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/exclusive.py +1 -1
  34. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/validate.py +20 -0
  35. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/api.py +3 -2
  36. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/engine.py +34 -13
  37. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/graph.py +28 -5
  38. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/store.py +5 -15
  39. stateous-0.3.0/src/stateous/model/branches.py +32 -0
  40. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/context.py +16 -3
  41. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/graph_file.py +24 -0
  42. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/guards.py +29 -15
  43. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/workflow.py +58 -6
  44. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_concepts.py +47 -10
  45. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_numbers.py +1 -1
  46. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_site.py +2 -2
  47. {stateous-0.2.2 → stateous-0.3.0}/tests/e2e/test_example.py +14 -0
  48. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_api_parallel.py +3 -1
  49. stateous-0.3.0/tests/unit/cli/test_command_failures.py +191 -0
  50. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_init_workflow.py +4 -4
  51. stateous-0.3.0/tests/unit/cli/test_running_paths.py +259 -0
  52. stateous-0.3.0/tests/unit/cli/test_serving_refusals.py +71 -0
  53. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_show_children.py +1 -1
  54. stateous-0.3.0/tests/unit/cli/test_show_context.py +91 -0
  55. stateous-0.3.0/tests/unit/client/test_client_requests.py +90 -0
  56. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_codegen.py +63 -0
  57. stateous-0.3.0/tests/unit/compiler/test_exclusive_domains.py +112 -0
  58. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_load.py +8 -0
  59. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_validate.py +37 -0
  60. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/support.py +18 -1
  61. stateous-0.3.0/tests/unit/control/test_api_edges.py +70 -0
  62. stateous-0.3.0/tests/unit/control/test_engine_edges.py +255 -0
  63. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_graph.py +8 -0
  64. stateous-0.3.0/tests/unit/control/test_into_engine.py +187 -0
  65. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_parallel.py +6 -6
  66. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_serving.py +2 -16
  67. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_store.py +0 -16
  68. stateous-0.3.0/tests/unit/control/test_store_edges.py +74 -0
  69. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_tasks.py +2 -1
  70. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_context.py +17 -3
  71. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_contract.py +4 -0
  72. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_graph_file.py +14 -0
  73. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_guards.py +43 -8
  74. stateous-0.3.0/tests/unit/model/test_into.py +192 -0
  75. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/fakes.py +25 -1
  76. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_folder.py +17 -0
  77. stateous-0.3.0/tests/unit/supervisor/test_outbox_errors.py +39 -0
  78. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_remote.py +1 -22
  79. stateous-0.3.0/tests/unit/supervisor/test_remote_answers.py +129 -0
  80. stateous-0.3.0/tests/unit/supervisor/test_supervisor_faults.py +542 -0
  81. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/test_public_names.py +2 -0
  82. {stateous-0.2.2 → stateous-0.3.0}/LICENSE +0 -0
  83. {stateous-0.2.2 → stateous-0.3.0}/NOTICE +0 -0
  84. {stateous-0.2.2 → stateous-0.3.0}/README.md +0 -0
  85. {stateous-0.2.2 → stateous-0.3.0}/docs/concepts/timers.md +0 -0
  86. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/change-a-workflow.md +0 -0
  87. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/run-supervisors.md +0 -0
  88. {stateous-0.2.2 → stateous-0.3.0}/docs/how-to/use-postgres.md +0 -0
  89. {stateous-0.2.2 → stateous-0.3.0}/docs/inside/event-graph.md +0 -0
  90. {stateous-0.2.2 → stateous-0.3.0}/docs/reference/limits.md +0 -0
  91. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc/build/sdlc.json +0 -0
  92. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc/flow.py +0 -0
  93. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc/schemas.py +0 -0
  94. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc-supervisor/coding.py +0 -0
  95. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc-supervisor/contract.py +0 -0
  96. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc-supervisor/stateous.yaml +0 -0
  97. {stateous-0.2.2 → stateous-0.3.0}/examples/sdlc-supervisor/testing.py +0 -0
  98. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/__main__.py +0 -0
  99. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/__init__.py +0 -0
  100. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/errors.py +0 -0
  101. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/main.py +0 -0
  102. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/project_file.py +0 -0
  103. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/running.py +0 -0
  104. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/cli/serving.py +0 -0
  105. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/client/__init__.py +0 -0
  106. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/client/client.py +0 -0
  107. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/__init__.py +0 -0
  108. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/load.py +0 -0
  109. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/compiler/plot.py +0 -0
  110. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/__init__.py +0 -0
  111. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/__init__.py +0 -0
  112. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/errors.py +0 -0
  113. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/__init__.py +0 -0
  114. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/env.py +0 -0
  115. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/script.py.mako +0 -0
  116. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/versions/0001_baseline.py +0 -0
  117. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/versions/0002_claims.py +0 -0
  118. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/migrations/versions/0003_children.py +0 -0
  119. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/control/store/tables.py +0 -0
  120. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/__init__.py +0 -0
  121. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/contract.py +0 -0
  122. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/durations.py +0 -0
  123. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/events.py +0 -0
  124. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/host.py +0 -0
  125. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/model/wire.py +0 -0
  126. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/py.typed +0 -0
  127. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/__init__.py +0 -0
  128. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/folder.py +0 -0
  129. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/outbox.py +0 -0
  130. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/registry.py +0 -0
  131. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/remote.py +0 -0
  132. {stateous-0.2.2 → stateous-0.3.0}/src/stateous/supervisor/supervisor.py +0 -0
  133. {stateous-0.2.2 → stateous-0.3.0}/tests/conftest.py +0 -0
  134. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/pages.py +0 -0
  135. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_how_to.py +0 -0
  136. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_inside.py +0 -0
  137. {stateous-0.2.2 → stateous-0.3.0}/tests/docs/test_reference.py +0 -0
  138. {stateous-0.2.2 → stateous-0.3.0}/tests/examples/fake_phd.py +0 -0
  139. {stateous-0.2.2 → stateous-0.3.0}/tests/examples/test_coding.py +0 -0
  140. {stateous-0.2.2 → stateous-0.3.0}/tests/examples/test_sdlc.py +0 -0
  141. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/conftest.py +0 -0
  142. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/supervising.py +0 -0
  143. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_api_refusals.py +0 -0
  144. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_api_tasks.py +0 -0
  145. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_api_tokens.py +0 -0
  146. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_client.py +0 -0
  147. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_parallel_flow.py +0 -0
  148. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_prepare_serve.py +0 -0
  149. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_project_uv_sync.py +0 -0
  150. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_serve_command.py +0 -0
  151. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_supervise_cli.py +0 -0
  152. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_supervise_process.py +0 -0
  153. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_supervisor_flow.py +0 -0
  154. {stateous-0.2.2 → stateous-0.3.0}/tests/integration/test_token_commands.py +0 -0
  155. {stateous-0.2.2 → stateous-0.3.0}/tests/parallel.py +0 -0
  156. {stateous-0.2.2 → stateous-0.3.0}/tests/postgres/test_concurrency.py +0 -0
  157. {stateous-0.2.2 → stateous-0.3.0}/tests/postgres/test_connection.py +0 -0
  158. {stateous-0.2.2 → stateous-0.3.0}/tests/postgres/test_migrations.py +0 -0
  159. {stateous-0.2.2 → stateous-0.3.0}/tests/postgres/test_serving.py +0 -0
  160. {stateous-0.2.2 → stateous-0.3.0}/tests/postgres/test_time_limits.py +0 -0
  161. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_clock.py +0 -0
  162. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_closed_output.py +0 -0
  163. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_compile_command.py +0 -0
  164. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_configure_logging.py +0 -0
  165. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_errors.py +0 -0
  166. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_graceful_server.py +0 -0
  167. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_listen.py +0 -0
  168. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_project_file.py +0 -0
  169. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_startup.py +0 -0
  170. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_supervise_helpers.py +0 -0
  171. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_supervised_server.py +0 -0
  172. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/cli/test_token_lines.py +0 -0
  173. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_compile.py +0 -0
  174. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_exclusive.py +0 -0
  175. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/compiler/test_plot.py +0 -0
  176. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_case_race.py +0 -0
  177. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_claim_requests.py +0 -0
  178. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_clock.py +0 -0
  179. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_deadlock.py +0 -0
  180. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_engine.py +0 -0
  181. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_inputs.py +0 -0
  182. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_migrations.py +0 -0
  183. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_parallel_steering.py +0 -0
  184. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/control/test_pause.py +0 -0
  185. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_durations.py +0 -0
  186. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_host.py +0 -0
  187. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_wire.py +0 -0
  188. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/model/test_workflow.py +0 -0
  189. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_open.py +0 -0
  190. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_outbox.py +0 -0
  191. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_registry.py +0 -0
  192. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_supervisor.py +0 -0
  193. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_supervisor_edges.py +0 -0
  194. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/supervisor/test_supervisor_outbox.py +0 -0
  195. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/test_layout.py +0 -0
  196. {stateous-0.2.2 → stateous-0.3.0}/tests/unit/test_test_databases.py +0 -0
@@ -11,3 +11,5 @@ try/
11
11
  try.db
12
12
  .stateous/
13
13
  site/
14
+ .coverage
15
+ .coverage.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stateous
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A process as a graph of states. A control plane keeps every token durable.
5
5
  Project-URL: Homepage, https://github.com/StateousLabs/stateous
6
6
  Project-URL: Source, https://github.com/StateousLabs/stateous
@@ -6,7 +6,7 @@ A routine runs a coding agent with the library PHD, and Stateous keeps the agent
6
6
 
7
7
  ## Why it exists
8
8
 
9
- A coding agent can work for an hour, hang, or crash with its computer. Its answer must be one of the outcomes that the state allows. When it fails, the next try should continue its session, not start again.
9
+ A coding agent can work for an hour, hang, or crash with its computer. Its answer must name one of the events that the state allows. When it fails, the next try should continue its session, not start again.
10
10
 
11
11
  Stateous and PHD share this work. PHD runs the agent and checks its answer. Stateous keeps the task, the retries and the session id. PHD knows nothing of workflows, and it does not report to Stateous.
12
12
 
@@ -137,7 +137,7 @@ async def code(ctx: TicketContext) -> events.done | events.stuck:
137
137
 
138
138
  ## Guarantees
139
139
 
140
- - Only an outcome that the state can emit is accepted. PHD checks it, and the control plane checks it again.
140
+ - Only an event that the state can emit is accepted. PHD checks it, and the control plane checks it again.
141
141
  - The task's lease stays alive while the routine runs, for as long as the agent works.
142
142
  - When the token leaves the state, or is cancelled, the supervisor cancels the routine at the next heartbeat, and PHD stops the run.
143
143
  - The session id in `ctx.memory` stays after a crash, so the next attempt can resume the session.
@@ -27,7 +27,7 @@ These are the fields of `ctx`:
27
27
  | `ctx.task` | The task's id. |
28
28
  | `ctx.visit` | 1 the first time the token is in this state, 2 the next time, and so on. |
29
29
  | `ctx.attempt` | 1, then 2 after a retry, and so on. |
30
- | `ctx.entered_by` | The event that brought the token here: `.name` and `.payload`. In the first state, `.name` is `__started__`. |
30
+ | `ctx.entered_by` | The name of the event that brought the token here, as `ctx.entered_by.name`. In the first state, it is `__started__`. A routine does not get the payload: an event whose payload the token keeps declares `into=`. See [Where data goes](data.md). |
31
31
  | `ctx.schedule(event, after="10m")` | A timer. See [Timers and timeouts](timers.md). |
32
32
 
33
33
  ### The context
@@ -0,0 +1,115 @@
1
+ # Where data goes
2
+
3
+ ## In one sentence
4
+
5
+ A routine reads and writes only the context: an event says why a token moved, and `into=` keeps its payload in the context.
6
+
7
+ ## Why it exists
8
+
9
+ A token carries data from state to state. Without one rule, data can go to many places, and you must choose each time.
10
+
11
+ Stateous has two rules, and the API follows them:
12
+
13
+ 1. **Data.** A routine reads and writes only the context. An event's name and payload say *why* the token moved. Guards and the history read the payload. If the token must keep a payload, the event declares `into=`.
14
+ 2. **Decisions.** Routines decide, and guards only route. A routine returns the event that names its decision. A guard only chooses a transition, from data that is already there.
15
+
16
+ ## How it works
17
+
18
+ | Place | What it is for | Who reads it | When it is saved |
19
+ |---|---|---|---|
20
+ | The context, `ctx["key"]` | The data of the token: all that later states need. | Each routine of the token, and the guards. | With the event, or not at all. |
21
+ | An event's payload | Why the token moved, for example a `Verdict`. | The guards, and the history. Not the routines. | With the event, in the history. |
22
+ | `into="key"` on an event | Keeps the event's payload in the context, as `ctx["key"]`. | Each later routine, through the context. | With the event, before the guards run. |
23
+ | `ctx.memory` | The routine's own bookkeeping, for example an agent's session id. | The routine of the same state, on a retry or a later visit. | At once. |
24
+ | `ctx.note(text)` | One line of progress, for people. | People, in the history. | At once. |
25
+ | `inputs=` on a parallel state | Data that goes down, from the parent's context to each child's context. | The routines of the child. | When the child starts. |
26
+ | `into="key"` on a parallel state | Data that comes back up: the `Branches` of the join, with each child's context. | Each later routine of the parent. | When the join is met. |
27
+
28
+ Some facts:
29
+
30
+ - A routine knows which event brought the token in: `ctx.entered_by.name`, for example `timeout`. It does not get that event's payload. For the payload, declare `into=`.
31
+ - `into=` works for each source of the event: a routine's report, `stateous token fire`, a timer and a resume.
32
+ - If a routine writes the same key that the event's `into=` names, the payload wins.
33
+ - `stateous compile` checks `into=`. The field must be in the context model, and its type must take the payload model, for example `Verdict` or `Verdict | None`.
34
+ - `stateous token show` prints the context, so you can see what each routine wrote.
35
+
36
+ ## Example
37
+
38
+ This is a `triage` workflow for support tickets. `classify` decides if a ticket is urgent. `escalate` pages an engineer. `answer` writes the reply.
39
+
40
+ ```python
41
+ # triage/schemas.py
42
+ from pydantic import BaseModel
43
+
44
+
45
+ class Ticket(BaseModel):
46
+ title: str
47
+ body: str
48
+ on_call: str = "" # escalate writes it
49
+ reply: str = "" # answer writes it
50
+ ```
51
+
52
+ The rule "the body has the word 'down'" is a decision. So a routine makes it, and returns `urgent` or `normal`. A guard does not search the text.
53
+
54
+ ```python
55
+ # triage-supervisor/classify.py
56
+ import re
57
+
58
+ from stateous import on_state
59
+
60
+ from contract import TicketContext, events, states
61
+
62
+
63
+ @on_state(states.classify)
64
+ def classify(ctx: TicketContext) -> events.urgent | events.normal:
65
+ if re.search(r"\bdown\b", ctx["body"].lower()):
66
+ return events.urgent()
67
+ return events.normal()
68
+ ```
69
+
70
+ `escalate` writes the engineer's name to the context. `answer`, a later state, reads it there:
71
+
72
+ ```python
73
+ # triage-supervisor/escalate.py
74
+ from stateous import on_state
75
+
76
+ from contract import TicketContext, events, states
77
+
78
+
79
+ @on_state(states.escalate)
80
+ def escalate(ctx: TicketContext) -> events.paged:
81
+ ctx["on_call"] = "Ada"
82
+ ctx.note("paged Ada")
83
+ return events.paged()
84
+ ```
85
+
86
+ ```python
87
+ # triage-supervisor/answer.py
88
+ from stateous import on_state
89
+
90
+ from contract import TicketContext, events, states
91
+
92
+
93
+ @on_state(states.answer)
94
+ def answer(ctx: TicketContext) -> events.replied:
95
+ if ctx["on_call"]:
96
+ ctx["reply"] = f"This is urgent. {ctx['on_call']} works on it now."
97
+ else:
98
+ ctx["reply"] = "Thank you. We reply soon."
99
+ return events.replied()
100
+ ```
101
+
102
+ `stateous token show TICKET-1` then prints the context with `on_call` and `reply`.
103
+
104
+ ## Guarantees
105
+
106
+ - A routine's context changes and an event's `into=` are saved in one transaction with the event, or not at all.
107
+ - A routine never sees a payload. So it cannot depend on data that the token does not keep.
108
+ - `stateous compile` refuses an `into=` whose field is not in the context model. It also refuses a field whose type does not take the payload.
109
+
110
+ ## See also
111
+
112
+ - [Context, notes and memory](context-notes-memory.md): each field of `ctx`.
113
+ - [Guards](guards.md): what a guard can read.
114
+ - [Parallel and nested work](parallel.md): `inputs=` and `into=` on a parallel state.
115
+ - [The model](model.md): the words context, payload and event.
@@ -6,10 +6,12 @@ A guard is a small condition in `when=` that chooses which transition an event t
6
6
 
7
7
  ## Why it exists
8
8
 
9
- One outcome can lead to different places. For example, a failed test run goes back to `coding` two times, but the third failure ends the token. The routine must not know this rule, because it does not see the graph.
9
+ One event can lead to different places. For example, a failed test run goes back to `coding` two times, but the third failure ends the token. The routine must not know this rule, because it does not see the graph.
10
10
 
11
11
  So the graph holds the rule, as a guard on the transition. Stateous checks the guards when you compile, so a token never has two ways to go.
12
12
 
13
+ Routines decide, and guards only route. A decision about the work, such as "the body of the ticket has the word 'down'", is the work of a routine. The routine returns the event that names the decision, for example `urgent` or `normal`. A guard only chooses a transition from data that is already there: a count, a field of the payload, a field of the context. So the guard language has no text search and no arithmetic. See [Where data goes](data.md).
14
+
13
15
  ## How it works
14
16
 
15
17
  A guard is an expression in a small language. It is not Python, and nothing runs it as Python. It can use these items:
@@ -38,6 +40,14 @@ When an event comes, the control plane looks at the transitions from the token's
38
40
  2. If no guard matches, the token takes the transition with `otherwise=True`.
39
41
  3. If no guard matches and there is no `otherwise=True`, the control plane refuses the event. The token stays where it is.
40
42
 
43
+ **The rule of `otherwise=True`.** This is the one place where the docs give this rule:
44
+
45
+ - A transition that is the only one from its state on its event needs no guard and no `otherwise=True`.
46
+ - When a state has two or more transitions on one event, each has a guard, except at most one. That one has no guard, and it must have `otherwise=True`: it is the fallback.
47
+ - The fallback is optional. With no fallback, an event that no guard matches is refused.
48
+
49
+ Compile stops if a transition breaks this rule.
50
+
41
51
  ### The overlap check
42
52
 
43
53
  `stateous compile` makes sure that the guards on one state and one event never overlap. Two guards overlap when some data can make both of them true.
@@ -46,8 +56,6 @@ If two guards can overlap, compile stops with an error, and it gives an example
46
56
 
47
57
  Compile also stops when it cannot prove that two guards never overlap. This occurs when a guard compares two values with each other, orders a list, or uses `branches`. Then make one transition the fallback with `otherwise=True`.
48
58
 
49
- Next to transitions with guards, a transition without a guard must have `otherwise=True`. Compile stops if it does not.
50
-
51
59
  ## Example
52
60
 
53
61
  In `sdlc`, the event `failed` has two transitions from `testing`:
@@ -10,7 +10,7 @@ A long process has many steps, and each step can take minutes or days. A step ca
10
10
 
11
11
  Stateous puts the process in one graph. The graph tells all the places where a run can be, and all the ways to go on. A run never moves in a way that the graph does not show.
12
12
 
13
- This page defines each word of Stateous. Every other page uses each word in the same sense.
13
+ This page defines each word that you need to build with Stateous. Every other page uses each word in the same sense. The words of how Stateous works inside, such as lease and outbox, are on [The words inside](../inside/words.md).
14
14
 
15
15
  ## How it works
16
16
 
@@ -44,7 +44,7 @@ Two states are Stateous's own. `START` is where each token begins, and `END` is
44
44
 
45
45
  ### event
46
46
 
47
- An outcome of a state, for example `passed` or `failed`. Only an event moves a token. An event can have a payload.
47
+ What happened in a state, for example `passed` or `failed`. Only an event moves a token. Its name and its payload say why the token moved.
48
48
 
49
49
  Stateous has three events of its own: `timeout`, `error` and `joined`. Your transitions use them by name. You cannot declare them, and you cannot fire them.
50
50
 
@@ -62,25 +62,20 @@ The status is `active`, `paused`, `completed`, `failed` or `cancelled`. A token
62
62
 
63
63
  ### context
64
64
 
65
- The business data of a token, for example the path of a checkout. A pydantic model gives its fields. A routine reads it as `ctx["key"]`, and can write it.
65
+ The business data of a token, for example the path of a checkout. A pydantic model gives its fields. A routine reads it as `ctx["key"]`, and can write it. It is the only data that a routine reads and writes. See [Where data goes](data.md).
66
66
 
67
67
  ### payload
68
68
 
69
- The data of one event, for example the numbers of tests that passed and failed. A pydantic model gives its fields. An event without a payload model has no payload.
69
+ The data of one event, for example the numbers of tests that passed and failed. A pydantic model gives its fields. An event without a payload model has no payload. Guards and the history read the payload. To keep it for later states, the event declares `into="key"`, and the context keeps it as `ctx["key"]`.
70
70
 
71
71
  ### task
72
72
 
73
73
  "Do state S for token T". The control plane queues one task each time a token enters a state. A supervisor claims the task and runs its routine.
74
74
 
75
-
76
75
  ### visit
77
76
 
78
77
  One stay of a token in one state. The first time that a token enters a state is visit 1, the next time is visit 2. A routine reads it as `ctx.visit`. The history shows it, for example `entered coding (visit 2)`.
79
78
 
80
- ### claim
81
-
82
- A supervisor's request for a task of the states that it serves. The control plane answers with a grant: one queued task, its token, its visit, its attempt and its context. The task is then claimed.
83
-
84
79
  ### routine
85
80
 
86
81
  Your function that does the work of one state. It gets the token's context as `ctx`, and it returns an event. It is in the supervisor's folder, and it is marked with `@on_state(states.X)`.
@@ -97,45 +92,22 @@ The process that keeps every token, and chooses each next state. You start it wi
97
92
 
98
93
  The compiled workflow, as JSON, for example `sdlc/build/sdlc.json`. `stateous compile` writes it, and `stateous serve` serves it.
99
94
 
100
-
101
- ### checksum
102
-
103
- The hash of the whole graph file, for example `sha256:...`. Each change to the graph file changes it. A supervisor's `contract.py` is for one checksum.
104
-
105
- ### structure
106
-
107
- The hash of the graph file without the `timeout=` and `retries=` of its states. A database serves one structure at a time. See [Change a workflow](../how-to/change-a-workflow.md).
108
-
109
95
  ### contract
110
96
 
111
97
  The file `contract.py` in the supervisor's folder. `stateous init supervisor` writes it from the graph. It gives the states, the events that each state can emit, and the models. It is all that a supervisor knows of the workflow.
112
98
 
113
- ### ledger
114
-
115
- The history of each token. Each change adds one row, and the rows are never changed. `stateous token show` prints the ledger of a token.
116
-
117
99
  ### guard
118
100
 
119
- A condition on a transition, written in `when=`. It reads the context, the payload and counts of events. See [Guards](guards.md).
101
+ A condition on a transition, written in `when=`. It reads the context, the payload and counts of events. A guard only routes: a routine makes the decisions. See [Guards](guards.md).
120
102
 
121
103
  ### timer
122
104
 
123
105
  An event that the clock fires later, if the token is still in the same state. A state's `timeout=` is a timer too. See [Timers and timeouts](timers.md).
124
106
 
125
- ### lease
126
-
127
- The time for which a supervisor holds a claimed task. A heartbeat renews it. See [Tasks and routines](tasks-and-routines.md).
128
-
129
-
130
- ### heartbeat
131
-
132
- The message that a supervisor sends every 30 seconds for all the tasks that it holds. It renews their leases. Its answer names each task that the supervisor does not hold now, for example because the token left the state.
133
-
134
107
  ### attempt
135
108
 
136
109
  One try of a task. The first claim is attempt 1. When an attempt fails and the state has retries left, the next claim is attempt 2.
137
110
 
138
-
139
111
  ### retries
140
112
 
141
113
  The number of more attempts that a state gives after a failed attempt: `retries=2` gives three attempts in all. After the last failed attempt, the token gets the event `error`.
@@ -156,24 +128,10 @@ A state whose work is other workflows, which run at the same time. It has no tas
156
128
 
157
129
  A token that a parallel state starts, one for each workflow of the state. Its parent is the token in the parallel state.
158
130
 
159
-
160
131
  ### join
161
132
 
162
133
  The rule that tells when a parallel state is done. `join="all"` waits for each child token to end. `join="any"` continues when the first child token ends. When the join is met, the control plane fires `joined` on the parent.
163
134
 
164
- ### outbox
165
-
166
- The supervisor's local file of messages that the control plane did not answer yet: `.stateous/outbox.db`. See [Reliability](reliability.md).
167
-
168
- ### idempotency key
169
-
170
- A key that you give with an event from outside. A second event with the same key applies nothing, and gets the answer "duplicate". See [Reliability](reliability.md).
171
-
172
-
173
- ### held event
174
-
175
- An event that comes to a paused token. The control plane keeps it, and applies it on resume, in the order of arrival. The history shows `done held while paused`. See [Pause, resume and cancel](pause-resume-cancel.md).
176
-
177
135
  ## Example
178
136
 
179
137
  The `sdlc` workflow has two states. In `coding`, an agent writes code. In `testing`, a routine runs the tests.
@@ -240,7 +198,7 @@ stateDiagram-v2
240
198
  - A token is in one state at a time. Only an event moves it, and only along a transition of the graph.
241
199
  - An event that the token's state cannot take is refused. The token stays where it is, and the ledger records the refusal.
242
200
  - The ledger keeps each change. Stateous never changes or deletes a row of the ledger.
243
- - Not guaranteed: that your routine does the right work. Stateous makes sure only that its outcome is one that the graph allows.
201
+ - Not guaranteed: that your routine does the right work. Stateous makes sure only that its event is one that the graph allows.
244
202
 
245
203
  ## See also
246
204
 
@@ -47,9 +47,11 @@ When the join is met, the control plane fires the event `joined` on the parent.
47
47
  - `token`: the child's id;
48
48
  - `status`: `completed` or `failed`;
49
49
  - `final`: the state that the child was in when it ended;
50
- - `payload`: the payload of the event that brought the child into that state.
50
+ - `context`: the child's context when it ended.
51
51
 
52
- A guard on `joined` can use `branches.all(...)` and `branches.any(...)`. In the parentheses, use `workflow`, `token`, `status`, `final` and `payload.field`. The guard can also use `ctx` and `count`.
52
+ A guard on `joined` can use `branches.all(...)` and `branches.any(...)`. In the parentheses, use `workflow`, `token`, `status`, `final` and `context.field`. `stateous compile` checks that a child workflow of the state has the field. The guard can also use `ctx` and `count`.
53
+
54
+ To keep the results of the children for later states, give the parallel state `into="reviews"`. The parent's context then keeps the payload of `joined` as `ctx["reviews"]`. Its type in the context model is `Branches` (from `stateous`), or `Branches | None`. So data goes down to the children with `inputs=`, and comes back with `into=`, always through the context. See [Where data goes](data.md).
53
55
 
54
56
  `stateous compile` cannot prove that two guards with `branches` never overlap. So give the guard to one transition, and make the other the fallback with `otherwise=True`. If no transition takes `joined`, the parent fails.
55
57
 
@@ -103,6 +103,10 @@ error: the control plane at http://127.0.0.1:8080 refused (409): token 'TICKET-3
103
103
 
104
104
  ```text
105
105
  TICKET-3: testing, cancelled
106
+ context:
107
+ {
108
+ "repo": "acme/api"
109
+ }
106
110
  22:39:47 #1 started
107
111
  22:39:47 #2 __started__ in START
108
112
  22:39:47 #3 entered coding
@@ -130,7 +130,7 @@ build-box-2 took task 1 (attempt 2)
130
130
 
131
131
  ## See also
132
132
 
133
- - [The model](model.md): the words [ledger](model.md#ledger), [lease](model.md#lease), [outbox](model.md#outbox) and [idempotency key](model.md#idempotency-key).
133
+ - [The words inside](../inside/words.md): [ledger](../inside/words.md#ledger), [lease](../inside/words.md#lease), [outbox](../inside/words.md#outbox) and [idempotency key](../inside/words.md#idempotency-key).
134
134
  - [Tasks and routines](tasks-and-routines.md): claims, heartbeats and attempts.
135
135
  - [Context, notes and memory](context-notes-memory.md): the routine's memory.
136
136
  - [Pause, resume and cancel](pause-resume-cancel.md).
@@ -20,7 +20,7 @@ A task goes through these steps:
20
20
  4. **Heartbeat.** While the routine runs, the supervisor sends a heartbeat every 30 seconds. One heartbeat names all of its running tasks, and it renews their leases.
21
21
  5. **Report.** The routine returns, and the supervisor reports the outcome. An event moves the token, and the task is done.
22
22
 
23
- The supervisor reports one of three kinds of outcome:
23
+ The supervisor reports the **outcome** of the task. An outcome is one of three kinds:
24
24
 
25
25
  | Outcome | When | What the control plane does |
26
26
  |---|---|---|
@@ -128,9 +128,9 @@ build-box-2 took task 1 (attempt 2)
128
128
 
129
129
  ## See also
130
130
 
131
- - [The model](model.md): the words [task](model.md#task), [routine](model.md#routine), [lease](model.md#lease) and [attempt](model.md#attempt).
131
+ - [The model](model.md): the words [task](model.md#task), [routine](model.md#routine), [lease](../inside/words.md#lease) and [attempt](model.md#attempt).
132
132
  - [The two sides](two-sides.md): what crosses the network.
133
133
  - [Timers and timeouts](timers.md): a state's `timeout=` covers all attempts.
134
134
  - [Context, notes and memory](context-notes-memory.md): every field of `ctx`.
135
135
  - [Reliability](reliability.md): leases, the outbox, and what happens when a supervisor stops.
136
- - [Write a routine](../how-to/write-a-routine.md): mark a routine, return an outcome, and fail.
136
+ - [Write a routine](../how-to/write-a-routine.md): mark a routine, return an event, and fail.
@@ -12,7 +12,7 @@ So Stateous has two sides. The control plane decides, and the supervisors do the
12
12
 
13
13
  The supervisor never sees the graph, for two reasons:
14
14
 
15
- - A routine cannot depend on the next state. It can only return an outcome, so you can change the graph without a change to the routines.
15
+ - A routine cannot depend on the next state. It can only return an event, so you can change the graph without a change to the routines.
16
16
  - A supervisor has only the information that its work needs. The rest of the process stays on the control plane.
17
17
 
18
18
  ## How it works
@@ -36,48 +36,99 @@ Next steps:
36
36
 
37
37
  On Windows, the paths in these lines have `\` in place of `/`.
38
38
 
39
- This is `orders/flow.py`:
39
+ This is `orders/flow.py`, as `init` writes it:
40
40
 
41
41
  ```python
42
42
  # orders/flow.py: the workflow. `stateous compile orders/flow.py` checks it and writes
43
43
  # the graph file, orders/build/orders.json.
44
44
  from stateous import END, START, Workflow
45
45
 
46
+ from schemas import Job, Result
47
+
48
+ # The workflow. Each token has a context: a Job (see schemas.py). The context is
49
+ # the data of a token. A routine reads and writes only the context.
50
+ flow = Workflow("orders", context=Job)
51
+
52
+ # The states. A token is in one state at a time. A routine does the work of a state.
53
+ work = flow.state("work")
54
+
55
+ # The events. An event moves a token. Its payload says why the token moved.
56
+ # `into="result"` keeps the payload in the context, as ctx["result"].
57
+ done = flow.event("done", payload=Result, into="result")
58
+
59
+ # The transitions: where each event takes a token. START and END begin and end each token.
60
+ # A routine decides which event to return. A guard (`when=`) only chooses a transition.
61
+ # See https://stateouslabs.github.io/stateous/concepts/data/
62
+ flow.transition(START, work)
63
+ flow.transition(work, END, on=done)
64
+ ```
65
+
66
+ The starter has one state, `work`, and one event, `done`. Its comments give two rules:
67
+
68
+ - **Where data goes.** The context is the data of a token. A routine reads and writes only the context. An event's payload says why the token moved. `into=` keeps a payload in the context. See [Where data goes](concepts/data.md).
69
+ - **Who decides.** A routine decides, and returns the event that names its decision. A guard only chooses a transition.
70
+
71
+ ## Make it yours
72
+
73
+ Now change the starter into `orders`. Replace `orders/schemas.py` with this:
74
+
75
+ ```python
76
+ # orders/schemas.py (your version)
77
+ from pydantic import BaseModel
78
+
79
+
80
+ class Verdict(BaseModel):
81
+ """The payload of the event `reviewed`."""
82
+
83
+ approved: bool
84
+ note: str = ""
85
+
86
+
87
+ class Item(BaseModel):
88
+ """The context of each token of `orders`."""
89
+
90
+ title: str
91
+ verdict: Verdict | None = None # `reviewed` keeps its payload here
92
+ ```
93
+
94
+ Replace `orders/flow.py` with this:
95
+
96
+ ```python
97
+ # orders/flow.py (your version)
98
+ from stateous import END, START, Workflow
99
+
46
100
  from schemas import Item, Verdict
47
101
 
48
- # The workflow and its name. Each token has a context: an Item (see schemas.py).
49
102
  flow = Workflow("orders", context=Item)
50
103
 
51
- # The states. A token is in one state at a time. A routine does the work of a state.
52
- # `timeout=` and `retries=` are optional. See
53
- # https://stateouslabs.github.io/stateous/how-to/write-a-workflow/
54
104
  draft = flow.state("draft")
55
105
  review = flow.state("review")
56
106
 
57
- # The events: the outcomes that move a token. A payload is optional.
58
107
  submitted = flow.event("submitted") # no payload
59
- reviewed = flow.event("reviewed", payload=Verdict) # the payload is a Verdict
108
+ reviewed = flow.event("reviewed", payload=Verdict, into="verdict")
60
109
 
61
- # The transitions: where each event takes a token. START and END begin and end each token.
62
110
  flow.transition(START, draft) # a new token goes to `draft` at once
63
111
  flow.transition(draft, review, on=submitted)
64
- # A guard (`when=`) is optional. Here, the payload of `reviewed` chooses the target.
112
+ # The routine of `review` decides. The guard only routes on its Verdict.
65
113
  flow.transition(review, END, on=reviewed, when="payload.approved == true")
66
- # `otherwise=True` marks the transition to use when no guard matches.
67
114
  flow.transition(review, draft, on=reviewed, otherwise=True)
68
115
  ```
69
116
 
117
+ Then compile it again:
118
+
119
+ ```bash
120
+ stateous compile orders/flow.py
121
+ ```
122
+
70
123
  What each part does:
71
124
 
72
125
  - `Workflow("orders", context=Item)` makes the workflow. A **token** is one run of the workflow. Each token has a **context**: here, an `Item`.
73
126
  - `flow.state(...)` adds a **state**. A token is in one state at a time. When a token enters a state, the control plane makes a **task** for it.
74
- - `flow.event(...)` adds an **event**: an outcome of a state. Only an event moves a token. An event can have a **payload**, for example a `Verdict`.
127
+ - `flow.event(...)` adds an **event**. Only an event moves a token. An event can have a **payload**, for example a `Verdict`. With `into="verdict"`, the context keeps the `Verdict` as `ctx["verdict"]`.
75
128
  - `flow.transition(...)` tells where an event takes a token. `START` is where each token begins, and `END` is where it ends.
76
129
  - `when=` is a **guard**: a condition on the payload or the context. Here, an approved `Verdict` ends the token.
77
130
  - `otherwise=True` marks the transition that applies when no guard matches. Here, a `Verdict` that does not approve sends the token back to `draft`.
78
131
 
79
- `orders/schemas.py` holds the two models. `Item` has one field, `title`. `Verdict` has the fields `approved` and `note`.
80
-
81
132
  This is the graph of `orders`:
82
133
 
83
134
  ```mermaid
@@ -88,7 +139,7 @@ stateDiagram-v2
88
139
  review --> draft: reviewed (otherwise)
89
140
  ```
90
141
 
91
- `init` also compiled `flow.py` to the graph file `orders/build/orders.json`. After you change `flow.py`, compile it again with `stateous compile orders/flow.py`.
142
+ `stateous compile` writes the graph file `orders/build/orders.json`. After each change to `flow.py` or `schemas.py`, compile again.
92
143
 
93
144
  ## Terminal 1: serve the workflow
94
145
 
@@ -161,10 +212,18 @@ stateous token start ORDER-1 '{"title": "first order"}'
161
212
  stateous token show ORDER-1
162
213
  ```
163
214
 
164
- `token start` prints `ORDER-1: draft, active`. One or two seconds later, `token show` prints the token and its history:
215
+ `token start` prints `ORDER-1: draft, active`. One or two seconds later, `token show` prints the token, its context and its history:
165
216
 
166
217
  ```text
167
218
  ORDER-1: review, completed
219
+ context:
220
+ {
221
+ "title": "first order",
222
+ "verdict": {
223
+ "approved": true,
224
+ "note": ""
225
+ }
226
+ }
168
227
  22:23:31 #1 started
169
228
  22:23:31 #2 __started__ in START
170
229
  22:23:31 #3 entered draft
@@ -175,9 +234,10 @@ ORDER-1: review, completed
175
234
  22:23:32 #8 entered review
176
235
  22:23:32 #9 task 2 queued for review
177
236
  22:23:32 #10 build-box-1 took task 2 (attempt 1)
178
- 22:23:32 #11 reviewed in review: {"approved": true, "note": ""}
179
- 22:23:32 #12 task 2 done
180
- 22:23:32 #13 completed
237
+ 22:23:32 #11 context changed: verdict
238
+ 22:23:32 #12 reviewed in review: {"approved": true, "note": ""}
239
+ 22:23:32 #13 task 2 done
240
+ 22:23:32 #14 completed
181
241
  ```
182
242
 
183
243
  The first line gives the last state and the status. `build-box-1` is the name of the supervisor: by default, the host name of its computer.
@@ -193,6 +253,14 @@ The review sends `ORDER-2` back to `draft`. There, `draft` returns `None`, so th
193
253
 
194
254
  ```text
195
255
  ORDER-2: draft (visit 2), active
256
+ context:
257
+ {
258
+ "title": "reject me",
259
+ "verdict": {
260
+ "approved": false,
261
+ "note": ""
262
+ }
263
+ }
196
264
  22:23:40 #1 started
197
265
  22:23:40 #2 __started__ in START
198
266
  22:23:40 #3 entered draft
@@ -203,13 +271,14 @@ ORDER-2: draft (visit 2), active
203
271
  22:23:40 #8 entered review
204
272
  22:23:40 #9 task 4 queued for review
205
273
  22:23:40 #10 build-box-1 took task 4 (attempt 1)
206
- 22:23:40 #11 reviewed in review: {"approved": false, "note": ""}
207
- 22:23:40 #12 task 4 done
208
- 22:23:40 #13 entered draft (visit 2)
209
- 22:23:40 #14 task 5 queued for draft
210
- 22:23:40 #15 build-box-1 took task 5 (attempt 1)
211
- 22:23:40 #16 the review sent this order back: a person must change it
212
- 22:23:40 #17 task 5 waits: the work goes on outside
274
+ 22:23:40 #11 context changed: verdict
275
+ 22:23:40 #12 reviewed in review: {"approved": false, "note": ""}
276
+ 22:23:40 #13 task 4 done
277
+ 22:23:40 #14 entered draft (visit 2)
278
+ 22:23:40 #15 task 5 queued for draft
279
+ 22:23:40 #16 build-box-1 took task 5 (attempt 1)
280
+ 22:23:40 #17 the review sent this order back: a person must change it
281
+ 22:23:40 #18 task 5 waits: the work goes on outside
213
282
  ```
214
283
 
215
284
  The token stays in `draft` until a person acts. For example, `stateous token cancel ORDER-2` cancels it. [Steer tokens](how-to/steer-tokens.md) tells what else a person can do.
@@ -47,16 +47,22 @@ The duplicate shows the token as it is now. A key stays used for the life of the
47
47
 
48
48
  ```bash
49
49
  stateous token show TICKET-1
50
+ stateous token show TICKET-1 --json
50
51
  stateous token show
51
52
  stateous token show --graph
52
53
  ```
53
54
 
54
- - With an id, `show` prints the token's line, its children, and its history. The times are local.
55
+ - With an id, `show` prints the token's line, its context, its children, and its history. The times are local.
56
+ - With an id and `--json`, it prints the same as one JSON object, for scripts.
55
57
  - With no id, it lists the tokens, at most 100, the most recently moved first.
56
58
  - `--graph` draws the workflow as a Mermaid diagram.
57
59
 
58
60
  ```text
59
61
  TICKET-1: testing, completed
62
+ context:
63
+ {
64
+ "repo": "acme/api"
65
+ }
60
66
  23:43:39 #1 started
61
67
  23:43:39 #2 __started__ in START
62
68
  23:43:39 #3 entered coding
@@ -157,6 +163,6 @@ The `Idempotency-Key` header is the key. A payload goes in the body, as `{"paylo
157
163
  - [Pause, resume and cancel](../concepts/pause-resume-cancel.md): held events, and the order of replay.
158
164
  - [Reliability](../concepts/reliability.md): idempotency keys.
159
165
  - [Tasks and routines](../concepts/tasks-and-routines.md): tasks that wait.
160
- - [The model](../concepts/model.md): the words [token](../concepts/model.md#token) and [idempotency key](../concepts/model.md#idempotency-key).
166
+ - [The model](../concepts/model.md): the words [token](../concepts/model.md#token) and [idempotency key](../inside/words.md#idempotency-key).
161
167
  - [The commands](../reference/cli.md): every `token` command and its options.
162
168
  - [The HTTP API](../reference/http.md): every route, and the refusal codes.