Skip to content

Conversation

@NiveditJain
Copy link
Member

No description provided.

@NiveditJain NiveditJain requested a review from nk-ag August 10, 2025 08:24
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for handling multiple outputs when executing a state, allowing for the creation of multiple subsequent states as needed.
    • Introduced a new background task to automatically generate next states based on graph templates and parent state outputs.
    • Enhanced state tracking by adding a "parents" field to each state, improving lineage and dependency management.
  • Bug Fixes

    • Improved error handling for missing graph templates, invalid parent references, and output field resolution during state transitions.

Walkthrough

The changes introduce a new asynchronous task, create_next_state, which is scheduled as a background task after a state is executed. The State model is extended with a parents field to track parent states. Endpoint and controller signatures are updated to support background task scheduling, and logic for creating subsequent states based on a graph template is implemented.

Changes

Cohort / File(s) Change Summary
Add Background Task Scheduling to State Execution
state-manager/app/controller/executed_state.py, state-manager/app/routes.py
Updated the executed_state controller and its route to accept a background_tasks parameter, enabling scheduling of background tasks after state execution. The controller now schedules the new create_next_state task.
State Model Enhancement
state-manager/app/models/db/state.py
Added a parents field (dictionary mapping strings to ObjectId) to the State model to track parent states.
Next State Creation Logic
state-manager/app/tasks/create_next_state.py
Introduced the create_next_state async function, which waits for graph validation, resolves input dependencies using parent states, creates subsequent states, and handles errors.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API (executed_state_route)
    participant Controller (executed_state)
    participant BackgroundTasks
    participant Task (create_next_state)
    participant DB

    Client->>API (executed_state_route): POST /executed_state
    API->>Controller (executed_state): Call with background_tasks
    Controller (executed_state)->>DB: Update state to EXECUTED, set outputs
    Controller (executed_state)->>BackgroundTasks: Schedule create_next_state(state)
    BackgroundTasks->>Task (create_next_state): Run asynchronously
    Task (create_next_state)->>DB: Wait for graph validation, fetch templates
    Task (create_next_state)->>DB: Fetch parent states, resolve inputs
    Task (create_next_state)->>DB: Create next states
    Task (create_next_state)->>DB: Update current state status to SUCCESS or ERRORED
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Poem

In burrows deep, where states are born,
A parent’s trace is now adorned.
With tasks that run behind the scene,
Next states emerge, their paths unseen.
Graphs validated, inputs parsed right—
The rabbit hops through code by night!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @NiveditJain, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've implemented a new background task that automates the creation of subsequent states in a workflow. This task is triggered asynchronously whenever a state is marked as 'executed', ensuring that the system can automatically progress through defined graph templates. I've also updated the State model to include parent state references, which is essential for correctly mapping inputs for the next states.

Highlights

  • Automated State Progression: Introduced a new background task, create_next_state, which is responsible for automatically generating and saving the next states in a workflow based on predefined graph templates and the outputs of the current state.
  • Asynchronous Task Execution: Integrated the create_next_state task into the executed_state endpoint using FastAPI's BackgroundTasks, allowing the system to respond quickly while processing the next state creation asynchronously.
  • State Model Enhancement: Modified the State database model to include a parents field, a dictionary mapping identifiers to ObjectIds, which is crucial for tracking the lineage of states and retrieving necessary input data from preceding states.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a background task to create subsequent states in a graph, which is a good architectural choice for improving API responsiveness. My review focuses on the new background task logic in create_next_state.py and its integration. I've identified several critical issues related to correctness, such as handling stale data and potential crashes from unsafe dictionary access or string parsing. I've also included suggestions to improve robustness through better error handling, and to enhance performance by addressing polling loops and redundant database queries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (1)
state-manager/app/tasks/create_next_state.py (1)

90-97: Persist final status on success; currently not saved.

The state is saved on error but not on success. As written, status remains EXECUTED in DB.

Apply this diff:

-    state.status = StateStatusEnum.SUCCESS
+    state.status = StateStatusEnum.SUCCESS
+    await state.save()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d629cb and d95eb51.

📒 Files selected for processing (4)
  • state-manager/app/controller/executed_state.py (2 hunks)
  • state-manager/app/models/db/state.py (2 hunks)
  • state-manager/app/routes.py (2 hunks)
  • state-manager/app/tasks/create_next_state.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
state-manager/app/routes.py (3)
state-manager/app/models/executed_models.py (1)
  • ExecutedRequestModel (5-6)
state-manager/app/utils/check_secret.py (1)
  • check_api_key (15-19)
state-manager/app/controller/executed_state.py (1)
  • executed_state (12-34)
🔇 Additional comments (3)
state-manager/app/models/db/state.py (1)

17-18: Parents field addition looks good; verify ObjectId serialization support.

The field shape is appropriate for dependency tracking. Ensure your BaseDatabaseModel (or Pydantic config) allows arbitrary ObjectId types and serializes them correctly when included in API responses/logs. If not, consider using a custom Pydantic ObjectId type or serializer.

Would you like me to add a serializer/validator for ObjectId on this model?

state-manager/app/routes.py (1)

84-84: BackgroundTasks injection on the executed route: LGTM.

Signature change is correct and consistent with FastAPI’s BackgroundTasks usage.

state-manager/app/controller/executed_state.py (1)

3-3: Import of BackgroundTasks: LGTM.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
state-manager/app/tasks/create_next_state.py (8)

43-51: Don’t silently continue on missing node templates or unregistered nodes. Fail fast with clear errors.

Continuing hides graph/template issues and may leave the system in a partial state. Raise to mark the current state as ERRORED via the outer handler.

-            next_node_template = graph_template.get_node_by_identifier(identifier)
-            if not next_node_template:
-                continue
+            next_node_template = graph_template.get_node_by_identifier(identifier)
+            if not next_node_template:
+                raise Exception(f"Node template '{identifier}' not found in graph '{state.graph_name}'")
@@
-            if not registered_node:
-                continue
+            if not registered_node:
+                raise Exception(f"Registered node '{next_node_template.node_name}' not found in namespace '{next_node_template.namespace}'")

63-92: Make placeholder interpolation robust: validate format, handle unclosed braces, support current-state refs, cast outputs, and use cache.

The current split-based parsing is brittle and assumes strings; it also fails for unclosed placeholders and wraps ObjectIds unnecessarily.

-                constructed_string = ""
-                for split in splits:
-                    if "}}" in split:
-                        placeholder_content = split.split("}}")[0]
-                        parts = [p.strip() for p in placeholder_content.split('.')]
-                            
-                        if len(parts) != 3 or parts[1] != 'outputs':
-                            raise Exception(f"Invalid input placeholder format: '{placeholder_content}' for field {field_name}")
-                            
-                        input_identifier = parts[0]
-                        input_field = parts[2]
-
-                        parent_id = state.parents.get(input_identifier)
-                            
-                        if not parent_id:
-                            raise Exception(f"Parent identifier '{input_identifier}' not found in state parents.")
-                            
-                        dependent_state = await State.get(ObjectId(parent_id))
-                        if not dependent_state:
-                            raise Exception(f"Dependent state {input_identifier} not found")
-                            
-                        if input_field not in dependent_state.outputs:
-                            raise Exception(f"Input field {input_field} not found in dependent state {input_identifier}")
-                            
-                        constructed_string += dependent_state.outputs[input_field] + split.split("}}")[1]
-
-                    else:
-                        constructed_string += split
-                    
-                next_node_input_data[field_name] = constructed_string
+                constructed = ""
+                remainder = raw_value
+                while True:
+                    start = remainder.find("${{")
+                    if start == -1:
+                        constructed += remainder
+                        break
+                    constructed += remainder[:start]
+                    remainder = remainder[start+3:]
+                    end = remainder.find("}}")
+                    if end == -1:
+                        raise Exception(f"Unclosed placeholder in input '{field_name}' for node '{next_node_template.identifier}'")
+                    expr = remainder[:end].strip()
+                    remainder = remainder[end+2:]
+                    parts = [p.strip() for p in expr.split(".")]
+                    if len(parts) != 3 or parts[1] != "outputs":
+                        raise Exception(f"Invalid input placeholder '{expr}' for field '{field_name}'")
+                    ref_identifier, _, ref_field = parts
+                    # Resolve dependent state id (allow referencing current state)
+                    if ref_identifier == state.identifier:
+                        dep_state_id = state.id
+                    else:
+                        dep_state_id = state.parents.get(ref_identifier)
+                    if not dep_state_id:
+                        raise Exception(f"Dependent state '{ref_identifier}' not found")
+                    # Fetch dependent state with simple cache
+                    dep_state = state_cache.get(ref_identifier)
+                    if not dep_state:
+                        dep_state = await State.get(dep_state_id)
+                        if not dep_state:
+                            raise Exception(f"Dependent state '{ref_identifier}' not found")
+                        state_cache[ref_identifier] = dep_state
+                    if not isinstance(dep_state.outputs, dict) or ref_field not in dep_state.outputs:
+                        raise Exception(f"Output field '{ref_field}' not found on state '{ref_identifier}'")
+                    constructed += str(dep_state.outputs[ref_field])
+                next_node_input_data[field_name] = constructed

14-16: Avoid stale ORM instances: accept state_id and reload the state in the task.

Background tasks should load the latest state to prevent stale reads and concurrency issues. Replace the signature and load by id.

-async def create_next_state(state: State):
-    graph_template = None
+async def create_next_state(state_id: ObjectId):
+    state = await State.get(state_id)
+    if not state:
+        return
+    graph_template = None

Outside this hunk, remember to update the call sites to pass state.id instead of the state object.


38-41: Treat terminal nodes as successful completion instead of error.

If there are no next nodes, this branch is complete and should not error.

-        if not next_node_identifier:
-            raise Exception(f"Node template {state.identifier} has no next nodes")
+        if not next_node_identifier:
+            state.status = StateStatusEnum.SUCCESS
+            await state.save()
+            return

52-54: Micro-optimization: cache dependent states during placeholder resolution.

Avoid repeated DB fetches when multiple placeholders reference the same parent state.

             next_node_input_model = create_model(registered_node.inputs_schema)
             next_node_input_data = {}
+            state_cache = {}

75-83: Remove redundant ObjectId cast when fetching parent states.

parents already stores ObjectIds. Casting again is unnecessary and can be error-prone.

-                        dependent_state = await State.get(ObjectId(parent_id))
+                        dependent_state = await State.get(parent_id)

55-62: Guard against missing inputs, non-string values, and fix unreachable condition.

  • KeyError if the template misses a required field.
  • Inputs may be non-strings; .split would crash.
  • len(splits) == 0 is unreachable; .split returns at least one element.
-            for field_name, _ in next_node_input_model.model_fields.items():
-                temporary_input = next_node_template.inputs[field_name]
-                splits = temporary_input.split("${{")
-                    
-                if len(splits) == 0:
-                    next_node_input_data[field_name] = temporary_input
-                    continue
+            for field_name, _ in next_node_input_model.model_fields.items():
+                if field_name not in next_node_template.inputs:
+                    raise Exception(f"Required input '{field_name}' missing in node template '{next_node_template.identifier}'")
+                raw_value = next_node_template.inputs[field_name]
+                if not isinstance(raw_value, str) or "${{" not in raw_value:
+                    next_node_input_data[field_name] = raw_value
+                    continue

94-107: Fix parents mapping (wrong key) and avoid unnecessary ObjectId casting; also validate inputs with the generated model.

  • Parents should map the current state's identifier to its id, not the child’s identifier.
  • state.id and parents already hold ObjectIds; avoid wrapping.
  • Validate inputs via the Pydantic model to catch type mismatches early.
