Store workflow definitions as table rows and drop YAML

This commit is contained in:
Ian Gulliver
2026-10-20 21:30:39 -07:00
parent 5823af6ff9
commit 32ebf08b66
17 changed files with 624 additions and 454 deletions
+64 -28
View File
@@ -15,12 +15,12 @@ import (
"g.fc.run/ai/byre/internal/workflow"
)
func (m *Manager) SaveWorkflow(ctx context.Context, name, from string, file []byte, actor string) (api.WorkflowSummary, error) {
func (m *Manager) SaveWorkflow(ctx context.Context, name, from string, definition []byte, actor string) (api.WorkflowSummary, error) {
name, err := workflow.CleanName(name)
if err != nil {
return api.WorkflowSummary{}, err
}
def, err := workflow.Parse(file)
def, err := workflow.Parse(definition)
if err != nil {
return api.WorkflowSummary{}, err
}
@@ -58,24 +58,12 @@ func reviewProblem(def api.WorkflowDefinition) error {
return nil
}
func (m *Manager) WorkflowFile(ctx context.Context, name string) ([]byte, error) {
def, err := m.store.GetWorkflow(ctx, name)
if err != nil {
return nil, err
}
return workflow.Format(def)
}
func (m *Manager) TaskWorkflow(ctx context.Context, taskID string) (api.TaskWorkflow, error) {
frozen, err := m.store.TaskWorkflow(ctx, taskID)
if err != nil {
return api.TaskWorkflow{}, err
}
file, err := workflow.Format(frozen.Definition)
if err != nil {
return api.TaskWorkflow{}, err
}
return api.TaskWorkflow{Name: frozen.Name, Definition: string(file)}, nil
return api.TaskWorkflow{Name: frozen.Name, Definition: frozen.Definition}, nil
}
func (m *Manager) WorkflowRun(ctx context.Context, taskID string) (api.WorkflowRun, error) {
@@ -719,7 +707,48 @@ func stageGuidance(frozen store.FrozenWorkflow, stage string) string {
"%s. Until you call it, the workflow waits on this stage.", stage, frozen.Name, then)
}
const workflowFileHelp = "The file's YAML format is the read_knowledge article workflow-files."
const workflowHelp = "The definition's fields are the read_knowledge article workflow-definitions."
const workflowDefinitionSchema = `{
"type": "object",
"properties": {
"description": {"type": "string"},
"steps": {"type": "array", "items": {"$ref": "#/$defs/step"}}
},
"required": ["steps"],
"additionalProperties": false
}`
const workflowSchemaDefs = `"$defs": {
"step": {
"type": "object",
"description": "Exactly one of stage (with model or agents, and prompt), order or parallel.",
"properties": {
"stage": {"type": "string"},
"model": {"type": "string"},
"prompt": {"type": "string"},
"agents": {"type": "array", "items": {"$ref": "#/$defs/agent"}},
"order": {"type": "array", "items": {"$ref": "#/$defs/step"}},
"parallel": {"type": "array", "items": {"$ref": "#/$defs/step"}}
},
"additionalProperties": false
},
"agent": {
"type": "object",
"properties": {
"name": {"type": "string"},
"model": {"type": "string"},
"prompt": {"type": "string"}
},
"required": ["name", "model", "prompt"],
"additionalProperties": false
}
}`
func formatDefinition(def api.WorkflowDefinition) (string, error) {
b, err := json.MarshalIndent(def, "", " ")
return string(b), err
}
func (m *Manager) workflowTools(tool func(name, description, schema string, run func(ctx context.Context, user api.User, raw json.RawMessage) (string, error)) mcp.Tool) []mcp.Tool {
return []mcp.Tool{
@@ -746,7 +775,7 @@ func (m *Manager) workflowTools(tool func(name, description, schema string, run
}),
tool("read_workflow",
"Read a workflow's file: with name, the workflow on the server; with task_id, the copy that task froze when it was created. "+workflowFileHelp,
"Read a workflow's definition: with name, the workflow on the server; with task_id, the copy that task froze when it was created. "+workflowHelp,
`{"type": "object", "properties": {
"name": {"type": "string"},
"task_id": {"type": "string"}
@@ -764,33 +793,40 @@ func (m *Manager) workflowTools(tool func(name, description, schema string, run
return "", fmt.Errorf("%w: give exactly one of name and task_id", ErrInvalid)
}
if args.TaskID != "" {
tw, err := m.TaskWorkflow(ctx, args.TaskID)
frozen, err := m.store.TaskWorkflow(ctx, args.TaskID)
if err != nil {
return "", err
}
return fmt.Sprintf("Task %s froze workflow %q:\n\n%s", args.TaskID, tw.Name, tw.Definition), nil
text, err := formatDefinition(frozen.Definition)
if err != nil {
return "", err
}
return fmt.Sprintf("Task %s froze workflow %q:\n\n%s", args.TaskID, frozen.Name, text), nil
}
file, err := m.WorkflowFile(ctx, args.Name)
return string(file), err
def, err := m.store.GetWorkflow(ctx, args.Name)
if err != nil {
return "", err
}
return formatDefinition(def)
}),
tool("save_workflow",
"Create or replace a workflow on the server from its file. Tasks that already froze it keep their copy. "+
"The name is one line of 1 to 100 characters, shown to the human on buttons and pickers. "+workflowFileHelp,
"Create or replace a workflow on the server from its definition. Tasks that already froze it keep their copy. "+
"The name is one line of 1 to 100 characters, shown to the human on buttons and pickers. "+workflowHelp,
`{"type": "object", "properties": {
"name": {"type": "string"},
"definition": {"type": "string", "description": "The workflow's file, as YAML."}
}, "required": ["name", "definition"]}`,
"definition": `+workflowDefinitionSchema+`
}, "required": ["name", "definition"], `+workflowSchemaDefs+`}`,
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
args := struct {
Name string `json:"name"`
Definition string `json:"definition"`
Name string `json:"name"`
Definition json.RawMessage `json:"definition"`
}{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", err
}
w, err := m.SaveWorkflow(ctx, args.Name, "", []byte(args.Definition), leadActor(user.Login))
w, err := m.SaveWorkflow(ctx, args.Name, "", args.Definition, leadActor(user.Login))
if err != nil {
return "", err
}