You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ConstellationEditor provides a high-level, command pattern-based interface for safe and comprehensive TaskConstellation manipulation. It offers undo/redo capabilities, batch operations, validation, and observer patterns for building, modifying, and managing complex workflow DAGs interactively.
The editor uses the Command Pattern to encapsulate all operations as reversible command objects, enabling undo/redo with full command history, transactional safety with atomic operations, complete operation tracking for auditability, and easy extensibility for new command types.
Usage in Galaxy: The ConstellationEditor is primarily used by the Constellation Agent to programmatically build task workflows, but can also be used directly for manual constellation creation and debugging.
🏗️ Architecture
Core Components
graph TD
A[ConstellationEditor] -->|manages| B[TaskConstellation]
A -->|uses| C[CommandInvoker]
C -->|executes| D[Commands]
D -->|modifies| B
A -->|notifies| E[Observers]
style A fill:#87CEEB
style B fill:#90EE90
style C fill:#FFD700
style D fill:#FFB6C1
style E fill:#DDA0DD
fromgalaxy.constellationimportTaskConstellationfromgalaxy.constellation.editorimportConstellationEditor# Create editor with new constellationeditor=ConstellationEditor()
# Create editor with existing constellationexisting=TaskConstellation(name="my_workflow")
editor=ConstellationEditor(
constellation=existing,
enable_history=True, # Enable undo/redomax_history_size=100# Keep last 100 commands
)
# Access constellationprint(f"Editing: {editor.constellation.name}")
🎯 Task Operations
Adding Tasks
fromgalaxy.constellationimportTaskStar# Method 1: Add existing TaskStartask=TaskStar(
task_id="fetch_data",
description="Download dataset from S3",
target_device_id="linux_server_1"
)
added_task=editor.add_task(task)
# Method 2: Add from dictionarytask_dict= {
"task_id": "preprocess",
"description": "Clean and normalize data",
"target_device_id": "linux_server_2",
"timeout": 300.0
}
added_task=editor.add_task(task_dict)
# Method 3: Create and add in one steptask=editor.create_and_add_task(
task_id="train_model",
description="Train neural network on preprocessed data",
name="Model Training",
target_device_id="gpu_server",
priority="HIGH",
timeout=3600.0,
retry_count=2
)
Updating Tasks
# Update task propertiesupdated_task=editor.update_task(
task_id="train_model",
description="Train BERT model on preprocessed text data",
timeout=7200.0,
priority="CRITICAL"
)
# Update with task_dataeditor.update_task(
task_id="train_model",
task_data={
"model_type": "BERT",
"epochs": 10,
"batch_size": 32
}
)
Removing Tasks
# Remove task (also removes related dependencies)removed_id=editor.remove_task("preprocess")
print(f"Removed task: {removed_id}")
Querying Tasks
# Get specific tasktask=editor.get_task("fetch_data")
# List all tasksall_tasks=editor.list_tasks()
fortaskinall_tasks:
print(f"{task.name}: {task.status.value}")
# Get ready tasksready=editor.get_ready_tasks()
🔗 Dependency Operations
Adding Dependencies
fromgalaxy.constellationimportTaskStarLine# Method 1: Add existing TaskStarLinedep=TaskStarLine.create_success_only(
from_task_id="fetch_data",
to_task_id="preprocess",
description="Preprocess after successful download"
)
added_dep=editor.add_dependency(dep)
# Method 2: Add from dictionarydep_dict= {
"from_task_id": "preprocess",
"to_task_id": "train_model",
"dependency_type": "SUCCESS_ONLY",
"condition_description": "Train on preprocessed data"
}
added_dep=editor.add_dependency(dep_dict)
# Method 3: Create and add in one stepdep=editor.create_and_add_dependency(
from_task_id="train_model",
to_task_id="evaluate_model",
dependency_type="UNCONDITIONAL",
condition_description="Evaluate after training completes"
)
Updating Dependencies
# Update dependency propertiesupdated_dep=editor.update_dependency(
dependency_id=dep.line_id,
dependency_type="CONDITIONAL",
condition_description="Evaluate only if training accuracy > 90%"
)
# Get specific dependencydep=editor.get_dependency(dep_id)
# List all dependenciesall_deps=editor.list_dependencies()
# Get dependencies for specific tasktask_deps=editor.get_task_dependencies("train_model")
🔄 Undo/Redo Operations
Basic Undo/Redo
# Add a tasktask=editor.create_and_add_task(
task_id="test_task",
description="Run unit tests"
)
# Oops, didn't mean to add thatifeditor.can_undo():
editor.undo()
print("✅ Task addition undone")
# Actually, let's keep itifeditor.can_redo():
editor.redo()
print("✅ Task addition redone")
Checking Undo/Redo Availability
# Check if undo/redo is availableprint(f"Can undo: {editor.can_undo()}")
print(f"Can redo: {editor.can_redo()}")
# Get description of what would be undone/redoneifeditor.can_undo():
print(f"Undo: {editor.get_undo_description()}")
ifeditor.can_redo():
print(f"Redo: {editor.get_redo_description()}")
Command History
# Get command historyhistory=editor.get_history()
fori, cmd_descinenumerate(history):
print(f"{i+1}. {cmd_desc}")
# Example output:# 1. Add task: fetch_data# 2. Add task: preprocess# 3. Add dependency: fetch_data → preprocess# 4. Update task: preprocess# Clear history (cannot undo after this)editor.clear_history()
# Extract subgraph with specific taskstask_ids= ["fetch_data", "preprocess", "train_model"]
subgraph_editor=editor.create_subgraph(task_ids)
print(f"Subgraph tasks: {subgraph_editor.constellation.task_count}")
print(f"Subgraph deps: {subgraph_editor.constellation.dependency_count}")
# Subgraph includes only dependencies between included tasks
Merging Constellations
# Create two separate workflowseditor1=ConstellationEditor()
editor1.create_and_add_task("task_a", "Task A from editor1")
editor2=ConstellationEditor()
editor2.create_and_add_task("task_b", "Task B from editor2")
# Merge editor2 into editor1 with prefixeditor1.merge_constellation(
other_editor=editor2,
prefix="imported_"
)
# editor1 now contains: task_a, imported_task_b
🛡️ Error Handling
Validation Errors
try:
# Try to add task with duplicate IDeditor.create_and_add_task("existing_id", "Duplicate task")
exceptExceptionase:
print(f"❌ Error: {e}")
# Can undo to previous valid stateifeditor.can_undo():
editor.undo()
Cyclic Dependency Detection
# Create cycle: A → B → C → Aeditor.create_and_add_task("a", "Task A")
editor.create_and_add_task("b", "Task B")
editor.create_and_add_task("c", "Task C")
editor.create_and_add_dependency("a", "b", "UNCONDITIONAL")
editor.create_and_add_dependency("b", "c", "UNCONDITIONAL")
try:
# This creates a cycleeditor.create_and_add_dependency("c", "a", "UNCONDITIONAL")
exceptExceptionase:
print(f"❌ Cycle detected: {e}")
# Undo the failed operation# (Actually, the operation fails before execution, so nothing to undo)
Enable history: Always enable undo/redo for interactive editing sessions
Validate frequently: Run validate_constellation() after major structural changes
Use observers: Add observers for logging, metrics tracking, or UI updates
Batch operations: Use batch_operations() for multiple related changes to improve efficiency
Save incrementally: Create constellation checkpoints during complex editing workflows
Command Pattern Benefits
The command pattern architecture provides several key advantages:
Undo/Redo: Full operation history with rollback capabilities
Audit trail: Every change is recorded and traceable
Transaction safety: Operations are atomic and validated
Extensibility: New operation types can be added easily
!!!warning "Common Pitfalls"
- Forgetting to validate: Always validate before passing to orchestrator for execution
- Clearing history prematurely: Cannot undo operations after calling clear_history()
- Modifying running constellations: Editor operations will fail if constellation is currently executing
- Ignoring observer errors: Observers should handle their own exceptions to avoid breaking the editor
📚 Command Registry
Available Commands
# List all available commandscommands=editor.list_available_commands()
forname, metadataincommands.items():
print(f"{name}: {metadata['description']}")
print(f" Category: {metadata['category']}")
# Get command categoriescategories=editor.get_command_categories()
print(f"Categories: {categories}")
# Get metadata for specific commandmetadata=editor.get_command_metadata("add_task")
print(metadata)
Executing Commands by Name
# Execute command using registryresult=editor.execute_command_by_name(
"add_task",
task_data={"task_id": "new_task", "description": "New task"}
)
# This is equivalent to:# editor.add_task({"task_id": "new_task", "description": "New task"})
🔗 Related Components
TaskStar — Individual tasks that can be edited and managed
TaskStarLine — Dependencies between tasks that define execution order