gobby 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl

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 (198) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +96 -35
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/adapters/gemini.py +140 -38
  10. gobby/agents/definitions.py +11 -1
  11. gobby/agents/isolation.py +525 -0
  12. gobby/agents/registry.py +11 -0
  13. gobby/agents/sandbox.py +261 -0
  14. gobby/agents/session.py +1 -0
  15. gobby/agents/spawn.py +42 -287
  16. gobby/agents/spawn_executor.py +415 -0
  17. gobby/agents/spawners/__init__.py +24 -0
  18. gobby/agents/spawners/command_builder.py +189 -0
  19. gobby/agents/spawners/embedded.py +21 -2
  20. gobby/agents/spawners/headless.py +21 -2
  21. gobby/agents/spawners/macos.py +26 -1
  22. gobby/agents/spawners/prompt_manager.py +125 -0
  23. gobby/cli/__init__.py +0 -2
  24. gobby/cli/install.py +4 -4
  25. gobby/cli/installers/claude.py +6 -0
  26. gobby/cli/installers/gemini.py +6 -0
  27. gobby/cli/installers/shared.py +103 -4
  28. gobby/cli/memory.py +185 -0
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/utils.py +9 -2
  31. gobby/clones/git.py +177 -0
  32. gobby/config/__init__.py +12 -97
  33. gobby/config/app.py +10 -94
  34. gobby/config/extensions.py +2 -2
  35. gobby/config/features.py +7 -130
  36. gobby/config/skills.py +31 -0
  37. gobby/config/tasks.py +4 -28
  38. gobby/hooks/__init__.py +0 -13
  39. gobby/hooks/event_handlers.py +150 -8
  40. gobby/hooks/hook_manager.py +21 -3
  41. gobby/hooks/plugins.py +1 -1
  42. gobby/hooks/webhooks.py +1 -1
  43. gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
  44. gobby/llm/resolver.py +3 -2
  45. gobby/mcp_proxy/importer.py +62 -4
  46. gobby/mcp_proxy/instructions.py +4 -2
  47. gobby/mcp_proxy/registries.py +22 -8
  48. gobby/mcp_proxy/services/recommendation.py +43 -11
  49. gobby/mcp_proxy/tools/agent_messaging.py +93 -44
  50. gobby/mcp_proxy/tools/agents.py +76 -740
  51. gobby/mcp_proxy/tools/artifacts.py +43 -9
  52. gobby/mcp_proxy/tools/clones.py +0 -385
  53. gobby/mcp_proxy/tools/memory.py +2 -2
  54. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  55. gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
  56. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  57. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  58. gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
  59. gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
  60. gobby/mcp_proxy/tools/skills/__init__.py +14 -29
  61. gobby/mcp_proxy/tools/spawn_agent.py +455 -0
  62. gobby/mcp_proxy/tools/tasks/_context.py +18 -0
  63. gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
  64. gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
  65. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
  66. gobby/mcp_proxy/tools/tasks/_session.py +22 -7
  67. gobby/mcp_proxy/tools/workflows.py +84 -34
  68. gobby/mcp_proxy/tools/worktrees.py +32 -350
  69. gobby/memory/extractor.py +15 -1
  70. gobby/memory/ingestion/__init__.py +5 -0
  71. gobby/memory/ingestion/multimodal.py +221 -0
  72. gobby/memory/manager.py +62 -283
  73. gobby/memory/search/__init__.py +10 -0
  74. gobby/memory/search/coordinator.py +248 -0
  75. gobby/memory/services/__init__.py +5 -0
  76. gobby/memory/services/crossref.py +142 -0
  77. gobby/prompts/loader.py +5 -2
  78. gobby/runner.py +13 -0
  79. gobby/servers/http.py +1 -4
  80. gobby/servers/routes/admin.py +14 -0
  81. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  82. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  83. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  84. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  85. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  86. gobby/servers/routes/mcp/hooks.py +51 -4
  87. gobby/servers/routes/mcp/tools.py +48 -1506
  88. gobby/servers/websocket.py +57 -1
  89. gobby/sessions/analyzer.py +2 -2
  90. gobby/sessions/lifecycle.py +1 -1
  91. gobby/sessions/manager.py +9 -0
  92. gobby/sessions/processor.py +10 -0
  93. gobby/sessions/transcripts/base.py +1 -0
  94. gobby/sessions/transcripts/claude.py +15 -5
  95. gobby/sessions/transcripts/gemini.py +100 -34
  96. gobby/skills/parser.py +30 -2
  97. gobby/storage/database.py +9 -2
  98. gobby/storage/memories.py +32 -21
  99. gobby/storage/migrations.py +174 -368
  100. gobby/storage/sessions.py +45 -7
  101. gobby/storage/skills.py +80 -7
  102. gobby/storage/tasks/_lifecycle.py +18 -3
  103. gobby/sync/memories.py +1 -1
  104. gobby/tasks/external_validator.py +1 -1
  105. gobby/tasks/validation.py +22 -20
  106. gobby/tools/summarizer.py +91 -10
  107. gobby/utils/project_context.py +2 -3
  108. gobby/utils/status.py +13 -0
  109. gobby/workflows/actions.py +221 -1217
  110. gobby/workflows/artifact_actions.py +31 -0
  111. gobby/workflows/autonomous_actions.py +11 -0
  112. gobby/workflows/context_actions.py +50 -1
  113. gobby/workflows/detection_helpers.py +38 -24
  114. gobby/workflows/enforcement/__init__.py +47 -0
  115. gobby/workflows/enforcement/blocking.py +281 -0
  116. gobby/workflows/enforcement/commit_policy.py +283 -0
  117. gobby/workflows/enforcement/handlers.py +269 -0
  118. gobby/workflows/enforcement/task_policy.py +542 -0
  119. gobby/workflows/engine.py +93 -0
  120. gobby/workflows/evaluator.py +110 -0
  121. gobby/workflows/git_utils.py +106 -0
  122. gobby/workflows/hooks.py +41 -0
  123. gobby/workflows/llm_actions.py +30 -0
  124. gobby/workflows/mcp_actions.py +20 -1
  125. gobby/workflows/memory_actions.py +91 -0
  126. gobby/workflows/safe_evaluator.py +191 -0
  127. gobby/workflows/session_actions.py +44 -0
  128. gobby/workflows/state_actions.py +60 -1
  129. gobby/workflows/stop_signal_actions.py +55 -0
  130. gobby/workflows/summary_actions.py +217 -51
  131. gobby/workflows/task_sync_actions.py +347 -0
  132. gobby/workflows/todo_actions.py +34 -1
  133. gobby/workflows/webhook_actions.py +185 -0
  134. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
  135. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
  136. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
  137. gobby/adapters/codex.py +0 -1332
  138. gobby/cli/tui.py +0 -34
  139. gobby/install/claude/commands/gobby/bug.md +0 -51
  140. gobby/install/claude/commands/gobby/chore.md +0 -51
  141. gobby/install/claude/commands/gobby/epic.md +0 -52
  142. gobby/install/claude/commands/gobby/eval.md +0 -235
  143. gobby/install/claude/commands/gobby/feat.md +0 -49
  144. gobby/install/claude/commands/gobby/nit.md +0 -52
  145. gobby/install/claude/commands/gobby/ref.md +0 -52
  146. gobby/mcp_proxy/tools/session_messages.py +0 -1055
  147. gobby/prompts/defaults/expansion/system.md +0 -119
  148. gobby/prompts/defaults/expansion/user.md +0 -48
  149. gobby/prompts/defaults/external_validation/agent.md +0 -72
  150. gobby/prompts/defaults/external_validation/external.md +0 -63
  151. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  152. gobby/prompts/defaults/external_validation/system.md +0 -6
  153. gobby/prompts/defaults/features/import_mcp.md +0 -22
  154. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  155. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  156. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  157. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  158. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  159. gobby/prompts/defaults/features/server_description.md +0 -20
  160. gobby/prompts/defaults/features/server_description_system.md +0 -6
  161. gobby/prompts/defaults/features/task_description.md +0 -31
  162. gobby/prompts/defaults/features/task_description_system.md +0 -6
  163. gobby/prompts/defaults/features/tool_summary.md +0 -17
  164. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  165. gobby/prompts/defaults/handoff/compact.md +0 -63
  166. gobby/prompts/defaults/handoff/session_end.md +0 -57
  167. gobby/prompts/defaults/memory/extract.md +0 -61
  168. gobby/prompts/defaults/research/step.md +0 -58
  169. gobby/prompts/defaults/validation/criteria.md +0 -47
  170. gobby/prompts/defaults/validation/validate.md +0 -38
  171. gobby/storage/migrations_legacy.py +0 -1359
  172. gobby/tui/__init__.py +0 -5
  173. gobby/tui/api_client.py +0 -278
  174. gobby/tui/app.py +0 -329
  175. gobby/tui/screens/__init__.py +0 -25
  176. gobby/tui/screens/agents.py +0 -333
  177. gobby/tui/screens/chat.py +0 -450
  178. gobby/tui/screens/dashboard.py +0 -377
  179. gobby/tui/screens/memory.py +0 -305
  180. gobby/tui/screens/metrics.py +0 -231
  181. gobby/tui/screens/orchestrator.py +0 -903
  182. gobby/tui/screens/sessions.py +0 -412
  183. gobby/tui/screens/tasks.py +0 -440
  184. gobby/tui/screens/workflows.py +0 -289
  185. gobby/tui/screens/worktrees.py +0 -174
  186. gobby/tui/widgets/__init__.py +0 -21
  187. gobby/tui/widgets/chat.py +0 -210
  188. gobby/tui/widgets/conductor.py +0 -104
  189. gobby/tui/widgets/menu.py +0 -132
  190. gobby/tui/widgets/message_panel.py +0 -160
  191. gobby/tui/widgets/review_gate.py +0 -224
  192. gobby/tui/widgets/task_tree.py +0 -99
  193. gobby/tui/widgets/token_budget.py +0 -166
  194. gobby/tui/ws_client.py +0 -258
  195. gobby/workflows/task_enforcement_actions.py +0 -1343
  196. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
  197. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
  198. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
