Skip to content

Conversation

@mathiusj
Copy link
Contributor

@mathiusj mathiusj commented Nov 18, 2025

TL;DR

Fixed template resolution in workflows by making it aware of the workflow directory.

What changed?

  • Moved the template method from CogInputContext to CogInputManager to access workflow directory information
  • Added workflow_dir parameter to CogInputManager, ExecutionManager, and system cogs
  • Enhanced template path resolution to first check relative to the workflow directory before falling back to the current working directory
  • Added comprehensive tests for the template method to verify different resolution scenarios

How to test?

  1. Create a workflow with templates in the prompts directory
  2. Use the shorthand syntax to reference templates (e.g., template("my_template", { name: "Test" }))
  3. Run the workflow from a different directory and verify templates are correctly resolved
  4. Try with both relative and absolute paths to ensure both work as expected

Why make this change?

Previously, the template method could only resolve templates relative to the current working directory, causing issues when workflows were run from a different directory than where they were defined. This change ensures templates are properly resolved relative to the workflow's location, making workflows more portable and robust.

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@mathiusj mathiusj force-pushed the mathiusj/gh-545-fix-bug-shorthand-syntax branch from 4011fa5 to 05372cf Compare November 29, 2025 01:19
@mathiusj mathiusj marked this pull request as ready for review November 29, 2025 01:34
@mathiusj mathiusj changed the title wip: fix bug in shorthand syntax fix(dsl): Enable shorthand template syntax with workflow directory resolution Nov 29, 2025
Copy link
Contributor

@nfgrep nfgrep left a comment

Choose a reason for hiding this comment

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

LGTM, would await @juniper-shopify's input though.

Maybe update/add a little DSL example that leverages this?

Copy link
Contributor

@juniper-shopify juniper-shopify left a comment

Choose a reason for hiding this comment

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

thanks for doing this!

A more robust priority list of search locations will be helpful, and adding the workflow dir to the WorkflowContext will reduce the size of these method signatures that are already kind of too big


#: (String, ?Hash) -> String
def template(path, args = {})
unless File.exist?(path)
Copy link
Contributor

Choose a reason for hiding this comment

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

small nit: I'd prefer to use Pathname objects instead of doing manipulation on strings that represent paths. The template method itself should ideally have Pathname | String as the type of its path argument, and it should coerce both to a pathname. this just makes it easier to reason about any code in the codebase (dsl part of it at least) that deals with file paths

end

#: (String, ?Hash) -> String
def template(path, args = {})
Copy link
Contributor

Choose a reason for hiding this comment

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

you should actually remove this method definition from cog_input_context when you add it here. it's getting overwritten by the binding, so it works, but it will be very confusing to have a defunct implementation lying around

Comment on lines +161 to +168
if @workflow_dir
# Try relative to workflow directory
relative_path = @workflow_dir.join("prompts/#{path}.md.erb")
path = relative_path.to_s if relative_path.exist?
else
# Fallback to current working directory
path = "prompts/#{path}.md.erb"
end
Copy link
Contributor

Choose a reason for hiding this comment

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

this has a regression from the existing logic in that it only considers paths inside a prompts/ directory, and only paths ending with .md.erb.

I think we should implement a priority stack of places to look for a matching file, roughly like this (assume path is a Pathname that is whatever the user provided):

  1. path if path.absolute?
  2. @workflow_dir / path if that .exist?
  3. @workflow_dir / "#{path}.erb" if that .exist?
  4. @workflow_dir / "#{path}.md.erb" if that .exist?
  5. repeat 2 - 4 for @workflow_dir / prompts
  6. repeat 2 - 4 for Pathname.pwd
  7. repeat 2 - 4 for Pathname.pwd / prompts

and then just use the first one that exists

end

unless File.exist?(path)
raise CogInputContext::ContextNotFoundError, "The prompt #{path} could not be found"
Copy link
Contributor

Choose a reason for hiding this comment

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

template will generally be used for prompts, but not exclusively. For example, I have a workflow where I'm using template to generate some JSON files containing filter expressions for a system I want an agent to search, and then telling the agent how to run searches with those files, instead of putting the filter expressions into the prompt itself.

Suggested change
raise CogInputContext::ContextNotFoundError, "The prompt #{path} could not be found"
raise CogInputContext::ContextNotFoundError, "The file '#{path}' could not be found"

Comment on lines 35 to 41
#| WorkflowContext,
#| ?scope: Symbol?,
#| ?scope_value: untyped?,
#| ?scope_index: Integer
#| ?scope_index: Integer,
#| ?workflow_dir: Pathname?
#| ) -> void
def initialize(
Copy link
Contributor

Choose a reason for hiding this comment

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

[Re: lines +31 to +41]

add the workflow dir to the WorkflowContext, instead of passing it through explicitly. it should also be non-nilable

See this comment inline on Graphite.

@cog_registry = cog_registry
@cogs = cogs
@workflow_context = workflow_context
@workflow_dir = workflow_dir
Copy link
Contributor

Choose a reason for hiding this comment

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

add the workflow dir to the WorkflowContext, instead of passing it through explicitly. it should also be non-nilable

Comment on lines +287 to +289
#: (String, ?Hash) -> String
def template(path, args = {}); end

Copy link
Contributor

Choose a reason for hiding this comment

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

this is a good opportunity to add some method doc for this while we're at it!

you can use the /docs:write-comments custom claude command to automatically write these for you; then you'll just have the edit them and make sure they make sense. do something like this:

  1. add a comment on this method with some rough notes about what it does, how it works, and things you want to include in the doc. super rough. (sometimes you don't even need this, but this method has no analogue that claude can use to figure out what it does, so some explanation would be helpful)
  2. in an interactive claude, do /docs:write-comments sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi and it will do its thing
  3. read what it wrote and tweak it.
  4. check that it didn't mess up any other comments, and revert anything if it did

workflow_dir = Pathname.new(@workflow_dir)
manager = CogInputManager.new(@cog_registry, @cogs, @workflow_context, workflow_dir)

# Change to different directory to test the fix
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: test comments should not refer to "fixes". they should simply describe the objective behaviour being tested

test "template method fails with shorthand syntax when not in correct directory and no workflow_dir" do
manager = CogInputManager.new(@cog_registry, @cogs, @workflow_context, nil)

# This test demonstrates the bug when workflow_dir is not provided
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: same as above -- test comment shouldn't refer to a bug that no longer exists, just describe the expected behaviour it is testing.

(okay, to be fair, there are cases where I think it's useful to have some comments in a test case explaining the particulars of a bug. But, I'd reserve that explicitly for when the bug is very complicated and counterintuitive, and non-obvious to understand what could go wrong to someone with even medium familiarity with the component)

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.

3 participants