Skip to content

Commit cfd65d3

Browse files
authored
Merge pull request #1618 from derekmisler/prompt-file-cli-flag-support
feat: add --prompt-file CLI flag for including file contents as system context
2 parents 52d70e1 + b412e26 commit cfd65d3

3 files changed

Lines changed: 147 additions & 2 deletions

File tree

‎cmd/root/run.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type runExecFlags struct {
3333
remoteAddress string
3434
connectRPC bool
3535
modelOverrides []string
36+
promptFiles []string
3637
dryRun bool
3738
runConfig config.RuntimeConfig
3839
sessionDB string
@@ -83,6 +84,7 @@ func addRunOrExecFlags(cmd *cobra.Command, flags *runExecFlags) {
8384
cmd.PersistentFlags().BoolVar(&flags.autoApprove, "yolo", false, "Automatically approve all tool calls without prompting")
8485
cmd.PersistentFlags().BoolVar(&flags.hideToolResults, "hide-tool-results", false, "Hide tool call results")
8586
cmd.PersistentFlags().StringVar(&flags.attachmentPath, "attach", "", "Attach an image file to the message")
87+
cmd.PersistentFlags().StringArrayVar(&flags.promptFiles, "prompt-file", nil, "Append file contents to the prompt (repeatable)")
8688
cmd.PersistentFlags().StringArrayVar(&flags.modelOverrides, "model", nil, "Override agent model: [agent=]provider/model (repeatable)")
8789
cmd.PersistentFlags().BoolVar(&flags.dryRun, "dry-run", false, "Initialize the agent without executing anything")
8890
cmd.PersistentFlags().StringVar(&flags.remoteAddress, "remote", "", "Use remote runtime with specified address")
@@ -258,7 +260,14 @@ func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []s
258260
}
259261

260262
func (f *runExecFlags) loadAgentFrom(ctx context.Context, agentSource config.Source) (*teamloader.LoadResult, error) {
261-
result, err := teamloader.LoadWithConfig(ctx, agentSource, &f.runConfig, teamloader.WithModelOverrides(f.modelOverrides))
263+
opts := []teamloader.Opt{
264+
teamloader.WithModelOverrides(f.modelOverrides),
265+
}
266+
if len(f.promptFiles) > 0 {
267+
opts = append(opts, teamloader.WithPromptFiles(f.promptFiles))
268+
}
269+
270+
result, err := teamloader.LoadWithConfig(ctx, agentSource, &f.runConfig, opts...)
262271
if err != nil {
263272
return nil, err
264273
}

‎pkg/teamloader/teamloader.go‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func isThinkingBudgetDisabled(tb *latest.ThinkingBudget) bool {
4343

4444
type loadOptions struct {
4545
modelOverrides []string
46+
promptFiles []string
4647
toolsetRegistry *ToolsetRegistry
4748
}
4849

@@ -55,6 +56,15 @@ func WithModelOverrides(overrides []string) Opt {
5556
}
5657
}
5758

59+
// WithPromptFiles adds additional prompt files to all agents.
60+
// These are merged with any prompt files defined in the agent config.
61+
func WithPromptFiles(files []string) Opt {
62+
return func(opts *loadOptions) error {
63+
opts.promptFiles = files
64+
return nil
65+
}
66+
}
67+
5868
// WithToolsetRegistry allows using a custom toolset registry instead of the default
5969
func WithToolsetRegistry(registry *ToolsetRegistry) Opt {
6070
return func(opts *loadOptions) error {
@@ -143,14 +153,29 @@ func LoadWithConfig(ctx context.Context, agentSource config.Source, runConfig *c
143153
skillsEnabled = *agentConfig.Skills
144154
}
145155

156+
// Merge CLI prompt files with agent config prompt files, deduplicating
157+
promptFiles := append([]string{}, agentConfig.AddPromptFiles...)
158+
promptFiles = append(promptFiles, loadOpts.promptFiles...)
159+
160+
// Deduplicate to avoid redundant context (saves tokens)
161+
seen := make(map[string]bool)
162+
unique := promptFiles[:0]
163+
for _, f := range promptFiles {
164+
if !seen[f] {
165+
seen[f] = true
166+
unique = append(unique, f)
167+
}
168+
}
169+
promptFiles = unique
170+
146171
opts := []agent.Opt{
147172
agent.WithName(agentConfig.Name),
148173
agent.WithDescription(expander.Expand(ctx, agentConfig.Description)),
149174
agent.WithWelcomeMessage(expander.Expand(ctx, agentConfig.WelcomeMessage)),
150175
agent.WithAddDate(agentConfig.AddDate),
151176
agent.WithAddEnvironmentInfo(agentConfig.AddEnvironmentInfo),
152177
agent.WithAddDescriptionParameter(agentConfig.AddDescriptionParameter),
153-
agent.WithAddPromptFiles(agentConfig.AddPromptFiles),
178+
agent.WithAddPromptFiles(promptFiles),
154179
agent.WithMaxIterations(agentConfig.MaxIterations),
155180
agent.WithNumHistoryItems(agentConfig.NumHistoryItems),
156181
agent.WithCommands(expander.ExpandCommands(ctx, agentConfig.Commands)),

‎pkg/teamloader/teamloader_test.go‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,114 @@ func TestIsThinkingBudgetDisabled(t *testing.T) {
287287
})
288288
}
289289
}
290+
291+
func TestWithPromptFiles(t *testing.T) {
292+
t.Setenv("OPENAI_API_KEY", "dummy")
293+
294+
tests := []struct {
295+
name string
296+
cliPromptFiles []string
297+
expected []string
298+
}{
299+
{
300+
name: "no CLI prompt files",
301+
cliPromptFiles: nil,
302+
expected: []string{}, // basic.yaml has no add_prompt_files
303+
},
304+
{
305+
name: "single CLI prompt file",
306+
cliPromptFiles: []string{"AGENTS.md"},
307+
expected: []string{"AGENTS.md"},
308+
},
309+
{
310+
name: "multiple CLI prompt files",
311+
cliPromptFiles: []string{"AGENTS.md", "CLAUDE.md"},
312+
expected: []string{"AGENTS.md", "CLAUDE.md"},
313+
},
314+
}
315+
316+
for _, tt := range tests {
317+
t.Run(tt.name, func(t *testing.T) {
318+
agentSource, err := config.Resolve("testdata/basic.yaml", nil)
319+
require.NoError(t, err)
320+
321+
var opts []Opt
322+
if len(tt.cliPromptFiles) > 0 {
323+
opts = append(opts, WithPromptFiles(tt.cliPromptFiles))
324+
}
325+
326+
team, err := Load(t.Context(), agentSource, &config.RuntimeConfig{}, opts...)
327+
require.NoError(t, err)
328+
329+
rootAgent, err := team.Agent("root")
330+
require.NoError(t, err)
331+
332+
assert.Equal(t, tt.expected, rootAgent.AddPromptFiles())
333+
})
334+
}
335+
}
336+
337+
func TestWithPromptFilesMergesWithConfig(t *testing.T) {
338+
t.Setenv("OPENAI_API_KEY", "dummy")
339+
340+
// Create a temp agent file with add_prompt_files configured
341+
tempDir := t.TempDir()
342+
agentFile := filepath.Join(tempDir, "agent.yaml")
343+
agentYAML := `version: "2"
344+
agents:
345+
root:
346+
model: openai/gpt-4o
347+
instruction: test
348+
add_prompt_files:
349+
- config-file.md
350+
`
351+
require.NoError(t, os.WriteFile(agentFile, []byte(agentYAML), 0o644))
352+
353+
agentSource, err := config.Resolve(agentFile, nil)
354+
require.NoError(t, err)
355+
356+
// Load with CLI prompt files - should merge with config
357+
team, err := Load(t.Context(), agentSource, &config.RuntimeConfig{},
358+
WithPromptFiles([]string{"cli-file.md"}))
359+
require.NoError(t, err)
360+
361+
rootAgent, err := team.Agent("root")
362+
require.NoError(t, err)
363+
364+
// Config files come first, then CLI files
365+
expected := []string{"config-file.md", "cli-file.md"}
366+
assert.Equal(t, expected, rootAgent.AddPromptFiles())
367+
}
368+
369+
func TestWithPromptFilesDeduplicates(t *testing.T) {
370+
t.Setenv("OPENAI_API_KEY", "dummy")
371+
372+
// Create a temp agent file with add_prompt_files configured
373+
tempDir := t.TempDir()
374+
agentFile := filepath.Join(tempDir, "agent.yaml")
375+
agentYAML := `version: "2"
376+
agents:
377+
root:
378+
model: openai/gpt-4o
379+
instruction: test
380+
add_prompt_files:
381+
- AGENTS.md
382+
- CLAUDE.md
383+
`
384+
require.NoError(t, os.WriteFile(agentFile, []byte(agentYAML), 0o644))
385+
386+
agentSource, err := config.Resolve(agentFile, nil)
387+
require.NoError(t, err)
388+
389+
// CLI specifies a file that's already in config - should deduplicate
390+
team, err := Load(t.Context(), agentSource, &config.RuntimeConfig{},
391+
WithPromptFiles([]string{"AGENTS.md", "extra.md"}))
392+
require.NoError(t, err)
393+
394+
rootAgent, err := team.Agent("root")
395+
require.NoError(t, err)
396+
397+
// AGENTS.md should only appear once (from config), extra.md added at end
398+
expected := []string{"AGENTS.md", "CLAUDE.md", "extra.md"}
399+
assert.Equal(t, expected, rootAgent.AddPromptFiles())
400+
}

0 commit comments

Comments
 (0)