-            new_state = State(
+            # Validate inputs against the generated model
+            validated_inputs = next_node_input_model(**next_node_input_data).model_dump()
+            new_state = State(
                 node_name=next_node_template.node_name,
                 namespace_name=next_node_template.namespace,
                 identifier=next_node_template.identifier,
                 graph_name=state.graph_name,
-                status=StateStatusEnum.CREATED,
-                inputs=next_node_input_data,
+                status=StateStatusEnum.CREATED,
+                inputs=validated_inputs,
                 outputs={},
                 error=None,
                 parents={
-                    **state.parents,
-                    next_node_template.identifier: ObjectId(state.id)
+                    **state.parents,
+                    state.identifier: state.id
                 }
             )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d95eb51 and fe7a85a.

📒 Files selected for processing (1)
  • state-manager/app/tasks/create_next_state.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
state-manager/app/tasks/create_next_state.py (5)
state-manager/app/models/db/state.py (1)
  • State (8-18)
state-manager/app/models/db/graph_template_model.py (2)
  • GraphTemplate (13-81)
  • get_node_by_identifier (30-35)
state-manager/app/models/graph_template_validation_status.py (1)
  • GraphTemplateValidationStatus (4-8)
state-manager/app/models/db/registered_node.py (1)
  • RegisteredNode (6-13)
state-manager/app/models/state_status_enum.py (1)
  • StateStatusEnum (4-14)
🔇 Additional comments (2)
state-manager/app/tasks/create_next_state.py (2)

21-33: Good: bounded wait with timeout prevents infinite polling.

The loop now fails fast on timeout and avoids indefinite hangs. LGTM.


110-112: Confirm parent status semantics after spawning children.

Marking the current state as SUCCESS immediately after creating next states may be premature if SUCCESS implies end-of-branch completion. Consider EXECUTED (unchanged) or a transitional NEXT_CREATED on the parent if that aligns with your state machine.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (6)
state-manager/app/controller/executed_state.py (1)

29-29: Schedule task with state_id, not the full State object

Pass only the id and load fresh in the task to avoid stale fields and heavy serialization. This also makes retries idempotent and simplifies task signature.

-            background_tasks.add_task(create_next_state, state)
+            background_tasks.add_task(create_next_state, state.id)
@@
-            background_tasks.add_task(create_next_state, state)
+            background_tasks.add_task(create_next_state, state.id)
@@
-                background_tasks.add_task(create_next_state, new_state)
+                background_tasks.add_task(create_next_state, new_state.id)

Pair this with the task signature change suggested in create_next_state.py.

Also applies to: 35-35, 50-50

state-manager/app/tasks/create_next_state.py (5)

14-15: Accept state_id and load fresh state inside the task

Avoid passing full State objects from the controller. Load the latest state to prevent staleness and reduce coupling.

-async def create_next_state(state: State):
-    graph_template = None
+async def create_next_state(state_id: ObjectId):
+    # Load latest state
+    state = await State.get(state_id)
+    if not state:
+        return
+    graph_template = None

Ensure controller passes state.id, as suggested in the paired comment.


100-113: Fix parents mapping: use current state’s identifier and don’t re-wrap ObjectId

Key should be the current state’s identifier; value is state.id (already an ObjectId). Using the child’s identifier here breaks dependency resolution downstream.

             new_state = State(
@@
-                parents={
-                    **state.parents,
-                    next_node_template.identifier: ObjectId(state.id)
-                }
+                parents={
+                    **state.parents,
+                    state.identifier: state.id,
+                }
             )

105-105: Optional: prefer NEXT_CREATED to distinguish follow-up states

If your domain uses NEXT_CREATED, it’s clearer than CREATED for generated next states.

-                status=StateStatusEnum.CREATED,
+                status=StateStatusEnum.NEXT_CREATED,

38-41: Terminal node should mark SUCCESS, not error

A node with no next_nodes is a terminal branch. Mark it SUCCESS and return instead of erroring.

-        next_node_identifier = node_template.next_nodes
-        if not next_node_identifier:
-            raise Exception(f"Node template {state.identifier} has no next nodes")
+        next_node_identifier = node_template.next_nodes
+        if not next_node_identifier:
+            state.status = StateStatusEnum.SUCCESS
+            await state.save()
+            return

57-99: Placeholder resolution is brittle; handle non-strings, validate shape, and support current-state references

Issues:

  • Assumes string inputs; will crash for non-strings.
  • len(splits) == 0 branch is unreachable.
  • Fragile parsing; no validation.
  • Cannot reference current state outputs (only parents).
  • Concatenation may TypeError if outputs are non-strings.

Safer approach below, plus validate inputs using the generated model:

-            for field_name, _ in next_node_input_model.model_fields.items():
-                temporary_input = next_node_template.inputs[field_name]
-                splits = temporary_input.split("${{")
-                    
-                if len(splits) == 0:
-                    next_node_input_data[field_name] = temporary_input
-                    continue
-
-                constructed_string = ""
-                for split in splits:
-                    if "}}" in split:
-                        placeholder_content = split.split("}}")[0]
-                        parts = [p.strip() for p in placeholder_content.split('.')]
-                            
-                        if len(parts) != 3 or parts[1] != 'outputs':
-                            raise Exception(f"Invalid input placeholder format: '{placeholder_content}' for field {field_name}")
-                            
-                        input_identifier = parts[0]
-                        input_field = parts[2]
-
-                        parent_id = state.parents.get(input_identifier)
-                            
-                        if not parent_id:
-                            raise Exception(f"Parent identifier '{input_identifier}' not found in state parents.")
-
-                        if parent_id not in cache_states:
-                            dependent_state = await State.get(ObjectId(parent_id))
-                            if not dependent_state:
-                                raise Exception(f"Dependent state {input_identifier} not found")
-                            cache_states[parent_id] = dependent_state
-                        else:
-                            dependent_state = cache_states[parent_id]
-                            
-                        if input_field not in dependent_state.outputs:
-                            raise Exception(f"Input field {input_field} not found in dependent state {input_identifier}")
-                            
-                        constructed_string += dependent_state.outputs[input_field] + split.split("}}")[1]
-
-                    else:
-                        constructed_string += split
-                    
-                next_node_input_data[field_name] = constructed_string
+            for field_name, _ in next_node_input_model.model_fields.items():
+                if field_name not in next_node_template.inputs:
+                    raise Exception(f"Required input '{field_name}' missing in node template '{next_node_template.identifier}'")
+                raw_value = next_node_template.inputs[field_name]
+                # Only interpolate strings
+                if not isinstance(raw_value, str) or '${{' not in raw_value:
+                    next_node_input_data[field_name] = raw_value
+                    continue
+                constructed = ""
+                remainder = raw_value
+                while True:
+                    start = remainder.find('${{')
+                    if start == -1:
+                        constructed += remainder
+                        break
+                    constructed += remainder[:start]
+                    remainder = remainder[start+3:]
+                    end = remainder.find('}}')
+                    if end == -1:
+                        raise Exception(f"Unclosed placeholder in input '{field_name}' for node '{next_node_template.identifier}'")
+                    expr = remainder[:end].strip()
+                    remainder = remainder[end+2:]
+                    parts = [p.strip() for p in expr.split('.')]
+                    if len(parts) != 3 or parts[1] != 'outputs':
+                        raise Exception(f"Invalid input placeholder format: '{expr}' for field '{field_name}'")
+                    ref_identifier, _, ref_field = parts
+                    # Resolve dependent state (support current state)
+                    if ref_identifier == state.identifier:
+                        dep_state = state
+                    else:
+                        parent_id = state.parents.get(ref_identifier)
+                        if not parent_id:
+                            raise Exception(f"Parent identifier '{ref_identifier}' not found in state.parents")
+                        if parent_id not in cache_states:
+                            dep_state = await State.get(parent_id)
+                            if not dep_state:
+                                raise Exception(f"Dependent state '{ref_identifier}' not found")
+                            cache_states[parent_id] = dep_state
+                        else:
+                            dep_state = cache_states[parent_id]
+                    if not isinstance(dep_state.outputs, dict) or ref_field not in dep_state.outputs:
+                        raise Exception(f"Output field '{ref_field}' not found on state '{ref_identifier}'")
+                    constructed += str(dep_state.outputs[ref_field])
+                next_node_input_data[field_name] = constructed
+            # Validate/coerce inputs
+            next_node_input_data = next_node_input_model(**next_node_input_data).model_dump()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe7a85a and 2a2f6c9.

📒 Files selected for processing (2)
  • state-manager/app/controller/executed_state.py (2 hunks)
  • state-manager/app/tasks/create_next_state.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
state-manager/app/controller/executed_state.py (4)
state-manager/app/models/db/state.py (1)
  • State (8-18)
state-manager/app/models/state_status_enum.py (1)
  • StateStatusEnum (4-14)
state-manager/app/tasks/create_next_state.py (1)
  • create_next_state (14-124)
state-manager/app/models/executed_models.py (2)
  • ExecutedRequestModel (5-6)
  • ExecutedResponseModel (9-10)
🔇 Additional comments (3)
state-manager/app/controller/executed_state.py (1)

12-12: BackgroundTasks integration: LGTM

Adding BackgroundTasks to the controller signature is correct and aligns with the PR objective.

state-manager/app/tasks/create_next_state.py (2)

21-33: Bounded wait for template validity: LGTM

You added a 5-minute timeout with a 1s backoff; this mitigates infinite polling. Good.


117-123: Finalize state status on success/error: LGTM

Marking SUCCESS at the end and ERRORED on exceptions with persisted error is appropriate.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
state-manager/app/controller/executed_state.py (1)

8-8: Import is fine; avoid passing full State objects to the task.

See comments below: schedule with state_id (not the whole State) and load fresh in the task to prevent stale data and reduce coupling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2f6c9 and b2988d9.

📒 Files selected for processing (1)
  • state-manager/app/controller/executed_state.py (2 hunks)
🔇 Additional comments (2)
state-manager/app/controller/executed_state.py (2)

3-3: Importing BackgroundTasks is correct.

This enables lightweight async scheduling via FastAPI’s background runner.


12-12: executed_state signature verified
Confirmed that background_tasks is passed through the router to the controller:

  • state-manager/app/routes.py:94:
    return await executed_state(namespace_name, ObjectId(state_id), body, x_exosphere_request_id, background_tasks)

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant