Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ e2e-test-project/
tmp/
worktrees/

plugins/
plugins/
# jscpd JSON report (generated, .md is the tracked stub)
report/jscpd-report.json
19 changes: 19 additions & 0 deletions .openfox/agents/dream-reviewer.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
id: dream-reviewer
name: Dream Reviewer
description: Top-level workflow agent for the dream demo's review step. Reviews the change for correctness and style, then calls step_done().
subagent: false
color: '#a855f7'
allowedTools:
- read_file
- run_command
---

You are a code reviewer running as a top-level workflow step.

Your job:

1. Review the completed change for correctness and style.
2. Approve it or note requested changes, concisely.

When you are finished, you MUST call `step_done()` to signal completion. Do not loop: one review pass + `step_done()` is enough.
22 changes: 22 additions & 0 deletions .openfox/agents/dream-verifier.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
id: dream-verifier
name: Dream Verifier
description: Top-level workflow agent for the dream demo's verify step. Runs tests and lint, then calls step_done() so the llm_decision transition can route.
subagent: false
color: '#22c55e'
allowedTools:
- read_file
- run_command
- session_metadata
- web_fetch
---

You are a verifier running as a top-level workflow step.

Your job:

1. Run the project's tests and lint (if any exist).
2. Read the changed files to confirm the work meets the request.
3. Report the outcome concisely.

When you are finished, you MUST call `step_done()` to signal completion. Do not loop: a single verification pass + `step_done()` is enough.
147 changes: 147 additions & 0 deletions .openfox/workflows/dream.workflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"metadata": {
"id": "dream",
"name": "Dream — Dynamic Orchestration",
"description": "Demo workflow for the per-step / team / llm_decision dream: an LLM orchestrator decides which step runs next. Bind a team (Phase 2) or set per-step overrides (Phase 1) so each step runs on its own model; the verify step's resolved model is the one asked to route.",
"version": "1.0.0",
"color": "#8b5cf6"
},
"entryStep": "build",
"settings": {
"maxIterations": 10
},
"steps": [
{
"id": "build",
"name": "Build",
"type": "agent",
"phase": "build",
"agentId": "builder",
"prompt": "Implement the requested change. When you are done, call step_done().",
"transitions": [
{
"when": {
"type": "always"
},
"goto": "verify"
}
]
},
{
"id": "verify",
"name": "Verify & Route",
"type": "agent",
"phase": "verification",
"agentId": "dream-verifier",
"prompt": "Run the tests and lint. Report the outcome via step_done().",
"transitions": [
{
"when": {
"type": "custom",
"handler": "llm_decision",
"config": {
"prompt": "Given the verification outcome, what should we do next?",
"candidates": [
{
"goto": "build",
"label": "Retry build",
"description": "Tests or lint failed; rebuild after fixes"
},
{
"goto": "review",
"label": "Send to review",
"description": "Everything passed; have it reviewed"
},
{
"goto": "$done",
"label": "Finish",
"description": "Nothing left to do"
}
],
"thisGoto": "build"
}
},
"goto": "build"
},
{
"when": {
"type": "custom",
"handler": "llm_decision",
"config": {
"prompt": "Given the verification outcome, what should we do next?",
"candidates": [
{
"goto": "build",
"label": "Retry build",
"description": "Tests or lint failed; rebuild after fixes"
},
{
"goto": "review",
"label": "Send to review",
"description": "Everything passed; have it reviewed"
},
{
"goto": "$done",
"label": "Finish",
"description": "Nothing left to do"
}
],
"thisGoto": "review"
}
},
"goto": "review"
},
{
"when": {
"type": "custom",
"handler": "llm_decision",
"config": {
"prompt": "Given the verification outcome, what should we do next?",
"candidates": [
{
"goto": "build",
"label": "Retry build",
"description": "Tests or lint failed; rebuild after fixes"
},
{
"goto": "review",
"label": "Send to review",
"description": "Everything passed; have it reviewed"
},
{
"goto": "$done",
"label": "Finish",
"description": "Nothing left to do"
}
],
"thisGoto": "$done"
}
},
"goto": "$done"
},
{
"when": {
"type": "always"
},
"goto": "$done"
}
]
},
{
"id": "review",
"name": "Review",
"type": "agent",
"phase": "review",
"agentId": "dream-reviewer",
"prompt": "Review the change for correctness and style. When done, call step_done().",
"transitions": [
{
"when": {
"type": "always"
},
"goto": "$done"
}
]
}
]
}
96 changes: 96 additions & 0 deletions docs/DREAM-ORCHESTRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Dream — Per-step dynamic orchestration

This describes the four-phase feature that lets you choose, per workflow step,
which LLM orchestrates, which produces code, and which verifies — transparently
once configured, with no cap on the number of steps or models.

| Phase | What it adds | Where |
| ----- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| 0 | Plugin API extension point for custom transition handlers | `src/server/providers/plugins/registry.ts` |
| 1 | Per-step model override (`workflowId:stepId`) | `src/server/agents/model-overrides.ts` |
| 2 | Teams: a named bundle of `stepId -> { providerId, model, reasoningEffort? }` bound to a workflow | `src/server/agents/teams.ts`, `src/server/routes/teams.ts` |
| 3 | Built-in `llm_decision` transition handler: an LLM picks the next step | `src/server/workflows/llm-decision-handler.ts` |

## How they compose

Resolution precedence for a step's LLM client (in `resolveLLMClientForStep`):

```
explicit step override > team assignment > agent override > session model
```

When a step's transitions use `when: { type: 'custom', handler: 'llm_decision',
config }`, the executor passes that step's **resolved** client to the handler.
So "which LLM orchestrates this step" is exactly the model you assigned to that
step — per step, via a team, or via an explicit override. One LLM call is made
per `(workflow, step, outcome)` and shared across sibling transitions; only the
transition whose `thisGoto` matches the LLM's choice fires. If the call fails or
the response is unparseable, the handler returns false and a following `always`
fallback wins — routing never blocks.

## Configuring it

### 1. Bind a team to a workflow (recommended for N steps / N models)

```bash
# Create a team that assigns a different model to each step.
curl -X PUT localhost:3000/api/teams/dream-team \
-H 'content-type: application/json' \
-d '{
"name": "Dream team",
"assignments": {
"build": { "providerId": "openai", "model": "gpt-4o" },
"verify": { "providerId": "anthropic", "model": "claude-sonnet-5", "reasoningEffort": "high" },
"review": { "providerId": "openai", "model": "gpt-4o-mini" }
}
}'

# Bind the workflow to the team (every run resolves steps from it).
curl -X PUT localhost:3000/api/teams/bindings/dream \
-H 'content-type: application/json' \
-d '{"teamId":"dream-team"}'
```

### 2. Or override a single step

```bash
curl -X PUT localhost:3000/api/workflows/dream/steps/verify/model \
-H 'content-type: application/json' \
-d '{ "providerId": "anthropic", "model": "claude-sonnet-5", "reasoningEffort": "high" }'
```

### 3. Use `llm_decision` in the workflow

See `.openfox/workflows/dream.workflow.json` for a full example. The routing
step declares one `custom` transition per candidate, each with the shared
`candidates` list and its own `thisGoto`:

```json
{
"when": {
"type": "custom",
"handler": "llm_decision",
"config": {
"prompt": "Given the verification outcome, what should we do next?",
"candidates": [
{ "goto": "build", "label": "Retry build", "description": "Tests failed" },
{ "goto": "review", "label": "Send to review", "description": "Passed" },
{ "goto": "$done", "label": "Finish", "description": "Nothing left" }
],
"thisGoto": "build"
}
},
"goto": "build"
}
```

Repeat for each candidate's `thisGoto`, then add an `always` fallback. The
handler is registered as a built-in before plugins load; a plugin registering
`llm_decision` overrides it.

## Verification

`src/server/workflows/phase4-integration.test.ts` proves the composition
end-to-end: the `llm_decision` call is made on the step-resolved (team) client,
the session client is not consulted, and routing follows the LLM's choice (or
falls through to the `always` fallback on an unparseable response).
8 changes: 8 additions & 0 deletions src/provider/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ModelConfig } from '../shared/types.js'
import type { LLMCompletionRequest, LLMCompletionResponse, LLMStreamEvent } from '../server/llm/types.js'
import type { TransitionHandler } from '../server/workflows/transition-handlers.js'

// ============================================================================
// Auth Types
Expand Down Expand Up @@ -105,6 +106,12 @@ export interface ProviderPluginRegistry {
registerAuth(adapter: ProviderAuthAdapter): void
registerTransport(adapter: ProviderTransportAdapter): void
registerPreset(preset: ProviderPreset): void
/**
* Register a custom workflow transition handler. A workflow step declares a
* transition with `when: { type: 'custom', handler: '<handlerId>' }` and the
* executor invokes the registered handler to decide whether it fires.
*/
registerTransitionHandler(handlerId: string, handler: TransitionHandler): void
readonly runtime: ProviderPluginRuntime
}

Expand Down Expand Up @@ -170,3 +177,4 @@ export type {
LLMToolDefinition,
} from '../server/llm/types.js'
export type { ModelConfig, ToolCall } from '../shared/types.js'
export type { TransitionHandler, TransitionHandlerContext } from '../server/workflows/transition-handlers.js'
44 changes: 44 additions & 0 deletions src/server/agents/__fixtures__/avocat-du-diable.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
id: avocat-du-diable
name: Avocat du diable
description: "Olgenius : cherche activement ce qui va casser. Couvre 10 axes, écrit OBJECTIONS.md. N'approuve jamais. Ne code pas."
subagent: false
color: '#ef4444'
allowedTools:
- read_file
- write_file
- run_command
- session_metadata
---

Tu es **l'Avocat du diable** du système Olgenius. Ton mandat : **chercher activement ce qui va casser.** Tu n'es pas là pour approuver. Si tu ne trouves rien, tu l'écris explicitement et tu justifies pourquoi chaque axe est couvert : un accord silencieux est un échec de ta part. Tu ne codes pas.

## Grille à couvrir, dans l'ordre

1. Hypothèses non vérifiées du planificateur
2. Architecture : couplage, choix irréversibles, sur-ingénierie autant que sous-ingénierie
3. Modèle de données et migrations
4. Sécurité : authentification, autorisation, secrets, injections, données personnelles
5. Cas limites et modes de défaillance
6. Testabilité : ce que le découpage rend difficile à tester
7. Dépendances externes : maturité, licence, verrouillage
8. Exploitation : configuration, journalisation, reprise sur erreur, rollback
9. Périmètre : ce qui a été ajouté sans être demandé, ce qui a été oublié
10. Ordonnancement : phases qui vont devoir être refaites à cause de l'ordre choisi

## Format de chaque objection

```
### OBJ-001 · [BLOQUANT|MAJEUR|MINEUR] · phase 03
Constat : ...
Conséquence : ce qui casse concrètement, et quand
Proposition : la correction précise
```

- **BLOQUANT** : le plan produira un système faux, non sécurisé, ou irréparable sans reprise lourde.
- **MAJEUR** : coût significatif si on ne corrige pas maintenant.
- **MINEUR** : préférence, style, optimisation.

## Interdits

Approuver par politesse. Inventer un risque non documenté pour remplir la grille. Recopier le contenu du PRD/PLAN dans tes messages : tu donnes des chemins et des références de ligne. Modifier le code.
Loading