-
Notifications
You must be signed in to change notification settings - Fork 50
fix: resolve #793 — Two workflow primitives gaps: role-based context loss and no convergence loops #833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: resolve #793 — Two workflow primitives gaps: role-based context loss and no convergence loops #833
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { Context } from '@agentworkforce/relay'; | ||
|
|
||
| export interface Plan { | ||
| strategy: string; | ||
| reasoning: string; | ||
| } | ||
|
|
||
| export interface DeveloperOutput { | ||
| decisions: string[]; | ||
| implementationDetails: string; | ||
| } | ||
|
|
||
| export interface ReviewerOutput { | ||
| deviations: string[]; | ||
| approved: boolean; | ||
| } | ||
|
|
||
| export const featureDevTemplate = { | ||
| async run(requirements: string, initialContext?: Record<string, unknown>): Promise<{ | ||
| plan: Plan; | ||
| development: DeveloperOutput; | ||
| review: ReviewerOutput; | ||
| }> { | ||
| const ctx = new Context(initialContext); | ||
| ctx.set('requirements', requirements); | ||
|
|
||
| const plan = await this.planner(ctx); | ||
| ctx.set('plan', plan); | ||
|
|
||
| const development = await this.developer(ctx); | ||
| ctx.set('development', development); | ||
|
|
||
| const review = await this.reviewer(ctx); | ||
|
|
||
| return { plan, development, review }; | ||
| }, | ||
|
|
||
| async planner(context: Context): Promise<Plan> { | ||
| const requirements = context.get('requirements') as string; | ||
| return { | ||
| strategy: `Implement ${requirements}`, | ||
| reasoning: 'Detailed reasoning based on requirements.' | ||
| }; | ||
| }, | ||
|
|
||
| async developer(context: Context): Promise<DeveloperOutput> { | ||
| const plan = context.get('plan') as Plan; | ||
| return { | ||
| decisions: [`Following strategy: ${plan.strategy}`], | ||
| implementationDetails: 'Code changes made according to plan.' | ||
| }; | ||
| }, | ||
|
|
||
| async reviewer(context: Context): Promise<ReviewerOutput> { | ||
| const plan = context.get('plan') as Plan; | ||
| const development = context.get('development') as DeveloperOutput; | ||
| const deviations: string[] = []; | ||
| if (!development.decisions.some(d => d.includes(plan.strategy))) { | ||
| deviations.push('Implementation deviates from plan strategy'); | ||
| } | ||
| return { deviations, approved: deviations.length === 0 }; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||||||||
| import { LoopStep, Step, StepContext } from '@agentworkforce/relay'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export interface ReviewStepOutput { | ||||||||||||||||||
| passed: boolean; | ||||||||||||||||||
| feedback?: string; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export interface AddressFeedbackInput { | ||||||||||||||||||
| code: string; | ||||||||||||||||||
| feedback: string; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export interface AddressFeedbackOutput { | ||||||||||||||||||
| code: string; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export interface ReviewLoopConfig { | ||||||||||||||||||
| maxIterations?: number; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export function createReviewLoop( | ||||||||||||||||||
| reviewStep: Step<unknown, ReviewStepOutput>, | ||||||||||||||||||
| addressFeedbackStep: Step<AddressFeedbackInput, AddressFeedbackOutput>, | ||||||||||||||||||
| config: ReviewLoopConfig = {} | ||||||||||||||||||
| ): Step<{ code: string }, { finalCode: string; iterations: number }> { | ||||||||||||||||||
| const maxIterations = config.maxIterations ?? 5; | ||||||||||||||||||
|
|
||||||||||||||||||
| return new LoopStep<{ code: string }, { finalCode: string; iterations: number }>({ | ||||||||||||||||||
| steps: [reviewStep, addressFeedbackStep], | ||||||||||||||||||
| while: (ctx: StepContext) => { | ||||||||||||||||||
| const reviewOut = ctx.getStepOutput(reviewStep) as ReviewStepOutput; | ||||||||||||||||||
| return !reviewOut.passed; | ||||||||||||||||||
| }, | ||||||||||||||||||
|
Comment on lines
+30
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: I couldn’t find any authoritative source in the AgentWorkforce/relay repo or its published docs that defines (1) the evaluation timing of LoopStep’s Citations:
🏁 Script executed: find . -type f -name "*.ts" -o -name "*.tsx" | head -20Repository: AgentWorkforce/relay Length of output: 934 🏁 Script executed: # Search for LoopStep and StepContext definitions
rg "class LoopStep|interface LoopStep|type LoopStep" -A 10Repository: AgentWorkforce/relay Length of output: 1414 🏁 Script executed: # Search for StepContext definition
rg "class StepContext|interface StepContext|type StepContext" -A 10Repository: AgentWorkforce/relay Length of output: 553 🏁 Script executed: # Search for getStepOutput method
rg "getStepOutput" -B 2 -A 5Repository: AgentWorkforce/relay Length of output: 6625 🏁 Script executed: # Look at the review-loop.ts file context
cat -n src/templates/review-loop.tsRepository: AgentWorkforce/relay Length of output: 1598 🏁 Script executed: # Find LoopStep implementation (class, not just interface)
rg "class LoopStep|export.*LoopStep.*=" -A 20Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Check for LoopStep in node_modules or dist
find . -name "*loop*" -type f | grep -E "\.(ts|js)$"Repository: AgentWorkforce/relay Length of output: 508 🏁 Script executed: # Look at the imports in review-loop.ts to find where LoopStep comes from
head -5 src/templates/review-loop.tsRepository: AgentWorkforce/relay Length of output: 210 🏁 Script executed: # Search for when conditions/while loops are evaluated in execution logic
rg "while.*condition|condition.*while|evaluateLoop|executeLoop" -B 3 -A 5Repository: AgentWorkforce/relay Length of output: 1870 🏁 Script executed: # Search for LoopStep implementation (likely in relay package)
rg "export.*class LoopStep" -A 30Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look for LoopStep in the relay package specifically
fd ".*loop.*" packages/ --type f -e ts -e tsx | head -20Repository: AgentWorkforce/relay Length of output: 104 🏁 Script executed: # Check what's exported from the main relay package
cat packages/relay/src/index.ts 2>/dev/null || cat packages/sdk/src/index.ts 2>/dev/null | grep -i loopRepository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Search for usage of while predicate in loop execution
rg "\.while\s*\(" -B 5 -A 10Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look at the actual LoopStep type more carefully - check all files with "loop" in workflow
rg "while.*StepContext|condition.*StepContext" -B 2 -A 5Repository: AgentWorkforce/relay Length of output: 1021 🏁 Script executed: # Look at ralph-loop example for LoopStep usage
cat packages/sdk/src/examples/ralph-loop.tsRepository: AgentWorkforce/relay Length of output: 12710 🏁 Script executed: # Check for LoopStep tests
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "LoopStep" 2>/dev/nullRepository: AgentWorkforce/relay Length of output: 48 🏁 Script executed: # Look for StepContext interface extensions or actual implementation
rg "getStepOutput" -B 5 -A 3Repository: AgentWorkforce/relay Length of output: 7255 🏁 Script executed: # Check package.json to understand relay dependencies
cat package.json | grep -A 5 "@agentworkforce"Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look at how ctx parameter is used in loops - check the actual LoopStep implementation in relay module
find . -path ./node_modules -prune -o -name "*.ts" -type f -exec grep -l "class.*LoopStep\|interface.*LoopStep" {} \;Repository: AgentWorkforce/relay Length of output: 140 🏁 Script executed: # Search for LoopStep class/implementation in relay module
rg "new LoopStep" -B 5 -A 15Repository: AgentWorkforce/relay Length of output: 3891 🏁 Script executed: # Look for where LoopStep is actually defined/implemented
find . -path ./node_modules/@agentworkforce -name "*.ts" -o -path ./node_modules/@agentworkforce -name "*.js" | head -20Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Check what's in `@agentworkforce/relay` node_modules
ls -la node_modules/@agentworkforce 2>/dev/null || echo "Module not installed or not in node_modules"Repository: AgentWorkforce/relay Length of output: 108 🏁 Script executed: # Look at the actual StepContext definition more carefully - maybe it's extended somewhere
rg "interface StepContext|type StepContext" -A 15Repository: AgentWorkforce/relay Length of output: 832 🏁 Script executed: # Check for implementations that show when while/condition is evaluated
rg "executeLoop|runLoop|loopBody|while.*steps" -B 3 -A 10Repository: AgentWorkforce/relay Length of output: 4517 🏁 Script executed: # Search for where StepContext is extended with getStepOutput method
rg "StepContext.*getStepOutput|getStepOutput.*StepContext" -B 5 -A 5Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look for Step generic type definition to understand the actual types involved
rg "type Step<|interface Step<" -A 5Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Check if there's a compiled version or dist folder with implementation
find . -name "*.d.ts" -path "*/relay/*" | head -10Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look at line 36 more carefully - it already uses | undefined, check what's different
sed -n '28,41p' src/templates/review-loop.tsRepository: AgentWorkforce/relay Length of output: 648 🏁 Script executed: # Search for how ctx parameter is used in other while/condition predicates
rg "\.while\s*:\s*\(|condition:\s*\(" -A 5 -B 2Repository: AgentWorkforce/relay Length of output: 439 🏁 Script executed: # Look at the full LoopStep interface definition and search for any implementation hints
cat src/workflow/types.tsRepository: AgentWorkforce/relay Length of output: 552 🏁 Script executed: # Search for tests or usage examples that show when while/condition is evaluated
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "LoopStep\|while.*condition" 2>/dev/nullRepository: AgentWorkforce/relay Length of output: 48 🏁 Script executed: # Check if there's documentation or comments about loop execution timing
rg "before.*iteration|first.*iteration|while.*evaluated" -i -B 2 -A 2Repository: AgentWorkforce/relay Length of output: 46 🏁 Script executed: # Look at the finalize function - it suggests the context after loop completes, so while would be evaluated after first iteration
rg "finalize" -B 3 -A 5Repository: AgentWorkforce/relay Length of output: 14284 Guard loop condition against missing review output.
Proposed fix while: (ctx: StepContext) => {
- const reviewOut = ctx.getStepOutput(reviewStep) as ReviewStepOutput;
- return !reviewOut.passed;
+ const reviewOut = ctx.getStepOutput(reviewStep) as ReviewStepOutput | undefined;
+ return reviewOut ? !reviewOut.passed : true;
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| maxLoops: maxIterations, | ||||||||||||||||||
| finalize: (ctx: StepContext) => { | ||||||||||||||||||
| const addressOut = ctx.getStepOutput(addressFeedbackStep) as AddressFeedbackOutput | undefined; | ||||||||||||||||||
| const finalCode = addressOut?.code ?? ctx.input.code; | ||||||||||||||||||
| return { finalCode, iterations: ctx.iterationCount }; | ||||||||||||||||||
| }, | ||||||||||||||||||
| }); | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| export interface StepContext { | ||
| data: Record<string, unknown>; | ||
| metadata?: Record<string, unknown>; | ||
| history?: string[]; | ||
| } | ||
|
|
||
| export interface WorkflowStep { | ||
| name: string; | ||
| execute(context: StepContext): Promise<StepContext> | StepContext; | ||
| } | ||
|
|
||
| export interface LoopStep extends WorkflowStep { | ||
| condition: (context: StepContext) => boolean | Promise<boolean>; | ||
| steps: WorkflowStep[]; | ||
| maxIterations?: number; | ||
| } | ||
|
|
||
| export interface Workflow { | ||
| name: string; | ||
| steps: WorkflowStep[]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce feedback when review fails.
ReviewStepOutputallowspassed: falsewith nofeedback, but the next step requiresfeedback: string. Encode this in the type to prevent invalid loop state.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents