redesign fully scaffolded and web login works
This commit is contained in:
461
gateway/src/harness/workflows/README.md
Normal file
461
gateway/src/harness/workflows/README.md
Normal file
@@ -0,0 +1,461 @@
|
||||
# Workflows
|
||||
|
||||
LangGraph-based workflows for multi-step agent orchestration.
|
||||
|
||||
## What are Workflows?
|
||||
|
||||
Workflows are state machines that orchestrate complex multi-step tasks with:
|
||||
|
||||
- **State Management**: Typed state with annotations
|
||||
- **Conditional Routing**: Different paths based on state
|
||||
- **Validation Loops**: Retry with fixes
|
||||
- **Human-in-the-Loop**: Approval gates and interrupts
|
||||
- **Error Recovery**: Graceful handling of failures
|
||||
|
||||
Built on [LangGraph.js](https://langchain-ai.github.io/langgraphjs/).
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
workflows/
|
||||
├── base-workflow.ts # Base class and utilities
|
||||
├── {workflow-name}/
|
||||
│ ├── config.yaml # Workflow configuration
|
||||
│ ├── state.ts # State schema (Annotations)
|
||||
│ ├── nodes.ts # Node implementations
|
||||
│ └── graph.ts # StateGraph definition
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Workflow Components
|
||||
|
||||
### State (state.ts)
|
||||
|
||||
Defines what data flows through the workflow:
|
||||
|
||||
```typescript
|
||||
import { Annotation } from '@langchain/langgraph';
|
||||
import { BaseWorkflowState } from '../base-workflow.js';
|
||||
|
||||
export const MyWorkflowState = Annotation.Root({
|
||||
...BaseWorkflowState.spec, // Inherit base fields
|
||||
|
||||
// Your custom fields
|
||||
input: Annotation<string>(),
|
||||
result: Annotation<string | null>({ default: () => null }),
|
||||
errorCount: Annotation<number>({ default: () => 0 }),
|
||||
});
|
||||
|
||||
export type MyWorkflowStateType = typeof MyWorkflowState.State;
|
||||
```
|
||||
|
||||
### Nodes (nodes.ts)
|
||||
|
||||
Functions that transform state:
|
||||
|
||||
```typescript
|
||||
export function createMyNode(deps: Dependencies) {
|
||||
return async (state: MyWorkflowStateType): Promise<Partial<MyWorkflowStateType>> => {
|
||||
// Do work
|
||||
const result = await doSomething(state.input);
|
||||
|
||||
// Return partial state update
|
||||
return { result };
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Graph (graph.ts)
|
||||
|
||||
Connects nodes with edges:
|
||||
|
||||
```typescript
|
||||
import { StateGraph } from '@langchain/langgraph';
|
||||
import { BaseWorkflow } from '../base-workflow.js';
|
||||
|
||||
export class MyWorkflow extends BaseWorkflow<MyWorkflowStateType> {
|
||||
buildGraph(): StateGraph<MyWorkflowStateType> {
|
||||
const graph = new StateGraph(MyWorkflowState);
|
||||
|
||||
// Add nodes
|
||||
graph
|
||||
.addNode('step1', createStep1Node())
|
||||
.addNode('step2', createStep2Node());
|
||||
|
||||
// Add edges
|
||||
graph
|
||||
.addEdge('__start__', 'step1')
|
||||
.addEdge('step1', 'step2')
|
||||
.addEdge('step2', '__end__');
|
||||
|
||||
return graph;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Config (config.yaml)
|
||||
|
||||
Workflow settings:
|
||||
|
||||
```yaml
|
||||
name: my-workflow
|
||||
description: What it does
|
||||
|
||||
timeout: 300000 # 5 minutes
|
||||
maxRetries: 3
|
||||
requiresApproval: true
|
||||
approvalNodes:
|
||||
- human_approval
|
||||
|
||||
# Custom settings
|
||||
myCustomSetting: value
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Validation Loop (Retry with Fixes)
|
||||
|
||||
```typescript
|
||||
graph
|
||||
.addNode('validate', validateNode)
|
||||
.addNode('fix', fixNode)
|
||||
.addConditionalEdges('validate', (state) => {
|
||||
if (state.isValid) return 'next_step';
|
||||
if (state.retryCount >= 3) return '__end__'; // Give up
|
||||
return 'fix'; // Try to fix
|
||||
})
|
||||
.addEdge('fix', 'validate'); // Loop back
|
||||
```
|
||||
|
||||
### 2. Human-in-the-Loop (Approval)
|
||||
|
||||
```typescript
|
||||
const approvalNode = async (state) => {
|
||||
// Send approval request to user's channel
|
||||
await sendToChannel(state.userContext.activeChannel, {
|
||||
type: 'approval_request',
|
||||
data: {
|
||||
action: 'execute_trade',
|
||||
details: state.tradeDetails,
|
||||
}
|
||||
});
|
||||
|
||||
// Mark as waiting for approval
|
||||
return { approvalRequested: true, userApproved: false };
|
||||
};
|
||||
|
||||
graph.addConditionalEdges('approval', (state) => {
|
||||
return state.userApproved ? 'execute' : '__end__';
|
||||
});
|
||||
|
||||
// To resume after user input:
|
||||
// const updated = await workflow.execute({ ...state, userApproved: true });
|
||||
```
|
||||
|
||||
### 3. Parallel Execution
|
||||
|
||||
```typescript
|
||||
import { Branch } from '@langchain/langgraph';
|
||||
|
||||
graph
|
||||
.addNode('parallel_start', startNode)
|
||||
.addNode('task_a', taskANode)
|
||||
.addNode('task_b', taskBNode)
|
||||
.addNode('merge', mergeNode);
|
||||
|
||||
// Branch to parallel tasks
|
||||
graph.addEdge('parallel_start', Branch.parallel(['task_a', 'task_b']));
|
||||
|
||||
// Merge results
|
||||
graph
|
||||
.addEdge('task_a', 'merge')
|
||||
.addEdge('task_b', 'merge');
|
||||
```
|
||||
|
||||
### 4. Error Recovery
|
||||
|
||||
```typescript
|
||||
const resilientNode = async (state) => {
|
||||
try {
|
||||
const result = await riskyOperation();
|
||||
return { result, error: null };
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Operation failed');
|
||||
return {
|
||||
error: error.message,
|
||||
fallbackUsed: true,
|
||||
result: await fallbackOperation()
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 5. Conditional Routing
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges('decision', (state) => {
|
||||
if (state.score > 0.8) return 'high_confidence';
|
||||
if (state.score > 0.5) return 'medium_confidence';
|
||||
return 'low_confidence';
|
||||
});
|
||||
|
||||
graph
|
||||
.addNode('high_confidence', autoApproveNode)
|
||||
.addNode('medium_confidence', humanReviewNode)
|
||||
.addNode('low_confidence', rejectNode);
|
||||
```
|
||||
|
||||
## Available Workflows
|
||||
|
||||
### strategy-validation
|
||||
|
||||
Validates trading strategies with multiple steps and a validation loop.
|
||||
|
||||
**Flow:**
|
||||
1. Code Review (using CodeReviewerSubagent)
|
||||
2. If issues → Fix Code → loop back
|
||||
3. Backtest (via MCP)
|
||||
4. If failed → Fix Code → loop back
|
||||
5. Risk Assessment
|
||||
6. Human Approval
|
||||
7. Final Recommendation
|
||||
|
||||
**Features:**
|
||||
- Max 3 retry attempts
|
||||
- Multi-file memory from subagent
|
||||
- Risk-based auto-approval
|
||||
- Comprehensive state tracking
|
||||
|
||||
### trading-request
|
||||
|
||||
Human-in-the-loop workflow for trade execution.
|
||||
|
||||
**Flow:**
|
||||
1. Analyze market conditions
|
||||
2. Calculate risk and position size
|
||||
3. Request human approval (PAUSE)
|
||||
4. If approved → Execute trade
|
||||
5. Generate summary
|
||||
|
||||
**Features:**
|
||||
- Interrupt at approval node
|
||||
- Channel-aware approval UI
|
||||
- Risk validation
|
||||
- Execution confirmation
|
||||
|
||||
## Creating a New Workflow
|
||||
|
||||
### 1. Create Directory
|
||||
|
||||
```bash
|
||||
mkdir -p workflows/my-workflow
|
||||
```
|
||||
|
||||
### 2. Define State
|
||||
|
||||
```typescript
|
||||
// state.ts
|
||||
import { Annotation } from '@langchain/langgraph';
|
||||
import { BaseWorkflowState } from '../base-workflow.js';
|
||||
|
||||
export const MyWorkflowState = Annotation.Root({
|
||||
...BaseWorkflowState.spec,
|
||||
|
||||
// Add your fields
|
||||
input: Annotation<string>(),
|
||||
step1Result: Annotation<string | null>({ default: () => null }),
|
||||
step2Result: Annotation<string | null>({ default: () => null }),
|
||||
});
|
||||
|
||||
export type MyWorkflowStateType = typeof MyWorkflowState.State;
|
||||
```
|
||||
|
||||
### 3. Create Nodes
|
||||
|
||||
```typescript
|
||||
// nodes.ts
|
||||
import { MyWorkflowStateType } from './state.js';
|
||||
|
||||
export function createStep1Node(deps: any) {
|
||||
return async (state: MyWorkflowStateType) => {
|
||||
const result = await doStep1(state.input);
|
||||
return { step1Result: result };
|
||||
};
|
||||
}
|
||||
|
||||
export function createStep2Node(deps: any) {
|
||||
return async (state: MyWorkflowStateType) => {
|
||||
const result = await doStep2(state.step1Result);
|
||||
return { step2Result: result, output: result };
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Build Graph
|
||||
|
||||
```typescript
|
||||
// graph.ts
|
||||
import { StateGraph } from '@langchain/langgraph';
|
||||
import { BaseWorkflow, WorkflowConfig } from '../base-workflow.js';
|
||||
import { MyWorkflowState, MyWorkflowStateType } from './state.js';
|
||||
import { createStep1Node, createStep2Node } from './nodes.js';
|
||||
|
||||
export class MyWorkflow extends BaseWorkflow<MyWorkflowStateType> {
|
||||
constructor(config: WorkflowConfig, private deps: any, logger: Logger) {
|
||||
super(config, logger);
|
||||
}
|
||||
|
||||
buildGraph(): StateGraph<MyWorkflowStateType> {
|
||||
const graph = new StateGraph(MyWorkflowState);
|
||||
|
||||
const step1 = createStep1Node(this.deps);
|
||||
const step2 = createStep2Node(this.deps);
|
||||
|
||||
graph
|
||||
.addNode('step1', step1)
|
||||
.addNode('step2', step2)
|
||||
.addEdge('__start__', 'step1')
|
||||
.addEdge('step1', 'step2')
|
||||
.addEdge('step2', '__end__');
|
||||
|
||||
return graph;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Config
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
name: my-workflow
|
||||
description: My workflow description
|
||||
|
||||
timeout: 60000
|
||||
maxRetries: 3
|
||||
requiresApproval: false
|
||||
|
||||
model: claude-3-5-sonnet-20241022
|
||||
```
|
||||
|
||||
### 6. Add Factory Function
|
||||
|
||||
```typescript
|
||||
// graph.ts (continued)
|
||||
export async function createMyWorkflow(
|
||||
deps: any,
|
||||
logger: Logger,
|
||||
configPath: string
|
||||
): Promise<MyWorkflow> {
|
||||
const config = await loadYAML(configPath);
|
||||
const workflow = new MyWorkflow(config, deps, logger);
|
||||
workflow.compile();
|
||||
return workflow;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Execute Workflow
|
||||
|
||||
```typescript
|
||||
import { createMyWorkflow } from './harness/workflows';
|
||||
|
||||
const workflow = await createMyWorkflow(deps, logger, configPath);
|
||||
|
||||
const result = await workflow.execute({
|
||||
userContext,
|
||||
input: 'my input'
|
||||
});
|
||||
|
||||
console.log(result.output);
|
||||
```
|
||||
|
||||
### Stream Workflow
|
||||
|
||||
```typescript
|
||||
for await (const state of workflow.stream({ userContext, input })) {
|
||||
console.log('Current state:', state);
|
||||
}
|
||||
```
|
||||
|
||||
### With Interrupts (Human-in-the-Loop)
|
||||
|
||||
```typescript
|
||||
// Initial execution (pauses at interrupt)
|
||||
const pausedState = await workflow.execute(initialState);
|
||||
|
||||
// User provides input
|
||||
const userInput = await getUserApproval();
|
||||
|
||||
// Resume from paused state
|
||||
const finalState = await workflow.execute({
|
||||
...pausedState,
|
||||
userApproved: userInput.approved
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### State Design
|
||||
|
||||
- **Immutable Updates**: Return partial state, don't mutate
|
||||
- **Type Safety**: Use TypeScript annotations
|
||||
- **Defaults**: Provide sensible defaults
|
||||
- **Nullable Fields**: Use `| null` with `default: () => null`
|
||||
|
||||
### Node Implementation
|
||||
|
||||
- **Pure Functions**: Avoid side effects in state logic
|
||||
- **Error Handling**: Catch errors, return error state
|
||||
- **Logging**: Log entry/exit of nodes
|
||||
- **Partial Updates**: Only return fields that changed
|
||||
|
||||
### Graph Design
|
||||
|
||||
- **Single Responsibility**: Each node does one thing
|
||||
- **Clear Flow**: Easy to visualize the graph
|
||||
- **Error Paths**: Handle failures gracefully
|
||||
- **Idempotency**: Safe to retry nodes
|
||||
|
||||
### Configuration
|
||||
|
||||
- **Timeouts**: Set reasonable limits
|
||||
- **Retries**: Don't retry forever
|
||||
- **Approvals**: Mark approval nodes explicitly
|
||||
- **Documentation**: Explain complex config values
|
||||
|
||||
## Debugging
|
||||
|
||||
### View Graph
|
||||
|
||||
```typescript
|
||||
// Get graph structure
|
||||
const compiled = workflow.compile();
|
||||
console.log(compiled.getGraph());
|
||||
```
|
||||
|
||||
### Log State
|
||||
|
||||
```typescript
|
||||
const debugNode = async (state) => {
|
||||
logger.debug({ state }, 'Current state');
|
||||
return {}; // No changes
|
||||
};
|
||||
|
||||
graph.addNode('debug', debugNode);
|
||||
```
|
||||
|
||||
### Test Nodes in Isolation
|
||||
|
||||
```typescript
|
||||
const step1 = createStep1Node(deps);
|
||||
const result = await step1({ input: 'test', /* ... */ });
|
||||
expect(result.step1Result).toBe('expected');
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [LangGraph.js Docs](https://langchain-ai.github.io/langgraphjs/)
|
||||
- [LangChain.js Docs](https://js.langchain.com/)
|
||||
- [Example: strategy-validation](./strategy-validation/graph.ts)
|
||||
- [Example: trading-request](./trading-request/graph.ts)
|
||||
Reference in New Issue
Block a user