Skip to content

Commit d525b30

Browse files
refactor: use ForgeConfig for Workflow (#2716)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent d02477e commit d525b30

37 files changed

Lines changed: 847 additions & 1006 deletions

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/forge_api/src/api.rs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::{Path, PathBuf};
1+
use std::path::PathBuf;
22

33
use anyhow::Result;
44
use forge_app::dto::ToolsOverview;
@@ -53,19 +53,6 @@ pub trait API: Sync + Send {
5353
/// Adds a new conversation to the conversation store
5454
async fn upsert_conversation(&self, conversation: Conversation) -> Result<()>;
5555

56-
/// Initializes a workflow configuration from the given path
57-
/// The workflow at the specified path is merged with the default
58-
/// configuration If no path is provided, it will try to find forge.yaml
59-
/// in the current directory or its parent directories
60-
async fn read_workflow(&self, path: Option<&Path>) -> Result<Workflow>;
61-
62-
/// Reads the workflow from the given path and merges it with a default
63-
/// workflow. This provides a convenient way to get a complete workflow
64-
/// configuration without having to manually handle the merge logic.
65-
/// If no path is provided, it will try to find forge.yaml in the current
66-
/// directory or its parent directories
67-
async fn read_merged(&self, path: Option<&Path>) -> Result<Workflow>;
68-
6956
/// Returns the conversation with the given ID
7057
async fn conversation(&self, conversation_id: &ConversationId) -> Result<Option<Conversation>>;
7158

crates/forge_api/src/forge_api.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::{Path, PathBuf};
1+
use std::path::PathBuf;
22
use std::sync::Arc;
33
use std::time::Duration;
44

@@ -146,14 +146,6 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra + SkillRepository + GrpcInf
146146
self.services.get_environment().clone()
147147
}
148148

149-
async fn read_workflow(&self, path: Option<&Path>) -> anyhow::Result<Workflow> {
150-
self.app().read_workflow(path).await
151-
}
152-
153-
async fn read_merged(&self, path: Option<&Path>) -> anyhow::Result<Workflow> {
154-
self.app().read_workflow_merged(path).await
155-
}
156-
157149
async fn conversation(
158150
&self,
159151
conversation_id: &ConversationId,

crates/forge_app/src/app.rs

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::path::{Path, PathBuf};
21
use std::sync::Arc;
32

43
use anyhow::Result;
@@ -12,17 +11,14 @@ use crate::dto::ToolsOverview;
1211
use crate::hooks::{CompactionHandler, DoomLoopDetector, TitleGenerationHandler, TracingHandler};
1312
use crate::init_conversation_metrics::InitConversationMetrics;
1413
use crate::orch::Orchestrator;
15-
use crate::services::{
16-
AgentRegistry, CustomInstructionsService, ProviderAuthService, TemplateService,
17-
};
14+
use crate::services::{AgentRegistry, CustomInstructionsService, ProviderAuthService};
1815
use crate::set_conversation_id::SetConversationId;
1916
use crate::system_prompt::SystemPrompt;
2017
use crate::tool_registry::ToolRegistry;
2118
use crate::tool_resolver::ToolResolver;
2219
use crate::user_prompt::UserPromptGenerator;
2320
use crate::{
2421
AgentProviderResolver, ConversationService, FileDiscoveryService, ProviderService, Services,
25-
WorkflowService,
2622
};
2723

2824
/// ForgeApp handles the core chat functionality by orchestrating various
@@ -56,21 +52,11 @@ impl<S: Services> ForgeApp<S> {
5652
.expect("conversation for the request should've been created at this point.");
5753

5854
// Discover files using the discovery service
59-
let workflow = self.services.read_merged(None).await.unwrap_or_default();
55+
let workflow = services.get_environment();
6056
let environment = services.get_environment();
6157

6258
let files = services.list_current_directory().await?;
6359

64-
// Register templates using workflow path or environment fallback
65-
let template_path = workflow
66-
.templates
67-
.as_ref()
68-
.map_or(environment.templates(), |templates| {
69-
PathBuf::from(templates)
70-
});
71-
72-
services.register_template(template_path).await?;
73-
7460
let custom_instructions = services.get_custom_instructions().await;
7561

7662
// Prepare agents with user configuration
@@ -82,7 +68,7 @@ impl<S: Services> ForgeApp<S> {
8268
.get_agent(&agent_id)
8369
.await?
8470
.ok_or(crate::Error::AgentNotFound(agent_id.clone()))?
85-
.apply_workflow_config(&workflow)
71+
.apply_env(&workflow)
8672
.set_compact_model_if_none();
8773

8874
let agent_provider = agent_provider_resolver
@@ -212,7 +198,7 @@ impl<S: Services> ForgeApp<S> {
212198
let original_messages = context.messages.len();
213199
let original_token_count = *context.token_count();
214200

215-
let workflow = self.services.read_merged(None).await.unwrap_or_default();
201+
let workflow = self.services.get_environment();
216202

217203
// Get agent and apply workflow config
218204
let agent = self.services.get_agent(&active_agent_id).await?;
@@ -228,7 +214,7 @@ impl<S: Services> ForgeApp<S> {
228214

229215
// Get compact config from the agent
230216
let compact = agent
231-
.apply_workflow_config(&workflow)
217+
.apply_env(&workflow)
232218
.set_compact_model_if_none()
233219
.compact;
234220

@@ -307,12 +293,4 @@ impl<S: Services> ForgeApp<S> {
307293

308294
Ok(results)
309295
}
310-
311-
pub async fn read_workflow(&self, path: Option<&Path>) -> Result<Workflow> {
312-
self.services.read_workflow(path).await
313-
}
314-
315-
pub async fn read_workflow_merged(&self, path: Option<&Path>) -> Result<Workflow> {
316-
self.services.read_merged(path).await
317-
}
318296
}

crates/forge_app/src/orch_spec/orch_runner.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,7 @@ impl Runner {
9191

9292
let agent = setup.agent.clone();
9393
let system_tools = setup.tools.clone();
94-
let agent = agent
95-
.apply_workflow_config(&setup.workflow)
96-
.model(setup.model.clone());
94+
let agent = agent.apply_env(&setup.env).model(setup.model.clone());
9795

9896
// Render system prompt into context.
9997
let conversation = SystemPrompt::new(services.clone(), setup.env.clone(), agent.clone())

crates/forge_app/src/orch_spec/orch_setup.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use derive_setters::Setters;
66
use forge_domain::{
77
Agent, AgentId, Attachment, ChatCompletionMessage, ChatResponse, Conversation, Environment,
88
Event, File, HttpConfig, MessageEntry, ModelId, ProviderId, RetryConfig, Role, Template,
9-
ToolCallFull, ToolDefinition, ToolResult, Workflow,
9+
ToolCallFull, ToolDefinition, ToolResult,
1010
};
1111
use url::Url;
1212

@@ -25,7 +25,6 @@ pub struct TestContext {
2525
pub mock_tool_call_responses: Vec<(ToolCallFull, ToolResult)>,
2626
pub mock_assistant_responses: Vec<ChatCompletionMessage>,
2727
pub mock_shell_outputs: Vec<ShellOutput>,
28-
pub workflow: Workflow,
2928
pub templates: HashMap<String, String>,
3029
pub files: Vec<File>,
3130
pub env: Environment,
@@ -49,7 +48,6 @@ impl Default for TestContext {
4948
mock_assistant_responses: Default::default(),
5049
mock_tool_call_responses: Default::default(),
5150
mock_shell_outputs: Default::default(),
52-
workflow: Workflow::new().tool_supported(true),
5351
templates: Default::default(),
5452
files: Default::default(),
5553
attachments: Default::default(),
@@ -61,6 +59,7 @@ impl Default for TestContext {
6159
shell: "bash".to_string(),
6260
base_path: PathBuf::from("/Users/tushar/projects"),
6361
service_url: Url::parse("http://localhost:8000").unwrap(),
62+
tool_supported: true,
6463

6564
// No retry policy by default
6665
retry_config: RetryConfig {
@@ -99,6 +98,14 @@ impl Default for TestContext {
9998
commit: None,
10099
suggest: None,
101100
is_restricted: false,
101+
temperature: None,
102+
top_p: None,
103+
top_k: None,
104+
max_tokens: None,
105+
max_tool_failure_per_turn: None,
106+
max_requests_per_turn: None,
107+
compact: None,
108+
updates: None,
102109
},
103110
title: Some("test-conversation".into()),
104111
agent: Agent::new(

crates/forge_app/src/orch_spec/orch_system_spec.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
use forge_domain::{ChatCompletionMessage, CommandOutput, Content, FinishReason, Workflow};
1+
use forge_domain::{ChatCompletionMessage, CommandOutput, Content, FinishReason};
22
use insta::assert_snapshot;
33

44
use crate::ShellOutput;
55
use crate::orch_spec::orch_runner::TestContext;
66

77
#[tokio::test]
88
async fn test_system_prompt() {
9-
let mut ctx = TestContext::default()
10-
.workflow(Workflow::default().tool_supported(false))
11-
.mock_assistant_responses(vec![
12-
ChatCompletionMessage::assistant(Content::full("Sure"))
13-
.finish_reason(FinishReason::Stop),
14-
]);
9+
let mut ctx = TestContext::default().mock_assistant_responses(vec![
10+
ChatCompletionMessage::assistant(Content::full("Sure")).finish_reason(FinishReason::Stop),
11+
]);
1512

1613
ctx.run("This is a test").await.unwrap();
1714
let system_messages = ctx.output.system_messages().unwrap().join("\n\n");
@@ -21,11 +18,6 @@ async fn test_system_prompt() {
2118
#[tokio::test]
2219
async fn test_system_prompt_tool_supported() {
2320
let mut ctx = TestContext::default()
24-
.workflow(
25-
Workflow::default()
26-
.tool_supported(true)
27-
.custom_rules("Do it nicely"),
28-
)
2921
.files(vec![
3022
forge_domain::File { path: "/users/john/foo.txt".to_string(), is_dir: false },
3123
forge_domain::File { path: "/users/jason/bar.txt".to_string(), is_dir: false },
@@ -55,7 +47,6 @@ async fn test_system_prompt_with_extensions() {
5547
};
5648

5749
let mut ctx = TestContext::default()
58-
.workflow(Workflow::default().tool_supported(true))
5950
.mock_shell_outputs(vec![shell_output])
6051
.mock_assistant_responses(vec![
6152
ChatCompletionMessage::assistant(Content::full("Sure"))
@@ -92,7 +83,6 @@ async fn test_system_prompt_with_extensions_truncated() {
9283
};
9384

9485
let mut ctx = TestContext::default()
95-
.workflow(Workflow::default().tool_supported(true))
9686
.mock_shell_outputs(vec![shell_output])
9787
.mock_assistant_responses(vec![
9888
ChatCompletionMessage::assistant(Content::full("Sure"))

crates/forge_app/src/orch_spec/snapshots/forge_app__orch_spec__orch_system_spec__system_prompt.snap

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,9 @@ You are Forge
1111
<home_directory>/Users/tushar</home_directory>
1212
</system_information>
1313

14-
<available_tools>
15-
<tool>{"name":"fs_read","description":"","arguments":{}}</tool>
16-
<tool>{"name":"fs_write","description":"","arguments":{}}</tool>
17-
</available_tools>
18-
19-
<tool_usage_example>
20-
1. You can only make one tool call per message.
21-
2. Each tool call must be wrapped in `<forge_tool_call>` tags.
22-
3. The tool call must be in JSON format with the following structure:
23-
- The `name` field must specify the tool name.
24-
- The `arguments` field must contain the required parameters for the tool.
25-
26-
Here's a correct example structure:
27-
28-
Example 1:
29-
<forge_tool_call>
30-
{"name": "read", "arguments": {"path": "/a/b/c.txt"}}
31-
</forge_tool_call>
32-
33-
Example 2:
34-
<forge_tool_call>
35-
{"name": "write", "arguments": {"path": "/a/b/c.txt", "content": "Hello World!"}}
36-
</forge_tool_call>
37-
38-
Important:
39-
1. ALWAYS use JSON format inside `forge_tool_call` tags.
40-
2. Specify the name of tool in the `name` field.
41-
3. Specify the tool arguments in the `arguments` field.
42-
4. If you need to make multiple tool calls, send them in separate messages.
43-
44-
Before using a tool, ensure all required arguments are available.
45-
If any required arguments are missing, do not attempt to use the tool.
46-
</tool_usage_example>
4714

4815
<tool_usage_instructions>
49-
- You have access to set of tools as described in the <available_tools> tag.
50-
- You can use one tool per message, and will receive the result of that tool use in the user's response.
51-
- You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
16+
- For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools (for eg: `patch`, `read`) simultaneously rather than sequentially.
5217
- NEVER ever refer to tool names when speaking to the USER even when user has asked for it. For example, instead of saying 'I need to use the edit_file tool to edit your file', just say 'I will edit your file'.
5318
- If you need to read a file, prefer to read larger sections of the file at once over multiple smaller calls.
5419
</tool_usage_instructions>

crates/forge_app/src/orch_spec/snapshots/forge_app__orch_spec__orch_system_spec__system_prompt_tool_supported.snap

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ You are Forge
2222
- If you need to read a file, prefer to read larger sections of the file at once over multiple smaller calls.
2323
</tool_usage_instructions>
2424

25-
<project_guidelines>
26-
Do it nicely
27-
</project_guidelines>
2825

2926
<non_negotiable_rules>
3027
- ALWAYS present the result of your work in a neatly structured format (using markdown syntax in your response) to the user at the end of every task.
@@ -51,5 +48,5 @@ Do it nicely
5148

5249
- User may tag files using the format @[<file name>] and send it as a part of the message. Do not attempt to reread those files.
5350
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
54-
- Always follow all the `project_guidelines` without exception.
51+
5552
</non_negotiable_rules>

0 commit comments

Comments
 (0)