Skip to content
Merged
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Progi Monitoring gives you a live view of every running and completed task — s

**4. Optimize as you go**

Tweak playbooks between runs. Because workflows live in a database and survive context resets, every future task picks up your changes automatically — your process gets sharper with each iteration.
Tweak playbooks in Progi Monitoring between runs. Because workflows live in a database and survive context resets, every future task picks up your changes automatically — your process gets sharper with each iteration.

---

Expand All @@ -88,7 +88,6 @@ Tweak playbooks between runs. Because workflows live in a database and survive c
| `get_playbook_authoring_prompt` | Return the Pass 2 system prompt for authoring a step's playbook (injects workflow context) |
| `save_workflow` | Persist a new workflow, its steps, and playbooks |
| `list_workflows` | Return all workflows with their ordered steps |
| `update_playbook` | Replace the playbook content for a step |

Authoring is two passes: Pass 1 turns a plain-language description into a structured skeleton; Pass 2 authors each step's playbook. `save_workflow` persists both.

Expand Down
10 changes: 2 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,15 @@ Before I save this, a few questions:

Questions would be specific to your use case of course.

Then, Progi saves the playbooks:
Then, Progi saves the workflow:
```md
Now let me save the skeleton first, then I'll get the playbook authoring prompts for each step.

Progi [save_workflow]

Progi [update_playbook]
Progi [update_playbook]
Progi [update_playbook]
...
Progi [update_playbook]

```

Finally, Progi saves your complete workflow:
Finally, Progi confirms your complete workflow:
```
Done. The Agentic Fix-Up workflow is saved with 4 steps and full playbooks.

Expand Down
33 changes: 33 additions & 0 deletions progi/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,39 @@ def get_workflow_with_playbooks(cfg: Config, workflow_id: int) -> dict[str, Any]
}


def export_workflow(cfg: Config, workflow_id: int) -> dict[str, Any]:
"""Return a workflow in the save_workflow-compatible export format."""
wf = get_workflow_with_playbooks(cfg, workflow_id)
step_by_id = {s["id"]: s for s in wf["steps"]}
return {
"name": wf["name"],
"description": wf["description"],
"process": [
{
"order": s["order"],
"name": s["name"],
"input_spec": s["input_spec"],
"output_spec": s["output_spec"],
}
for s in wf["steps"]
],
"edges": [
{
"from": step_by_id[e["from_step_id"]]["name"],
"to": step_by_id[e["to_step_id"]]["name"],
"condition": e["condition"],
"priority": e["priority"],
}
for e in wf["edges"]
],
"playbooks": {
s["name"]: s["playbook"]
for s in wf["steps"]
if s["playbook"] is not None
},
}


def get_step_detail(cfg: Config, workflow_id: int, step_id: int) -> dict[str, Any]:
"""Return a single step with its playbook, and derived prev/next steps."""
engine = get_engine(cfg)
Expand Down
8 changes: 1 addition & 7 deletions progi/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
- **Work loop**: create_task, list_tasks,
start_or_continue_task, update_progress_notes, submit_output.
- **Workflow authoring**: get_process_skeleton_prompt,
get_playbook_authoring_prompt, save_workflow, list_workflows,
update_playbook.
get_playbook_authoring_prompt, save_workflow, list_workflows.

IMPORTANT (stdio hygiene): when running over stdio, stdout is the MCP protocol
channel. Never `print()` to stdout. Use logging configured to stderr (see
Expand Down Expand Up @@ -235,11 +234,6 @@ def list_workflows() -> dict:
return {"workflows": db.list_workflows(_cfg), "monitoring_url": _monitoring_url("/workflows")}


@mcp.tool(title="Update Playbook")
def update_playbook(step_id: int, content: str) -> dict:
"""Replace the playbook content for a step."""
return db.update_playbook(_cfg, step_id, content)


def run(cfg: Config | None = None) -> None:
"""Run the MCP server over stdio. Blocks until the client disconnects."""
Expand Down
17 changes: 17 additions & 0 deletions progi/web/routers/workflows.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import json

from fastapi import APIRouter, Body, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, Response
from fastapi.templating import Jinja2Templates
Expand Down Expand Up @@ -78,6 +80,21 @@ def step_detail(workflow_id: int, step_id: int, request: Request):
)


@router.get("/workflows/{workflow_id}/export")
def export_workflow(workflow_id: int, request: Request):
cfg = request.app.state.cfg
try:
data = db.export_workflow(cfg, workflow_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc))
filename = data["name"].replace(" ", "_") + ".json"
return Response(
content=json.dumps(data, indent=2),
media_type="application/json",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)


@router.patch("/workflows/{workflow_id}", response_class=JSONResponse)
def rename_workflow(
workflow_id: int,
Expand Down
5 changes: 5 additions & 0 deletions progi/web/templates/pages/workflows.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
@click.stop="startRename(wf)"
class="w-full text-left px-3 py-1.5 text-xs text-muted hover:bg-surface-1 hover:text-primary transition-colors"
>Rename</button>
<a
:href="`/workflows/${wf.id}/export`"
@click.stop="openMenuId = null"
class="block w-full text-left px-3 py-1.5 text-xs text-muted hover:bg-surface-1 hover:text-primary transition-colors"
>Export JSON</a>
<button
type="button"
@click.stop="deleteWorkflow(wf.id)"
Expand Down
Loading