@@ -113,12 +113,20 @@ def create_workflows_registry(
113
113
  Returns:
114
114
  InternalToolRegistry with workflow tools registered
115
115
  """
116
+ from gobby.utils.project_context import get_project_context
117
+
116
118
  # Create defaults if not provided
117
119
  _db = db or LocalDatabase()
118
120
  _loader = loader or WorkflowLoader()
119
121
  _state_manager = state_manager or WorkflowStateManager(_db)
120
122
  _session_manager = session_manager or LocalSessionManager(_db)
121
123
 
124
+ def _resolve_session_id(ref: str) -> str:
125
+ """Resolve session reference (#N, N, UUID, or prefix) to UUID."""
126
+ project_ctx = get_project_context()
127
+ project_id = project_ctx.get("id") if project_ctx else None
128
+ return _session_manager.resolve_session_reference(ref, project_id)
129
+
122
130
  registry = InternalToolRegistry(
123
131
  name="gobby-workflows",
124
132
  description="Workflow management - list, activate, status, transition, end",
@@ -264,7 +272,7 @@ def create_workflows_registry(
264
272
 
265
273
  @registry.tool(
266
274
  name="activate_workflow",
267
- description="Activate a step-based workflow for the current session.",
275
+ description="Activate a step-based workflow for the current session. Accepts #N, N, UUID, or prefix for session_id.",
268
276
  )
269
277
  def activate_workflow(
270
278
  name: str,
@@ -278,7 +286,7 @@ def create_workflows_registry(
278
286
 
279
287
  Args:
280
288
  name: Workflow name (e.g., "plan-act-reflect", "auto-task")
281
- session_id: Required session ID (must be provided to prevent cross-session bleed)
289
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
282
290
  initial_step: Optional starting step (defaults to first step)
283
291
  variables: Optional initial variables to set (merged with workflow defaults)
284
292
  project_path: Project directory path. Auto-discovered from cwd if not provided.
@@ -290,7 +298,7 @@ def create_workflows_registry(
290
298
  activate_workflow(
291
299
  name="auto-task",
292
300
  variables={"session_task": "#47"},
293
- session_id="..."
301
+ session_id="#5"
294
302
  )
295
303
 
296
304
  Errors if:
@@ -325,12 +333,18 @@ def create_workflows_registry(
325
333
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
326
334
  }
327
335
 
336
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
337
+ try:
338
+ resolved_session_id = _resolve_session_id(session_id)
339
+ except ValueError as e:
340
+ return {"success": False, "error": str(e)}
341
+
328
342
  # Check for existing workflow
329
343
  # Allow if:
330
344
  # - No existing state
331
345
  # - Existing is __lifecycle__ placeholder
332
346
  # - Existing is a lifecycle-type workflow (they run concurrently with step workflows)
333
- existing = _state_manager.get_state(session_id)
347
+ existing = _state_manager.get_state(resolved_session_id)
334
348
  if existing and existing.workflow_name != "__lifecycle__":
335
349
  # Check if existing workflow is a lifecycle type
336
350
  existing_def = _loader.load_workflow(existing.workflow_name, proj)
@@ -366,12 +380,12 @@ def create_workflows_registry(
366
380
  session_task_val = merged_variables["session_task"]
367
381
  if isinstance(session_task_val, str):
368
382
  merged_variables["session_task"] = _resolve_session_task_value(
369
- session_task_val, session_id, _session_manager, _db
383
+ session_task_val, resolved_session_id, _session_manager, _db
370
384
  )
371
385
 
372
386
  # Create state
373
387
  state = WorkflowState(
374
- session_id=session_id,
388
+ session_id=resolved_session_id,
375
389
  workflow_name=name,
376
390
  step=step,
377
391
  step_entered_at=datetime.now(UTC),
@@ -391,7 +405,7 @@ def create_workflows_registry(
391
405
 
392
406
  return {
393
407
  "success": True,
394
- "session_id": session_id,
408
+ "session_id": resolved_session_id,
395
409
  "workflow": name,
396
410
  "step": step,
397
411
  "steps": [s.name for s in definition.steps],
@@ -400,7 +414,7 @@ def create_workflows_registry(
400
414
 
401
415
  @registry.tool(
402
416
  name="end_workflow",
403
- description="End the currently active step-based workflow.",
417
+ description="End the currently active step-based workflow. Accepts #N, N, UUID, or prefix for session_id.",
404
418
  )
405
419
  def end_workflow(
406
420
  session_id: str | None = None,
@@ -413,7 +427,7 @@ def create_workflows_registry(
413
427
  Does not affect lifecycle workflows (they continue running).
414
428
 
415
429
  Args:
416
- session_id: Required session ID (must be provided to prevent cross-session bleed)
430
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
417
431
  reason: Optional reason for ending
418
432
 
419
433
  Returns:
@@ -426,24 +440,30 @@ def create_workflows_registry(
426
440
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
427
441
  }
428
442
 
429
- state = _state_manager.get_state(session_id)
443
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
444
+ try:
445
+ resolved_session_id = _resolve_session_id(session_id)
446
+ except ValueError as e:
447
+ return {"success": False, "error": str(e)}
448
+
449
+ state = _state_manager.get_state(resolved_session_id)
430
450
  if not state:
431
451
  return {"error": "No workflow active for session"}
432
452
 
433
- _state_manager.delete_state(session_id)
453
+ _state_manager.delete_state(resolved_session_id)
434
454
 
435
455
  return {}
436
456
 
437
457
  @registry.tool(
438
458
  name="get_workflow_status",
439
- description="Get current workflow step and state.",
459
+ description="Get current workflow step and state. Accepts #N, N, UUID, or prefix for session_id.",
440
460
  )
441
461
  def get_workflow_status(session_id: str | None = None) -> dict[str, Any]:
442
462
  """
443
463
  Get current workflow step and state.
444
464
 
445
465
  Args:
446
- session_id: Required session ID (must be provided to prevent cross-session bleed)
466
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
447
467
 
448
468
  Returns:
449
469
  Workflow state including step, action counts, artifacts
@@ -455,13 +475,19 @@ def create_workflows_registry(
455
475
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
456
476
  }
457
477
 
458
- state = _state_manager.get_state(session_id)
478
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
479
+ try:
480
+ resolved_session_id = _resolve_session_id(session_id)
481
+ except ValueError as e:
482
+ return {"has_workflow": False, "error": str(e)}
483
+
484
+ state = _state_manager.get_state(resolved_session_id)
459
485
  if not state:
460
- return {"has_workflow": False, "session_id": session_id}
486
+ return {"has_workflow": False, "session_id": resolved_session_id}
461
487
 
462
488
  return {
463
489
  "has_workflow": True,
464
- "session_id": session_id,
490
+ "session_id": resolved_session_id,
465
491
  "workflow_name": state.workflow_name,
466
492
  "step": state.step,
467
493
  "step_action_count": state.step_action_count,
@@ -479,7 +505,7 @@ def create_workflows_registry(
479
505
 
480
506
  @registry.tool(
481
507
  name="request_step_transition",
482
- description="Request transition to a different step.",
508
+ description="Request transition to a different step. Accepts #N, N, UUID, or prefix for session_id.",
483
509
  )
484
510
  def request_step_transition(
485
511
  to_step: str,
@@ -494,7 +520,7 @@ def create_workflows_registry(
494
520
  Args:
495
521
  to_step: Target step name
496
522
  reason: Reason for transition
497
- session_id: Required session ID (must be provided to prevent cross-session bleed)
523
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
498
524
  force: Skip exit condition checks
499
525
  project_path: Project directory path. Auto-discovered from cwd if not provided.
500
526
 
@@ -516,7 +542,13 @@ def create_workflows_registry(
516
542
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
517
543
  }
518
544
 
519
- state = _state_manager.get_state(session_id)
545
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
546
+ try:
547
+ resolved_session_id = _resolve_session_id(session_id)
548
+ except ValueError as e:
549
+ return {"success": False, "error": str(e)}
550
+
551
+ state = _state_manager.get_state(resolved_session_id)
520
552
  if not state:
521
553
  return {"success": False, "error": "No workflow active for session"}
522
554
 
@@ -565,7 +597,7 @@ def create_workflows_registry(
565
597
 
566
598
  @registry.tool(
567
599
  name="mark_artifact_complete",
568
- description="Register an artifact as complete (plan, spec, etc.).",
600
+ description="Register an artifact as complete (plan, spec, etc.). Accepts #N, N, UUID, or prefix for session_id.",
569
601
  )
570
602
  def mark_artifact_complete(
571
603
  artifact_type: str,
@@ -578,7 +610,7 @@ def create_workflows_registry(
578
610
  Args:
579
611
  artifact_type: Type of artifact (e.g., "plan", "spec", "test")
580
612
  file_path: Path to the artifact file
581
- session_id: Required session ID (must be provided to prevent cross-session bleed)
613
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
582
614
 
583
615
  Returns:
584
616
  Success status
@@ -590,7 +622,13 @@ def create_workflows_registry(
590
622
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
591
623
  }
592
624
 
593
- state = _state_manager.get_state(session_id)
625
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
626
+ try:
627
+ resolved_session_id = _resolve_session_id(session_id)
628
+ except ValueError as e:
629
+ return {"success": False, "error": str(e)}
630
+
631
+ state = _state_manager.get_state(resolved_session_id)
594
632
  if not state:
595
633
  return {"error": "No workflow active for session"}
596
634
 
@@ -602,7 +640,7 @@ def create_workflows_registry(
602
640
 
603
641
  @registry.tool(
604
642
  name="set_variable",
605
- description="Set a workflow variable for the current session (session-scoped, not persisted to YAML).",
643
+ description="Set a workflow variable for the current session (session-scoped, not persisted to YAML). Accepts #N, N, UUID, or prefix for session_id.",
606
644
  )
607
645
  def set_variable(
608
646
  name: str,
@@ -623,7 +661,7 @@ def create_workflows_registry(
623
661
  Args:
624
662
  name: Variable name (e.g., "session_epic", "is_worktree")
625
663
  value: Variable value (string, number, boolean, or null)
626
- session_id: Required session ID (must be provided to prevent cross-session bleed)
664
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
627
665
 
628
666
  Returns:
629
667
  Success status and updated variables
@@ -635,12 +673,18 @@ def create_workflows_registry(
635
673
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
636
674
  }
637
675
 
676
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
677
+ try:
678
+ resolved_session_id = _resolve_session_id(session_id)
679
+ except ValueError as e:
680
+ return {"success": False, "error": str(e)}
681
+
638
682
  # Get or create state
639
- state = _state_manager.get_state(session_id)
683
+ state = _state_manager.get_state(resolved_session_id)
640
684
  if not state:
641
685
  # Create a minimal lifecycle state for variable storage
642
686
  state = WorkflowState(
643
- session_id=session_id,
687
+ session_id=resolved_session_id,
644
688
  workflow_name="__lifecycle__",
645
689
  step="",
646
690
  step_entered_at=datetime.now(UTC),
@@ -664,7 +708,7 @@ def create_workflows_registry(
664
708
  # Resolve session_task references (#N or N) to UUIDs upfront
665
709
  # This prevents repeated resolution failures in condition evaluation
666
710
  if name == "session_task" and isinstance(value, str):
667
- value = _resolve_session_task_value(value, session_id, _session_manager, _db)
711
+ value = _resolve_session_task_value(value, resolved_session_id, _session_manager, _db)
668
712
 
669
713
  # Set the variable
670
714
  state.variables[name] = value
@@ -684,7 +728,7 @@ def create_workflows_registry(
684
728
 
685
729
  @registry.tool(
686
730
  name="get_variable",
687
- description="Get workflow variable(s) for the current session.",
731
+ description="Get workflow variable(s) for the current session. Accepts #N, N, UUID, or prefix for session_id.",
688
732
  )
689
733
  def get_variable(
690
734
  name: str | None = None,
@@ -695,7 +739,7 @@ def create_workflows_registry(
695
739
 
696
740
  Args:
697
741
  name: Variable name to get (if None, returns all variables)
698
- session_id: Required session ID (must be provided to prevent cross-session bleed)
742
+ session_id: Session reference (accepts #N, N, UUID, or prefix) - required to prevent cross-session bleed
699
743
 
700
744
  Returns:
701
745
  Variable value(s) and session info
@@ -707,19 +751,25 @@ def create_workflows_registry(
707
751
  "error": "session_id is required. Pass the session ID explicitly to prevent cross-session variable bleed.",
708
752
  }
709
753
 
710
- state = _state_manager.get_state(session_id)
754
+ # Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
755
+ try:
756
+ resolved_session_id = _resolve_session_id(session_id)
757
+ except ValueError as e:
758
+ return {"success": False, "error": str(e)}
759
+
760
+ state = _state_manager.get_state(resolved_session_id)
711
761
  if not state:
712
762
  if name:
713
763
  return {
714
764
  "success": True,
715
- "session_id": session_id,
765
+ "session_id": resolved_session_id,
716
766
  "variable": name,
717
767
  "value": None,
718
768
  "exists": False,
719
769
  }
720
770
  return {
721
771
  "success": True,
722
- "session_id": session_id,
772
+ "session_id": resolved_session_id,
723
773
  "variables": {},
724
774
  }
725
775
 
@@ -727,7 +777,7 @@ def create_workflows_registry(
727
777
  value = state.variables.get(name)
728
778
  return {
729
779
  "success": True,
730
- "session_id": session_id,
780
+ "session_id": resolved_session_id,
731
781
  "variable": name,
732
782
  "value": value,
733
783
  "exists": name in state.variables,
@@ -735,7 +785,7 @@ def create_workflows_registry(
735
785
 
736
786
  return {
737
787
  "success": True,
738
- "session_id": session_id,
788
+ "session_id": resolved_session_id,
739
789
  "variables": state.variables,
740
790
  }
741
791