diff --git a/agentic-workflows/bounded-agent/README.md b/agentic-workflows/bounded-agent/README.md index aa3364b..5000919 100644 --- a/agentic-workflows/bounded-agent/README.md +++ b/agentic-workflows/bounded-agent/README.md @@ -1,6 +1,6 @@ # Bounded Agent -A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract. +A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run. ## Run @@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write ./run.sh ``` -Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json). +Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json). -Published image: `ghcr.io/uffescience/akoflow-showcase-bounded-agent:v1`. +Published image: `ghcr.io/uffescience/akoflow-examples/showcase-bounded-agent:v1`. + +## Run locally step by step + +### 1. Check the prerequisites + +Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite: + +```sh +export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api +export AKOFLOW_API_TOKEN=akoflow-development-token + +python3 --version +docker version +curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/" +``` + +### 2. Run the deterministic smoke test + +From this showcase directory: + +```sh +./run.sh +``` + +The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`. + +### 3. Build the image used by the workflow + +```sh +IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1) +docker build -f docker/Dockerfile -t "$IMAGE" . +``` + +The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`. + +### 4. Register the local infrastructure and workflow + +```sh +for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml" +do + set -- $item + curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/" +done +``` + +The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime. + +### 5. Plan and execute in AkôFlow Desktop + +1. Open **Workflows → Definitions** and select this showcase. +2. Choose **Generate plan**. +3. Select the execution scope imported from `scope.yaml`. +4. Run **HEFT** and select its feasible candidate. +5. Choose **Execute plan** and confirm **Real execution**. +6. Wait until the run is `completed` and every activity is settled. + +### 6. Verify the result + +Open the completed run and inspect: + +- **Activities** for the engine-rendered DAG and per-activity status; +- **Workflow** for the observed execution lifecycle and timing decomposition; +- **Data** for workspace transfers; +- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums. + +The versioned evidence is stored in `screenshots/`: + +- `workflow.png`: completed activity DAG; +- `execution.png`: observed execution lifecycle and timing decomposition; +- `outputs.png`: data transfers between activity workspaces; +- `generated-files.png`: files observed in the final output-producing activity. diff --git a/agentic-workflows/bounded-agent/docker/Dockerfile b/agentic-workflows/bounded-agent/docker/Dockerfile index 8c87e64..5c5afae 100644 --- a/agentic-workflows/bounded-agent/docker/Dockerfile +++ b/agentic-workflows/bounded-agent/docker/Dockerfile @@ -2,5 +2,4 @@ FROM python:3.12-slim WORKDIR /app COPY data/input.json scenario.json COPY scripts/run_showcase.py /app/run_showcase.py -ENTRYPOINT ["python", "/app/run_showcase.py"] diff --git a/agentic-workflows/bounded-agent/environment.yaml b/agentic-workflows/bounded-agent/environment.yaml index fc9d21c..e4da758 100644 --- a/agentic-workflows/bounded-agent/environment.yaml +++ b/agentic-workflows/bounded-agent/environment.yaml @@ -14,14 +14,14 @@ version: configurationHash: showcase-bounded-agent-v1 runtimes: - environmentVersionId: showcase-bounded-agent-v1 - id: local + id: showcase-bounded-agent-local name: Local Docker host driver: local mode: execution role: compute capabilities: {batch: true, container: true} resources: - - id: local-docker + - id: showcase-bounded-agent-local-docker environmentVersionId: showcase-bounded-agent-v1 type: machine name: Local Docker host @@ -39,5 +39,5 @@ storages: [] connections: [] activityResourceProfiles: [] resourceRuntimeBindings: - - {resourceId: local-docker, runtimeId: local, enabled: true} + - {resourceId: showcase-bounded-agent-local-docker, runtimeId: showcase-bounded-agent-local, enabled: true} diff --git a/agentic-workflows/bounded-agent/run.sh b/agentic-workflows/bounded-agent/run.sh index 9ec2ebc..e44c741 100755 --- a/agentic-workflows/bounded-agent/run.sh +++ b/agentic-workflows/bounded-agent/run.sh @@ -1,8 +1,7 @@ #!/bin/sh set -eu root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -rm -rf "$root/outputs" "$root/screenshots" -mkdir -p "$root/outputs" "$root/screenshots" +rm -rf "$root/outputs" +mkdir -p "$root/outputs" python3 "$root/scripts/run_showcase.py" --output "$root/outputs" -node "$root/scripts/capture_screenshots.mjs" "$root/validate.sh" diff --git a/agentic-workflows/bounded-agent/screenshots/execution.png b/agentic-workflows/bounded-agent/screenshots/execution.png index 51ff13f..61a094d 100644 Binary files a/agentic-workflows/bounded-agent/screenshots/execution.png and b/agentic-workflows/bounded-agent/screenshots/execution.png differ diff --git a/agentic-workflows/bounded-agent/screenshots/generated-files.png b/agentic-workflows/bounded-agent/screenshots/generated-files.png new file mode 100644 index 0000000..c383686 Binary files /dev/null and b/agentic-workflows/bounded-agent/screenshots/generated-files.png differ diff --git a/agentic-workflows/bounded-agent/screenshots/outputs.png b/agentic-workflows/bounded-agent/screenshots/outputs.png index 351c468..522f41d 100644 Binary files a/agentic-workflows/bounded-agent/screenshots/outputs.png and b/agentic-workflows/bounded-agent/screenshots/outputs.png differ diff --git a/agentic-workflows/bounded-agent/screenshots/workflow.png b/agentic-workflows/bounded-agent/screenshots/workflow.png index 8e9b3b6..7a559ed 100644 Binary files a/agentic-workflows/bounded-agent/screenshots/workflow.png and b/agentic-workflows/bounded-agent/screenshots/workflow.png differ diff --git a/agentic-workflows/bounded-agent/scripts/capture_screenshots.mjs b/agentic-workflows/bounded-agent/scripts/capture_screenshots.mjs deleted file mode 100755 index 1e34a74..0000000 --- a/agentic-workflows/bounded-agent/scripts/capture_screenshots.mjs +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node -// Generates browser screenshots after a successful Docker execution. -import {execFileSync} from "node:child_process"; -import fs from "node:fs"; -import path from "node:path"; -import {fileURLToPath} from "node:url"; -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const output = path.join(root, "outputs"); -const shots = path.join(root, "screenshots"); -fs.mkdirSync(shots, {recursive: true}); -const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8"); -execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"}); -for (const view of ["workflow", "execution", "outputs"]) { - const page = path.join(shots, view + ".html"); - fs.writeFileSync(page, "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/agentic-workflows/bounded-agent/scripts/validate.py b/agentic-workflows/bounded-agent/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/agentic-workflows/bounded-agent/scripts/validate.py
+++ b/agentic-workflows/bounded-agent/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/agentic-workflows/bounded-agent/workflow.yaml b/agentic-workflows/bounded-agent/workflow.yaml
index f0504cf..541c930 100644
--- a/agentic-workflows/bounded-agent/workflow.yaml
+++ b/agentic-workflows/bounded-agent/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-bounded-agent:v1
activities:
- name: goal
- runtime: local
+ runtime: showcase-bounded-agent-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "goal", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: agent
- runtime: local
+ runtime: showcase-bounded-agent-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "agent", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- goal
- name: validator
- runtime: local
+ runtime: showcase-bounded-agent-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "validator", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/agentic-workflows/multi-agent-review/README.md b/agentic-workflows/multi-agent-review/README.md
index 543d188..38fd473 100644
--- a/agentic-workflows/multi-agent-review/README.md
+++ b/agentic-workflows/multi-agent-review/README.md
@@ -1,6 +1,6 @@
# Multi-Agent Review
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-multi-agent-review:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-multi-agent-review:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/agentic-workflows/multi-agent-review/docker/Dockerfile b/agentic-workflows/multi-agent-review/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/agentic-workflows/multi-agent-review/docker/Dockerfile
+++ b/agentic-workflows/multi-agent-review/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/agentic-workflows/multi-agent-review/environment.yaml b/agentic-workflows/multi-agent-review/environment.yaml
index 854c70d..f56af9d 100644
--- a/agentic-workflows/multi-agent-review/environment.yaml
+++ b/agentic-workflows/multi-agent-review/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-multi-agent-review-v1
runtimes:
- environmentVersionId: showcase-multi-agent-review-v1
- id: local
+ id: showcase-multi-agent-review-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-multi-agent-review-local-docker
environmentVersionId: showcase-multi-agent-review-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-multi-agent-review-local-docker, runtimeId: showcase-multi-agent-review-local, enabled: true}
diff --git a/agentic-workflows/multi-agent-review/run.sh b/agentic-workflows/multi-agent-review/run.sh
index 9ec2ebc..e44c741 100755
--- a/agentic-workflows/multi-agent-review/run.sh
+++ b/agentic-workflows/multi-agent-review/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/agentic-workflows/multi-agent-review/screenshots/execution.png b/agentic-workflows/multi-agent-review/screenshots/execution.png
index 1ccf793..04f9cfa 100644
Binary files a/agentic-workflows/multi-agent-review/screenshots/execution.png and b/agentic-workflows/multi-agent-review/screenshots/execution.png differ
diff --git a/agentic-workflows/multi-agent-review/screenshots/generated-files.png b/agentic-workflows/multi-agent-review/screenshots/generated-files.png
new file mode 100644
index 0000000..f3a1e01
Binary files /dev/null and b/agentic-workflows/multi-agent-review/screenshots/generated-files.png differ
diff --git a/agentic-workflows/multi-agent-review/screenshots/outputs.png b/agentic-workflows/multi-agent-review/screenshots/outputs.png
index dd8d16f..643c2d4 100644
Binary files a/agentic-workflows/multi-agent-review/screenshots/outputs.png and b/agentic-workflows/multi-agent-review/screenshots/outputs.png differ
diff --git a/agentic-workflows/multi-agent-review/screenshots/workflow.png b/agentic-workflows/multi-agent-review/screenshots/workflow.png
index 13bac6d..296924a 100644
Binary files a/agentic-workflows/multi-agent-review/screenshots/workflow.png and b/agentic-workflows/multi-agent-review/screenshots/workflow.png differ
diff --git a/agentic-workflows/multi-agent-review/scripts/capture_screenshots.mjs b/agentic-workflows/multi-agent-review/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/agentic-workflows/multi-agent-review/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/agentic-workflows/multi-agent-review/scripts/validate.py b/agentic-workflows/multi-agent-review/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/agentic-workflows/multi-agent-review/scripts/validate.py
+++ b/agentic-workflows/multi-agent-review/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/agentic-workflows/multi-agent-review/workflow.yaml b/agentic-workflows/multi-agent-review/workflow.yaml
index bd4d2e6..0262dda 100644
--- a/agentic-workflows/multi-agent-review/workflow.yaml
+++ b/agentic-workflows/multi-agent-review/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-multi-agent-review:v1
activities:
- name: researcher
- runtime: local
+ runtime: showcase-multi-agent-review-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "researcher", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: reviewer
- runtime: local
+ runtime: showcase-multi-agent-review-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "reviewer", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- researcher
- name: critic
- runtime: local
+ runtime: showcase-multi-agent-review-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "critic", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- reviewer
- name: synthesis
- runtime: local
+ runtime: showcase-multi-agent-review-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "synthesis", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/agentic-workflows/supervised-tool-loop/README.md b/agentic-workflows/supervised-tool-loop/README.md
index 7ec56d6..e325446 100644
--- a/agentic-workflows/supervised-tool-loop/README.md
+++ b/agentic-workflows/supervised-tool-loop/README.md
@@ -1,6 +1,6 @@
# Supervised Tool Loop
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-supervised-tool-loop:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-supervised-tool-loop:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/agentic-workflows/supervised-tool-loop/docker/Dockerfile b/agentic-workflows/supervised-tool-loop/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/agentic-workflows/supervised-tool-loop/docker/Dockerfile
+++ b/agentic-workflows/supervised-tool-loop/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/agentic-workflows/supervised-tool-loop/environment.yaml b/agentic-workflows/supervised-tool-loop/environment.yaml
index 8f22e0c..a03fb40 100644
--- a/agentic-workflows/supervised-tool-loop/environment.yaml
+++ b/agentic-workflows/supervised-tool-loop/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-supervised-tool-loop-v1
runtimes:
- environmentVersionId: showcase-supervised-tool-loop-v1
- id: local
+ id: showcase-supervised-tool-loop-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-supervised-tool-loop-local-docker
environmentVersionId: showcase-supervised-tool-loop-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-supervised-tool-loop-local-docker, runtimeId: showcase-supervised-tool-loop-local, enabled: true}
diff --git a/agentic-workflows/supervised-tool-loop/run.sh b/agentic-workflows/supervised-tool-loop/run.sh
index 9ec2ebc..e44c741 100755
--- a/agentic-workflows/supervised-tool-loop/run.sh
+++ b/agentic-workflows/supervised-tool-loop/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/agentic-workflows/supervised-tool-loop/screenshots/execution.png b/agentic-workflows/supervised-tool-loop/screenshots/execution.png
index c0c2686..acfe407 100644
Binary files a/agentic-workflows/supervised-tool-loop/screenshots/execution.png and b/agentic-workflows/supervised-tool-loop/screenshots/execution.png differ
diff --git a/agentic-workflows/supervised-tool-loop/screenshots/generated-files.png b/agentic-workflows/supervised-tool-loop/screenshots/generated-files.png
new file mode 100644
index 0000000..bf5090d
Binary files /dev/null and b/agentic-workflows/supervised-tool-loop/screenshots/generated-files.png differ
diff --git a/agentic-workflows/supervised-tool-loop/screenshots/outputs.png b/agentic-workflows/supervised-tool-loop/screenshots/outputs.png
index 985ad3f..12a9024 100644
Binary files a/agentic-workflows/supervised-tool-loop/screenshots/outputs.png and b/agentic-workflows/supervised-tool-loop/screenshots/outputs.png differ
diff --git a/agentic-workflows/supervised-tool-loop/screenshots/workflow.png b/agentic-workflows/supervised-tool-loop/screenshots/workflow.png
index d325dd9..039d657 100644
Binary files a/agentic-workflows/supervised-tool-loop/screenshots/workflow.png and b/agentic-workflows/supervised-tool-loop/screenshots/workflow.png differ
diff --git a/agentic-workflows/supervised-tool-loop/scripts/capture_screenshots.mjs b/agentic-workflows/supervised-tool-loop/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/agentic-workflows/supervised-tool-loop/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/agentic-workflows/supervised-tool-loop/scripts/validate.py b/agentic-workflows/supervised-tool-loop/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/agentic-workflows/supervised-tool-loop/scripts/validate.py
+++ b/agentic-workflows/supervised-tool-loop/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/agentic-workflows/supervised-tool-loop/workflow.yaml b/agentic-workflows/supervised-tool-loop/workflow.yaml
index a4ab8d8..f51d4eb 100644
--- a/agentic-workflows/supervised-tool-loop/workflow.yaml
+++ b/agentic-workflows/supervised-tool-loop/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-supervised-tool-loop:v1
activities:
- name: proposal
- runtime: local
+ runtime: showcase-supervised-tool-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "proposal", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: approval
- runtime: local
+ runtime: showcase-supervised-tool-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "approval", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- proposal
- name: execution
- runtime: local
+ runtime: showcase-supervised-tool-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "execution", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- approval
- name: verification
- runtime: local
+ runtime: showcase-supervised-tool-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "verification", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/generative-ai/audio-transcription/README.md b/generative-ai/audio-transcription/README.md
index dfd9586..3bbd074 100644
--- a/generative-ai/audio-transcription/README.md
+++ b/generative-ai/audio-transcription/README.md
@@ -1,6 +1,6 @@
# Audio Transcription
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-audio-transcription:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-audio-transcription:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/generative-ai/audio-transcription/docker/Dockerfile b/generative-ai/audio-transcription/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/generative-ai/audio-transcription/docker/Dockerfile
+++ b/generative-ai/audio-transcription/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/generative-ai/audio-transcription/environment.yaml b/generative-ai/audio-transcription/environment.yaml
index 2a2d72c..0c2a2ef 100644
--- a/generative-ai/audio-transcription/environment.yaml
+++ b/generative-ai/audio-transcription/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-audio-transcription-v1
runtimes:
- environmentVersionId: showcase-audio-transcription-v1
- id: local
+ id: showcase-audio-transcription-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-audio-transcription-local-docker
environmentVersionId: showcase-audio-transcription-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-audio-transcription-local-docker, runtimeId: showcase-audio-transcription-local, enabled: true}
diff --git a/generative-ai/audio-transcription/run.sh b/generative-ai/audio-transcription/run.sh
index 9ec2ebc..e44c741 100755
--- a/generative-ai/audio-transcription/run.sh
+++ b/generative-ai/audio-transcription/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/generative-ai/audio-transcription/screenshots/execution.png b/generative-ai/audio-transcription/screenshots/execution.png
index 5001de2..729a8d4 100644
Binary files a/generative-ai/audio-transcription/screenshots/execution.png and b/generative-ai/audio-transcription/screenshots/execution.png differ
diff --git a/generative-ai/audio-transcription/screenshots/generated-files.png b/generative-ai/audio-transcription/screenshots/generated-files.png
new file mode 100644
index 0000000..888af1e
Binary files /dev/null and b/generative-ai/audio-transcription/screenshots/generated-files.png differ
diff --git a/generative-ai/audio-transcription/screenshots/outputs.png b/generative-ai/audio-transcription/screenshots/outputs.png
index 9323445..ec44f1a 100644
Binary files a/generative-ai/audio-transcription/screenshots/outputs.png and b/generative-ai/audio-transcription/screenshots/outputs.png differ
diff --git a/generative-ai/audio-transcription/screenshots/workflow.png b/generative-ai/audio-transcription/screenshots/workflow.png
index fc1c5a2..3c1a22e 100644
Binary files a/generative-ai/audio-transcription/screenshots/workflow.png and b/generative-ai/audio-transcription/screenshots/workflow.png differ
diff --git a/generative-ai/audio-transcription/scripts/capture_screenshots.mjs b/generative-ai/audio-transcription/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/generative-ai/audio-transcription/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/generative-ai/audio-transcription/scripts/validate.py b/generative-ai/audio-transcription/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/generative-ai/audio-transcription/scripts/validate.py
+++ b/generative-ai/audio-transcription/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/generative-ai/audio-transcription/workflow.yaml b/generative-ai/audio-transcription/workflow.yaml
index 5d6ce39..5809f06 100644
--- a/generative-ai/audio-transcription/workflow.yaml
+++ b/generative-ai/audio-transcription/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-audio-transcription:v1
activities:
- name: audio
- runtime: local
+ runtime: showcase-audio-transcription-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "audio", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: split
- runtime: local
+ runtime: showcase-audio-transcription-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "split", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- audio
- name: transcribe
- runtime: local
+ runtime: showcase-audio-transcription-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "transcribe", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- split
- name: summarize
- runtime: local
+ runtime: showcase-audio-transcription-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "summarize", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/generative-ai/document-extraction/README.md b/generative-ai/document-extraction/README.md
index 6ae7d26..07b8c5c 100644
--- a/generative-ai/document-extraction/README.md
+++ b/generative-ai/document-extraction/README.md
@@ -1,6 +1,6 @@
# Document Extraction
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-document-extraction:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-document-extraction:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/generative-ai/document-extraction/docker/Dockerfile b/generative-ai/document-extraction/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/generative-ai/document-extraction/docker/Dockerfile
+++ b/generative-ai/document-extraction/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/generative-ai/document-extraction/environment.yaml b/generative-ai/document-extraction/environment.yaml
index c2eddae..950653d 100644
--- a/generative-ai/document-extraction/environment.yaml
+++ b/generative-ai/document-extraction/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-document-extraction-v1
runtimes:
- environmentVersionId: showcase-document-extraction-v1
- id: local
+ id: showcase-document-extraction-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-document-extraction-local-docker
environmentVersionId: showcase-document-extraction-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-document-extraction-local-docker, runtimeId: showcase-document-extraction-local, enabled: true}
diff --git a/generative-ai/document-extraction/run.sh b/generative-ai/document-extraction/run.sh
index 9ec2ebc..e44c741 100755
--- a/generative-ai/document-extraction/run.sh
+++ b/generative-ai/document-extraction/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/generative-ai/document-extraction/screenshots/execution.png b/generative-ai/document-extraction/screenshots/execution.png
index 7e82f37..e180548 100644
Binary files a/generative-ai/document-extraction/screenshots/execution.png and b/generative-ai/document-extraction/screenshots/execution.png differ
diff --git a/generative-ai/document-extraction/screenshots/generated-files.png b/generative-ai/document-extraction/screenshots/generated-files.png
new file mode 100644
index 0000000..86973c7
Binary files /dev/null and b/generative-ai/document-extraction/screenshots/generated-files.png differ
diff --git a/generative-ai/document-extraction/screenshots/outputs.png b/generative-ai/document-extraction/screenshots/outputs.png
index e3330e4..12d5f90 100644
Binary files a/generative-ai/document-extraction/screenshots/outputs.png and b/generative-ai/document-extraction/screenshots/outputs.png differ
diff --git a/generative-ai/document-extraction/screenshots/workflow.png b/generative-ai/document-extraction/screenshots/workflow.png
index 681a9e5..baf753c 100644
Binary files a/generative-ai/document-extraction/screenshots/workflow.png and b/generative-ai/document-extraction/screenshots/workflow.png differ
diff --git a/generative-ai/document-extraction/scripts/capture_screenshots.mjs b/generative-ai/document-extraction/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/generative-ai/document-extraction/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/generative-ai/document-extraction/scripts/validate.py b/generative-ai/document-extraction/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/generative-ai/document-extraction/scripts/validate.py
+++ b/generative-ai/document-extraction/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/generative-ai/document-extraction/workflow.yaml b/generative-ai/document-extraction/workflow.yaml
index b2a50fd..892e63d 100644
--- a/generative-ai/document-extraction/workflow.yaml
+++ b/generative-ai/document-extraction/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-document-extraction:v1
activities:
- name: pdf
- runtime: local
+ runtime: showcase-document-extraction-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "pdf", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: ocr
- runtime: local
+ runtime: showcase-document-extraction-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "ocr", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- pdf
- name: extract
- runtime: local
+ runtime: showcase-document-extraction-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "extract", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- ocr
- name: json
- runtime: local
+ runtime: showcase-document-extraction-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "json", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/generative-ai/rag-indexing/README.md b/generative-ai/rag-indexing/README.md
index 58c7914..3465a97 100644
--- a/generative-ai/rag-indexing/README.md
+++ b/generative-ai/rag-indexing/README.md
@@ -1,6 +1,6 @@
# RAG Indexing
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-rag-indexing:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-rag-indexing:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/generative-ai/rag-indexing/docker/Dockerfile b/generative-ai/rag-indexing/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/generative-ai/rag-indexing/docker/Dockerfile
+++ b/generative-ai/rag-indexing/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/generative-ai/rag-indexing/environment.yaml b/generative-ai/rag-indexing/environment.yaml
index 7349f30..b9d5812 100644
--- a/generative-ai/rag-indexing/environment.yaml
+++ b/generative-ai/rag-indexing/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-rag-indexing-v1
runtimes:
- environmentVersionId: showcase-rag-indexing-v1
- id: local
+ id: showcase-rag-indexing-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-rag-indexing-local-docker
environmentVersionId: showcase-rag-indexing-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-rag-indexing-local-docker, runtimeId: showcase-rag-indexing-local, enabled: true}
diff --git a/generative-ai/rag-indexing/run.sh b/generative-ai/rag-indexing/run.sh
index 9ec2ebc..e44c741 100755
--- a/generative-ai/rag-indexing/run.sh
+++ b/generative-ai/rag-indexing/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/generative-ai/rag-indexing/screenshots/execution.png b/generative-ai/rag-indexing/screenshots/execution.png
index 0561d7e..79a39b8 100644
Binary files a/generative-ai/rag-indexing/screenshots/execution.png and b/generative-ai/rag-indexing/screenshots/execution.png differ
diff --git a/generative-ai/rag-indexing/screenshots/generated-files.png b/generative-ai/rag-indexing/screenshots/generated-files.png
new file mode 100644
index 0000000..45799be
Binary files /dev/null and b/generative-ai/rag-indexing/screenshots/generated-files.png differ
diff --git a/generative-ai/rag-indexing/screenshots/outputs.png b/generative-ai/rag-indexing/screenshots/outputs.png
index 392998e..5095b02 100644
Binary files a/generative-ai/rag-indexing/screenshots/outputs.png and b/generative-ai/rag-indexing/screenshots/outputs.png differ
diff --git a/generative-ai/rag-indexing/screenshots/workflow.png b/generative-ai/rag-indexing/screenshots/workflow.png
index a193093..ee37549 100644
Binary files a/generative-ai/rag-indexing/screenshots/workflow.png and b/generative-ai/rag-indexing/screenshots/workflow.png differ
diff --git a/generative-ai/rag-indexing/scripts/capture_screenshots.mjs b/generative-ai/rag-indexing/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/generative-ai/rag-indexing/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/generative-ai/rag-indexing/scripts/validate.py b/generative-ai/rag-indexing/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/generative-ai/rag-indexing/scripts/validate.py
+++ b/generative-ai/rag-indexing/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/generative-ai/rag-indexing/workflow.yaml b/generative-ai/rag-indexing/workflow.yaml
index 0f1f5bb..1f5c502 100644
--- a/generative-ai/rag-indexing/workflow.yaml
+++ b/generative-ai/rag-indexing/workflow.yaml
@@ -5,49 +5,44 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-rag-indexing:v1
activities:
- name: documents
- runtime: local
+ runtime: showcase-rag-indexing-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "documents", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: chunk
- runtime: local
+ runtime: showcase-rag-indexing-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "chunk", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- documents
- name: embed
- runtime: local
+ runtime: showcase-rag-indexing-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "embed", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- chunk
- name: faiss
- runtime: local
+ runtime: showcase-rag-indexing-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "faiss", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- embed
- name: evaluate
- runtime: local
+ runtime: showcase-rag-indexing-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "evaluate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/generative-ai/synthetic-dataset/README.md b/generative-ai/synthetic-dataset/README.md
index fee93a1..1776cc3 100644
--- a/generative-ai/synthetic-dataset/README.md
+++ b/generative-ai/synthetic-dataset/README.md
@@ -1,6 +1,6 @@
# Synthetic Dataset
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-synthetic-dataset:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-synthetic-dataset:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/generative-ai/synthetic-dataset/docker/Dockerfile b/generative-ai/synthetic-dataset/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/generative-ai/synthetic-dataset/docker/Dockerfile
+++ b/generative-ai/synthetic-dataset/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/generative-ai/synthetic-dataset/environment.yaml b/generative-ai/synthetic-dataset/environment.yaml
index 1a76f98..a480202 100644
--- a/generative-ai/synthetic-dataset/environment.yaml
+++ b/generative-ai/synthetic-dataset/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-synthetic-dataset-v1
runtimes:
- environmentVersionId: showcase-synthetic-dataset-v1
- id: local
+ id: showcase-synthetic-dataset-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-synthetic-dataset-local-docker
environmentVersionId: showcase-synthetic-dataset-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-synthetic-dataset-local-docker, runtimeId: showcase-synthetic-dataset-local, enabled: true}
diff --git a/generative-ai/synthetic-dataset/run.sh b/generative-ai/synthetic-dataset/run.sh
index 9ec2ebc..e44c741 100755
--- a/generative-ai/synthetic-dataset/run.sh
+++ b/generative-ai/synthetic-dataset/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/generative-ai/synthetic-dataset/screenshots/execution.png b/generative-ai/synthetic-dataset/screenshots/execution.png
index f9d82a9..2684d8f 100644
Binary files a/generative-ai/synthetic-dataset/screenshots/execution.png and b/generative-ai/synthetic-dataset/screenshots/execution.png differ
diff --git a/generative-ai/synthetic-dataset/screenshots/generated-files.png b/generative-ai/synthetic-dataset/screenshots/generated-files.png
new file mode 100644
index 0000000..a61770d
Binary files /dev/null and b/generative-ai/synthetic-dataset/screenshots/generated-files.png differ
diff --git a/generative-ai/synthetic-dataset/screenshots/outputs.png b/generative-ai/synthetic-dataset/screenshots/outputs.png
index 3d44664..4dada68 100644
Binary files a/generative-ai/synthetic-dataset/screenshots/outputs.png and b/generative-ai/synthetic-dataset/screenshots/outputs.png differ
diff --git a/generative-ai/synthetic-dataset/screenshots/workflow.png b/generative-ai/synthetic-dataset/screenshots/workflow.png
index 360e1ee..53e7b60 100644
Binary files a/generative-ai/synthetic-dataset/screenshots/workflow.png and b/generative-ai/synthetic-dataset/screenshots/workflow.png differ
diff --git a/generative-ai/synthetic-dataset/scripts/capture_screenshots.mjs b/generative-ai/synthetic-dataset/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/generative-ai/synthetic-dataset/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/generative-ai/synthetic-dataset/scripts/validate.py b/generative-ai/synthetic-dataset/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/generative-ai/synthetic-dataset/scripts/validate.py
+++ b/generative-ai/synthetic-dataset/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/generative-ai/synthetic-dataset/workflow.yaml b/generative-ai/synthetic-dataset/workflow.yaml
index c2e1170..08123d3 100644
--- a/generative-ai/synthetic-dataset/workflow.yaml
+++ b/generative-ai/synthetic-dataset/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-synthetic-dataset:v1
activities:
- name: generate
- runtime: local
+ runtime: showcase-synthetic-dataset-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "generate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: deduplicate
- runtime: local
+ runtime: showcase-synthetic-dataset-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "deduplicate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- generate
- name: quality-check
- runtime: local
+ runtime: showcase-synthetic-dataset-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "quality-check", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/anomaly-detection/README.md b/machine-learning/anomaly-detection/README.md
index 98a4550..6a3dda6 100644
--- a/machine-learning/anomaly-detection/README.md
+++ b/machine-learning/anomaly-detection/README.md
@@ -1,6 +1,6 @@
# Anomaly Detection
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-anomaly-detection:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-anomaly-detection:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/anomaly-detection/docker/Dockerfile b/machine-learning/anomaly-detection/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/anomaly-detection/docker/Dockerfile
+++ b/machine-learning/anomaly-detection/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/anomaly-detection/environment.yaml b/machine-learning/anomaly-detection/environment.yaml
index 638cbe5..e9e3fa8 100644
--- a/machine-learning/anomaly-detection/environment.yaml
+++ b/machine-learning/anomaly-detection/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-anomaly-detection-v1
runtimes:
- environmentVersionId: showcase-anomaly-detection-v1
- id: local
+ id: showcase-anomaly-detection-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-anomaly-detection-local-docker
environmentVersionId: showcase-anomaly-detection-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-anomaly-detection-local-docker, runtimeId: showcase-anomaly-detection-local, enabled: true}
diff --git a/machine-learning/anomaly-detection/run.sh b/machine-learning/anomaly-detection/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/anomaly-detection/run.sh
+++ b/machine-learning/anomaly-detection/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/anomaly-detection/screenshots/execution.png b/machine-learning/anomaly-detection/screenshots/execution.png
index 023d3f8..098e069 100644
Binary files a/machine-learning/anomaly-detection/screenshots/execution.png and b/machine-learning/anomaly-detection/screenshots/execution.png differ
diff --git a/machine-learning/anomaly-detection/screenshots/generated-files.png b/machine-learning/anomaly-detection/screenshots/generated-files.png
new file mode 100644
index 0000000..c64c978
Binary files /dev/null and b/machine-learning/anomaly-detection/screenshots/generated-files.png differ
diff --git a/machine-learning/anomaly-detection/screenshots/outputs.png b/machine-learning/anomaly-detection/screenshots/outputs.png
index 75e5d28..030755b 100644
Binary files a/machine-learning/anomaly-detection/screenshots/outputs.png and b/machine-learning/anomaly-detection/screenshots/outputs.png differ
diff --git a/machine-learning/anomaly-detection/screenshots/workflow.png b/machine-learning/anomaly-detection/screenshots/workflow.png
index f7e8d30..672ce4a 100644
Binary files a/machine-learning/anomaly-detection/screenshots/workflow.png and b/machine-learning/anomaly-detection/screenshots/workflow.png differ
diff --git a/machine-learning/anomaly-detection/scripts/capture_screenshots.mjs b/machine-learning/anomaly-detection/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/anomaly-detection/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/anomaly-detection/scripts/validate.py b/machine-learning/anomaly-detection/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/anomaly-detection/scripts/validate.py
+++ b/machine-learning/anomaly-detection/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/anomaly-detection/workflow.yaml b/machine-learning/anomaly-detection/workflow.yaml
index fbcf955..60b9811 100644
--- a/machine-learning/anomaly-detection/workflow.yaml
+++ b/machine-learning/anomaly-detection/workflow.yaml
@@ -5,49 +5,44 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-anomaly-detection:v1
activities:
- name: generate-data
- runtime: local
+ runtime: showcase-anomaly-detection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "generate-data", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: feature-engineering
- runtime: local
+ runtime: showcase-anomaly-detection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "feature-engineering", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- generate-data
- name: train
- runtime: local
+ runtime: showcase-anomaly-detection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "train", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- feature-engineering
- name: score
- runtime: local
+ runtime: showcase-anomaly-detection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "score", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- train
- name: threshold
- runtime: local
+ runtime: showcase-anomaly-detection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "threshold", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/batch-inference/README.md b/machine-learning/batch-inference/README.md
index 50c24f8..eef1e4d 100644
--- a/machine-learning/batch-inference/README.md
+++ b/machine-learning/batch-inference/README.md
@@ -1,6 +1,6 @@
# Batch Inference
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-batch-inference:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-batch-inference:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/batch-inference/docker/Dockerfile b/machine-learning/batch-inference/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/batch-inference/docker/Dockerfile
+++ b/machine-learning/batch-inference/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/batch-inference/environment.yaml b/machine-learning/batch-inference/environment.yaml
index 244cba5..f7a90d3 100644
--- a/machine-learning/batch-inference/environment.yaml
+++ b/machine-learning/batch-inference/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-batch-inference-v1
runtimes:
- environmentVersionId: showcase-batch-inference-v1
- id: local
+ id: showcase-batch-inference-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-batch-inference-local-docker
environmentVersionId: showcase-batch-inference-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-batch-inference-local-docker, runtimeId: showcase-batch-inference-local, enabled: true}
diff --git a/machine-learning/batch-inference/run.sh b/machine-learning/batch-inference/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/batch-inference/run.sh
+++ b/machine-learning/batch-inference/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/batch-inference/screenshots/execution.png b/machine-learning/batch-inference/screenshots/execution.png
index b72a29a..bf97a54 100644
Binary files a/machine-learning/batch-inference/screenshots/execution.png and b/machine-learning/batch-inference/screenshots/execution.png differ
diff --git a/machine-learning/batch-inference/screenshots/generated-files.png b/machine-learning/batch-inference/screenshots/generated-files.png
new file mode 100644
index 0000000..0bc5e4a
Binary files /dev/null and b/machine-learning/batch-inference/screenshots/generated-files.png differ
diff --git a/machine-learning/batch-inference/screenshots/outputs.png b/machine-learning/batch-inference/screenshots/outputs.png
index f2d3774..ab4f6a6 100644
Binary files a/machine-learning/batch-inference/screenshots/outputs.png and b/machine-learning/batch-inference/screenshots/outputs.png differ
diff --git a/machine-learning/batch-inference/screenshots/workflow.png b/machine-learning/batch-inference/screenshots/workflow.png
index 86049e5..a0ad48c 100644
Binary files a/machine-learning/batch-inference/screenshots/workflow.png and b/machine-learning/batch-inference/screenshots/workflow.png differ
diff --git a/machine-learning/batch-inference/scripts/capture_screenshots.mjs b/machine-learning/batch-inference/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/batch-inference/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/batch-inference/scripts/validate.py b/machine-learning/batch-inference/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/batch-inference/scripts/validate.py
+++ b/machine-learning/batch-inference/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/batch-inference/workflow.yaml b/machine-learning/batch-inference/workflow.yaml
index d12633a..fe01ca7 100644
--- a/machine-learning/batch-inference/workflow.yaml
+++ b/machine-learning/batch-inference/workflow.yaml
@@ -5,69 +5,62 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-batch-inference:v1
activities:
- name: load-model
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "load-model", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: split-input
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "split-input", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- load-model
- name: worker-1
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "worker-1", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- split-input
- name: worker-2
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "worker-2", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- worker-1
- name: worker-3
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "worker-3", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- worker-2
- name: worker-4
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "worker-4", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- worker-3
- name: merge
- runtime: local
+ runtime: showcase-batch-inference-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "merge", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/computer-vision/README.md b/machine-learning/computer-vision/README.md
index d8469fe..c478701 100644
--- a/machine-learning/computer-vision/README.md
+++ b/machine-learning/computer-vision/README.md
@@ -1,6 +1,6 @@
# Computer Vision
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-computer-vision:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-computer-vision:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/computer-vision/docker/Dockerfile b/machine-learning/computer-vision/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/computer-vision/docker/Dockerfile
+++ b/machine-learning/computer-vision/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/computer-vision/environment.yaml b/machine-learning/computer-vision/environment.yaml
index 1032919..1161771 100644
--- a/machine-learning/computer-vision/environment.yaml
+++ b/machine-learning/computer-vision/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-computer-vision-v1
runtimes:
- environmentVersionId: showcase-computer-vision-v1
- id: local
+ id: showcase-computer-vision-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-computer-vision-local-docker
environmentVersionId: showcase-computer-vision-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-computer-vision-local-docker, runtimeId: showcase-computer-vision-local, enabled: true}
diff --git a/machine-learning/computer-vision/run.sh b/machine-learning/computer-vision/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/computer-vision/run.sh
+++ b/machine-learning/computer-vision/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/computer-vision/screenshots/execution.png b/machine-learning/computer-vision/screenshots/execution.png
index f79d52e..4b6d520 100644
Binary files a/machine-learning/computer-vision/screenshots/execution.png and b/machine-learning/computer-vision/screenshots/execution.png differ
diff --git a/machine-learning/computer-vision/screenshots/generated-files.png b/machine-learning/computer-vision/screenshots/generated-files.png
new file mode 100644
index 0000000..7059cfa
Binary files /dev/null and b/machine-learning/computer-vision/screenshots/generated-files.png differ
diff --git a/machine-learning/computer-vision/screenshots/outputs.png b/machine-learning/computer-vision/screenshots/outputs.png
index a67ff33..c19f9be 100644
Binary files a/machine-learning/computer-vision/screenshots/outputs.png and b/machine-learning/computer-vision/screenshots/outputs.png differ
diff --git a/machine-learning/computer-vision/screenshots/workflow.png b/machine-learning/computer-vision/screenshots/workflow.png
index bee7144..ea1c813 100644
Binary files a/machine-learning/computer-vision/screenshots/workflow.png and b/machine-learning/computer-vision/screenshots/workflow.png differ
diff --git a/machine-learning/computer-vision/scripts/capture_screenshots.mjs b/machine-learning/computer-vision/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/computer-vision/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/computer-vision/scripts/validate.py b/machine-learning/computer-vision/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/computer-vision/scripts/validate.py
+++ b/machine-learning/computer-vision/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/computer-vision/workflow.yaml b/machine-learning/computer-vision/workflow.yaml
index 17900a4..76b1ebb 100644
--- a/machine-learning/computer-vision/workflow.yaml
+++ b/machine-learning/computer-vision/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-computer-vision:v1
activities:
- name: augment
- runtime: local
+ runtime: showcase-computer-vision-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "augment", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: train
- runtime: local
+ runtime: showcase-computer-vision-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "train", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- augment
- name: evaluate
- runtime: local
+ runtime: showcase-computer-vision-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "evaluate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/hyperparameter-search/README.md b/machine-learning/hyperparameter-search/README.md
index a1bbcb0..b2d721d 100644
--- a/machine-learning/hyperparameter-search/README.md
+++ b/machine-learning/hyperparameter-search/README.md
@@ -1,6 +1,6 @@
# Hyperparameter Search
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-hyperparameter-search:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-hyperparameter-search:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/hyperparameter-search/docker/Dockerfile b/machine-learning/hyperparameter-search/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/hyperparameter-search/docker/Dockerfile
+++ b/machine-learning/hyperparameter-search/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/hyperparameter-search/environment.yaml b/machine-learning/hyperparameter-search/environment.yaml
index c307ca3..b22b0a1 100644
--- a/machine-learning/hyperparameter-search/environment.yaml
+++ b/machine-learning/hyperparameter-search/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-hyperparameter-search-v1
runtimes:
- environmentVersionId: showcase-hyperparameter-search-v1
- id: local
+ id: showcase-hyperparameter-search-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-hyperparameter-search-local-docker
environmentVersionId: showcase-hyperparameter-search-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-hyperparameter-search-local-docker, runtimeId: showcase-hyperparameter-search-local, enabled: true}
diff --git a/machine-learning/hyperparameter-search/run.sh b/machine-learning/hyperparameter-search/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/hyperparameter-search/run.sh
+++ b/machine-learning/hyperparameter-search/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/hyperparameter-search/screenshots/execution.png b/machine-learning/hyperparameter-search/screenshots/execution.png
index eb444ae..4102306 100644
Binary files a/machine-learning/hyperparameter-search/screenshots/execution.png and b/machine-learning/hyperparameter-search/screenshots/execution.png differ
diff --git a/machine-learning/hyperparameter-search/screenshots/generated-files.png b/machine-learning/hyperparameter-search/screenshots/generated-files.png
new file mode 100644
index 0000000..185d841
Binary files /dev/null and b/machine-learning/hyperparameter-search/screenshots/generated-files.png differ
diff --git a/machine-learning/hyperparameter-search/screenshots/outputs.png b/machine-learning/hyperparameter-search/screenshots/outputs.png
index dc313e3..de6466f 100644
Binary files a/machine-learning/hyperparameter-search/screenshots/outputs.png and b/machine-learning/hyperparameter-search/screenshots/outputs.png differ
diff --git a/machine-learning/hyperparameter-search/screenshots/workflow.png b/machine-learning/hyperparameter-search/screenshots/workflow.png
index 7275ebf..d5e3657 100644
Binary files a/machine-learning/hyperparameter-search/screenshots/workflow.png and b/machine-learning/hyperparameter-search/screenshots/workflow.png differ
diff --git a/machine-learning/hyperparameter-search/scripts/capture_screenshots.mjs b/machine-learning/hyperparameter-search/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/hyperparameter-search/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/hyperparameter-search/scripts/validate.py b/machine-learning/hyperparameter-search/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/hyperparameter-search/scripts/validate.py
+++ b/machine-learning/hyperparameter-search/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/hyperparameter-search/workflow.yaml b/machine-learning/hyperparameter-search/workflow.yaml
index 06c4990..548411a 100644
--- a/machine-learning/hyperparameter-search/workflow.yaml
+++ b/machine-learning/hyperparameter-search/workflow.yaml
@@ -5,79 +5,71 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-hyperparameter-search:v1
activities:
- name: load
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "load", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: preprocess
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "preprocess", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- load
- name: rf-1
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "rf-1", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- preprocess
- name: rf-2
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "rf-2", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- rf-1
- name: rf-3
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "rf-3", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- rf-2
- name: rf-4
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "rf-4", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- rf-3
- name: compare
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "compare", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- rf-4
- name: best-model
- runtime: local
+ runtime: showcase-hyperparameter-search-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "best-model", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/inference-benchmark/README.md b/machine-learning/inference-benchmark/README.md
index df961b9..9d9bf0b 100644
--- a/machine-learning/inference-benchmark/README.md
+++ b/machine-learning/inference-benchmark/README.md
@@ -1,6 +1,6 @@
# Inference Benchmark
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-inference-benchmark:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-inference-benchmark:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/inference-benchmark/docker/Dockerfile b/machine-learning/inference-benchmark/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/inference-benchmark/docker/Dockerfile
+++ b/machine-learning/inference-benchmark/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/inference-benchmark/environment.yaml b/machine-learning/inference-benchmark/environment.yaml
index 235298f..aaa9daa 100644
--- a/machine-learning/inference-benchmark/environment.yaml
+++ b/machine-learning/inference-benchmark/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-inference-benchmark-v1
runtimes:
- environmentVersionId: showcase-inference-benchmark-v1
- id: local
+ id: showcase-inference-benchmark-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-inference-benchmark-local-docker
environmentVersionId: showcase-inference-benchmark-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-inference-benchmark-local-docker, runtimeId: showcase-inference-benchmark-local, enabled: true}
diff --git a/machine-learning/inference-benchmark/run.sh b/machine-learning/inference-benchmark/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/inference-benchmark/run.sh
+++ b/machine-learning/inference-benchmark/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/inference-benchmark/screenshots/execution.png b/machine-learning/inference-benchmark/screenshots/execution.png
index ac3786b..2d3aeca 100644
Binary files a/machine-learning/inference-benchmark/screenshots/execution.png and b/machine-learning/inference-benchmark/screenshots/execution.png differ
diff --git a/machine-learning/inference-benchmark/screenshots/generated-files.png b/machine-learning/inference-benchmark/screenshots/generated-files.png
new file mode 100644
index 0000000..128db5e
Binary files /dev/null and b/machine-learning/inference-benchmark/screenshots/generated-files.png differ
diff --git a/machine-learning/inference-benchmark/screenshots/outputs.png b/machine-learning/inference-benchmark/screenshots/outputs.png
index 27ec516..46995f1 100644
Binary files a/machine-learning/inference-benchmark/screenshots/outputs.png and b/machine-learning/inference-benchmark/screenshots/outputs.png differ
diff --git a/machine-learning/inference-benchmark/screenshots/workflow.png b/machine-learning/inference-benchmark/screenshots/workflow.png
index 5ea6c97..6aba329 100644
Binary files a/machine-learning/inference-benchmark/screenshots/workflow.png and b/machine-learning/inference-benchmark/screenshots/workflow.png differ
diff --git a/machine-learning/inference-benchmark/scripts/capture_screenshots.mjs b/machine-learning/inference-benchmark/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/inference-benchmark/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/inference-benchmark/scripts/validate.py b/machine-learning/inference-benchmark/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/inference-benchmark/scripts/validate.py
+++ b/machine-learning/inference-benchmark/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/inference-benchmark/workflow.yaml b/machine-learning/inference-benchmark/workflow.yaml
index 12b1ec4..9cbe127 100644
--- a/machine-learning/inference-benchmark/workflow.yaml
+++ b/machine-learning/inference-benchmark/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-inference-benchmark:v1
activities:
- name: cpu
- runtime: local
+ runtime: showcase-inference-benchmark-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "cpu", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: gpu
- runtime: local
+ runtime: showcase-inference-benchmark-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "gpu", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- cpu
- name: edge
- runtime: local
+ runtime: showcase-inference-benchmark-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "edge", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- gpu
- name: report
- runtime: local
+ runtime: showcase-inference-benchmark-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "report", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/infrastructure-selection/README.md b/machine-learning/infrastructure-selection/README.md
index 48f2f10..a2412ed 100644
--- a/machine-learning/infrastructure-selection/README.md
+++ b/machine-learning/infrastructure-selection/README.md
@@ -1,6 +1,6 @@
# Infrastructure Selection
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-infrastructure-selection:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-infrastructure-selection:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/infrastructure-selection/docker/Dockerfile b/machine-learning/infrastructure-selection/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/infrastructure-selection/docker/Dockerfile
+++ b/machine-learning/infrastructure-selection/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/infrastructure-selection/environment.yaml b/machine-learning/infrastructure-selection/environment.yaml
index a5073b2..119e01b 100644
--- a/machine-learning/infrastructure-selection/environment.yaml
+++ b/machine-learning/infrastructure-selection/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-infrastructure-selection-v1
runtimes:
- environmentVersionId: showcase-infrastructure-selection-v1
- id: local
+ id: showcase-infrastructure-selection-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-infrastructure-selection-local-docker
environmentVersionId: showcase-infrastructure-selection-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-infrastructure-selection-local-docker, runtimeId: showcase-infrastructure-selection-local, enabled: true}
diff --git a/machine-learning/infrastructure-selection/run.sh b/machine-learning/infrastructure-selection/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/infrastructure-selection/run.sh
+++ b/machine-learning/infrastructure-selection/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/infrastructure-selection/screenshots/execution.png b/machine-learning/infrastructure-selection/screenshots/execution.png
index 9bd7395..03637de 100644
Binary files a/machine-learning/infrastructure-selection/screenshots/execution.png and b/machine-learning/infrastructure-selection/screenshots/execution.png differ
diff --git a/machine-learning/infrastructure-selection/screenshots/generated-files.png b/machine-learning/infrastructure-selection/screenshots/generated-files.png
new file mode 100644
index 0000000..436b7a6
Binary files /dev/null and b/machine-learning/infrastructure-selection/screenshots/generated-files.png differ
diff --git a/machine-learning/infrastructure-selection/screenshots/outputs.png b/machine-learning/infrastructure-selection/screenshots/outputs.png
index 1c2a1cc..e7334c8 100644
Binary files a/machine-learning/infrastructure-selection/screenshots/outputs.png and b/machine-learning/infrastructure-selection/screenshots/outputs.png differ
diff --git a/machine-learning/infrastructure-selection/screenshots/workflow.png b/machine-learning/infrastructure-selection/screenshots/workflow.png
index e93a4b6..7e92a69 100644
Binary files a/machine-learning/infrastructure-selection/screenshots/workflow.png and b/machine-learning/infrastructure-selection/screenshots/workflow.png differ
diff --git a/machine-learning/infrastructure-selection/scripts/capture_screenshots.mjs b/machine-learning/infrastructure-selection/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/infrastructure-selection/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/infrastructure-selection/scripts/validate.py b/machine-learning/infrastructure-selection/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/infrastructure-selection/scripts/validate.py
+++ b/machine-learning/infrastructure-selection/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/infrastructure-selection/workflow.yaml b/machine-learning/infrastructure-selection/workflow.yaml
index 48895f8..cace704 100644
--- a/machine-learning/infrastructure-selection/workflow.yaml
+++ b/machine-learning/infrastructure-selection/workflow.yaml
@@ -5,39 +5,35 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-infrastructure-selection:v1
activities:
- name: estimate-local
- runtime: local
+ runtime: showcase-infrastructure-selection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "estimate-local", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: estimate-cloud
- runtime: local
+ runtime: showcase-infrastructure-selection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "estimate-cloud", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- estimate-local
- name: estimate-hpc
- runtime: local
+ runtime: showcase-infrastructure-selection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "estimate-hpc", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- estimate-cloud
- name: decision
- runtime: local
+ runtime: showcase-infrastructure-selection-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "decision", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/lora-fine-tuning/README.md b/machine-learning/lora-fine-tuning/README.md
index 8497dcf..ff7c425 100644
--- a/machine-learning/lora-fine-tuning/README.md
+++ b/machine-learning/lora-fine-tuning/README.md
@@ -1,6 +1,6 @@
# LoRA Fine Tuning
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-lora-fine-tuning:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-lora-fine-tuning:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/lora-fine-tuning/docker/Dockerfile b/machine-learning/lora-fine-tuning/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/lora-fine-tuning/docker/Dockerfile
+++ b/machine-learning/lora-fine-tuning/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/lora-fine-tuning/environment.yaml b/machine-learning/lora-fine-tuning/environment.yaml
index e2acb64..dfc4281 100644
--- a/machine-learning/lora-fine-tuning/environment.yaml
+++ b/machine-learning/lora-fine-tuning/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-lora-fine-tuning-v1
runtimes:
- environmentVersionId: showcase-lora-fine-tuning-v1
- id: local
+ id: showcase-lora-fine-tuning-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-lora-fine-tuning-local-docker
environmentVersionId: showcase-lora-fine-tuning-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-lora-fine-tuning-local-docker, runtimeId: showcase-lora-fine-tuning-local, enabled: true}
diff --git a/machine-learning/lora-fine-tuning/run.sh b/machine-learning/lora-fine-tuning/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/lora-fine-tuning/run.sh
+++ b/machine-learning/lora-fine-tuning/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/lora-fine-tuning/screenshots/execution.png b/machine-learning/lora-fine-tuning/screenshots/execution.png
index 21f50d7..31190c0 100644
Binary files a/machine-learning/lora-fine-tuning/screenshots/execution.png and b/machine-learning/lora-fine-tuning/screenshots/execution.png differ
diff --git a/machine-learning/lora-fine-tuning/screenshots/generated-files.png b/machine-learning/lora-fine-tuning/screenshots/generated-files.png
new file mode 100644
index 0000000..4082585
Binary files /dev/null and b/machine-learning/lora-fine-tuning/screenshots/generated-files.png differ
diff --git a/machine-learning/lora-fine-tuning/screenshots/outputs.png b/machine-learning/lora-fine-tuning/screenshots/outputs.png
index 7fbf766..9fb1ad7 100644
Binary files a/machine-learning/lora-fine-tuning/screenshots/outputs.png and b/machine-learning/lora-fine-tuning/screenshots/outputs.png differ
diff --git a/machine-learning/lora-fine-tuning/screenshots/workflow.png b/machine-learning/lora-fine-tuning/screenshots/workflow.png
index 5d7a26f..d32bf92 100644
Binary files a/machine-learning/lora-fine-tuning/screenshots/workflow.png and b/machine-learning/lora-fine-tuning/screenshots/workflow.png differ
diff --git a/machine-learning/lora-fine-tuning/scripts/capture_screenshots.mjs b/machine-learning/lora-fine-tuning/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/lora-fine-tuning/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/lora-fine-tuning/scripts/validate.py b/machine-learning/lora-fine-tuning/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/lora-fine-tuning/scripts/validate.py
+++ b/machine-learning/lora-fine-tuning/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/lora-fine-tuning/workflow.yaml b/machine-learning/lora-fine-tuning/workflow.yaml
index eecd9a7..c2face8 100644
--- a/machine-learning/lora-fine-tuning/workflow.yaml
+++ b/machine-learning/lora-fine-tuning/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-lora-fine-tuning:v1
activities:
- name: prepare
- runtime: local
+ runtime: showcase-lora-fine-tuning-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "prepare", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: finetune
- runtime: local
+ runtime: showcase-lora-fine-tuning-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "finetune", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- prepare
- name: evaluate
- runtime: local
+ runtime: showcase-lora-fine-tuning-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "evaluate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/model-comparison/README.md b/machine-learning/model-comparison/README.md
index a2092e3..5a87668 100644
--- a/machine-learning/model-comparison/README.md
+++ b/machine-learning/model-comparison/README.md
@@ -1,6 +1,6 @@
# Model Comparison
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-model-comparison:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-model-comparison:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/model-comparison/docker/Dockerfile b/machine-learning/model-comparison/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/model-comparison/docker/Dockerfile
+++ b/machine-learning/model-comparison/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/model-comparison/environment.yaml b/machine-learning/model-comparison/environment.yaml
index b661410..9db46c1 100644
--- a/machine-learning/model-comparison/environment.yaml
+++ b/machine-learning/model-comparison/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-model-comparison-v1
runtimes:
- environmentVersionId: showcase-model-comparison-v1
- id: local
+ id: showcase-model-comparison-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-model-comparison-local-docker
environmentVersionId: showcase-model-comparison-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-model-comparison-local-docker, runtimeId: showcase-model-comparison-local, enabled: true}
diff --git a/machine-learning/model-comparison/run.sh b/machine-learning/model-comparison/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/model-comparison/run.sh
+++ b/machine-learning/model-comparison/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/model-comparison/screenshots/execution.png b/machine-learning/model-comparison/screenshots/execution.png
index 3063300..41828a4 100644
Binary files a/machine-learning/model-comparison/screenshots/execution.png and b/machine-learning/model-comparison/screenshots/execution.png differ
diff --git a/machine-learning/model-comparison/screenshots/generated-files.png b/machine-learning/model-comparison/screenshots/generated-files.png
new file mode 100644
index 0000000..0f1baa1
Binary files /dev/null and b/machine-learning/model-comparison/screenshots/generated-files.png differ
diff --git a/machine-learning/model-comparison/screenshots/outputs.png b/machine-learning/model-comparison/screenshots/outputs.png
index a21ed72..0fa8181 100644
Binary files a/machine-learning/model-comparison/screenshots/outputs.png and b/machine-learning/model-comparison/screenshots/outputs.png differ
diff --git a/machine-learning/model-comparison/screenshots/workflow.png b/machine-learning/model-comparison/screenshots/workflow.png
index 3b07f00..89880c9 100644
Binary files a/machine-learning/model-comparison/screenshots/workflow.png and b/machine-learning/model-comparison/screenshots/workflow.png differ
diff --git a/machine-learning/model-comparison/scripts/capture_screenshots.mjs b/machine-learning/model-comparison/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/model-comparison/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/model-comparison/scripts/validate.py b/machine-learning/model-comparison/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/model-comparison/scripts/validate.py
+++ b/machine-learning/model-comparison/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/model-comparison/workflow.yaml b/machine-learning/model-comparison/workflow.yaml
index f63bc51..81172fb 100644
--- a/machine-learning/model-comparison/workflow.yaml
+++ b/machine-learning/model-comparison/workflow.yaml
@@ -5,49 +5,44 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-model-comparison:v1
activities:
- name: dataset
- runtime: local
+ runtime: showcase-model-comparison-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "dataset", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: model-a
- runtime: local
+ runtime: showcase-model-comparison-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "model-a", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- dataset
- name: model-b
- runtime: local
+ runtime: showcase-model-comparison-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "model-b", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- model-a
- name: model-c
- runtime: local
+ runtime: showcase-model-comparison-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "model-c", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- model-b
- name: comparison
- runtime: local
+ runtime: showcase-model-comparison-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "comparison", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/model-conversion/README.md b/machine-learning/model-conversion/README.md
index 9c92357..fbef808 100644
--- a/machine-learning/model-conversion/README.md
+++ b/machine-learning/model-conversion/README.md
@@ -1,6 +1,6 @@
# Model Conversion
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-model-conversion:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-model-conversion:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/model-conversion/docker/Dockerfile b/machine-learning/model-conversion/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/model-conversion/docker/Dockerfile
+++ b/machine-learning/model-conversion/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/model-conversion/environment.yaml b/machine-learning/model-conversion/environment.yaml
index bd66985..3f97f52 100644
--- a/machine-learning/model-conversion/environment.yaml
+++ b/machine-learning/model-conversion/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-model-conversion-v1
runtimes:
- environmentVersionId: showcase-model-conversion-v1
- id: local
+ id: showcase-model-conversion-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-model-conversion-local-docker
environmentVersionId: showcase-model-conversion-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-model-conversion-local-docker, runtimeId: showcase-model-conversion-local, enabled: true}
diff --git a/machine-learning/model-conversion/run.sh b/machine-learning/model-conversion/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/model-conversion/run.sh
+++ b/machine-learning/model-conversion/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/model-conversion/screenshots/execution.png b/machine-learning/model-conversion/screenshots/execution.png
index ef6823d..777d61a 100644
Binary files a/machine-learning/model-conversion/screenshots/execution.png and b/machine-learning/model-conversion/screenshots/execution.png differ
diff --git a/machine-learning/model-conversion/screenshots/generated-files.png b/machine-learning/model-conversion/screenshots/generated-files.png
new file mode 100644
index 0000000..fb617de
Binary files /dev/null and b/machine-learning/model-conversion/screenshots/generated-files.png differ
diff --git a/machine-learning/model-conversion/screenshots/outputs.png b/machine-learning/model-conversion/screenshots/outputs.png
index e03cbdf..91a2db9 100644
Binary files a/machine-learning/model-conversion/screenshots/outputs.png and b/machine-learning/model-conversion/screenshots/outputs.png differ
diff --git a/machine-learning/model-conversion/screenshots/workflow.png b/machine-learning/model-conversion/screenshots/workflow.png
index 4868e36..9d3dad1 100644
Binary files a/machine-learning/model-conversion/screenshots/workflow.png and b/machine-learning/model-conversion/screenshots/workflow.png differ
diff --git a/machine-learning/model-conversion/scripts/capture_screenshots.mjs b/machine-learning/model-conversion/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/model-conversion/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/model-conversion/scripts/validate.py b/machine-learning/model-conversion/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/model-conversion/scripts/validate.py
+++ b/machine-learning/model-conversion/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/model-conversion/workflow.yaml b/machine-learning/model-conversion/workflow.yaml
index a22a2e9..579bf1f 100644
--- a/machine-learning/model-conversion/workflow.yaml
+++ b/machine-learning/model-conversion/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-model-conversion:v1
activities:
- name: onnx
- runtime: local
+ runtime: showcase-model-conversion-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "onnx", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: gguf
- runtime: local
+ runtime: showcase-model-conversion-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "gguf", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- onnx
- name: tensorrt
- runtime: local
+ runtime: showcase-model-conversion-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "tensorrt", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/model-distillation/README.md b/machine-learning/model-distillation/README.md
index 60f1f51..f2daa5a 100644
--- a/machine-learning/model-distillation/README.md
+++ b/machine-learning/model-distillation/README.md
@@ -1,6 +1,6 @@
# Model Distillation
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-model-distillation:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-model-distillation:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/model-distillation/docker/Dockerfile b/machine-learning/model-distillation/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/model-distillation/docker/Dockerfile
+++ b/machine-learning/model-distillation/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/model-distillation/environment.yaml b/machine-learning/model-distillation/environment.yaml
index c69c114..855d827 100644
--- a/machine-learning/model-distillation/environment.yaml
+++ b/machine-learning/model-distillation/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-model-distillation-v1
runtimes:
- environmentVersionId: showcase-model-distillation-v1
- id: local
+ id: showcase-model-distillation-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-model-distillation-local-docker
environmentVersionId: showcase-model-distillation-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-model-distillation-local-docker, runtimeId: showcase-model-distillation-local, enabled: true}
diff --git a/machine-learning/model-distillation/run.sh b/machine-learning/model-distillation/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/model-distillation/run.sh
+++ b/machine-learning/model-distillation/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/model-distillation/screenshots/execution.png b/machine-learning/model-distillation/screenshots/execution.png
index dc88e51..569a285 100644
Binary files a/machine-learning/model-distillation/screenshots/execution.png and b/machine-learning/model-distillation/screenshots/execution.png differ
diff --git a/machine-learning/model-distillation/screenshots/generated-files.png b/machine-learning/model-distillation/screenshots/generated-files.png
new file mode 100644
index 0000000..b9ca214
Binary files /dev/null and b/machine-learning/model-distillation/screenshots/generated-files.png differ
diff --git a/machine-learning/model-distillation/screenshots/outputs.png b/machine-learning/model-distillation/screenshots/outputs.png
index a5ef8eb..1125ce9 100644
Binary files a/machine-learning/model-distillation/screenshots/outputs.png and b/machine-learning/model-distillation/screenshots/outputs.png differ
diff --git a/machine-learning/model-distillation/screenshots/workflow.png b/machine-learning/model-distillation/screenshots/workflow.png
index cb7b430..fe6c115 100644
Binary files a/machine-learning/model-distillation/screenshots/workflow.png and b/machine-learning/model-distillation/screenshots/workflow.png differ
diff --git a/machine-learning/model-distillation/scripts/capture_screenshots.mjs b/machine-learning/model-distillation/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/model-distillation/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/model-distillation/scripts/validate.py b/machine-learning/model-distillation/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/model-distillation/scripts/validate.py
+++ b/machine-learning/model-distillation/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/model-distillation/workflow.yaml b/machine-learning/model-distillation/workflow.yaml
index c1b7aa0..992587f 100644
--- a/machine-learning/model-distillation/workflow.yaml
+++ b/machine-learning/model-distillation/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-model-distillation:v1
activities:
- name: teacher
- runtime: local
+ runtime: showcase-model-distillation-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "teacher", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: generate-labels
- runtime: local
+ runtime: showcase-model-distillation-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "generate-labels", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- teacher
- name: student
- runtime: local
+ runtime: showcase-model-distillation-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "student", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/model-ensemble/README.md b/machine-learning/model-ensemble/README.md
index b45d3ed..e2c2358 100644
--- a/machine-learning/model-ensemble/README.md
+++ b/machine-learning/model-ensemble/README.md
@@ -1,6 +1,6 @@
# Model Ensemble
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-model-ensemble:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-model-ensemble:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/model-ensemble/docker/Dockerfile b/machine-learning/model-ensemble/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/model-ensemble/docker/Dockerfile
+++ b/machine-learning/model-ensemble/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/model-ensemble/environment.yaml b/machine-learning/model-ensemble/environment.yaml
index b916782..06ae420 100644
--- a/machine-learning/model-ensemble/environment.yaml
+++ b/machine-learning/model-ensemble/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-model-ensemble-v1
runtimes:
- environmentVersionId: showcase-model-ensemble-v1
- id: local
+ id: showcase-model-ensemble-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-model-ensemble-local-docker
environmentVersionId: showcase-model-ensemble-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-model-ensemble-local-docker, runtimeId: showcase-model-ensemble-local, enabled: true}
diff --git a/machine-learning/model-ensemble/run.sh b/machine-learning/model-ensemble/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/model-ensemble/run.sh
+++ b/machine-learning/model-ensemble/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/model-ensemble/screenshots/execution.png b/machine-learning/model-ensemble/screenshots/execution.png
index 44a9c12..48d5d9a 100644
Binary files a/machine-learning/model-ensemble/screenshots/execution.png and b/machine-learning/model-ensemble/screenshots/execution.png differ
diff --git a/machine-learning/model-ensemble/screenshots/generated-files.png b/machine-learning/model-ensemble/screenshots/generated-files.png
new file mode 100644
index 0000000..827956e
Binary files /dev/null and b/machine-learning/model-ensemble/screenshots/generated-files.png differ
diff --git a/machine-learning/model-ensemble/screenshots/outputs.png b/machine-learning/model-ensemble/screenshots/outputs.png
index d34400a..21edb12 100644
Binary files a/machine-learning/model-ensemble/screenshots/outputs.png and b/machine-learning/model-ensemble/screenshots/outputs.png differ
diff --git a/machine-learning/model-ensemble/screenshots/workflow.png b/machine-learning/model-ensemble/screenshots/workflow.png
index 4cc4491..f306617 100644
Binary files a/machine-learning/model-ensemble/screenshots/workflow.png and b/machine-learning/model-ensemble/screenshots/workflow.png differ
diff --git a/machine-learning/model-ensemble/scripts/capture_screenshots.mjs b/machine-learning/model-ensemble/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/model-ensemble/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/model-ensemble/scripts/validate.py b/machine-learning/model-ensemble/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/model-ensemble/scripts/validate.py
+++ b/machine-learning/model-ensemble/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/model-ensemble/workflow.yaml b/machine-learning/model-ensemble/workflow.yaml
index f1cd91a..0d113e8 100644
--- a/machine-learning/model-ensemble/workflow.yaml
+++ b/machine-learning/model-ensemble/workflow.yaml
@@ -5,49 +5,44 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-model-ensemble:v1
activities:
- name: dataset
- runtime: local
+ runtime: showcase-model-ensemble-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "dataset", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: rf
- runtime: local
+ runtime: showcase-model-ensemble-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "rf", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- dataset
- name: svm
- runtime: local
+ runtime: showcase-model-ensemble-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "svm", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- rf
- name: xgb
- runtime: local
+ runtime: showcase-model-ensemble-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "xgb", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- svm
- name: ensemble
- runtime: local
+ runtime: showcase-model-ensemble-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "ensemble", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/machine-learning/training-with-mlflow/README.md b/machine-learning/training-with-mlflow/README.md
index 8beaf0e..ea55a9c 100644
--- a/machine-learning/training-with-mlflow/README.md
+++ b/machine-learning/training-with-mlflow/README.md
@@ -1,6 +1,6 @@
# Training with MLflow
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-training-with-mlflow:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-training-with-mlflow:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/machine-learning/training-with-mlflow/docker/Dockerfile b/machine-learning/training-with-mlflow/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/machine-learning/training-with-mlflow/docker/Dockerfile
+++ b/machine-learning/training-with-mlflow/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/machine-learning/training-with-mlflow/environment.yaml b/machine-learning/training-with-mlflow/environment.yaml
index 52d5082..15f59dd 100644
--- a/machine-learning/training-with-mlflow/environment.yaml
+++ b/machine-learning/training-with-mlflow/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-training-with-mlflow-v1
runtimes:
- environmentVersionId: showcase-training-with-mlflow-v1
- id: local
+ id: showcase-training-with-mlflow-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-training-with-mlflow-local-docker
environmentVersionId: showcase-training-with-mlflow-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-training-with-mlflow-local-docker, runtimeId: showcase-training-with-mlflow-local, enabled: true}
diff --git a/machine-learning/training-with-mlflow/run.sh b/machine-learning/training-with-mlflow/run.sh
index 9ec2ebc..e44c741 100755
--- a/machine-learning/training-with-mlflow/run.sh
+++ b/machine-learning/training-with-mlflow/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/machine-learning/training-with-mlflow/screenshots/execution.png b/machine-learning/training-with-mlflow/screenshots/execution.png
index 31b135a..b6d66b0 100644
Binary files a/machine-learning/training-with-mlflow/screenshots/execution.png and b/machine-learning/training-with-mlflow/screenshots/execution.png differ
diff --git a/machine-learning/training-with-mlflow/screenshots/generated-files.png b/machine-learning/training-with-mlflow/screenshots/generated-files.png
new file mode 100644
index 0000000..65603b8
Binary files /dev/null and b/machine-learning/training-with-mlflow/screenshots/generated-files.png differ
diff --git a/machine-learning/training-with-mlflow/screenshots/outputs.png b/machine-learning/training-with-mlflow/screenshots/outputs.png
index 3be73cc..eda66d6 100644
Binary files a/machine-learning/training-with-mlflow/screenshots/outputs.png and b/machine-learning/training-with-mlflow/screenshots/outputs.png differ
diff --git a/machine-learning/training-with-mlflow/screenshots/workflow.png b/machine-learning/training-with-mlflow/screenshots/workflow.png
index 0f7ac39..9259c97 100644
Binary files a/machine-learning/training-with-mlflow/screenshots/workflow.png and b/machine-learning/training-with-mlflow/screenshots/workflow.png differ
diff --git a/machine-learning/training-with-mlflow/scripts/capture_screenshots.mjs b/machine-learning/training-with-mlflow/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/machine-learning/training-with-mlflow/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/machine-learning/training-with-mlflow/scripts/validate.py b/machine-learning/training-with-mlflow/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/machine-learning/training-with-mlflow/scripts/validate.py
+++ b/machine-learning/training-with-mlflow/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/machine-learning/training-with-mlflow/workflow.yaml b/machine-learning/training-with-mlflow/workflow.yaml
index b4745b1..8aacbd7 100644
--- a/machine-learning/training-with-mlflow/workflow.yaml
+++ b/machine-learning/training-with-mlflow/workflow.yaml
@@ -5,49 +5,44 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-training-with-mlflow:v1
activities:
- name: load-data
- runtime: local
+ runtime: showcase-training-with-mlflow-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "load-data", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: preprocess
- runtime: local
+ runtime: showcase-training-with-mlflow-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "preprocess", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- load-data
- name: train
- runtime: local
+ runtime: showcase-training-with-mlflow-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "train", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- preprocess
- name: evaluate
- runtime: local
+ runtime: showcase-training-with-mlflow-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "evaluate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- train
- name: register-model
- runtime: local
+ runtime: showcase-training-with-mlflow-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "register-model", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/scientific-ai/ai-assisted-analysis/README.md b/scientific-ai/ai-assisted-analysis/README.md
index e58270a..3ff39e9 100644
--- a/scientific-ai/ai-assisted-analysis/README.md
+++ b/scientific-ai/ai-assisted-analysis/README.md
@@ -1,6 +1,6 @@
# AI Assisted Analysis
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-ai-assisted-analysis:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-ai-assisted-analysis:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/scientific-ai/ai-assisted-analysis/docker/Dockerfile b/scientific-ai/ai-assisted-analysis/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/scientific-ai/ai-assisted-analysis/docker/Dockerfile
+++ b/scientific-ai/ai-assisted-analysis/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/scientific-ai/ai-assisted-analysis/environment.yaml b/scientific-ai/ai-assisted-analysis/environment.yaml
index 27b08cb..189b807 100644
--- a/scientific-ai/ai-assisted-analysis/environment.yaml
+++ b/scientific-ai/ai-assisted-analysis/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-ai-assisted-analysis-v1
runtimes:
- environmentVersionId: showcase-ai-assisted-analysis-v1
- id: local
+ id: showcase-ai-assisted-analysis-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-ai-assisted-analysis-local-docker
environmentVersionId: showcase-ai-assisted-analysis-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-ai-assisted-analysis-local-docker, runtimeId: showcase-ai-assisted-analysis-local, enabled: true}
diff --git a/scientific-ai/ai-assisted-analysis/run.sh b/scientific-ai/ai-assisted-analysis/run.sh
index 9ec2ebc..e44c741 100755
--- a/scientific-ai/ai-assisted-analysis/run.sh
+++ b/scientific-ai/ai-assisted-analysis/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/scientific-ai/ai-assisted-analysis/screenshots/execution.png b/scientific-ai/ai-assisted-analysis/screenshots/execution.png
index ecc9281..5059b9e 100644
Binary files a/scientific-ai/ai-assisted-analysis/screenshots/execution.png and b/scientific-ai/ai-assisted-analysis/screenshots/execution.png differ
diff --git a/scientific-ai/ai-assisted-analysis/screenshots/generated-files.png b/scientific-ai/ai-assisted-analysis/screenshots/generated-files.png
new file mode 100644
index 0000000..da5c634
Binary files /dev/null and b/scientific-ai/ai-assisted-analysis/screenshots/generated-files.png differ
diff --git a/scientific-ai/ai-assisted-analysis/screenshots/outputs.png b/scientific-ai/ai-assisted-analysis/screenshots/outputs.png
index 6305221..86bf712 100644
Binary files a/scientific-ai/ai-assisted-analysis/screenshots/outputs.png and b/scientific-ai/ai-assisted-analysis/screenshots/outputs.png differ
diff --git a/scientific-ai/ai-assisted-analysis/screenshots/workflow.png b/scientific-ai/ai-assisted-analysis/screenshots/workflow.png
index 65ac61d..fc14185 100644
Binary files a/scientific-ai/ai-assisted-analysis/screenshots/workflow.png and b/scientific-ai/ai-assisted-analysis/screenshots/workflow.png differ
diff --git a/scientific-ai/ai-assisted-analysis/scripts/capture_screenshots.mjs b/scientific-ai/ai-assisted-analysis/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/scientific-ai/ai-assisted-analysis/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/scientific-ai/ai-assisted-analysis/scripts/validate.py b/scientific-ai/ai-assisted-analysis/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/scientific-ai/ai-assisted-analysis/scripts/validate.py
+++ b/scientific-ai/ai-assisted-analysis/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/scientific-ai/ai-assisted-analysis/workflow.yaml b/scientific-ai/ai-assisted-analysis/workflow.yaml
index 68a3101..e5a278e 100644
--- a/scientific-ai/ai-assisted-analysis/workflow.yaml
+++ b/scientific-ai/ai-assisted-analysis/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-ai-assisted-analysis:v1
activities:
- name: simulation
- runtime: local
+ runtime: showcase-ai-assisted-analysis-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "simulation", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: analysis
- runtime: local
+ runtime: showcase-ai-assisted-analysis-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "analysis", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- simulation
- name: llm-interpretation
- runtime: local
+ runtime: showcase-ai-assisted-analysis-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "llm-interpretation", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/scientific-ai/experiment-feedback-loop/README.md b/scientific-ai/experiment-feedback-loop/README.md
index 92c5944..0080618 100644
--- a/scientific-ai/experiment-feedback-loop/README.md
+++ b/scientific-ai/experiment-feedback-loop/README.md
@@ -1,6 +1,6 @@
# Experiment Feedback Loop
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-experiment-feedback-loop:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-experiment-feedback-loop:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/scientific-ai/experiment-feedback-loop/docker/Dockerfile b/scientific-ai/experiment-feedback-loop/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/scientific-ai/experiment-feedback-loop/docker/Dockerfile
+++ b/scientific-ai/experiment-feedback-loop/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/scientific-ai/experiment-feedback-loop/environment.yaml b/scientific-ai/experiment-feedback-loop/environment.yaml
index 452fe46..3cfb0d6 100644
--- a/scientific-ai/experiment-feedback-loop/environment.yaml
+++ b/scientific-ai/experiment-feedback-loop/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-experiment-feedback-loop-v1
runtimes:
- environmentVersionId: showcase-experiment-feedback-loop-v1
- id: local
+ id: showcase-experiment-feedback-loop-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-experiment-feedback-loop-local-docker
environmentVersionId: showcase-experiment-feedback-loop-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-experiment-feedback-loop-local-docker, runtimeId: showcase-experiment-feedback-loop-local, enabled: true}
diff --git a/scientific-ai/experiment-feedback-loop/run.sh b/scientific-ai/experiment-feedback-loop/run.sh
index 9ec2ebc..e44c741 100755
--- a/scientific-ai/experiment-feedback-loop/run.sh
+++ b/scientific-ai/experiment-feedback-loop/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/scientific-ai/experiment-feedback-loop/screenshots/execution.png b/scientific-ai/experiment-feedback-loop/screenshots/execution.png
index 90982d3..c4a7b79 100644
Binary files a/scientific-ai/experiment-feedback-loop/screenshots/execution.png and b/scientific-ai/experiment-feedback-loop/screenshots/execution.png differ
diff --git a/scientific-ai/experiment-feedback-loop/screenshots/generated-files.png b/scientific-ai/experiment-feedback-loop/screenshots/generated-files.png
new file mode 100644
index 0000000..ae3bc16
Binary files /dev/null and b/scientific-ai/experiment-feedback-loop/screenshots/generated-files.png differ
diff --git a/scientific-ai/experiment-feedback-loop/screenshots/outputs.png b/scientific-ai/experiment-feedback-loop/screenshots/outputs.png
index 91e80e1..2458f87 100644
Binary files a/scientific-ai/experiment-feedback-loop/screenshots/outputs.png and b/scientific-ai/experiment-feedback-loop/screenshots/outputs.png differ
diff --git a/scientific-ai/experiment-feedback-loop/screenshots/workflow.png b/scientific-ai/experiment-feedback-loop/screenshots/workflow.png
index 97b1b45..8e006be 100644
Binary files a/scientific-ai/experiment-feedback-loop/screenshots/workflow.png and b/scientific-ai/experiment-feedback-loop/screenshots/workflow.png differ
diff --git a/scientific-ai/experiment-feedback-loop/scripts/capture_screenshots.mjs b/scientific-ai/experiment-feedback-loop/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/scientific-ai/experiment-feedback-loop/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/scientific-ai/experiment-feedback-loop/scripts/validate.py b/scientific-ai/experiment-feedback-loop/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/scientific-ai/experiment-feedback-loop/scripts/validate.py
+++ b/scientific-ai/experiment-feedback-loop/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/scientific-ai/experiment-feedback-loop/workflow.yaml b/scientific-ai/experiment-feedback-loop/workflow.yaml
index ad0dee1..35f20d4 100644
--- a/scientific-ai/experiment-feedback-loop/workflow.yaml
+++ b/scientific-ai/experiment-feedback-loop/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-experiment-feedback-loop:v1
activities:
- name: experiment
- runtime: local
+ runtime: showcase-experiment-feedback-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "experiment", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: model
- runtime: local
+ runtime: showcase-experiment-feedback-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "model", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- experiment
- name: next-batch
- runtime: local
+ runtime: showcase-experiment-feedback-loop-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "next-batch", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
diff --git a/scientific-ai/surrogate-model/README.md b/scientific-ai/surrogate-model/README.md
index 708accb..8498abb 100644
--- a/scientific-ai/surrogate-model/README.md
+++ b/scientific-ai/surrogate-model/README.md
@@ -1,6 +1,6 @@
# Surrogate Model
-A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, captures three browser screenshots, and validates its output contract.
+A reproducible AkôFlow showcase. It executes a real Docker image locally, writes deterministic artifacts, validates its output contract, and includes four versioned screenshots captured from a completed AkôFlow engine run.
## Run
@@ -8,7 +8,78 @@ A reproducible AkôFlow showcase. It executes a real Docker image locally, write
./run.sh
```
-Requires Docker, Node.js, and a Playwright-compatible Chromium installation (the screenshot command provisions it when needed). The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
+Requires Python 3 for the standalone smoke test and Docker for execution through AkôFlow. The workflow graph is in [workflow.yaml](./workflow.yaml); the expected contract is in [expected/manifest.json](./expected/manifest.json).
-Published image: `ghcr.io/uffescience/akoflow-showcase-surrogate-model:v1`.
+Published image: `ghcr.io/uffescience/akoflow-examples/showcase-surrogate-model:v1`.
+
+## Run locally step by step
+
+### 1. Check the prerequisites
+
+Install Python 3, Docker, and a local AkôFlow server/Desktop. Set the local API connection, then confirm every prerequisite:
+
+```sh
+export AKOFLOW_API_URL=http://127.0.0.1:8080/akoflow-api
+export AKOFLOW_API_TOKEN=akoflow-development-token
+
+python3 --version
+docker version
+curl --fail -H "Authorization: Bearer $AKOFLOW_API_TOKEN" "$AKOFLOW_API_URL/health/"
+```
+
+### 2. Run the deterministic smoke test
+
+From this showcase directory:
+
+```sh
+./run.sh
+```
+
+The command recreates `outputs/`, runs the complete deterministic scenario, and validates every artifact declared in `expected/manifest.json`.
+
+### 3. Build the image used by the workflow
+
+```sh
+IMAGE=$(sed -n 's/^[[:space:]]*image: //p' workflow.yaml | head -n 1)
+docker build -f docker/Dockerfile -t "$IMAGE" .
+```
+
+The workflow command is authoritative. The image intentionally has no competing `ENTRYPOINT`.
+
+### 4. Register the local infrastructure and workflow
+
+```sh
+for item in "environments environment.yaml" "execution-scopes scope.yaml" "network-topologies topology.yaml" "workflow-definitions workflow.yaml"
+do
+ set -- $item
+ curl --fail-with-body -H "Authorization: Bearer $AKOFLOW_API_TOKEN" -H 'Content-Type: application/yaml' --data-binary "@$2" "$AKOFLOW_API_URL/$1/"
+done
+```
+
+The order matters: the execution scope references the environment version, the topology references the scope, and the workflow references its namespaced local runtime.
+
+### 5. Plan and execute in AkôFlow Desktop
+
+1. Open **Workflows → Definitions** and select this showcase.
+2. Choose **Generate plan**.
+3. Select the execution scope imported from `scope.yaml`.
+4. Run **HEFT** and select its feasible candidate.
+5. Choose **Execute plan** and confirm **Real execution**.
+6. Wait until the run is `completed` and every activity is settled.
+
+### 6. Verify the result
+
+Open the completed run and inspect:
+
+- **Activities** for the engine-rendered DAG and per-activity status;
+- **Workflow** for the observed execution lifecycle and timing decomposition;
+- **Data** for workspace transfers;
+- the final activity's **Generated files** table for file names, sizes, and SHA-256 checksums.
+
+The versioned evidence is stored in `screenshots/`:
+
+- `workflow.png`: completed activity DAG;
+- `execution.png`: observed execution lifecycle and timing decomposition;
+- `outputs.png`: data transfers between activity workspaces;
+- `generated-files.png`: files observed in the final output-producing activity.
diff --git a/scientific-ai/surrogate-model/docker/Dockerfile b/scientific-ai/surrogate-model/docker/Dockerfile
index 8c87e64..5c5afae 100644
--- a/scientific-ai/surrogate-model/docker/Dockerfile
+++ b/scientific-ai/surrogate-model/docker/Dockerfile
@@ -2,5 +2,4 @@ FROM python:3.12-slim
WORKDIR /app
COPY data/input.json scenario.json
COPY scripts/run_showcase.py /app/run_showcase.py
-ENTRYPOINT ["python", "/app/run_showcase.py"]
diff --git a/scientific-ai/surrogate-model/environment.yaml b/scientific-ai/surrogate-model/environment.yaml
index 52a7ea4..df86649 100644
--- a/scientific-ai/surrogate-model/environment.yaml
+++ b/scientific-ai/surrogate-model/environment.yaml
@@ -14,14 +14,14 @@ version:
configurationHash: showcase-surrogate-model-v1
runtimes:
- environmentVersionId: showcase-surrogate-model-v1
- id: local
+ id: showcase-surrogate-model-local
name: Local Docker host
driver: local
mode: execution
role: compute
capabilities: {batch: true, container: true}
resources:
- - id: local-docker
+ - id: showcase-surrogate-model-local-docker
environmentVersionId: showcase-surrogate-model-v1
type: machine
name: Local Docker host
@@ -39,5 +39,5 @@ storages: []
connections: []
activityResourceProfiles: []
resourceRuntimeBindings:
- - {resourceId: local-docker, runtimeId: local, enabled: true}
+ - {resourceId: showcase-surrogate-model-local-docker, runtimeId: showcase-surrogate-model-local, enabled: true}
diff --git a/scientific-ai/surrogate-model/run.sh b/scientific-ai/surrogate-model/run.sh
index 9ec2ebc..e44c741 100755
--- a/scientific-ai/surrogate-model/run.sh
+++ b/scientific-ai/surrogate-model/run.sh
@@ -1,8 +1,7 @@
#!/bin/sh
set -eu
root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
-rm -rf "$root/outputs" "$root/screenshots"
-mkdir -p "$root/outputs" "$root/screenshots"
+rm -rf "$root/outputs"
+mkdir -p "$root/outputs"
python3 "$root/scripts/run_showcase.py" --output "$root/outputs"
-node "$root/scripts/capture_screenshots.mjs"
"$root/validate.sh"
diff --git a/scientific-ai/surrogate-model/screenshots/execution.png b/scientific-ai/surrogate-model/screenshots/execution.png
index 2a4ec24..4d2057a 100644
Binary files a/scientific-ai/surrogate-model/screenshots/execution.png and b/scientific-ai/surrogate-model/screenshots/execution.png differ
diff --git a/scientific-ai/surrogate-model/screenshots/generated-files.png b/scientific-ai/surrogate-model/screenshots/generated-files.png
new file mode 100644
index 0000000..f64c127
Binary files /dev/null and b/scientific-ai/surrogate-model/screenshots/generated-files.png differ
diff --git a/scientific-ai/surrogate-model/screenshots/outputs.png b/scientific-ai/surrogate-model/screenshots/outputs.png
index 93bc8de..83d11fc 100644
Binary files a/scientific-ai/surrogate-model/screenshots/outputs.png and b/scientific-ai/surrogate-model/screenshots/outputs.png differ
diff --git a/scientific-ai/surrogate-model/screenshots/workflow.png b/scientific-ai/surrogate-model/screenshots/workflow.png
index 7d72180..c67adac 100644
Binary files a/scientific-ai/surrogate-model/screenshots/workflow.png and b/scientific-ai/surrogate-model/screenshots/workflow.png differ
diff --git a/scientific-ai/surrogate-model/scripts/capture_screenshots.mjs b/scientific-ai/surrogate-model/scripts/capture_screenshots.mjs
deleted file mode 100755
index 1e34a74..0000000
--- a/scientific-ai/surrogate-model/scripts/capture_screenshots.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env node
-// Generates browser screenshots after a successful Docker execution.
-import {execFileSync} from "node:child_process";
-import fs from "node:fs";
-import path from "node:path";
-import {fileURLToPath} from "node:url";
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const output = path.join(root, "outputs");
-const shots = path.join(root, "screenshots");
-fs.mkdirSync(shots, {recursive: true});
-const manifest = fs.readFileSync(path.join(output, "manifest.json"), "utf8");
-execFileSync("npx", ["--yes", "playwright@1.63.0", "install", "chromium"], {stdio: "inherit"});
-for (const view of ["workflow", "execution", "outputs"]) {
- const page = path.join(shots, view + ".html");
- fs.writeFileSync(page, "AkôFlow — " + view + "
" + manifest.replace(/");
- execFileSync("npx", ["--yes", "playwright@1.63.0", "screenshot", "--device=Desktop Chrome", "file://" + page, path.join(shots, view + ".png")], {stdio: "inherit"});
- fs.unlinkSync(page);
-}
diff --git a/scientific-ai/surrogate-model/scripts/validate.py b/scientific-ai/surrogate-model/scripts/validate.py
index f2854b0..ba1f608 100755
--- a/scientific-ai/surrogate-model/scripts/validate.py
+++ b/scientific-ai/surrogate-model/scripts/validate.py
@@ -12,7 +12,7 @@
missing = [name for name in expected["artifacts"] if not (root / "outputs" / name).is_file()]
if missing:
sys.exit("missing artifacts: " + ", ".join(missing))
-missing = [name for name in ("workflow.png", "execution.png", "outputs.png") if not (root / "screenshots" / name).is_file()]
+missing = [name for name in ("workflow.png", "execution.png", "outputs.png", "generated-files.png") if not (root / "screenshots" / name).is_file()]
if missing:
sys.exit("missing screenshots: " + ", ".join(missing))
print("validated " + expected["showcase"])
diff --git a/scientific-ai/surrogate-model/workflow.yaml b/scientific-ai/surrogate-model/workflow.yaml
index 7bb2430..96aa4b1 100644
--- a/scientific-ai/surrogate-model/workflow.yaml
+++ b/scientific-ai/surrogate-model/workflow.yaml
@@ -5,29 +5,26 @@ spec:
image: ghcr.io/uffescience/akoflow-examples/showcase-surrogate-model:v1
activities:
- name: simulation
- runtime: local
+ runtime: showcase-surrogate-model-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "simulation", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
- name: dataset
- runtime: local
+ runtime: showcase-surrogate-model-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "dataset", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn:
- simulation
- name: train-surrogate
- runtime: local
+ runtime: showcase-surrogate-model-local
command:
entrypoint: python
arguments: ["/app/run_showcase.py", "--stage", "train-surrogate", "--output", "."]
- workingDirectory: /workspace
cpuLimit: "1"
memoryLimit: 256Mi
dependsOn: