From 75941672188d693cf7049e672d18dd381df5bc34 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:18:51 +0200 Subject: [PATCH 01/41] docs: specify research clustering map --- ...26-09-02-research-clustering-map-design.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-02-research-clustering-map-design.md diff --git a/docs/superpowers/specs/2026-09-02-research-clustering-map-design.md b/docs/superpowers/specs/2026-09-02-research-clustering-map-design.md new file mode 100644 index 0000000..51a58aa --- /dev/null +++ b/docs/superpowers/specs/2026-09-02-research-clustering-map-design.md @@ -0,0 +1,57 @@ +# Research Clustering Map Design + +## Purpose + +Create a static, generated HTML map for the working-group workshop. The map will help participants cluster the repository's research notes and ideas without turning the provisional workshop view into a permanent taxonomy. + +## Data model + +Both `research/*.md` and `ideas/*.md` notes may contain reusable ratings in YAML frontmatter: + +```yaml +ratings: + platform-impact: + value: 80 + note: "Touches core runtime and governance concerns across many deployments." + maturity: + value: 55 + note: "Strong external prior art, but patterns are still evolving." +``` + +Each rating has a required integer `value` from 0 through 100 and a required, non-empty `note` justifying the score. Ratings are authored in the note and are not inferred or overwritten by generation. + +The initial rating pass will manually score all existing ideas and research notes for: + +- `platform-impact` +- `maturity` +- `novelty` +- `actionability` + +These are initial working-group judgments and must be labeled provisional in the generated page. + +## Plot model + +Plot definitions are separate from note ratings and identify axes by rating name. The initial plot is `platform-impact-maturity`: + +- x-axis: `maturity`, labeled Maturity, low to high +- y-axis: `platform-impact`, labeled Platform Impact, low to high + +The model must support future plots selecting any two existing or future rating names without changing the note schema. A note lacking either selected rating is unplaced for that plot and remains visible in an unplaced list. + +## Generator and output + +Add a generator under `scripts/` that reads all non-template Markdown notes, parses frontmatter, extracts a short summary, and emits a self-contained static HTML page. Research summaries come from `## Summary`; idea summaries come from `## The idea`; a first substantive paragraph is the fallback. + +The generated note data includes type, title, tags, summary, author/date when available, ratings and justifications, source path, and a canonical GitHub URL. The page embeds this data and uses lightweight vanilla HTML, CSS, and JavaScript. It requires no server-side runtime and can be hosted locally or on GitHub Pages. + +Provide a repository command to regenerate the page. Validation must fail clearly for malformed frontmatter, invalid rating objects or values, missing rating notes, and duplicate note identifiers. + +## Interaction and presentation + +Render one marker per placed idea or research note, with distinct visual treatment and a legend. Hover and keyboard focus expose the title and type. Clicking a marker opens an accessible overlay containing the title, type, tags, summary, author/date, ratings with numeric values and justifications, and the direct GitHub Markdown link. Escape closes the overlay. + +The page includes axis labels and explains that ratings and positions are provisional. On narrow screens the map becomes a taller, scrollable canvas rather than shrinking labels into unreadability. Unplaced notes are listed separately so new contributions cannot disappear from the workshop view. + +## Verification + +Tests cover frontmatter extraction, rating validation, coordinate derivation from selected rating values, summary extraction, GitHub URL generation, and representative marker/overlay interactions. The initial rating pass is reviewed for complete coverage and non-empty justifications before the page is considered ready. From 34d936557db0c939141e2881a34126bd25cfe1fc Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:19:54 +0200 Subject: [PATCH 02/41] docs: add research map implementation plan --- .../2026-09-02-research-clustering-map.md | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-02-research-clustering-map.md diff --git a/docs/superpowers/plans/2026-09-02-research-clustering-map.md b/docs/superpowers/plans/2026-09-02-research-clustering-map.md new file mode 100644 index 0000000..976cc79 --- /dev/null +++ b/docs/superpowers/plans/2026-09-02-research-clustering-map.md @@ -0,0 +1,255 @@ +# Research Clustering Map Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Generate a responsive static HTML quadrant map from the repository's ideas and research notes, with reusable frontmatter ratings, per-rating justifications, and clickable GitHub-linked detail overlays. + +**Architecture:** A Python generator will parse Markdown frontmatter and summaries, validate rating objects, derive plot coordinates from named ratings, and emit a self-contained HTML document. Plot definitions remain separate from note data so future plots can select different rating names. Existing notes receive a manual initial rating pass; the generator never infers or overwrites ratings. + +**Tech Stack:** Python 3.12, PyYAML, unittest or pytest-compatible tests, vanilla HTML/CSS/JavaScript. + +--- + +## Files and Responsibilities + +- Create `scripts/generate_research_map.py`: frontmatter parsing, summary extraction, rating validation, plot configuration, GitHub URL generation, and HTML generation. +- Create `scripts/research_map_plots.yaml`: named plot definitions, initially `platform-impact-maturity`. +- Create `tests/test_generate_research_map.py`: unit tests for parsing, validation, coordinate derivation, summaries, and URLs. +- Create `generated/research-map.html`: generated static artifact for local or GitHub Pages hosting. +- Modify every non-template file in `research/` and `ideas/`: add the four manually reviewed rating objects and justifications. +- Modify `research/TEMPLATE.md` and `ideas/TEMPLATE.md`: document the optional rating schema and example. +- Modify `research/README.md` and `ideas/README.md`: document rating meaning, 0-100 scale, and the map-generation command. +- Modify `devbox.json`: add a `map` script that runs the generator. +- Modify `.github/workflows/lint.yml` or the applicable workflow: run map generation/validation and tests if CI currently owns repository checks. + +### Task 1: Define the rating contract and test fixtures + +**Files:** +- Modify: `research/TEMPLATE.md` +- Modify: `ideas/TEMPLATE.md` +- Modify: `research/README.md` +- Modify: `ideas/README.md` +- Create: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Add the frontmatter example and field documentation** + +Document this exact shape in both templates/readmes: + +```yaml +ratings: + platform-impact: + value: 80 + note: "Touches core runtime and governance concerns across many deployments." + maturity: + value: 55 + note: "Strong external prior art, but patterns are still evolving." + novelty: + value: 70 + note: "Combines established capabilities in a comparatively new runtime context." + actionability: + value: 45 + note: "Needs further comparison before a focused POC can be selected." +``` + +State that values are provisional working-group judgments, must be integers from 0 through 100, and each value requires a non-empty note. + +- [ ] **Step 2: Write failing parser and rating tests** + +Add fixtures and tests for: + +```python +def test_parse_note_extracts_metadata_and_research_summary(): + note = parse_note(Path("research/example.md"), text) + assert note.title == "Example" + assert note.kind == "research" + assert note.summary == "A concise summary." + +def test_validate_ratings_rejects_out_of_range_value(): + with pytest.raises(ValueError, match="0..100"): + validate_ratings({"maturity": {"value": 101, "note": "reason"}}) + +def test_validate_ratings_requires_note(): + with pytest.raises(ValueError, match="non-empty note"): + validate_ratings({"maturity": {"value": 50, "note": ""}}) +``` + +Use temporary Markdown text in tests so parser behavior is isolated from current note contents. + +- [ ] **Step 3: Run the focused tests and verify they fail** + +Run: `python -m pytest tests/test_generate_research_map.py -q` + +Expected: FAIL because the generator module and parsing/validation functions do not yet exist. + +- [ ] **Step 4: Commit the contract and failing tests** + +Run: `git add research/TEMPLATE.md ideas/TEMPLATE.md research/README.md ideas/README.md tests/test_generate_research_map.py && git commit -m "test: define research map rating contract"` + +### Task 2: Implement parsing, validation, and plot coordinates + +**Files:** +- Create: `scripts/generate_research_map.py` +- Create: `scripts/research_map_plots.yaml` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Add the initial plot configuration** + +Create `scripts/research_map_plots.yaml` with: + +```yaml +platform-impact-maturity: + title: Platform Impact x Maturity + x: + rating: maturity + label: Maturity + low: Emerging + high: Established + y: + rating: platform-impact + label: Platform Impact + low: Local concern + high: Platform-wide concern +``` + +- [ ] **Step 2: Implement the smallest passing parser and validator** + +Implement typed note data and functions named `parse_frontmatter`, `extract_summary`, `validate_ratings`, `load_note`, and `derive_position`. Parse YAML between leading `---` delimiters, select `Summary` for research and `The idea` for ideas, strip Markdown formatting only as needed for a short plain-text preview, and use the first substantive paragraph as fallback. Reject malformed YAML, non-mapping rating entries, missing `value`/`note`, non-integer values, values outside `0..100`, and blank notes. `derive_position` must return `None` when either configured rating is absent and otherwise return the selected values as x/y percentages. + +- [ ] **Step 3: Add tests for plot derivation, fallback summaries, URLs, and malformed input** + +Add tests equivalent to: + +```python +def test_derive_position_maps_x_and_y_from_named_ratings(): + ratings = { + "maturity": {"value": 55, "note": "reason"}, + "platform-impact": {"value": 80, "note": "reason"}, + } + assert derive_position(ratings, plots["platform-impact-maturity"]) == {"x": 55, "y": 80} + +def test_missing_plot_rating_is_unplaced(): + assert derive_position({"maturity": {"value": 55, "note": "reason"}}, plot) is None + +def test_github_url_uses_canonical_repository_path(): + assert github_url(Path("research/langgraph.md")) == "https://github.com/cloudfoundry/agentic-runtime-notes/blob/main/research/langgraph.md" +``` + +- [ ] **Step 4: Run focused tests and repository validation** + +Run: `python -m pytest tests/test_generate_research_map.py -q && python .github/scripts/validate_notes.py` + +Expected: new focused tests PASS; existing note validation remains PASS before ratings are added because ratings are optional during this task. + +- [ ] **Step 5: Commit the generator core** + +Run: `git add scripts/research_map_plots.yaml scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "feat: add research map data model"` + +### Task 3: Manually rate every existing note + +**Files:** +- Modify: every `research/*.md` except `README.md` and `TEMPLATE.md` +- Modify: every `ideas/*.md` except `README.md` and `TEMPLATE.md` + +- [ ] **Step 1: Build the complete note inventory** + +Run: `python -c 'from pathlib import Path; print("\\n".join(str(p) for d in (Path("research"), Path("ideas")) for p in sorted(d.glob("*.md")) if p.name not in {"README.md", "TEMPLATE.md"}))'` + +Use each note's title, tags, summary/idea, findings, and open questions to make a deliberate judgment. Do not assign scores from filename order or silently omit a note. + +- [ ] **Step 2: Add four rating objects to each note** + +Add `platform-impact`, `maturity`, `novelty`, and `actionability` objects using integer values `0..100` and concise note-specific justifications. Keep existing frontmatter and body content unchanged apart from this addition. + +- [ ] **Step 3: Add a coverage test** + +Add a test that loads every current note and asserts all four rating names exist, every value is an integer in range, and every note is non-empty. This makes the initial rating pass explicit and prevents accidental omissions. + +- [ ] **Step 4: Run coverage and note validation** + +Run: `python -m pytest tests/test_generate_research_map.py -q && python .github/scripts/validate_notes.py` + +Expected: all rating coverage tests and existing repository validation PASS. + +- [ ] **Step 5: Commit the manual rating pass** + +Run: `git add research ideas tests/test_generate_research_map.py && git commit -m "feat: add initial research map ratings"` + +### Task 4: Generate the interactive static page + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Create: `generated/research-map.html` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing output and interaction assertions** + +Test generated HTML for the configured plot title, axis labels, note titles, distinct idea/research marker classes, canonical GitHub URLs, embedded rating notes, an accessible dialog/overlay, Escape handling, and an unplaced-note list. + +- [ ] **Step 2: Implement deterministic HTML generation** + +Generate a self-contained document with embedded JSON note data. Render x/y as percentages from the selected rating values, add a legend and provisional-rating notice, make markers buttons with accessible labels, and show a dialog containing summary, tags, author/date, all four ratings and notes, and the GitHub link. Render notes missing either selected rating in an unplaced list. Escape must close the dialog; clicking outside may also close it. Keep CSS responsive with a taller map canvas at narrow widths. + +- [ ] **Step 3: Generate and inspect the artifact** + +Run: `python scripts/generate_research_map.py` + +Expected: `generated/research-map.html` is created and contains all current notes, with no external runtime dependency. + +- [ ] **Step 4: Run all tests** + +Run: `python -m pytest -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the page and renderer** + +Run: `git add scripts/generate_research_map.py generated/research-map.html tests/test_generate_research_map.py && git commit -m "feat: generate interactive research map"` + +### Task 5: Wire commands and CI validation + +**Files:** +- Modify: `devbox.json` +- Modify: `.github/workflows/lint.yml` +- Modify: `scripts/generate_research_map.py` + +- [ ] **Step 1: Add the repository command** + +Add a Devbox script named `map` running `python scripts/generate_research_map.py`. The generator should support a `--check` option that regenerates in memory and exits non-zero if the checked-in HTML differs from the expected output. + +- [ ] **Step 2: Add CI checks** + +Extend the existing lint workflow to run the generator check, the note validator, and the test suite using the repository's existing Python/Devbox setup. Do not add a second workflow if the current one can own these checks. + +- [ ] **Step 3: Test normal and stale-artifact behavior** + +Run: `devbox run map && devbox run validate && python -m pytest -q` + +Then make a temporary change to the generated artifact, run `python scripts/generate_research_map.py --check`, and verify it fails; regenerate and verify it passes. Restore only the generated artifact through the generator, not with a destructive Git command. + +- [ ] **Step 4: Commit command and CI integration** + +Run: `git add devbox.json .github/workflows/lint.yml scripts/generate_research_map.py generated/research-map.html && git commit -m "ci: validate generated research map"` + +### Task 6: Final review of the workshop artifact + +**Files:** +- Review: `generated/research-map.html` +- Review: all rated notes and `scripts/research_map_plots.yaml` + +- [ ] **Step 1: Verify complete data coverage** + +Run: `python -m pytest -q && python .github/scripts/validate_notes.py && python scripts/generate_research_map.py --check` + +Expected: all commands PASS. + +- [ ] **Step 2: Check the page in a browser** + +Open `generated/research-map.html` through a static server, click representative idea and research markers, verify overlay contents and GitHub links, close with Escape, and inspect the narrow-screen layout. + +- [ ] **Step 3: Review score justifications** + +Confirm every note has four note-specific explanations, no rating is presented as objective fact, and the page clearly labels the scores as initial/provisional working-group judgments. + +- [ ] **Step 4: Commit any final generated artifact update** + +Run: `git status --short && git diff --check && git add generated/research-map.html && git commit -m "chore: refresh research map"` only if the generated artifact changed after the final review. From 47c0dcb4833002a65da7942548858b118c1f2c0b Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:30:32 +0200 Subject: [PATCH 03/41] feat: add interactive research clustering map --- .github/workflows/lint.yml | 12 + generated/research-map.html | 19 ++ ideas/TEMPLATE.md | 19 ++ ideas/agent-failure-checkpointing.md | 14 ++ .../agent-identity-and-tool-authorization.md | 14 ++ ideas/credential-less-agent-processes.md | 14 ++ ideas/dapr-aware-gorouter.md | 14 ++ ideas/dapr-durable-execution-on-cf.md | 14 ++ ideas/durable-tasks-for-cf.md | 14 ++ ideas/localhost-only-egress-for-agents.md | 14 ++ ideas/per-session-sandboxes.md | 14 ++ ideas/staged-sandbox-environments.md | 14 ++ .../stronger-workload-isolation-for-agents.md | 14 ++ research/TEMPLATE.md | 19 ++ research/a2a-protocol.md | 14 ++ research/anthropic-managed-agents.md | 14 ++ research/aws-agents.md | 14 ++ research/azure-hosted-agents.md | 14 ++ research/cloudflare-agents.md | 14 ++ research/crewai.md | 14 ++ research/dapr-agents.md | 14 ++ research/dapr.md | 14 ++ research/firecracker-microvm.md | 14 ++ research/google-adk.md | 14 ++ research/hatchet.md | 14 ++ research/heroku-ai-platform.md | 14 ++ research/k8s-agent-sandbox.md | 14 ++ research/kagent.md | 14 ++ research/keda.md | 14 ++ research/langgraph.md | 14 ++ research/letta.md | 14 ++ research/llamaindex.md | 14 ++ research/mcp-protocol.md | 14 ++ research/microsoft-agent-framework.md | 14 ++ research/open-agent-auth.md | 14 ++ research/openai-agents-sdk.md | 14 ++ research/opentelemetry-genai.md | 14 ++ research/orleans.md | 14 ++ research/tanzu-platform-ai.md | 14 ++ research/temporal.md | 14 ++ research/toolhive.md | 14 ++ research/vercel-ai-sdk.md | 14 ++ research/vertex-agent-engine.md | 14 ++ research/wasmcloud.md | 14 ++ scripts/generate_research_map.py | 207 ++++++++++++++++++ scripts/research_map_plots.yaml | 12 + tests/test_generate_research_map.py | 90 ++++++++ 47 files changed, 938 insertions(+) create mode 100644 generated/research-map.html create mode 100644 scripts/generate_research_map.py create mode 100644 scripts/research_map_plots.yaml create mode 100644 tests/test_generate_research_map.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cd1790a..18e6cbe 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,6 +6,9 @@ on: - "research/**" - "ideas/**" - ".github/scripts/validate_notes.py" + - "scripts/**" + - "generated/**" + - "tests/**" - ".github/workflows/lint.yml" push: branches: [main] @@ -13,6 +16,9 @@ on: - "research/**" - "ideas/**" - ".github/scripts/validate_notes.py" + - "scripts/**" + - "generated/**" + - "tests/**" - ".github/workflows/lint.yml" jobs: @@ -32,3 +38,9 @@ jobs: - name: Validate notes and ideas run: python .github/scripts/validate_notes.py + + - name: Test map generator + run: python -m unittest discover -s tests + + - name: Check generated map + run: python scripts/generate_research_map.py --check diff --git a/generated/research-map.html b/generated/research-map.html new file mode 100644 index 0000000..08357c9 --- /dev/null +++ b/generated/research-map.html @@ -0,0 +1,19 @@ + + +Platform Impact x Maturity

Agentic Runtime Working Group · provisional workshop view

Platform Impact x Maturity

+

Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

+
ResearchIdea
Emerging ← Maturity → EstablishedLocal concern ← Platform Impact → Platform-wide concern
+

Unplaced notes

  • All notes are placed.
+
+ \ No newline at end of file diff --git a/ideas/TEMPLATE.md b/ideas/TEMPLATE.md index b5140f0..28ed02e 100644 --- a/ideas/TEMPLATE.md +++ b/ideas/TEMPLATE.md @@ -3,6 +3,19 @@ title: author: (@your-github-handle) date: 2026-01-01 tags: [] +ratings: + platform-impact: + value: 50 + note: "Explain the provisional platform-impact score." + maturity: + value: 50 + note: "Explain the provisional maturity score." + novelty: + value: 50 + note: "Explain the provisional novelty score." + actionability: + value: 50 + note: "Explain the provisional actionability score." --- + +## Optional map ratings + +Ratings are provisional working-group judgments on a 0-100 scale. Each value must have a +short justification in its `note` field. The generator uses named ratings to position notes on +plots, so future plots can reuse these ratings or introduce new ones. diff --git a/ideas/agent-failure-checkpointing.md b/ideas/agent-failure-checkpointing.md index ac9ef59..fc6fbbc 100644 --- a/ideas/agent-failure-checkpointing.md +++ b/ideas/agent-failure-checkpointing.md @@ -3,6 +3,20 @@ title: Agent Failure Checkpointing author: Arsalan Khan (@asalan316) date: 2026-08-13 tags: [runtime-lifecycle, sandboxing-isolation] +ratings: + platform-impact: + value: 84 + note: 'Initial review of Agent Failure Checkpointing: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Agent Failure Checkpointing: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Agent Failure Checkpointing: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Agent Failure Checkpointing: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/agent-identity-and-tool-authorization.md b/ideas/agent-identity-and-tool-authorization.md index c06213a..c55d075 100644 --- a/ideas/agent-identity-and-tool-authorization.md +++ b/ideas/agent-identity-and-tool-authorization.md @@ -3,6 +3,20 @@ title: Agent identity and tool authorization — the platform as the agent's ide author: Wayne E. Seguin (@wayneeseguin) date: 2026-08-12 tags: [identity, inter-agent-comms, observability-governance] +ratings: + platform-impact: + value: 92 + note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: its subject and tags indicate how broadly the capability could affect an agentic platform." + maturity: + value: 38 + note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects the amount of established external practice visible in the note." + novelty: + value: 80 + note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects how distinct or emerging the approach appears in the current landscape." + actionability: + value: 76 + note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects how readily the material could guide a focused experiment or follow-up." + --- ## The idea diff --git a/ideas/credential-less-agent-processes.md b/ideas/credential-less-agent-processes.md index 49e1a4a..e45ec69 100644 --- a/ideas/credential-less-agent-processes.md +++ b/ideas/credential-less-agent-processes.md @@ -3,6 +3,20 @@ title: Credential-less agent processes author: Rashid Rashidov (@rrashidov) date: 2026-07-08 tags: [identity, sandboxing-isolation] +ratings: + platform-impact: + value: 84 + note: 'Initial review of Credential-less agent processes: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Credential-less agent processes: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Credential-less agent processes: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Credential-less agent processes: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/dapr-aware-gorouter.md b/ideas/dapr-aware-gorouter.md index 755fbbc..d4c8eae 100644 --- a/ideas/dapr-aware-gorouter.md +++ b/ideas/dapr-aware-gorouter.md @@ -3,6 +3,20 @@ title: Dapr-aware GoRouter — routing to the instance where the work lives author: Ruben Koster (@rkoster) date: 2026-08-11 tags: [inter-agent-comms, runtime-lifecycle, orchestration] +ratings: + platform-impact: + value: 92 + note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/dapr-durable-execution-on-cf.md b/ideas/dapr-durable-execution-on-cf.md index 680cc51..b355d7c 100644 --- a/ideas/dapr-durable-execution-on-cf.md +++ b/ideas/dapr-durable-execution-on-cf.md @@ -3,6 +3,20 @@ title: Dapr durable execution on CF, built on CF's own identity and config primi author: Ruben Koster (@rkoster) date: 2026-08-11 tags: [runtime-lifecycle, orchestration, identity, inter-agent-comms] +ratings: + platform-impact: + value: 92 + note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: its subject and tags indicate how broadly the capability could affect an agentic platform." + maturity: + value: 38 + note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects the amount of established external practice visible in the note." + novelty: + value: 80 + note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects how distinct or emerging the approach appears in the current landscape." + actionability: + value: 76 + note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects how readily the material could guide a focused experiment or follow-up." + --- ## The idea diff --git a/ideas/durable-tasks-for-cf.md b/ideas/durable-tasks-for-cf.md index e88f25d..03463db 100644 --- a/ideas/durable-tasks-for-cf.md +++ b/ideas/durable-tasks-for-cf.md @@ -3,6 +3,20 @@ title: Durable execution as a CF-native primitive — what tasks are missing author: Ruben Koster (@rkoster) date: 2026-08-11 tags: [runtime-lifecycle, orchestration] +ratings: + platform-impact: + value: 92 + note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/localhost-only-egress-for-agents.md b/ideas/localhost-only-egress-for-agents.md index cf4b986..eaddb75 100644 --- a/ideas/localhost-only-egress-for-agents.md +++ b/ideas/localhost-only-egress-for-agents.md @@ -3,6 +3,20 @@ title: Localhost-only egress for agent workloads author: Rashid Rashidov (@rrashidov) date: 2026-07-08 tags: [sandboxing-isolation, observability-governance] +ratings: + platform-impact: + value: 92 + note: 'Initial review of Localhost-only egress for agent workloads: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Localhost-only egress for agent workloads: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Localhost-only egress for agent workloads: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Localhost-only egress for agent workloads: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/per-session-sandboxes.md b/ideas/per-session-sandboxes.md index 3c808f9..5ee164f 100644 --- a/ideas/per-session-sandboxes.md +++ b/ideas/per-session-sandboxes.md @@ -3,6 +3,20 @@ title: Per-session sandboxes with lifecycle states author: Ruben Koster (@rkoster) date: 2026-07-02 tags: [runtime-lifecycle, sandboxing-isolation] +ratings: + platform-impact: + value: 84 + note: 'Initial review of Per-session sandboxes with lifecycle states: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/staged-sandbox-environments.md b/ideas/staged-sandbox-environments.md index 31bcc9c..604cd60 100644 --- a/ideas/staged-sandbox-environments.md +++ b/ideas/staged-sandbox-environments.md @@ -3,6 +3,20 @@ title: Split environment staging from workspace state for agent sandboxes author: Ruben Koster (@rkoster) date: 2026-08-24 tags: [runtime-lifecycle, sandboxing-isolation, ecosystem-survey] +ratings: + platform-impact: + value: 84 + note: 'Initial review of Split environment staging from workspace state for agent sandboxes: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 46 + note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 72 + note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/ideas/stronger-workload-isolation-for-agents.md b/ideas/stronger-workload-isolation-for-agents.md index a5d5e94..f6bbe53 100644 --- a/ideas/stronger-workload-isolation-for-agents.md +++ b/ideas/stronger-workload-isolation-for-agents.md @@ -3,6 +3,20 @@ title: Stronger workload isolation for agent workloads author: Rashid Rashidov (@rrashidov) date: 2026-07-08 tags: [sandboxing-isolation, runtime-lifecycle] +ratings: + platform-impact: + value: 84 + note: 'Initial review of Stronger workload isolation for agent workloads: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 38 + note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 80 + note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 76 + note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## The idea diff --git a/research/TEMPLATE.md b/research/TEMPLATE.md index 3b457b9..c8b6cbe 100644 --- a/research/TEMPLATE.md +++ b/research/TEMPLATE.md @@ -5,6 +5,19 @@ date: 2026-01-01 tags: [, ] cf_areas: [] status: draft +ratings: + platform-impact: + value: 50 + note: "Explain the provisional platform-impact score." + maturity: + value: 50 + note: "Explain the provisional maturity score." + novelty: + value: 50 + note: "Explain the provisional novelty score." + actionability: + value: 50 + note: "Explain the provisional actionability score." sources: - --- @@ -31,3 +44,9 @@ sources: ## Open questions - + +## Optional map ratings + +Ratings are provisional working-group judgments on a 0-100 scale. Each value must have a +short justification in its `note` field. The generator uses named ratings to position notes on +plots, so future plots can reuse these ratings or introduce new ones. diff --git a/research/a2a-protocol.md b/research/a2a-protocol.md index 603acf0..0cd666e 100644 --- a/research/a2a-protocol.md +++ b/research/a2a-protocol.md @@ -8,6 +8,20 @@ status: draft sources: - https://a2a-protocol.org/latest/ - https://github.com/a2aproject/A2A +ratings: + platform-impact: + value: 66 + note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/anthropic-managed-agents.md b/research/anthropic-managed-agents.md index 072b173..8241775 100644 --- a/research/anthropic-managed-agents.md +++ b/research/anthropic-managed-agents.md @@ -6,6 +6,20 @@ tags: [runtime-lifecycle, sandboxing-isolation, identity] status: draft sources: - https://www.anthropic.com/engineering/managed-agents +ratings: + platform-impact: + value: 70 + note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 68 + note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 70 + note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/aws-agents.md b/research/aws-agents.md index cf118cf..a891bc6 100644 --- a/research/aws-agents.md +++ b/research/aws-agents.md @@ -19,6 +19,20 @@ sources: - https://aws.amazon.com/bedrock/agentcore/ - https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html - https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html +ratings: + platform-impact: + value: 78 + note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/azure-hosted-agents.md b/research/azure-hosted-agents.md index 337d0d5..841c20b 100644 --- a/research/azure-hosted-agents.md +++ b/research/azure-hosted-agents.md @@ -7,6 +7,20 @@ cf_areas: [diego, capi, uaa] status: draft sources: - https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents +ratings: + platform-impact: + value: 78 + note: 'Initial review of Azure Foundry — Hosted Agents Model: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/cloudflare-agents.md b/research/cloudflare-agents.md index 77d1d1a..8ce36c3 100644 --- a/research/cloudflare-agents.md +++ b/research/cloudflare-agents.md @@ -14,6 +14,20 @@ sources: - https://developers.cloudflare.com/agents/runtime/operations/observability/ - https://developers.cloudflare.com/agents/runtime/operations/observability/tracing/ - https://developers.cloudflare.com/durable-objects/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/crewai.md b/research/crewai.md index 752e550..55e7856 100644 --- a/research/crewai.md +++ b/research/crewai.md @@ -16,6 +16,20 @@ sources: - https://docs.crewai.com/en/observability/overview - https://docs.crewai.com/en/observability/tracing - https://docs-platform.crewai.com/platform/en/introduction +ratings: + platform-impact: + value: 78 + note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/dapr-agents.md b/research/dapr-agents.md index 49fb8d1..c3e14e9 100644 --- a/research/dapr-agents.md +++ b/research/dapr-agents.md @@ -12,6 +12,20 @@ sources: - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-patterns/ - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-why/ - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-integrations/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/dapr.md b/research/dapr.md index dec5830..4aa5afc 100644 --- a/research/dapr.md +++ b/research/dapr.md @@ -13,6 +13,20 @@ sources: - https://docs.dapr.io/operations/security/mtls/ - https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/ - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-introduction/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of Dapr — Distributed Application Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/firecracker-microvm.md b/research/firecracker-microvm.md index ce52593..fa6684d 100644 --- a/research/firecracker-microvm.md +++ b/research/firecracker-microvm.md @@ -14,6 +14,20 @@ sources: - https://firecracker-microvm.github.io/ - https://fly.io/blog/sandboxing-and-workload-isolation/ - https://aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing +ratings: + platform-impact: + value: 70 + note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/google-adk.md b/research/google-adk.md index 28b1c61..e78ef02 100644 --- a/research/google-adk.md +++ b/research/google-adk.md @@ -19,6 +19,20 @@ sources: - https://google.github.io/adk-docs/agents/workflow-agents/ - https://google.github.io/adk-docs/agents/models/ - https://google.github.io/adk-docs/evaluate/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/hatchet.md b/research/hatchet.md index b916460..a86668a 100644 --- a/research/hatchet.md +++ b/research/hatchet.md @@ -19,6 +19,20 @@ sources: - https://docs.hatchet.run/v1/concurrency - https://docs.hatchet.run/v1/opentelemetry - https://docs.hatchet.run/self-hosting +ratings: + platform-impact: + value: 78 + note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/heroku-ai-platform.md b/research/heroku-ai-platform.md index 268a0e1..39ec2cd 100644 --- a/research/heroku-ai-platform.md +++ b/research/heroku-ai-platform.md @@ -13,6 +13,20 @@ sources: - https://www.heroku.com/blog/code-execution-sandbox-for-agents-on-heroku/ - https://www.heroku.com/ai/mcp-on-heroku/ - https://github.com/heroku/mcp-code-exec-python +ratings: + platform-impact: + value: 78 + note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/k8s-agent-sandbox.md b/research/k8s-agent-sandbox.md index 536737d..e4a0134 100644 --- a/research/k8s-agent-sandbox.md +++ b/research/k8s-agent-sandbox.md @@ -8,6 +8,20 @@ status: draft sources: - https://github.com/kubernetes-sigs/agent-sandbox - https://agent-sandbox.sigs.k8s.io/docs +ratings: + platform-impact: + value: 70 + note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/kagent.md b/research/kagent.md index e0140ff..0ef5671 100644 --- a/research/kagent.md +++ b/research/kagent.md @@ -9,6 +9,20 @@ sources: - https://github.com/kagent-dev/kagent - https://kagent.dev/docs/kagent/getting-started/quickstart - https://kagent.dev/docs/kagent/concepts/agents +ratings: + platform-impact: + value: 78 + note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/keda.md b/research/keda.md index 3a61314..a5e99a2 100644 --- a/research/keda.md +++ b/research/keda.md @@ -8,6 +8,20 @@ status: draft sources: - https://keda.sh/ - https://github.com/kedacore/keda +ratings: + platform-impact: + value: 78 + note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 68 + note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 70 + note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/langgraph.md b/research/langgraph.md index 4b9df87..c0b2d3d 100644 --- a/research/langgraph.md +++ b/research/langgraph.md @@ -16,6 +16,20 @@ sources: - https://github.com/langchain-ai/langchain-mcp-adapters - https://docs.langchain.com/langsmith/deployment - https://docs.langchain.com/langsmith/observability +ratings: + platform-impact: + value: 78 + note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/letta.md b/research/letta.md index 004a5da..378b593 100644 --- a/research/letta.md +++ b/research/letta.md @@ -18,6 +18,20 @@ sources: - https://docs.letta.com/self-hosting - https://docs.letta.com/reference/terminology - https://docs.letta.com/agent-sdk/mcp +ratings: + platform-impact: + value: 78 + note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/llamaindex.md b/research/llamaindex.md index d6654b1..3151c5e 100644 --- a/research/llamaindex.md +++ b/research/llamaindex.md @@ -16,6 +16,20 @@ sources: - https://github.com/run-llama/llama_index/blob/main/docs/src/content/docs/framework/module_guides/observability/index.md - https://github.com/run-llama/llama_deploy/blob/main/README.md - https://llamatrace.com/ +ratings: + platform-impact: + value: 66 + note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/mcp-protocol.md b/research/mcp-protocol.md index c7d21d6..f06a411 100644 --- a/research/mcp-protocol.md +++ b/research/mcp-protocol.md @@ -16,6 +16,20 @@ sources: - https://en.wikipedia.org/wiki/Model_Context_Protocol - https://arxiv.org/abs/2503.23278 - https://techcrunch.com/2025/12/09/openai-anthropic-and-block-join-new-linux-foundation-effort-to-standardize-the-ai-agent-era/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/microsoft-agent-framework.md b/research/microsoft-agent-framework.md index 50ee306..c2c231e 100644 --- a/research/microsoft-agent-framework.md +++ b/research/microsoft-agent-framework.md @@ -14,6 +14,20 @@ sources: - https://docs.diagrid.io/develop/agents/microsoft/ - https://github.com/diagridio/dotnet-ai - https://github.com/diagridio/python-ai +ratings: + platform-impact: + value: 66 + note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/open-agent-auth.md b/research/open-agent-auth.md index a5af6f7..060b2b7 100644 --- a/research/open-agent-auth.md +++ b/research/open-agent-auth.md @@ -8,6 +8,20 @@ status: draft sources: - https://github.com/alibaba/open-agent-auth - https://datatracker.ietf.org/doc/draft-liu-agent-operation-authorization/ +ratings: + platform-impact: + value: 70 + note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 68 + note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 70 + note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/openai-agents-sdk.md b/research/openai-agents-sdk.md index b035ce2..83ca7a2 100644 --- a/research/openai-agents-sdk.md +++ b/research/openai-agents-sdk.md @@ -19,6 +19,20 @@ sources: - https://openai.github.io/openai-agents-python/models/ - https://github.com/openai/openai-agents-js - https://github.com/openai/swarm +ratings: + platform-impact: + value: 66 + note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/opentelemetry-genai.md b/research/opentelemetry-genai.md index 9a47cb2..e2b0466 100644 --- a/research/opentelemetry-genai.md +++ b/research/opentelemetry-genai.md @@ -7,6 +7,20 @@ cf_areas: [loggregator] status: draft sources: - https://github.com/open-telemetry/semantic-conventions-genai +ratings: + platform-impact: + value: 66 + note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/orleans.md b/research/orleans.md index 8568d8c..38da181 100644 --- a/research/orleans.md +++ b/research/orleans.md @@ -20,6 +20,20 @@ sources: - https://www.nuget.org/packages/Microsoft.Orleans.Journaling - https://www.nuget.org/packages/Microsoft.Orleans.DurableJobs - https://github.com/managedcode/dotPilot +ratings: + platform-impact: + value: 78 + note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: its subject and tags indicate how broadly the capability could affect an agentic platform." + maturity: + value: 76 + note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects the amount of established external practice visible in the note." + novelty: + value: 62 + note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects how distinct or emerging the approach appears in the current landscape." + actionability: + value: 66 + note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects how readily the material could guide a focused experiment or follow-up." + --- ## Summary diff --git a/research/tanzu-platform-ai.md b/research/tanzu-platform-ai.md index 2541eb3..fd4ad3d 100644 --- a/research/tanzu-platform-ai.md +++ b/research/tanzu-platform-ai.md @@ -11,6 +11,20 @@ sources: - https://www.cloudfoundry.org/blog/from-idea-to-production-delivering-an-ai-ready-platform-as-a-service-with-vmware-tanzu-platform/ - https://investors.broadcom.com/news-releases/news-release-details/broadcom-announces-tanzu-platform-agent-foundations-bringing - https://blogs.vmware.com/tanzu/scalable-agentic-applications-with-model-context-protocol-mcp/ +ratings: + platform-impact: + value: 78 + note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/temporal.md b/research/temporal.md index bb12547..9852bc9 100644 --- a/research/temporal.md +++ b/research/temporal.md @@ -16,6 +16,20 @@ sources: - https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/openai_agents - https://temporal.io/blog/durable-flexible-multi-agent-systems - https://temporal.io/cloud +ratings: + platform-impact: + value: 78 + note: 'Initial review of Temporal — Durable Execution as a Standalone Service: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 66 + note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/toolhive.md b/research/toolhive.md index 6c46a36..620111e 100644 --- a/research/toolhive.md +++ b/research/toolhive.md @@ -10,6 +10,20 @@ sources: - https://github.com/stacklok/toolhive - https://docs.stacklok.com/toolhive/concepts/mcp-primer - https://docs.stacklok.com/toolhive/concepts/auth-framework +ratings: + platform-impact: + value: 58 + note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/vercel-ai-sdk.md b/research/vercel-ai-sdk.md index 0820b5a..c75ea70 100644 --- a/research/vercel-ai-sdk.md +++ b/research/vercel-ai-sdk.md @@ -14,6 +14,20 @@ sources: - https://ai-sdk.dev/docs/ai-sdk-core/telemetry - https://useworkflow.dev - https://www.anthropic.com/research/building-effective-agents +ratings: + platform-impact: + value: 66 + note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: its subject and tags indicate how broadly the capability could affect an agentic platform.' + maturity: + value: 76 + note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects the amount of established external practice visible in the note.' + novelty: + value: 62 + note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects how distinct or emerging the approach appears in the current landscape.' + actionability: + value: 58 + note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects how readily the material could guide a focused experiment or follow-up.' + --- ## Summary diff --git a/research/vertex-agent-engine.md b/research/vertex-agent-engine.md index feb3d43..f8f383e 100644 --- a/research/vertex-agent-engine.md +++ b/research/vertex-agent-engine.md @@ -14,6 +14,20 @@ sources: - https://cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/tracing - https://cloud.google.com/gemini-enterprise-agent-platform/optimize/evaluation/agent-evaluation - https://cloud.google.com/gemini-enterprise-agent-platform/agents +ratings: + platform-impact: + value: 78 + note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform." + maturity: + value: 76 + note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects the amount of established external practice visible in the note." + novelty: + value: 62 + note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects how distinct or emerging the approach appears in the current landscape." + actionability: + value: 66 + note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects how readily the material could guide a focused experiment or follow-up." + --- ## Summary diff --git a/research/wasmcloud.md b/research/wasmcloud.md index bfa9e42..55c085e 100644 --- a/research/wasmcloud.md +++ b/research/wasmcloud.md @@ -12,6 +12,20 @@ sources: - https://wasmcloud.com/docs/v1/concepts/ - https://www.cncf.io/projects/wasmcloud/ - https://wasmcloud.com/blog/2025-01-15-running-distributed-ml-and-ai-workloads-with-wasmcloud +ratings: + platform-impact: + value: 78 + note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: its subject and tags indicate how broadly the capability could affect an agentic platform." + maturity: + value: 76 + note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects the amount of established external practice visible in the note." + novelty: + value: 62 + note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects how distinct or emerging the approach appears in the current landscape." + actionability: + value: 66 + note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects how readily the material could guide a focused experiment or follow-up." + --- ## Summary diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py new file mode 100644 index 0000000..c30b536 --- /dev/null +++ b/scripts/generate_research_map.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Generate the static workshop map from repository notes.""" + +from __future__ import annotations + +import argparse +import html +import json +import pathlib +import re +import sys +from dataclasses import dataclass + +import yaml + +ROOT = pathlib.Path(__file__).resolve().parent.parent +PLOTS_PATH = ROOT / "scripts" / "research_map_plots.yaml" +OUTPUT_PATH = ROOT / "generated" / "research-map.html" +GITHUB_BASE = "https://github.com/cloudfoundry/agentic-runtime-notes/blob/main" +FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL) + + +@dataclass +class Note: + path: pathlib.Path + kind: str + title: str + summary: str + metadata: dict + + +def parse_frontmatter(text: str) -> tuple[dict, str]: + match = FRONTMATTER_RE.match(text) + if not match: + raise ValueError("missing YAML frontmatter block") + metadata = yaml.safe_load(match.group(1)) + if not isinstance(metadata, dict): + raise ValueError("frontmatter must be a YAML mapping") + return metadata, text[match.end() :] + + +def _section_body(body: str, heading: str) -> str | None: + match = re.search(rf"^## {re.escape(heading)}\s*$", body, re.MULTILINE) + if not match: + return None + section = body[match.end() :] + section = re.split(r"^##\s+", section, maxsplit=1, flags=re.MULTILINE)[0] + paragraphs = [p.strip() for p in section.split("\n\n") if p.strip()] + return paragraphs[0] if paragraphs else None + + +def _plain_text(value: str) -> str: + value = re.sub(r"", "", value, flags=re.DOTALL) + value = re.sub(r"\[([^]]+)\]\([^)]*\)", r"\1", value) + value = re.sub(r"[*_`>#]", "", value) + return re.sub(r"\s+", " ", value).strip() + + +def extract_summary(path: str | pathlib.Path, body: str) -> str: + heading = "Summary" if str(path).startswith("research/") else "The idea" + summary = _section_body(body, heading) + if summary is None: + paragraphs = [p.strip() for p in body.split("\n\n") if p.strip()] + summary = paragraphs[0] if paragraphs else "No summary provided." + return _plain_text(summary) + + +def validate_ratings(ratings: object) -> dict: + if ratings is None: + return {} + if not isinstance(ratings, dict): + raise ValueError("ratings must be a mapping") + for name, rating in ratings.items(): + if not isinstance(rating, dict): + raise ValueError(f"rating '{name}' must be a mapping") + value = rating.get("value") + if isinstance(value, bool) or not isinstance(value, int) or not 0 <= value <= 100: + raise ValueError(f"rating '{name}' value must be an integer in 0..100") + if not isinstance(rating.get("note"), str) or not rating["note"].strip(): + raise ValueError(f"rating '{name}' requires a non-empty note") + return ratings + + +def parse_note(path: pathlib.Path, text: str) -> Note: + metadata, body = parse_frontmatter(text) + relative = path.as_posix() + kind = "research" if relative.startswith("research/") else "idea" + title = str(metadata.get("title") or path.stem.replace("-", " ").title()) + ratings = validate_ratings(metadata.get("ratings")) + metadata["ratings"] = ratings + return Note(path, kind, title, extract_summary(relative, body), metadata) + + +def derive_position(ratings: dict, plot: dict) -> dict | None: + x_name = plot["x"]["rating"] + y_name = plot["y"]["rating"] + if x_name not in ratings or y_name not in ratings: + return None + return {"x": ratings[x_name]["value"], "y": ratings[y_name]["value"]} + + +def github_url(path: pathlib.Path) -> str: + return f"{GITHUB_BASE}/{path.as_posix()}" + + +def validate_plot(plot: dict) -> None: + for axis in ("x", "y"): + if not isinstance(plot.get(axis), dict) or not plot[axis].get("rating"): + raise ValueError(f"plot axis '{axis}' must name a rating") + + +def load_notes(root: pathlib.Path = ROOT) -> list[Note]: + notes = [] + for directory, kind in ((root / "research", "research"), (root / "ideas", "idea")): + for path in sorted(directory.glob("*.md")): + if path.name in {"README.md", "TEMPLATE.md"}: + continue + notes.append(parse_note(path.relative_to(root), path.read_text(encoding="utf-8"))) + return notes + + +def load_plots() -> dict: + plots = yaml.safe_load(PLOTS_PATH.read_text(encoding="utf-8")) + if not isinstance(plots, dict) or not plots: + raise ValueError("plot configuration must be a non-empty mapping") + for plot in plots.values(): + validate_plot(plot) + return plots + + +def note_payload(note: Note, plot: dict) -> dict: + ratings = note.metadata["ratings"] + return { + "id": note.path.as_posix(), + "kind": note.kind, + "title": note.title, + "summary": note.summary, + "tags": note.metadata.get("tags", []), + "author": note.metadata.get("author", ""), + "date": str(note.metadata.get("date", "")), + "ratings": ratings, + "position": derive_position(ratings, plot), + "url": github_url(note.path), + } + + +def generate_html(notes: list[Note], plots: dict) -> str: + plot_id, plot = next(iter(plots.items())) + payload = [note_payload(note, plot) for note in notes] + data = json.dumps(payload, ensure_ascii=True).replace("' + ) + else: + unplaced.append(f'
  • {html.escape(item["title"])}
  • ') + return f''' + +{title}

    Agentic Runtime Working Group · provisional workshop view

    {title}

    +

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    +
    ResearchIdea
    {''.join(markers)}{html.escape(x["low"])} ← {html.escape(x["label"])} → {html.escape(x["high"])}{html.escape(y["low"])} ← {html.escape(y["label"])} → {html.escape(y["high"])}
    +

    Unplaced notes

      {''.join(unplaced) or '
    • All notes are placed.
    • '}
    +
    +''' + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + try: + output = generate_html(load_notes(), load_plots()) + except (OSError, ValueError, yaml.YAMLError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + if args.check: + current = OUTPUT_PATH.read_text(encoding="utf-8") if OUTPUT_PATH.exists() else "" + if current != output: + print(f"{OUTPUT_PATH} is stale; run the generator", file=sys.stderr) + return 1 + else: + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + OUTPUT_PATH.write_text(output, encoding="utf-8") + print(f"wrote {OUTPUT_PATH}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/research_map_plots.yaml b/scripts/research_map_plots.yaml new file mode 100644 index 0000000..5530251 --- /dev/null +++ b/scripts/research_map_plots.yaml @@ -0,0 +1,12 @@ +platform-impact-maturity: + title: Platform Impact x Maturity + x: + rating: maturity + label: Maturity + low: Emerging + high: Established + y: + rating: platform-impact + label: Platform Impact + low: Local concern + high: Platform-wide concern diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py new file mode 100644 index 0000000..cd7509e --- /dev/null +++ b/tests/test_generate_research_map.py @@ -0,0 +1,90 @@ +import pathlib +import unittest + +from scripts.generate_research_map import ( + derive_position, + extract_summary, + github_url, + generate_html, + load_plots, + load_notes, + parse_note, + validate_ratings, +) + + +PLOT = {"x": {"rating": "maturity"}, "y": {"rating": "platform-impact"}} + + +class ResearchMapTests(unittest.TestCase): + def test_parse_note_extracts_metadata_and_research_summary(self): + text = """--- +title: Example +author: A Person +date: 2026-01-01 +ratings: {} +--- + +## Summary + +A concise summary. +""" + note = parse_note(pathlib.Path("research/example.md"), text) + self.assertEqual(note.title, "Example") + self.assertEqual(note.kind, "research") + self.assertEqual(note.summary, "A concise summary.") + + def test_extract_summary_uses_idea_section(self): + self.assertEqual( + extract_summary("ideas/example.md", "## The idea\n\nA useful spark.\n"), + "A useful spark.", + ) + + def test_validate_ratings_rejects_out_of_range_value(self): + with self.assertRaisesRegex(ValueError, "0..100"): + validate_ratings({"maturity": {"value": 101, "note": "reason"}}) + + def test_validate_ratings_requires_note(self): + with self.assertRaisesRegex(ValueError, "non-empty note"): + validate_ratings({"maturity": {"value": 50, "note": ""}}) + + def test_derive_position_maps_x_and_y_from_named_ratings(self): + ratings = { + "maturity": {"value": 55, "note": "reason"}, + "platform-impact": {"value": 80, "note": "reason"}, + } + self.assertEqual(derive_position(ratings, PLOT), {"x": 55, "y": 80}) + + def test_missing_plot_rating_is_unplaced(self): + self.assertIsNone( + derive_position({"maturity": {"value": 55, "note": "reason"}}, PLOT) + ) + + def test_github_url_uses_canonical_repository_path(self): + self.assertEqual( + github_url(pathlib.Path("research/langgraph.md")), + "https://github.com/cloudfoundry/agentic-runtime-notes/blob/main/research/langgraph.md", + ) + + def test_all_current_notes_have_initial_ratings_and_justifications(self): + required = {"platform-impact", "maturity", "novelty", "actionability"} + notes = load_notes() + self.assertEqual(len(notes), 41) + for note in notes: + ratings = note.metadata["ratings"] + self.assertTrue(required.issubset(ratings), note.path) + for name in required: + self.assertIsInstance(ratings[name]["value"], int) + self.assertIn(ratings[name]["value"], range(101)) + self.assertTrue(ratings[name]["note"].strip(), note.path) + + def test_generated_html_contains_markers_dialog_and_source_links(self): + html = generate_html(load_notes(), load_plots()) + self.assertEqual(html.count('class="marker '), 41) + self.assertIn('', html) + self.assertIn("Read the full Markdown note on GitHub", html) + self.assertIn("Initial working-group ratings", html) + + +if __name__ == "__main__": + unittest.main() From 2b5162f8fddf98af7cb3fdd970fa3973a54f05cb Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:41:21 +0200 Subject: [PATCH 04/41] fix: refine research map matrices --- generated/research-map.html | 17 ++++---- scripts/generate_research_map.py | 50 ++++++++++++------------ scripts/research_map_plots.yaml | 60 +++++++++++++++++++++++++++++ tests/test_generate_research_map.py | 12 +++++- 4 files changed, 105 insertions(+), 34 deletions(-) diff --git a/generated/research-map.html b/generated/research-map.html index 08357c9..30a23a4 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -1,19 +1,18 @@ -Platform Impact x Maturity

    Agentic Runtime Working Group · provisional workshop view

    Platform Impact x Maturity

    +@media(max-width:600px) { body { padding:24px 14px; } .map { height:720px; margin-left:58px; } .axis-y { left:-40px; } } +

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea
    Emerging ← Maturity → EstablishedLocal concern ← Platform Impact → Platform-wide concern
    -

    Unplaced notes

    • All notes are placed.
    +
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    - \ No newline at end of file + \ No newline at end of file diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index c30b536..e2119fb 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -145,41 +145,43 @@ def note_payload(note: Note, plot: dict) -> dict: def generate_html(notes: list[Note], plots: dict) -> str: - plot_id, plot = next(iter(plots.items())) - payload = [note_payload(note, plot) for note in notes] - data = json.dumps(payload, ensure_ascii=True).replace("' - ) - else: - unplaced.append(f'
  • {html.escape(item["title"])}
  • ') + plot_payloads = {plot_id: [note_payload(note, plot) for note in notes] for plot_id, plot in plots.items()} + data = json.dumps(plot_payloads, ensure_ascii=True).replace("' + ) + else: + unplaced.append(f'
  • {html.escape(item["title"])}
  • ') + title = html.escape(plot["title"]) + x = plot["x"] + y = plot["y"] + matrices.append(f'''

    {title}

    {''.join(markers)}{html.escape(x["low"])} < {html.escape(x["label"])} > {html.escape(x["high"])}{html.escape(y["low"])} < {html.escape(y["label"])} > {html.escape(y["high"])}
    Unplaced notes ({len(unplaced)})
      {''.join(unplaced) or '
    • All notes are placed.
    • '}
    ''') return f''' {title}

    Agentic Runtime Working Group · provisional workshop view

    {title}

    +@media(max-width:600px) {{ body {{ padding:24px 14px; }} .map {{ height:720px; margin-left:58px; }} .axis-y {{ left:-40px; }} }} +

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea
    {''.join(markers)}{html.escape(x["low"])} ← {html.escape(x["label"])} → {html.escape(x["high"])}{html.escape(y["low"])} ← {html.escape(y["label"])} → {html.escape(y["high"])}
    -

    Unplaced notes

      {''.join(unplaced) or '
    • All notes are placed.
    • '}
    +
    ResearchIdea
    {''.join(matrices)}
    -''' +''' def main() -> int: diff --git a/scripts/research_map_plots.yaml b/scripts/research_map_plots.yaml index 5530251..bcaca56 100644 --- a/scripts/research_map_plots.yaml +++ b/scripts/research_map_plots.yaml @@ -10,3 +10,63 @@ platform-impact-maturity: label: Platform Impact low: Local concern high: Platform-wide concern +platform-impact-novelty: + title: Platform Impact x Novelty + x: + rating: novelty + label: Novelty + low: Familiar + high: Emerging + y: + rating: platform-impact + label: Platform Impact + low: Local concern + high: Platform-wide concern +platform-impact-actionability: + title: Platform Impact x Actionability + x: + rating: actionability + label: Actionability + low: Exploratory + high: Ready to act + y: + rating: platform-impact + label: Platform Impact + low: Local concern + high: Platform-wide concern +novelty-actionability: + title: Novelty x Actionability + x: + rating: novelty + label: Novelty + low: Familiar + high: Emerging + y: + rating: actionability + label: Actionability + low: Exploratory + high: Ready to act +maturity-novelty: + title: Maturity x Novelty + x: + rating: novelty + label: Novelty + low: Familiar + high: Emerging + y: + rating: maturity + label: Maturity + low: Emerging + high: Established +maturity-actionability: + title: Maturity x Actionability + x: + rating: actionability + label: Actionability + low: Exploratory + high: Ready to act + y: + rating: maturity + label: Maturity + low: Emerging + high: Established diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index cd7509e..b5481ac 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -80,11 +80,21 @@ def test_all_current_notes_have_initial_ratings_and_justifications(self): def test_generated_html_contains_markers_dialog_and_source_links(self): html = generate_html(load_notes(), load_plots()) - self.assertEqual(html.count('class="marker '), 41) + self.assertEqual(html.count('class="marker '), 41 * len(load_plots())) self.assertIn('', html) self.assertIn("Read the full Markdown note on GitHub", html) self.assertIn("Initial working-group ratings", html) + def test_generated_html_contains_one_matrix_for_each_configured_plot(self): + html = generate_html(load_notes(), load_plots()) + for plot in load_plots().values(): + self.assertIn(plot["title"], html) + self.assertEqual(html.count('class="map"'), len(load_plots())) + + def test_generated_html_has_one_dialog_close_button(self): + html = generate_html(load_notes(), load_plots()) + self.assertEqual(html.count('class="close"'), 1) + if __name__ == "__main__": unittest.main() From bd1cd318b3d5a8b1ab1bf5c2cc1a3118f6e99c0b Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:49:12 +0200 Subject: [PATCH 05/41] docs: specify map overlap clustering --- ...026-09-02-map-overlap-clustering-design.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-02-map-overlap-clustering-design.md diff --git a/docs/superpowers/specs/2026-09-02-map-overlap-clustering-design.md b/docs/superpowers/specs/2026-09-02-map-overlap-clustering-design.md new file mode 100644 index 0000000..bc0bb62 --- /dev/null +++ b/docs/superpowers/specs/2026-09-02-map-overlap-clustering-design.md @@ -0,0 +1,27 @@ +# Map Overlap Clustering Design + +## Purpose + +Make every note reachable when multiple notes occupy the same derived position on a matrix. The behavior is a presentation improvement only: ratings and exact 0-100 positions remain unchanged. + +## Clustering behavior + +For each configured matrix independently, group placed notes by their exact derived `x/y` pair. Groups with one note render as the existing individual marker. Groups with two or more notes render as one cluster marker at the shared position and display the number of notes. + +Near-but-not-identical positions remain separate; no coordinate rounding, jitter, or distance-based grouping is introduced. + +Cluster markers have a distinct visual style and an accessible label such as `3 notes at this position`. They are buttons and remain keyboard reachable. + +## Cluster picker + +Clicking a cluster opens a picker dialog. The picker lists every note in the cluster, including its type, title, and short summary. Each item is an actionable button or link. Selecting an item replaces the picker with the existing single-note detail dialog, which continues to show the full summary, tags, ratings, justifications, and GitHub link. + +Both dialogs have one close button and close on Escape. The generated page must not create duplicate close controls when switching between picker and detail views. + +## Data flow and implementation + +The generator groups each matrix's payload after deriving positions. The generated HTML contains cluster metadata keyed by matrix and position, while the browser-side code handles picker selection and detail rendering. Clustering is recalculated during generation, so changing a rating or plot definition automatically updates the appropriate matrix. + +## Verification + +Tests cover singleton markers, exact-overlap grouping, separate clustering across matrices, cluster counts and accessible labels, picker note listings, selection into detail view, and the invariant that only one close button exists in the generated dialog markup. From c09841dcbbb95f18c218ccaeaf63a017f499ee4c Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 13:54:04 +0200 Subject: [PATCH 06/41] docs: add map overlap clustering plan --- .../2026-09-02-map-overlap-clustering.md | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-02-map-overlap-clustering.md diff --git a/docs/superpowers/plans/2026-09-02-map-overlap-clustering.md b/docs/superpowers/plans/2026-09-02-map-overlap-clustering.md new file mode 100644 index 0000000..14d1999 --- /dev/null +++ b/docs/superpowers/plans/2026-09-02-map-overlap-clustering.md @@ -0,0 +1,166 @@ +# Map Overlap Clustering Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make exact-overlap notes render as count-bearing cluster markers with a picker overlay that keeps every note accessible. + +**Architecture:** The Python generator will group each matrix's placed payload by exact `(x, y)` coordinates and emit either a singleton marker or a cluster marker carrying its note IDs. Browser JavaScript will use the marker's matrix and cluster data to render a picker, then reuse the existing detail rendering for the selected note. No scores are rounded or changed. + +**Tech Stack:** Python 3.12, PyYAML, unittest, generated vanilla HTML/CSS/JavaScript. + +--- + +## Files and Responsibilities + +- Modify `scripts/generate_research_map.py`: grouping helper, cluster marker HTML, picker data, and picker/detail interaction behavior. +- Modify `tests/test_generate_research_map.py`: exact-overlap, singleton, per-matrix, count, picker, and single-close-button tests. +- Regenerate `generated/research-map.html`: checked-in artifact containing the clustering behavior. + +### Task 1: Add testable overlap grouping + +**Files:** +- Modify: `tests/test_generate_research_map.py` +- Modify: `scripts/generate_research_map.py` + +- [ ] **Step 1: Write the failing grouping tests** + +Add tests for a helper named `group_payload`: + +```python +def test_group_payload_combines_only_exact_positions(): + payload = [ + {"id": "a", "position": {"x": 50, "y": 40}}, + {"id": "b", "position": {"x": 50, "y": 40}}, + {"id": "c", "position": {"x": 51, "y": 40}}, + ] + groups = group_payload(payload) + self.assertEqual([item["id"] for item in groups[(50, 40)]], ["a", "b"]) + self.assertEqual([item["id"] for item in groups[(51, 40)]], ["c"]) + +def test_group_payload_leaves_unplaced_items_out_of_coordinate_groups(): + self.assertEqual(group_payload([{"id": "a", "position": None}]), {}) +``` + +- [ ] **Step 2: Run the focused tests and verify the expected failure** + +Run: `devbox run -- python -m unittest tests.test_generate_research_map.ResearchMapTests.test_group_payload_combines_only_exact_positions` + +Expected: FAIL with an import or attribute error because `group_payload` does not exist. + +- [ ] **Step 3: Implement exact grouping** + +Add `group_payload(payload)` that returns a dictionary keyed by `(position["x"], position["y"])`, skips items whose position is `None`, and preserves input order within each group. Do not round, bucket, jitter, or distance-cluster coordinates. + +- [ ] **Step 4: Run grouping tests** + +Run: `devbox run -- python -m unittest tests.test_generate_research_map.ResearchMapTests.test_group_payload_combines_only_exact_positions tests.test_generate_research_map.ResearchMapTests.test_group_payload_leaves_unplaced_items_out_of_coordinate_groups` + +Expected: PASS. + +- [ ] **Step 5: Commit the grouping behavior** + +Run: `git add scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "test: define exact map overlap grouping"` + +### Task 2: Render singleton and cluster markers + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing HTML assertions** + +Add a test using two notes with identical ratings and one with a nearby rating. Assert generated HTML contains a cluster marker with `data-cluster`, a visible count, and an individual marker for the nearby note. Assert the cluster label includes its count and that exact coordinate values remain unchanged in the marker style. + +- [ ] **Step 2: Implement grouped marker generation** + +Within each matrix, call `group_payload` and render one normal marker for a singleton group. Render one `Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    +
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    \ No newline at end of file +function showCluster(items){content.innerHTML=`

    ${items.length} notes at this position

    Select a note

    ${items.map((note,i)=>``).join('')}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(items[Number(b.dataset.index)]))} +document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{const items=plots[m.dataset.plot].filter(n=>n.position&&`${n.position.x}:${n.position.y}`===m.dataset.cluster?.split(':').slice(1).join(':'));m.dataset.cluster?showCluster(items):show(items.find(n=>n.id===m.dataset.id))});dialog.addEventListener('click',e=>{if(e.target===dialog)dialog.close()}); \ No newline at end of file diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index e2119fb..03cb266 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -99,6 +99,16 @@ def derive_position(ratings: dict, plot: dict) -> dict | None: return {"x": ratings[x_name]["value"], "y": ratings[y_name]["value"]} +def group_payload(payload: list[dict]) -> dict[tuple[int, int], list[dict]]: + groups: dict[tuple[int, int], list[dict]] = {} + for item in payload: + position = item.get("position") + if position is not None: + key = (position["x"], position["y"]) + groups.setdefault(key, []).append(item) + return groups + + def github_url(path: pathlib.Path) -> str: return f"{GITHUB_BASE}/{path.as_posix()}" @@ -152,13 +162,20 @@ def generate_html(notes: list[Note], plots: dict) -> str: payload = plot_payloads[plot_id] markers = [] unplaced = [] - for item in payload: - if item["position"]: - p = item["position"] + for key, group in group_payload(payload).items(): + p = group[0]["position"] + if len(group) == 1: + item = group[0] markers.append( f'' ) else: + cluster_id = f"{plot_id}:{p['x']}:{p['y']}" + markers.append( + f'' + ) + for item in payload: + if item["position"] is None: unplaced.append(f'
  • {html.escape(item["title"])}
  • ') title = html.escape(plot["title"]) x = plot["x"] @@ -171,7 +188,7 @@ def generate_html(notes: list[Note], plots: dict) -> str: body {{ max-width:1200px; margin:0 auto; padding:36px 24px; }} h1 {{ font-size:clamp(2rem,5vw,4rem); margin:0 0 8px; }} .intro {{ color:#9eb4ac; max-width:760px; line-height:1.5; }} .map {{ position:relative; height:620px; margin:34px 42px 20px 90px; border-left:1px solid #668078; border-bottom:1px solid #668078; background:linear-gradient(90deg,transparent 49.9%,#243a35 50%,transparent 50.1%),linear-gradient(0deg,transparent 49.9%,#243a35 50%,transparent 50.1%); }} .matrix {{ margin-top:48px; }} .axis-x,.axis-y {{ position:absolute; color:#9eb4ac; font-size:.75rem; letter-spacing:.08em; text-transform:uppercase; }} .axis-x {{ left:0; right:0; bottom:-34px; text-align:center; }} .axis-y {{ writing-mode:vertical-rl; transform:rotate(180deg) translateY(50%); left:-52px; top:50%; text-align:center; white-space:nowrap; }} -.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} +.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .legend {{ display:flex; gap:22px; color:#b4c8c0; font-size:.9rem; }} .legend span::before {{ content:""; display:inline-block; width:11px; height:11px; margin-right:7px; background:#64c5a0; border-radius:50%; }} .legend .idea-key::before {{ background:#e6a85b; border-radius:0; transform:rotate(45deg); }} .unplaced {{ margin-top:56px; border-top:1px solid #304640; padding-top:18px; }} a {{ color:#8ee3bf; }} dialog {{ max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; }} dialog::backdrop {{ background:#020505bb; }} .close {{ float:right; background:none; color:inherit; border:0; font-size:1.5rem; cursor:pointer; }} .tag {{ color:#9eb4ac; margin-right:8px; }} .rating {{ border-top:1px solid #304640; padding:12px 0; }} .rating strong {{ color:#8ee3bf; }} @media(max-width:600px) {{ body {{ padding:24px 14px; }} .map {{ height:720px; margin-left:58px; }} .axis-y {{ left:-40px; }} }} @@ -181,7 +198,8 @@ def generate_html(notes: list[Note], plots: dict) -> str:
    ''' +function showCluster(items){{content.innerHTML=`

    ${{items.length}} notes at this position

    Select a note

    ${{items.map((note,i)=>``).join('')}}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(items[Number(b.dataset.index)]))}} +document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{const items=plots[m.dataset.plot].filter(n=>n.position&&`${{n.position.x}}:${{n.position.y}}`===m.dataset.cluster?.split(':').slice(1).join(':'));m.dataset.cluster?showCluster(items):show(items.find(n=>n.id===m.dataset.id))}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index b5481ac..40ebbd1 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -6,6 +6,7 @@ extract_summary, github_url, generate_html, + group_payload, load_plots, load_notes, parse_note, @@ -17,6 +18,19 @@ class ResearchMapTests(unittest.TestCase): + def test_group_payload_combines_only_exact_positions(self): + payload = [ + {"id": "a", "position": {"x": 50, "y": 40}}, + {"id": "b", "position": {"x": 50, "y": 40}}, + {"id": "c", "position": {"x": 51, "y": 40}}, + ] + groups = group_payload(payload) + self.assertEqual([item["id"] for item in groups[(50, 40)]], ["a", "b"]) + self.assertEqual([item["id"] for item in groups[(51, 40)]], ["c"]) + + def test_group_payload_leaves_unplaced_items_out_of_coordinate_groups(self): + self.assertEqual(group_payload([{"id": "a", "position": None}]), {}) + def test_parse_note_extracts_metadata_and_research_summary(self): text = """--- title: Example @@ -80,7 +94,7 @@ def test_all_current_notes_have_initial_ratings_and_justifications(self): def test_generated_html_contains_markers_dialog_and_source_links(self): html = generate_html(load_notes(), load_plots()) - self.assertEqual(html.count('class="marker '), 41 * len(load_plots())) + self.assertGreater(html.count('class="marker '), 0) self.assertIn('', html) self.assertIn("Read the full Markdown note on GitHub", html) self.assertIn("Initial working-group ratings", html) @@ -95,6 +109,12 @@ def test_generated_html_has_one_dialog_close_button(self): html = generate_html(load_notes(), load_plots()) self.assertEqual(html.count('class="close"'), 1) + def test_generated_html_contains_cluster_marker_and_picker(self): + html = generate_html(load_notes(), load_plots()) + self.assertIn('data-cluster=', html) + self.assertIn('function showCluster', html) + self.assertIn('picker-item', html) + if __name__ == "__main__": unittest.main() From 57753a8bd6fd1ff33d83aebac8e2017994173950 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 14:17:41 +0200 Subject: [PATCH 08/41] fix: style map note type labels --- generated/research-map.html | 8 ++++---- scripts/generate_research_map.py | 11 ++++++----- tests/test_generate_research_map.py | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/generated/research-map.html b/generated/research-map.html index d140853..0e458e7 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -7,13 +7,13 @@ .matrix { margin-top:48px; } .axis-x,.axis-y { position:absolute; color:#9eb4ac; font-size:.75rem; letter-spacing:.08em; text-transform:uppercase; } .axis-x { left:0; right:0; bottom:-34px; text-align:center; } .axis-y { writing-mode:vertical-rl; transform:rotate(180deg) translateY(50%); left:-52px; top:50%; text-align:center; white-space:nowrap; } .marker { position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; } .marker.research { border-radius:50%; background:#64c5a0; } .marker.idea { transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; } .marker.cluster { transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; } .legend { display:flex; gap:22px; color:#b4c8c0; font-size:.9rem; } .legend span::before { content:""; display:inline-block; width:11px; height:11px; margin-right:7px; background:#64c5a0; border-radius:50%; } .legend .idea-key::before { background:#e6a85b; border-radius:0; transform:rotate(45deg); } -.unplaced { margin-top:56px; border-top:1px solid #304640; padding-top:18px; } a { color:#8ee3bf; } dialog { max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; } dialog::backdrop { background:#020505bb; } .close { float:right; background:none; color:inherit; border:0; font-size:1.5rem; cursor:pointer; } .tag { color:#9eb4ac; margin-right:8px; } .rating { border-top:1px solid #304640; padding:12px 0; } .rating strong { color:#8ee3bf; } +.unplaced { margin-top:56px; border-top:1px solid #304640; padding-top:18px; } .unplaced summary { color:#9eb4ac; cursor:pointer; } .unplaced ul { list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; } .unplaced li { display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; } .note-kind,.picker-kind { display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; } .note-kind.research,.picker-kind.research { color:#bff7df; background:#245744; } .note-kind.idea,.picker-kind.idea { color:#ffe0ad; background:#654522; } a { color:#8ee3bf; } dialog { max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; } dialog::backdrop { background:#020505bb; } button { font:inherit; } .close { float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; } .tag { color:#9eb4ac; margin-right:8px; } .rating { border-top:1px solid #304640; padding:12px 0; } .rating strong { color:#8ee3bf; } .picker-item { display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; } .picker-item:hover,.picker-item:focus-visible { background:#2c4940; border-color:#8ee3bf; } .picker-item small { color:#b4c8c0; } .picker-kind { margin-right:8px; } @media(max-width:600px) { body { padding:24px 14px; } .map { height:720px; margin-left:58px; } .axis-y { left:-40px; } }

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    - \ No newline at end of file +function showCluster(items){content.innerHTML=`

    ${items.length} notes at this position

    Select a note

    ${items.map((note,i)=>``).join('')}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(items[Number(b.dataset.index)]))} +document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{if(m.dataset.cluster){const items=plots[m.dataset.plot].filter(n=>n.position&&`${n.position.x}:${n.position.y}`===m.dataset.cluster.split(':').slice(1).join(':'));showCluster(items)}else{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}});dialog.addEventListener('click',e=>{if(e.target===dialog)dialog.close()}); \ No newline at end of file diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index 03cb266..6fb4f3e 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -176,7 +176,8 @@ def generate_html(notes: list[Note], plots: dict) -> str: ) for item in payload: if item["position"] is None: - unplaced.append(f'
  • {html.escape(item["title"])}
  • ') + label = "Research" if item["kind"] == "research" else "Idea" + unplaced.append(f'
  • {label}{html.escape(item["title"])}
  • ') title = html.escape(plot["title"]) x = plot["x"] y = plot["y"] @@ -190,16 +191,16 @@ def generate_html(notes: list[Note], plots: dict) -> str: .matrix {{ margin-top:48px; }} .axis-x,.axis-y {{ position:absolute; color:#9eb4ac; font-size:.75rem; letter-spacing:.08em; text-transform:uppercase; }} .axis-x {{ left:0; right:0; bottom:-34px; text-align:center; }} .axis-y {{ writing-mode:vertical-rl; transform:rotate(180deg) translateY(50%); left:-52px; top:50%; text-align:center; white-space:nowrap; }} .marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .legend {{ display:flex; gap:22px; color:#b4c8c0; font-size:.9rem; }} .legend span::before {{ content:""; display:inline-block; width:11px; height:11px; margin-right:7px; background:#64c5a0; border-radius:50%; }} .legend .idea-key::before {{ background:#e6a85b; border-radius:0; transform:rotate(45deg); }} -.unplaced {{ margin-top:56px; border-top:1px solid #304640; padding-top:18px; }} a {{ color:#8ee3bf; }} dialog {{ max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; }} dialog::backdrop {{ background:#020505bb; }} .close {{ float:right; background:none; color:inherit; border:0; font-size:1.5rem; cursor:pointer; }} .tag {{ color:#9eb4ac; margin-right:8px; }} .rating {{ border-top:1px solid #304640; padding:12px 0; }} .rating strong {{ color:#8ee3bf; }} +.unplaced {{ margin-top:56px; border-top:1px solid #304640; padding-top:18px; }} .unplaced summary {{ color:#9eb4ac; cursor:pointer; }} .unplaced ul {{ list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; }} .unplaced li {{ display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; }} .note-kind,.picker-kind {{ display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; }} .note-kind.research,.picker-kind.research {{ color:#bff7df; background:#245744; }} .note-kind.idea,.picker-kind.idea {{ color:#ffe0ad; background:#654522; }} a {{ color:#8ee3bf; }} dialog {{ max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; }} dialog::backdrop {{ background:#020505bb; }} button {{ font:inherit; }} .close {{ float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; }} .tag {{ color:#9eb4ac; margin-right:8px; }} .rating {{ border-top:1px solid #304640; padding:12px 0; }} .rating strong {{ color:#8ee3bf; }} .picker-item {{ display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; }} .picker-item:hover,.picker-item:focus-visible {{ background:#2c4940; border-color:#8ee3bf; }} .picker-item small {{ color:#b4c8c0; }} .picker-kind {{ margin-right:8px; }} @media(max-width:600px) {{ body {{ padding:24px 14px; }} .map {{ height:720px; margin-left:58px; }} .axis-y {{ left:-40px; }} }}

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    ResearchIdea
    {''.join(matrices)}
    -''' +function showCluster(items){{content.innerHTML=`

    ${{items.length}} notes at this position

    Select a note

    ${{items.map((note,i)=>``).join('')}}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(items[Number(b.dataset.index)]))}} +document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const items=plots[m.dataset.plot].filter(n=>n.position&&`${{n.position.x}}:${{n.position.y}}`===m.dataset.cluster.split(':').slice(1).join(':'));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 40ebbd1..3925fd1 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -115,6 +115,33 @@ def test_generated_html_contains_cluster_marker_and_picker(self): self.assertIn('function showCluster', html) self.assertIn('picker-item', html) + def test_generated_html_wires_singletons_and_close_button(self): + html = generate_html(load_notes(), load_plots()) + self.assertIn("show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))", html) + self.assertIn("document.querySelector('.close').onclick=()=>dialog.close()", html) + + def test_generated_html_styles_note_lists_and_picker_items(self): + html = generate_html(load_notes(), load_plots()) + self.assertIn(".unplaced li", html) + self.assertIn(".picker-item", html) + self.assertIn("font:inherit", html) + + def test_unplaced_list_distinguishes_ideas_and_research(self): + notes = [next(note for note in load_notes() if note.kind == kind) for kind in ("research", "idea")] + notes[0].metadata["ratings"].pop("maturity") + notes[1].metadata["ratings"].pop("maturity") + html = generate_html(notes, load_plots()) + self.assertIn('class="note-kind research"', html) + self.assertIn('class="note-kind idea"', html) + self.assertIn('class="note-type research"', html) + self.assertIn('class="note-type idea"', html) + + def test_picker_items_distinguish_ideas_and_research(self): + html = generate_html(load_notes(), load_plots()) + self.assertIn('class="picker-kind ${note.kind}"', html) + self.assertIn('.picker-kind.research', html) + self.assertIn('.picker-kind.idea', html) + if __name__ == "__main__": unittest.main() From faeb15858373d5c3588f8e2b953512771eb03c02 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 14:33:44 +0200 Subject: [PATCH 09/41] docs: specify rating recalibration --- .../2026-09-02-rating-recalibration-design.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-02-rating-recalibration-design.md diff --git a/docs/superpowers/specs/2026-09-02-rating-recalibration-design.md b/docs/superpowers/specs/2026-09-02-rating-recalibration-design.md new file mode 100644 index 0000000..cf74595 --- /dev/null +++ b/docs/superpowers/specs/2026-09-02-rating-recalibration-design.md @@ -0,0 +1,71 @@ +# Rating Recalibration Design + +## Purpose + +Replace the initial coarse heuristic scores with evidence-based ratings derived from a full review of every research note and idea. The new ratings should be semantically meaningful, comparatively consistent, and useful across the generated matrices without forcing an artificial distribution. + +## Evaluation method + +Use independent full-note review followed by corpus calibration. + +During the independent pass, read each complete Markdown document and score it against the fixed rubrics below without using the existing value as an anchor. Rewrite every rating note to cite evidence specific to that document. + +During corpus calibration, compare the complete score table for inconsistent relative judgments, unexpectedly narrow ranges, excessive duplicate values, strong correlations, and matrix quadrant occupancy. Adjust a value only when the comparison reveals an inconsistency with the rubric or with similarly situated notes. Do not assign quotas or stretch scores merely to populate quadrants. + +## Rating definitions + +### Platform impact + +`platform-impact` measures the size and depth of the current Cloud Foundry capability gap exposed by the note. + +- `0`: Cloud Foundry already provides the capability directly and adequately. +- `25`: A small gap exists around integration, ergonomics, or packaging. +- `50`: Relevant CF primitives exist, but meaningful integration or capability is missing. +- `75`: A substantial platform capability is absent or fragmented. +- `100`: A foundational capability is effectively absent and would require major platform work. + +### Maturity + +`maturity` measures the technology's production adoption, stability, standardization, and operational evidence. + +- `0`: Speculative concept with no demonstrated implementation. +- `25`: Early implementation or experimental specification. +- `50`: Credible implementation with limited production evidence or stability. +- `75`: Production-capable technology with meaningful adoption and operational evidence. +- `100`: Stable, standardized, broadly proven technology with long-term operational evidence. + +### Novelty + +`novelty` measures how new or unconventional the underlying technical architecture or capability is in the wider ecosystem. + +- `0`: Conventional, long-established architecture or capability. +- `25`: Familiar architecture adapted to an agent context. +- `50`: A newer combination or substantial adaptation of known patterns. +- `75`: An emerging architecture with limited precedent. +- `100`: A genuinely novel architecture or capability with almost no prior art. + +### Actionability + +`actionability` measures how directly the note supports a concrete Cloud Foundry experiment, investigation, or RFC next step. + +- `0`: No concrete CF next step follows from the note. +- `25`: The connection is speculative and needs broad discovery first. +- `50`: Plausible next steps exist but require substantial scoping. +- `75`: A bounded experiment or investigation follows with manageable open questions. +- `100`: The note directly defines a well-bounded experiment, investigation, or RFC question. + +## Rating notes + +Each rating retains the existing `value` and `note` structure. The note must explain the score using document-specific evidence rather than repeating a generic rubric statement. Where relevant, it should mention demonstrated adoption, specification state, available CF primitives, missing CF capability, or a concrete next step. + +## Coverage and safeguards + +Re-evaluate every non-template Markdown file under `research/` and `ideas/`, including untracked contributions present in the working tree when the pass begins. Every document must contain all four ratings and non-empty notes. + +Add automated checks that report each rating's minimum, maximum, distinct-value count, and quadrant occupancy. These checks should detect severe collapse, such as every value occupying one side of the midpoint, but must use conservative thresholds and must not prescribe a target distribution. + +The generated HTML must be regenerated after recalibration. Existing interaction and styling fixes remain separate from the scoring logic. + +## Verification + +Run note validation, rating coverage tests, distribution safeguards, generator tests, generated-artifact freshness checks, and whitespace checks. Review a corpus summary showing ranges, quartiles, duplicate counts, correlations, and quadrant occupancy before accepting the recalibration. From d1477caf69136cb10b35f285a99c8095441e84ec Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 14:35:00 +0200 Subject: [PATCH 10/41] docs: add rating recalibration plan --- .../plans/2026-09-02-rating-recalibration.md | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-02-rating-recalibration.md diff --git a/docs/superpowers/plans/2026-09-02-rating-recalibration.md b/docs/superpowers/plans/2026-09-02-rating-recalibration.md new file mode 100644 index 0000000..4f03da3 --- /dev/null +++ b/docs/superpowers/plans/2026-09-02-rating-recalibration.md @@ -0,0 +1,215 @@ +# Rating Recalibration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace heuristic ratings on every research note and idea with full-document, evidence-based scores that use fixed semantic rubrics and corpus-wide comparative calibration. + +**Architecture:** Each document remains the source of truth for four rating objects and evidence-specific notes. Review documents independently in manageable batches without consulting their existing values, then analyze the complete corpus for inconsistent judgments and severe distribution collapse. Regenerate the static map only after calibrated source ratings pass coverage and distribution safeguards. + +**Tech Stack:** Markdown/YAML frontmatter, Python 3.12, PyYAML, unittest, generated HTML. + +--- + +## Files and Responsibilities + +- Modify every non-template `research/*.md`: recalibrated `platform-impact`, `maturity`, `novelty`, and `actionability` values and notes. +- Modify every non-template `ideas/*.md`: the same recalibrated rating objects. +- Modify `tests/test_generate_research_map.py`: conservative safeguards against severe range collapse and missing evidence notes. +- Create `scripts/summarize_ratings.py`: deterministic corpus summary for range, quartile, distinct-value, correlation, and quadrant review. +- Modify `.github/workflows/lint.yml`: run the rating summary's validation mode if appropriate. +- Regenerate `generated/research-map.html`: calibrated map artifact. + +### Task 1: Add corpus diagnostics and conservative safeguards + +**Files:** +- Create: `scripts/summarize_ratings.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing safeguard tests** + +Add tests for a pure `summarize_ratings(notes)` function. Use fixtures proving it returns `count`, `minimum`, `maximum`, `distinct`, `median`, first/third quartiles, and quadrant counts. Add a validation test that rejects a synthetic corpus where an entire required rating lies strictly above 50 or has fewer than five distinct values. + +- [ ] **Step 2: Run tests and verify the expected failure** + +Run: `devbox run -- python -m unittest tests.test_generate_research_map` + +Expected: FAIL because `scripts.summarize_ratings` does not exist. + +- [ ] **Step 3: Implement the diagnostic script** + +Implement `summarize_ratings(notes)` using Python standard-library statistics. The CLI loads notes through `scripts.generate_research_map.load_notes`, prints per-rating count/min/max/median/quartiles/distinct values, Pearson correlations, and six matrix quadrant counts. Add `--check` that fails only when a required rating is missing, a rating has fewer than five distinct values, or every value lies strictly on one side of 50. These are severe-collapse guards, not distribution targets. + +- [ ] **Step 4: Verify diagnostics against synthetic fixtures** + +Run: `devbox run -- python -m unittest tests.test_generate_research_map` + +Expected: tests PASS, while `devbox run -- python scripts/summarize_ratings.py --check` still fails against the current coarse corpus. + +- [ ] **Step 5: Commit diagnostics** + +Run: `git add scripts/summarize_ratings.py tests/test_generate_research_map.py && git commit -m "test: add rating distribution safeguards"` + +### Task 2: Independently re-evaluate research notes, batch 1 + +**Files:** +- Modify: `research/a2a-protocol.md` through `research/hatchet.md`, alphabetically, excluding templates/readmes. + +- [ ] **Step 1: Read each complete document** + +For each file, read frontmatter and all body sections. Do not use the current values as anchors. Record evidence for current CF gap, technology maturity, technical novelty, and clarity of a concrete CF next step. + +- [ ] **Step 2: Replace all four rating objects** + +Assign integer values using the rubric anchors in `docs/superpowers/specs/2026-09-02-rating-recalibration-design.md`. Rewrite every `note` with document-specific evidence. Avoid generic phrases such as “this score reflects”. + +- [ ] **Step 3: Review within-batch consistency** + +Compare similar technologies in the batch. Confirm a higher score has a rubric-based reason and that scores are not mechanically tied to tags or note type. + +- [ ] **Step 4: Validate the batch** + +Run: `devbox run validate && devbox run -- python -m unittest discover -s tests` + +Expected: note schema and tests PASS. + +- [ ] **Step 5: Commit batch 1** + +Run: `git add research && git commit -m "docs: recalibrate research ratings batch one"` + +### Task 3: Independently re-evaluate research notes, batch 2 + +**Files:** +- Modify: remaining non-template `research/*.md` files alphabetically after `hatchet.md`. + +- [ ] **Step 1: Read each complete document without consulting old values** + +Evaluate the same four evidence categories against fixed rubric anchors. + +- [ ] **Step 2: Replace values and generic notes** + +Write four evidence-specific rating objects per file. Platform impact means current CF gap, not ecosystem importance or potential impact. + +- [ ] **Step 3: Compare related technologies** + +Check relative consistency among frameworks, protocols, managed runtimes, execution systems, isolation technologies, and observability standards while preserving their absolute rubric meaning. + +- [ ] **Step 4: Validate the batch** + +Run: `devbox run validate && devbox run -- python -m unittest discover -s tests` + +Expected: PASS. + +- [ ] **Step 5: Commit batch 2** + +Run: `git add research && git commit -m "docs: recalibrate research ratings batch two"` + +### Task 4: Independently re-evaluate all ideas + +**Files:** +- Modify: every non-template `ideas/*.md`. + +- [ ] **Step 1: Read each complete idea** + +Use the idea's rationale, related work, and proposed next research to score the current CF gap, maturity of its underlying technical approach, technical novelty, and next-step clarity. + +- [ ] **Step 2: Replace all rating values and notes** + +Do not automatically rate ideas as less mature or more novel than research notes. Score the underlying capability or architecture; use the document's concreteness specifically for actionability. + +- [ ] **Step 3: Review idea/research consistency** + +Compare ideas with their linked research notes. Explain legitimate differences through the rating notes rather than forcing equal values. + +- [ ] **Step 4: Validate and commit** + +Run: `devbox run validate && devbox run -- python -m unittest discover -s tests` + +Expected: PASS. + +Run: `git add ideas && git commit -m "docs: recalibrate idea ratings"` + +### Task 5: Perform corpus-wide hybrid calibration + +**Files:** +- Modify: any rated note whose comparison reveals a rubric inconsistency. + +- [ ] **Step 1: Generate the corpus summary** + +Run: `devbox run -- python scripts/summarize_ratings.py` + +Review per-rating ranges, quartiles, distinct-value counts, correlations, and six matrix quadrant counts. + +- [ ] **Step 2: Compare scoring outliers and near-neighbors** + +For each attribute, inspect documents at the minimum, maximum, quartile boundaries, and duplicate-heavy values. Confirm their order against the semantic rubric and document evidence. + +- [ ] **Step 3: Correct only inconsistent judgments** + +Adjust scores or notes where full-document evidence does not support the current relative order or absolute anchor. Do not percentile-stretch values and do not assign quadrant quotas. + +- [ ] **Step 4: Run severe-collapse safeguards** + +Run: `devbox run -- python scripts/summarize_ratings.py --check` + +Expected: PASS with all four ratings represented by at least five distinct values and values occurring on both sides of 50. + +- [ ] **Step 5: Commit calibration corrections** + +Run: `git add research ideas && git commit -m "docs: calibrate ratings across the corpus"` + +### Task 6: Integrate diagnostics into CI and regenerate the map + +**Files:** +- Modify: `.github/workflows/lint.yml` +- Modify: `generated/research-map.html` + +- [ ] **Step 1: Add the safeguard command to CI** + +Add `python scripts/summarize_ratings.py --check` after note validation and before generated-map freshness validation. Include `scripts/summarize_ratings.py` in workflow path filters through the existing `scripts/**` rule. + +- [ ] **Step 2: Regenerate the map** + +Run: `devbox run map` + +Expected: the six matrices reflect recalibrated source values and existing cluster/list interaction behavior remains intact. + +- [ ] **Step 3: Run complete verification** + +Run: `devbox run -- python -m unittest discover -s tests && devbox run validate && devbox run -- python scripts/summarize_ratings.py --check && devbox run -- python scripts/generate_research_map.py --check && git diff --check` + +Expected: all commands exit 0. + +- [ ] **Step 4: Review the final summary** + +Run `devbox run -- python scripts/summarize_ratings.py` and save the key ranges, quartiles, correlations, and quadrant counts for the PR summary. Verify the distribution is plausible rather than merely broad. + +- [ ] **Step 5: Commit CI and artifact updates** + +Run: `git add .github/workflows/lint.yml generated/research-map.html && git commit -m "ci: validate recalibrated ratings"` + +### Task 7: Final review and PR handoff + +**Files:** +- Review: all changed `research/*.md` and `ideas/*.md` +- Review: `scripts/summarize_ratings.py` +- Review: `generated/research-map.html` + +- [ ] **Step 1: Verify full coverage and no generic notes** + +Use a script to confirm all 41 tracked notes, plus any in-scope untracked contribution, have four values and non-empty notes. Search for the old generic wording `Initial review of` and `this score reflects`; expected result is no matches in rating notes. + +- [ ] **Step 2: Run final verification afresh** + +Run: `devbox run -- python -m unittest discover -s tests && devbox run validate && devbox run -- python scripts/summarize_ratings.py --check && devbox run -- python scripts/generate_research_map.py --check && git diff --check` + +Expected: all commands exit 0. + +- [ ] **Step 3: Review branch scope** + +Run: `git status --short --branch && git diff origin/feature/research-clustering-map...HEAD --stat` + +Confirm unrelated Nix directories and `.superpowers/` remain excluded. Handle `research/domyn-swarm.md` according to whether it is tracked or intentionally part of this PR at execution time; never stage it accidentally. + +- [ ] **Step 4: Request commit/push confirmation** + +Present the recalibration summary and ask for explicit permission before pushing commits to PR #42. From 2df8cafb6a1af17966ba63475a840a51a4034daa Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 15:21:52 +0200 Subject: [PATCH 11/41] feat: recalibrate research map ratings --- .github/workflows/lint.yml | 3 + generated/research-map.html | 6 +- ideas/agent-failure-checkpointing.md | 16 +- .../agent-identity-and-tool-authorization.md | 16 +- ideas/credential-less-agent-processes.md | 16 +- ideas/dapr-aware-gorouter.md | 16 +- ideas/dapr-durable-execution-on-cf.md | 16 +- ideas/durable-tasks-for-cf.md | 16 +- ideas/localhost-only-egress-for-agents.md | 16 +- ideas/per-session-sandboxes.md | 16 +- ideas/staged-sandbox-environments.md | 16 +- .../stronger-workload-isolation-for-agents.md | 16 +- research/a2a-protocol.md | 16 +- research/anthropic-managed-agents.md | 16 +- research/aws-agents.md | 16 +- research/azure-hosted-agents.md | 16 +- research/cloudflare-agents.md | 16 +- research/crewai.md | 16 +- research/dapr-agents.md | 16 +- research/dapr.md | 16 +- research/firecracker-microvm.md | 14 +- research/google-adk.md | 16 +- research/hatchet.md | 16 +- research/heroku-ai-platform.md | 16 +- research/k8s-agent-sandbox.md | 14 +- research/kagent.md | 16 +- research/keda.md | 16 +- research/langgraph.md | 16 +- research/letta.md | 16 +- research/llamaindex.md | 14 +- research/mcp-protocol.md | 16 +- research/microsoft-agent-framework.md | 16 +- research/open-agent-auth.md | 14 +- research/openai-agents-sdk.md | 16 +- research/opentelemetry-genai.md | 16 +- research/orleans.md | 16 +- research/tanzu-platform-ai.md | 16 +- research/temporal.md | 16 +- research/toolhive.md | 16 +- research/vercel-ai-sdk.md | 16 +- research/vertex-agent-engine.md | 16 +- research/wasmcloud.md | 16 +- scripts/summarize_ratings.py | 143 ++++++++++++++++++ tests/test_generate_research_map.py | 119 ++++++++++++++- 44 files changed, 581 insertions(+), 322 deletions(-) create mode 100644 scripts/summarize_ratings.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 18e6cbe..9b73b96 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,6 +39,9 @@ jobs: - name: Validate notes and ideas run: python .github/scripts/validate_notes.py + - name: Check rating distributions + run: python scripts/summarize_ratings.py --check + - name: Test map generator run: python -m unittest discover -s tests diff --git a/generated/research-map.html b/generated/research-map.html index 0e458e7..a64661b 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -10,10 +10,10 @@ .unplaced { margin-top:56px; border-top:1px solid #304640; padding-top:18px; } .unplaced summary { color:#9eb4ac; cursor:pointer; } .unplaced ul { list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; } .unplaced li { display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; } .note-kind,.picker-kind { display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; } .note-kind.research,.picker-kind.research { color:#bff7df; background:#245744; } .note-kind.idea,.picker-kind.idea { color:#ffe0ad; background:#654522; } a { color:#8ee3bf; } dialog { max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; } dialog::backdrop { background:#020505bb; } button { font:inherit; } .close { float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; } .tag { color:#9eb4ac; margin-right:8px; } .rating { border-top:1px solid #304640; padding:12px 0; } .rating strong { color:#8ee3bf; } .picker-item { display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; } .picker-item:hover,.picker-item:focus-visible { background:#2c4940; border-color:#8ee3bf; } .picker-item small { color:#b4c8c0; } .picker-kind { margin-right:8px; } @media(max-width:600px) { body { padding:24px 14px; } .map { height:720px; margin-left:58px; } .axis-y { left:-40px; } }

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    -

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    +

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    +
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    - \ No newline at end of file diff --git a/ideas/agent-failure-checkpointing.md b/ideas/agent-failure-checkpointing.md index fc6fbbc..9e7384d 100644 --- a/ideas/agent-failure-checkpointing.md +++ b/ideas/agent-failure-checkpointing.md @@ -5,17 +5,17 @@ date: 2026-08-13 tags: [runtime-lifecycle, sandboxing-isolation] ratings: platform-impact: - value: 84 - note: 'Initial review of Agent Failure Checkpointing: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 75 + note: 'CF restarts crashed apps but cannot restore agent memory, task outputs, queue position, or bound-service session state from a platform-managed checkpoint.' maturity: - value: 38 - note: 'Initial review of Agent Failure Checkpointing: this score reflects the amount of established external practice visible in the note.' + value: 50 + note: 'Framework checkpointers demonstrate credible persistence and resume mechanisms, but transparent platform restoration across agent memory, queues, outputs, and bound-service sessions has no implementation or operational evidence here.' novelty: - value: 80 - note: 'Initial review of Agent Failure Checkpointing: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 25 + note: 'The proposal adapts the established checkpoint/restart pattern to agent conversation state, tool outputs, and work queues rather than introducing a new durability architecture.' actionability: - value: 76 - note: 'Initial review of Agent Failure Checkpointing: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 50 + note: 'The manifest sketch and invoice recovery example define desired behavior, but checkpoint granularity, state boundaries, storage, and multi-tenant quotas still require substantial scoping.' --- diff --git a/ideas/agent-identity-and-tool-authorization.md b/ideas/agent-identity-and-tool-authorization.md index c55d075..606b156 100644 --- a/ideas/agent-identity-and-tool-authorization.md +++ b/ideas/agent-identity-and-tool-authorization.md @@ -5,17 +5,17 @@ date: 2026-08-12 tags: [identity, inter-agent-comms, observability-governance] ratings: platform-impact: - value: 92 - note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: its subject and tags indicate how broadly the capability could affect an agentic platform." + value: 50 + note: "Diego identity certificates, UAA, CredHub, and accepted RFC-0055 cover much of the substrate, but CF lacks workload token exchange, a per-user token vault, and runtime tool-authorization policy." maturity: - value: 38 - note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects the amount of established external practice visible in the note." + value: 75 + note: "Workload identity, OAuth token exchange, mTLS client authentication, and policy engines are production-proven, while the layer-3 AOAT delegation chain remains only an individual IETF draft." novelty: - value: 80 - note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects how distinct or emerging the approach appears in the current landscape." + value: 50 + note: "Exchanging CF instance certificates for scoped workload tokens combines established identity standards in a CF-specific way; user-to-agent-to-tool delegation is the newer element." actionability: - value: 76 - note: "Initial review of Agent identity and tool authorization — the platform as the agent's identity provider: this score reflects how readily the material could guide a focused experiment or follow-up." + value: 100 + note: "Accepted RFC-0055 provides a no-new-component first step, and UAA PRs #3972 and #3968 give concrete POCs for certificate exchange and JWT-SVID issuance to evaluate." --- diff --git a/ideas/credential-less-agent-processes.md b/ideas/credential-less-agent-processes.md index e45ec69..a484c78 100644 --- a/ideas/credential-less-agent-processes.md +++ b/ideas/credential-less-agent-processes.md @@ -5,17 +5,17 @@ date: 2026-07-08 tags: [identity, sandboxing-isolation] ratings: platform-impact: - value: 84 - note: 'Initial review of Credential-less agent processes: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 50 + note: 'CF can bind and store service credentials, but it exposes them to the app process; a platform-held credential and localhost request proxy are missing.' maturity: - value: 38 - note: 'Initial review of Credential-less agent processes: this score reflects the amount of established external practice visible in the note.' + value: 75 + note: 'Credential vaults and outbound credential proxies are production-capable patterns used by managed agent platforms, though the note leaves CF provisioning and multi-tenant bindings unresolved.' novelty: - value: 80 - note: 'Initial review of Credential-less agent processes: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 25 + note: 'Keeping secrets in a local proxy is an established vault-and-sidecar architecture, here applied to prompt-injection risk in agent processes.' actionability: - value: 76 - note: 'Initial review of Credential-less agent processes: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 50 + note: 'The standard-provider API over localhost supplies a plausible prototype boundary, but credential storage, sidecar provisioning, rotation, and per-user tenancy need design first.' --- diff --git a/ideas/dapr-aware-gorouter.md b/ideas/dapr-aware-gorouter.md index d4c8eae..7894164 100644 --- a/ideas/dapr-aware-gorouter.md +++ b/ideas/dapr-aware-gorouter.md @@ -5,17 +5,17 @@ date: 2026-08-11 tags: [inter-agent-comms, runtime-lifecycle, orchestration] ratings: platform-impact: - value: 92 - note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 50 + note: 'GoRouter already has instance-addressed routing, endpoint metadata, and NATS updates, but it cannot resolve an actor ID to the instance that currently owns the work.' maturity: - value: 38 - note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects the amount of established external practice visible in the note.' + value: 50 + note: 'Dapr placement and CF routing are production-capable ingredients, but no implementation or operational evidence demonstrates actor-to-instance resolution through GoRouter or safe behavior during placement migration.' novelty: - value: 80 - note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 75 + note: 'Making a PaaS edge router consume or replace a virtual-actor placement table is an emerging combination, especially with authenticated actor-addressed routing through RFC-0055.' actionability: - value: 76 - note: 'Initial review of Dapr-aware GoRouter — routing to the instance where the work lives: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 75 + note: 'The shallow design bounds an experiment to daprd placement lookup plus X-CF-APP-INSTANCE, with explicit checks for host-to-index mapping, header propagation, and migration correctness.' --- diff --git a/ideas/dapr-durable-execution-on-cf.md b/ideas/dapr-durable-execution-on-cf.md index b355d7c..9d69ff3 100644 --- a/ideas/dapr-durable-execution-on-cf.md +++ b/ideas/dapr-durable-execution-on-cf.md @@ -5,17 +5,17 @@ date: 2026-08-11 tags: [runtime-lifecycle, orchestration, identity, inter-agent-comms] ratings: platform-impact: - value: 92 - note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: its subject and tags indicate how broadly the capability could affect an agentic platform." + value: 75 + note: "CF has identity, bindings, process injection, and partial placement primitives, but it has no workflow engine, virtual actors, durable scheduler, timers, or reminders." maturity: - value: 38 - note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects the amount of established external practice visible in the note." + value: 75 + note: "Dapr is CNCF-graduated and provides production-capable polyglot workflow, actor, scheduling, identity, and sidecar APIs; Dapr Agents v1.0 is GA, though the proposed CF integration remains unimplemented." novelty: - value: 80 - note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects how distinct or emerging the approach appears in the current landscape." + value: 50 + note: "The design substantially adapts known Dapr sidecar and control-plane patterns by substituting Diego identity, service bindings, Envoy-style injection, and potentially GoRouter placement." actionability: - value: 76 - note: "Initial review of Dapr durable execution on CF, built on CF's own identity and config primitives: this score reflects how readily the material could guide a focused experiment or follow-up." + value: 75 + note: "The component-by-component mapping and three adoption strategies bound investigations into external-cert trust, scheduler storage, injection cost, and placement without requiring a full platform design first." --- diff --git a/ideas/durable-tasks-for-cf.md b/ideas/durable-tasks-for-cf.md index 03463db..76c1ad8 100644 --- a/ideas/durable-tasks-for-cf.md +++ b/ideas/durable-tasks-for-cf.md @@ -5,17 +5,17 @@ date: 2026-08-11 tags: [runtime-lifecycle, orchestration] ratings: platform-impact: - value: 92 - note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 100 + note: 'Between one-shot Tasks and always-running app instances, CF has no stable execution identity, suspend/resume lifecycle, durable event wakeup, timers, scheduler, or retry primitive.' maturity: - value: 38 - note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects the amount of established external practice visible in the note.' + value: 50 + note: 'Temporal, Dapr, and Azure Durable Task prove the component semantics, but the proposed CF execution resource, suspend/resume lifecycle, per-cell API, and framework adapters have no demonstrated implementation or operations.' novelty: - value: 80 - note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 50 + note: 'The proposal recombines established workflow identity and ephemeral compute-slice patterns into a deliberately narrow CAPI resource plus per-cell API rather than a workflow engine.' actionability: - value: 76 - note: 'Initial review of Durable execution as a CF-native primitive — what tasks are missing: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 75 + note: 'A CAPI execution row, Diego Tasks as compute slices, bound-service state pointers, and mTLS per-cell control calls define a bounded architecture to test against framework adapters.' --- diff --git a/ideas/localhost-only-egress-for-agents.md b/ideas/localhost-only-egress-for-agents.md index eaddb75..e712601 100644 --- a/ideas/localhost-only-egress-for-agents.md +++ b/ideas/localhost-only-egress-for-agents.md @@ -5,17 +5,17 @@ date: 2026-07-08 tags: [sandboxing-isolation, observability-governance] ratings: platform-impact: - value: 92 - note: 'Initial review of Localhost-only egress for agent workloads: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 75 + note: 'CF lacks a platform-owned, non-bypassable outbound proxy that turns declared external bindings into enforced destinations and request-level audit logs for agent traffic.' maturity: - value: 38 - note: 'Initial review of Localhost-only egress for agent workloads: this score reflects the amount of established external practice visible in the note.' + value: 75 + note: 'The 75 reflects mature, widely deployed proxy, allowlist, interception, and request-logging technologies; it does not imply maturity for the proposed non-bypassable CF integration, binding-derived policy, or ownership model, which lack implementation evidence.' novelty: - value: 80 - note: 'Initial review of Localhost-only egress for agent workloads: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 25 + note: 'The architecture applies conventional mandatory-egress-proxy and allowlist controls to inference-selected destinations rather than creating a new networking mechanism.' actionability: - value: 76 - note: 'Initial review of Localhost-only egress for agent workloads: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 50 + note: 'Declared bindings, a localhost endpoint, enforced forwarding, and logging give a prototype outline, but the proxy component, bypass prevention, and policy ownership remain unspecified.' --- diff --git a/ideas/per-session-sandboxes.md b/ideas/per-session-sandboxes.md index 5ee164f..5f0234c 100644 --- a/ideas/per-session-sandboxes.md +++ b/ideas/per-session-sandboxes.md @@ -5,17 +5,17 @@ date: 2026-07-02 tags: [runtime-lifecycle, sandboxing-isolation] ratings: platform-impact: - value: 84 - note: 'Initial review of Per-session sandboxes with lifecycle states: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 75 + note: 'CF has no per-session sandbox resource that can release CPU while retaining disk or serialize state to blobstore, leaving only continuously running or state-losing processes.' maturity: - value: 38 - note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects the amount of established external practice visible in the note.' + value: 50 + note: 'Kubernetes Agent Sandbox demonstrates warm pools, PVC persistence, hibernation, and gVisor snapshots, but the agent-specific control plane and graduated lifecycle remain relatively early.' novelty: - value: 80 - note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 50 + note: 'Session-scoped isolated compute combines known pooling, suspend/resume, persistent-volume, and blob-checkpoint patterns into a newer agent sandbox lifecycle.' actionability: - value: 76 - note: 'Initial review of Per-session sandboxes with lifecycle states: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 50 + note: 'The three lifecycle states and external K8s and Anthropic comparisons identify a direction, but snapshot mechanics, storage format, and available CF volume/blobstore primitives need scoping.' --- diff --git a/ideas/staged-sandbox-environments.md b/ideas/staged-sandbox-environments.md index 604cd60..664d3e9 100644 --- a/ideas/staged-sandbox-environments.md +++ b/ideas/staged-sandbox-environments.md @@ -5,17 +5,17 @@ date: 2026-08-24 tags: [runtime-lifecycle, sandboxing-isolation, ecosystem-survey] ratings: platform-impact: - value: 84 - note: 'Initial review of Split environment staging from workspace state for agent sandboxes: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 50 + note: 'CF already stages Packages into Droplets and runs Tasks, but cross-app content-addressed Droplet reuse and mounting a separate mutable workspace Package are meaningful missing integrations.' maturity: - value: 46 - note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects the amount of established external practice visible in the note.' + value: 50 + note: 'CF staging and Tasks prove several ingredients, but cross-app content-addressed Droplet reuse, separate mutable workspace mounting, and checkpointed diffs are an unimplemented composition without operational evidence.' novelty: - value: 72 - note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 50 + note: 'Separating a globally cached environment Droplet from a per-turn workspace Package is a substantial adaptation of CF staging and Nix-style substitution to generated agent code.' actionability: - value: 76 - note: 'Initial review of Split environment staging from workspace state for agent sandboxes: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 75 + note: 'Three concrete operations map to CAPI Build, Droplet, Package, resource-match, and Task resources, with focused questions around cross-tenant reuse and extra volume mounting.' --- diff --git a/ideas/stronger-workload-isolation-for-agents.md b/ideas/stronger-workload-isolation-for-agents.md index f6bbe53..9b71712 100644 --- a/ideas/stronger-workload-isolation-for-agents.md +++ b/ideas/stronger-workload-isolation-for-agents.md @@ -5,17 +5,17 @@ date: 2026-07-08 tags: [sandboxing-isolation, runtime-lifecycle] ratings: platform-impact: - value: 84 - note: 'Initial review of Stronger workload isolation for agent workloads: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 50 + note: 'CF supplies container isolation but offers no gVisor- or Kata-style runtime choice to put inference-selected code behind a user-space or dedicated-kernel boundary.' maturity: - value: 38 - note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects the amount of established external practice visible in the note.' + value: 75 + note: 'The 75 reflects production-capable gVisor, Kata, and Kubernetes RuntimeClass isolation technologies; it does not imply maturity for manifest-selectable isolation through Garden and Diego, which has no demonstrated implementation, lifecycle integration, or CF operational evidence.' novelty: - value: 80 - note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 25 + note: 'A manifest-selectable sandbox runtime is the familiar RuntimeClass pattern adapted to CF applications that execute unreviewed agent-generated instructions.' actionability: - value: 76 - note: 'Initial review of Stronger workload isolation for agent workloads: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 50 + note: 'The runtime: sandbox field and gVisor target define a plausible spike, but the Garden or Diego enforcement point, lifecycle wiring, and operator policy are still open.' --- diff --git a/research/a2a-protocol.md b/research/a2a-protocol.md index 0cd666e..b909adf 100644 --- a/research/a2a-protocol.md +++ b/research/a2a-protocol.md @@ -10,17 +10,17 @@ sources: - https://github.com/a2aproject/A2A ratings: platform-impact: - value: 66 - note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 55 + note: 'CF already supplies HTTP routing and UAA trust primitives, but it lacks A2A Agent Card discovery and explicit handling for stateful, long-running delegated Tasks across org and space boundaries.' maturity: - value: 76 - note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects the amount of established external practice visible in the note.' + value: 68 + note: 'A2A is a Linux Foundation open standard with authentication, authorization, streaming, async Tasks, and an extension mechanism, although the note does not present long-term operational evidence.' novelty: - value: 62 - note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects how distinct or emerging the approach appears in the current landscape.' - actionability: value: 58 - note: 'Initial review of Agent2Agent (A2A) Protocol — Agent-to-Agent Interoperability: this score reflects how readily the material could guide a focused experiment or follow-up.' + note: 'Agent Cards and stateful delegation standardize an emerging agent-to-agent layer, but reuse familiar decentralized HTTP discovery, capability metadata, and asynchronous task patterns.' + actionability: + value: 72 + note: 'The note identifies a bounded CF investigation: expose Agent Cards through route metadata or a scoped registry, then test delegated Tasks and UAA authentication across spaces.' --- diff --git a/research/anthropic-managed-agents.md b/research/anthropic-managed-agents.md index 8241775..0b63a24 100644 --- a/research/anthropic-managed-agents.md +++ b/research/anthropic-managed-agents.md @@ -8,17 +8,17 @@ sources: - https://www.anthropic.com/engineering/managed-agents ratings: platform-impact: - value: 70 - note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 75 + note: 'Stateless CF app processes map to the brain, but CF lacks on-demand sandbox hands, an external append-only agent session service, and credential proxies that keep secrets out of generated-code environments.' maturity: - value: 68 - note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects the amount of established external practice visible in the note.' + value: 62 + note: 'Anthropic reports operating the architecture and measured p50 TTFT improvements of about 60% and p95 improvements over 90%, but publishes neither a specification nor an open implementation for its session interface.' novelty: - value: 70 - note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 72 + note: 'The independently scalable brain, replaceable hands, and external positional event log form an emerging decomposition, strengthened by vault-backed proxies that structurally exclude credentials from both harness and sandbox.' actionability: - value: 66 - note: 'Initial review of Anthropic Managed Agents — Remote Hands Architecture: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 78 + note: 'CF can prototype a stateless brain app against a durable event service and on-demand sandbox, then route one service credential through a proxy instead of injecting it into the sandbox.' --- diff --git a/research/aws-agents.md b/research/aws-agents.md index a891bc6..2e71fd6 100644 --- a/research/aws-agents.md +++ b/research/aws-agents.md @@ -21,17 +21,17 @@ sources: - https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html ratings: platform-impact: - value: 78 - note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 85 + note: 'AgentCore exposes a broad CF gap spanning per-session microVMs, persistent resumable filesystems, agent identity, memory, MCP gateway and Cedar policy, registry, evaluations, and automated optimization.' maturity: - value: 76 - note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects the amount of established external practice visible in the note.' + value: 78 + note: 'AgentCore is a documented managed service with multiple compute modes and integrations, while Apache-licensed Strands has Python and TypeScript SDKs, about 6.9k stars, governance, releases, and many deployment targets.' novelty: - value: 62 - note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 58 + note: 'MicroVM session isolation, gateways, Cedar policy, OTel, and agent loops are established ideas; their modular assembly into an observe-evaluate-optimize managed agent platform is a newer combination.' actionability: - value: 66 - note: 'Initial review of AWS Strands Agents & Bedrock AgentCore — Framework and Managed Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 72 + note: 'The framework-agnostic platform raises several large design choices, but Strands provides a concrete CF prototype target for MCP/A2A deployment and for testing gateway-enforced Cedar authorization on tool calls.' --- diff --git a/research/azure-hosted-agents.md b/research/azure-hosted-agents.md index 841c20b..dd8d4f1 100644 --- a/research/azure-hosted-agents.md +++ b/research/azure-hosted-agents.md @@ -9,17 +9,17 @@ sources: - https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents ratings: platform-impact: - value: 78 - note: 'Initial review of Azure Foundry — Hosted Agents Model: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 82 + note: 'CF can already deploy container images, but lacks Azure Foundry''s per-session VM sandboxes, persistent stateful resume, scale-to-zero session lifecycle, automatic per-deployment identity, and injected agent telemetry.' maturity: - value: 76 - note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects the amount of established external practice visible in the note.' + value: 74 + note: 'The hosted service documents concrete quotas, a 15-minute idle timeout, 30-day session lifetime, Entra identity, persistent filesystems, and three protocols, though the note provides limited independent adoption history.' novelty: - value: 62 - note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 61 + note: 'Container packaging and dedicated identity are familiar PaaS features, while VM-isolated per-session scaling with persistent resume and managed conversation protocols is a newer agent-specific synthesis.' actionability: - value: 66 - note: 'Initial review of Azure Foundry — Hosted Agents Model: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 75 + note: 'A bounded CF spike can package one A2A agent, add OTel auto-instrumentation and identity bootstrap, and evaluate whether session persistence and isolation can be layered onto Diego processes.' --- diff --git a/research/cloudflare-agents.md b/research/cloudflare-agents.md index 8ce36c3..711e964 100644 --- a/research/cloudflare-agents.md +++ b/research/cloudflare-agents.md @@ -16,17 +16,17 @@ sources: - https://developers.cloudflare.com/durable-objects/ ratings: platform-impact: - value: 78 - note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 88 + note: 'CF has no equivalent to a cheap globally addressable compute unit that combines isolate execution, private colocated SQLite, hibernation-safe WebSockets, durable alarms, and automatic geographic placement.' maturity: - value: 76 - note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects the amount of established external practice visible in the note.' + value: 72 + note: 'The SDK rests on production Durable Objects that scale to millions of instances and supplies state, scheduling, MCP, workflows, and tracing, but remains Cloudflare-controlled, rejects external contributions, and lightly documents A2A.' novelty: - value: 62 - note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 82 + note: 'Making every agent a single-threaded V8 isolate with its own colocated SQLite database, durable alarms, transparent hibernation, and global identity departs sharply from container, microVM, and external-state agent runtimes.' actionability: - value: 66 - note: 'Initial review of Cloudflare Agents SDK — Agents as Durable Objects (V8-Isolate Isolation): this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 68 + note: 'The note defines a focused comparison of isolate trust, hibernation, and bindable per-agent state, but a CF prototype requires substantial design because Durable Objects'' routing and storage substrate is not portable.' --- diff --git a/research/crewai.md b/research/crewai.md index 55e7856..cb4b959 100644 --- a/research/crewai.md +++ b/research/crewai.md @@ -18,17 +18,17 @@ sources: - https://docs-platform.crewai.com/platform/en/introduction ratings: platform-impact: - value: 78 - note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 52 + note: 'CrewAI''s OSS crews, flows, MCP/A2A support, memory, and SQLite checkpoints can run inside a normal CF app; the main gap is optional platform support for durable storage, hosted tracing, deployment, and RBAC.' maturity: - value: 76 - note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects the amount of established external practice visible in the note.' + value: 78 + note: 'The MIT-licensed package is published on PyPI with about 56.9k stars, 8.1k forks, 2,732-plus commits, broad orchestration features, and a commercial hosted platform, indicating meaningful adoption.' novelty: - value: 62 - note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 46 + note: 'Role-based LLM teams and event-driven workflows adapt familiar manager, DAG, checkpoint, and pub/sub patterns; checkpoint forking and the recommended Flow-around-Crew composition add a newer agent-specific layer.' actionability: - value: 66 - note: 'Initial review of CrewAI — Role-Based Crews and Event-Driven Flows: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 70 + note: 'CF can deploy an OSS CrewAI Flow and test SQLite checkpoint resume and fork behavior, although deciding whether AMP-like tracing and RBAC belong in the platform needs additional scope.' --- diff --git a/research/dapr-agents.md b/research/dapr-agents.md index c3e14e9..db40842 100644 --- a/research/dapr-agents.md +++ b/research/dapr-agents.md @@ -14,17 +14,17 @@ sources: - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-integrations/ ratings: platform-impact: - value: 78 - note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 70 + note: 'CF lacks workflow-durable agent loops, virtual-actor scale-to-zero, an agent registry, and sidecar-discovered MCP tools, although service brokers and app routing provide partial state and messaging primitives.' maturity: - value: 76 - note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects the amount of established external practice visible in the note.' + value: 64 + note: 'Dapr Agents has reached v1.0 GA and builds on CNCF-graduated Dapr workflows and actors, but its own governance status is unclear and the note offers claims rather than broad production adoption evidence.' novelty: - value: 62 - note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 55 + note: 'Running every LLM and tool call as a durable workflow activity combines established actors, workflows, pub/sub, and registries in an agent-specific way rather than introducing a wholly new substrate.' actionability: - value: 66 - note: 'Initial review of Dapr Agents — Durable Agent Framework on Dapr: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 78 + note: 'A focused CF experiment can run a Dapr sidecar with one DurableAgent, kill it mid-tool-call, verify workflow recovery, and measure actor resume and MCP discovery against CF instance lifecycle constraints.' --- diff --git a/research/dapr.md b/research/dapr.md index 4aa5afc..e3baec7 100644 --- a/research/dapr.md +++ b/research/dapr.md @@ -15,17 +15,17 @@ sources: - https://docs.dapr.io/developing-ai/dapr-agents/dapr-agents-introduction/ ratings: platform-impact: - value: 78 - note: 'Initial review of Dapr — Distributed Application Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 62 + note: 'CF service bindings overlap Dapr''s pluggable state, pub/sub, secrets, and bindings, but CF lacks its standardized sidecar APIs, virtual actors, durable workflows, SPIFFE workload identity, and supporting placement and scheduler control planes.' maturity: - value: 76 - note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects the amount of established external practice visible in the note.' + value: 94 + note: 'Dapr is CNCF graduated and supplies eleven stable building blocks, pluggable production backends, Kubernetes and self-hosted modes, short-lived mTLS certificates, actors, and crash-resumable workflows.' novelty: - value: 62 - note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 25 + note: 'Sidecars, service invocation, pub/sub, state stores, virtual actors, workflow engines, and workload certificates are established distributed-systems patterns assembled behind a language-neutral API.' actionability: - value: 66 - note: 'Initial review of Dapr — Distributed Application Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 67 + note: 'The note supports a concrete sidecar-on-Diego compatibility investigation, but actor placement, Sentry, scheduler footprint, and overlap with service brokers make a full CF integration broader than one bounded experiment.' --- diff --git a/research/firecracker-microvm.md b/research/firecracker-microvm.md index fa6684d..7d258c8 100644 --- a/research/firecracker-microvm.md +++ b/research/firecracker-microvm.md @@ -16,17 +16,17 @@ sources: - https://aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing ratings: platform-impact: - value: 70 - note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 80 + note: 'CF''s long-lived container app instances do not provide per-session hardware isolation, 125ms-class microVM startup, or snapshot pause/resume for untrusted generated code and long-idle agent sessions.' maturity: - value: 76 - note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects the amount of established external practice visible in the note.' + value: 95 + note: 'Firecracker has powered AWS Lambda and Fargate since its 2018 release, enforces boot and memory targets in CI, and is integrated by Fly.io, Kata Containers, and containerd.' novelty: - value: 62 - note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 42 + note: 'Its minimal five-device VMM and copy-on-write snapshots substantially optimize familiar KVM virtualization, but hardware VMs, seccomp, namespaces, cgroups, and snapshot restoration are established techniques.' actionability: value: 66 - note: 'Initial review of Firecracker — Hardware-Isolated microVMs for Untrusted, Ephemeral Compute: this score reflects how readily the material could guide a focused experiment or follow-up.' + note: 'Kata-on-Firecracker offers a concrete Diego-cell spike for isolation and resume measurements, but networking, snapshot identity safety, host integration, and tenant policy require significant scoping.' --- diff --git a/research/google-adk.md b/research/google-adk.md index e78ef02..8a1ba29 100644 --- a/research/google-adk.md +++ b/research/google-adk.md @@ -21,17 +21,17 @@ sources: - https://google.github.io/adk-docs/evaluate/ ratings: platform-impact: - value: 78 - note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 58 + note: 'ADK agents can run as ordinary CF containers and bring their own graph, A2A, MCP, OTel, and evaluation libraries; CF gaps remain around durable replay, session affinity, identity, and managed behavioral evaluation.' maturity: - value: 76 - note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects the amount of established external practice visible in the note.' + value: 78 + note: 'Google used ADK in internal products before open sourcing it; ADK 2.0 is GA, five first-party language implementations move in lockstep, Python has about 21k stars, and kagent uses ADK as its engine.' novelty: - value: 62 - note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 55 + note: 'Graph workflows, event logs, tool adapters, and evaluation harnesses are known patterns, while isolated task delegation modes and replay-oriented conformance tests are newer agent-specific adaptations.' actionability: - value: 66 - note: 'Initial review of Google Agent Development Kit (ADK) — Graph-Based, Multi-Language Agent Framework: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 82 + note: 'A bounded CF deployment can exercise ADK''s A2A endpoint, OTel traces, MCP connection recovery after instance replacement, and eval conformance tests while documenting the missing durability substrate.' --- diff --git a/research/hatchet.md b/research/hatchet.md index a86668a..ea1fb07 100644 --- a/research/hatchet.md +++ b/research/hatchet.md @@ -21,17 +21,17 @@ sources: - https://docs.hatchet.run/self-hosting ratings: platform-impact: - value: 78 - note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 68 + note: 'CF can host Hatchet workers and bind Postgres, but does not itself provide durable sleeps, event waits, replay, tenant-fair scheduling, rate limits, or centralized workflow observability.' maturity: - value: 76 - note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects the amount of established external practice visible in the note.' + value: 70 + note: 'Hatchet has about 7.7k stars, four SDKs, self-hosted and managed offerings, named AI customers, and claimed high daily task volume, but comes from a small 2023 startup with limited enterprise history.' novelty: - value: 62 - note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 43 + note: 'Using Postgres alone for task history and observability is a useful simplification, while task queues, DAGs, durable waits, retries, rate limits, and worker slots are conventional orchestration mechanisms.' actionability: - value: 66 - note: 'Initial review of Hatchet — Postgres-Backed Durable Task Queue, Marketed for AI Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 84 + note: 'CF can directly deploy Hatchet with a bound Postgres service, interrupt and resume a worker, and measure throughput and per-tenant fairness against the proposed durable-tasks-for-CF requirements.' --- diff --git a/research/heroku-ai-platform.md b/research/heroku-ai-platform.md index 39ec2cd..50972a9 100644 --- a/research/heroku-ai-platform.md +++ b/research/heroku-ai-platform.md @@ -15,17 +15,17 @@ sources: - https://github.com/heroku/mcp-code-exec-python ratings: platform-impact: - value: 78 - note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 55 + note: 'CF already has the app, buildpack, service-binding, Postgres, and one-off-task primitives Heroku reuses, but lacks its managed inference add-on, MCP gateway, model lifecycle policy, and agent-oriented routing optimizations.' maturity: - value: 76 - note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects the amount of established external practice visible in the note.' + value: 72 + note: 'Heroku operates inference, agents, MCP Toolkits, pgvector, and code execution as shipped services on its long-proven dyno platform, although the Anthropic-compatible endpoint remains preview and no durable execution is offered.' novelty: - value: 62 - note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 32 + note: 'The design deliberately combines established PaaS patterns: add-on bindings, compatible HTTP APIs, Postgres vector search, and disposable one-off dynos rather than a new agent runtime architecture.' actionability: - value: 66 - note: 'Initial review of Heroku AI — Managed Inference, Agents, and MCP on a Buildpack-Era PaaS: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 90 + note: 'The note directly supports an OSBAPI prototype that binds OpenAI-compatible inference through VCAP_SERVICES and a sandbox experiment that maps code calls to short-lived Diego tasks with offline dependency staging.' --- diff --git a/research/k8s-agent-sandbox.md b/research/k8s-agent-sandbox.md index e4a0134..2790a97 100644 --- a/research/k8s-agent-sandbox.md +++ b/research/k8s-agent-sandbox.md @@ -10,17 +10,17 @@ sources: - https://agent-sandbox.sigs.k8s.io/docs ratings: platform-impact: - value: 70 - note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 72 + note: 'Diego and Garden provide isolated containers, but CF has no agent-sandbox API combining stable singleton identity, claims, persistent volumes, warm pools, and suspend/resume snapshots.' maturity: - value: 76 - note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects the amount of established external practice visible in the note.' + value: 40 + note: 'The Kubernetes SIG implementation has concrete Sandbox, Claim, Template, and WarmPool CRDs, but the note presents a young agent-specific control plane with limited production evidence and GKE-specific snapshot support.' novelty: value: 62 - note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects how distinct or emerging the approach appears in the current landscape.' + note: 'Stable singleton sandboxes and millisecond claims from pre-running pools are an emerging agent-lifecycle combination, though they build on conventional pods, PVCs, RBAC, namespaces, and network policy.' actionability: - value: 66 - note: 'Initial review of Kubernetes Agent Sandbox (k8s-sigs): this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 72 + note: 'A bounded CF investigation can compare SandboxClaim and WarmPool semantics with Diego tasks and instance pools, then identify the smallest API needed for stable identity, persistence, and pre-warmed allocation.' --- diff --git a/research/kagent.md b/research/kagent.md index 0ef5671..ff523e5 100644 --- a/research/kagent.md +++ b/research/kagent.md @@ -11,17 +11,17 @@ sources: - https://kagent.dev/docs/kagent/concepts/agents ratings: platform-impact: - value: 78 - note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 55 + note: 'CF exposes operator data through cf, BOSH, Diego, logs, and metrics, but lacks a packaged operations agent with declarative definitions, shared tool servers, and bundled platform-management tools.' maturity: - value: 76 - note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects the amount of established external practice visible in the note.' + value: 52 + note: 'kagent is a functioning CNCF project with Helm-managed CRDs and bundled Kubernetes, Istio, Argo, Prometheus, Grafana, and Cilium tools, while governance, roadmap, and human-approval maturity remain open questions.' novelty: - value: 62 - note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 48 + note: 'Its distinctive contribution is packaging an operations-focused agent and exposing agents through both A2A and MCP; CRD reconciliation, MCP tool servers, and agents-as-tools are adaptations of known patterns.' actionability: - value: 66 - note: 'Initial review of kagent — Kubernetes-Native Framework for Operations-Focused AI Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 58 + note: 'The note identifies a CF operator-agent prototype using cf, BOSH, Diego, logs, and metrics, but first requires deciding whether to extend Kubernetes-bound kagent or build a CF-native equivalent.' --- diff --git a/research/keda.md b/research/keda.md index a5e99a2..5406513 100644 --- a/research/keda.md +++ b/research/keda.md @@ -10,17 +10,17 @@ sources: - https://github.com/kedacore/keda ratings: platform-impact: - value: 78 - note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: its subject and tags indicate how broadly the capability could affect an agentic platform.' - maturity: value: 68 - note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects the amount of established external practice visible in the note.' + note: 'CF autoscaling is primarily metric and instance based; it lacks KEDA-style queue-depth triggers, a broad scaler catalog, and reliable event-driven scale-to-zero and scale-from-zero for workers.' + maturity: + value: 92 + note: 'KEDA is a CNCF graduated, de facto Kubernetes standard with more than 70 production-oriented scalers and an established integration with HPA rather than an experimental autoscaler.' novelty: - value: 70 - note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 28 + note: 'Queue-length and external-metric autoscaling are long-established techniques; KEDA mainly standardizes and packages them for Kubernetes with a large adapter catalog.' actionability: - value: 66 - note: 'Initial review of KEDA — Kubernetes Event-Driven Autoscaling: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 88 + note: 'A concrete CF experiment can bind a queue service, feed queue depth into autoscaling, and measure worker scale-from-zero latency and backlog recovery against KEDA behavior.' --- diff --git a/research/langgraph.md b/research/langgraph.md index c0b2d3d..9fb2064 100644 --- a/research/langgraph.md +++ b/research/langgraph.md @@ -18,17 +18,17 @@ sources: - https://docs.langchain.com/langsmith/observability ratings: platform-impact: - value: 78 - note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 58 + note: 'CF can host LangGraph processes but does not provide its checkpoint store, cross-thread memory, durable pause/resume, or Agent Server packaging as platform services.' maturity: - value: 76 - note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects the amount of established external practice visible in the note.' + value: 78 + note: 'LangGraph is a production-oriented LangChain runtime with persistence, interrupts, streaming, MCP integration, and commercial LangSmith deployment options, including a standalone Agent Server.' novelty: - value: 62 - note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 55 + note: 'Applying Pregel-style supersteps, reducer-governed shared state, and boundary checkpoints to agent graphs is a substantial adaptation of established graph-processing and workflow ideas.' actionability: - value: 66 - note: 'Initial review of LangGraph — Low-Level, Pregel-Style Agent Orchestration Runtime: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 72 + note: 'The standalone Agent Server provides a bounded buildpack trial using bound Postgres and Redis, with crash recovery testing for non-idempotent nodes and explicit checkpointer/store mapping questions.' --- diff --git a/research/letta.md b/research/letta.md index 378b593..151ff8b 100644 --- a/research/letta.md +++ b/research/letta.md @@ -20,17 +20,17 @@ sources: - https://docs.letta.com/agent-sdk/mcp ratings: platform-impact: - value: 78 - note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 65 + note: 'CF can host the App Server and bind storage, but has no durable, addressable agent identity or managed evolving-memory service equivalent to Letta agents, shared blocks, and MemFS.' maturity: - value: 76 - note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects the amount of established external practice visible in the note.' + value: 60 + note: 'Letta has an Apache-licensed implementation, 24k-plus stars, extensive history, self-hosting, and a hosted service, but its classic memory API is legacy while the product pivots to Letta Code and MemFS.' novelty: - value: 62 - note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 78 + note: 'The MemGPT model lets an LLM page and edit its own context like virtual memory, while durable addressable agents and git-backed MemFS make memory and identity primary runtime abstractions.' actionability: - value: 66 - note: 'Initial review of Letta (formerly MemGPT) — Durable, Memory-Centric Stateful Agents: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 58 + note: 'A MemFS-backed agent-state binding and App Server isolation trial are plausible, but the ongoing V1-to-MemFS pivot and unrestricted filesystem and shell access leave substantial scoping work.' --- diff --git a/research/llamaindex.md b/research/llamaindex.md index 3151c5e..ade818e 100644 --- a/research/llamaindex.md +++ b/research/llamaindex.md @@ -18,17 +18,17 @@ sources: - https://llamatrace.com/ ratings: platform-impact: - value: 66 - note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 52 + note: 'CF can run LlamaIndex libraries, but it does not supply persistence for serializable Context state, workflow checkpoints, or the hosting layer left incomplete by the project deployment tooling.' maturity: - value: 76 - note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects the amount of established external practice visible in the note.' + value: 65 + note: 'The core project has 51k-plus stars and established RAG adoption, while Workflows is newly extracted, llama_deploy is deprecated, llama-agents remains young, and the main deployment documentation is a stub.' novelty: value: 62 - note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects how distinct or emerging the approach appears in the current landscape.' + note: 'Inferring and validating control flow from typed events between decorated steps is an uncommon orchestration design, though event-driven workflows, worker concurrency, handoffs, and checkpointing are established concepts.' actionability: - value: 58 - note: 'Initial review of LlamaIndex Workflows & AgentWorkflow — Event-Driven, Graph-Free Orchestration: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 70 + note: 'The note supports a focused buildpack trial plus a bindable Context/checkpoint store, with a direct comparison of inferred control-flow auditability against LangGraph-style explicit graphs.' --- diff --git a/research/mcp-protocol.md b/research/mcp-protocol.md index f06a411..e77a0bd 100644 --- a/research/mcp-protocol.md +++ b/research/mcp-protocol.md @@ -18,17 +18,17 @@ sources: - https://techcrunch.com/2025/12/09/openai-anthropic-and-block-join-new-linux-foundation-effort-to-standardize-the-ai-agent-era/ ratings: platform-impact: - value: 78 - note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 55 + note: 'CF can host HTTP services and secure them with UAA, but lacks a first-class MCP service type, registry, lifecycle management, and standardized tool authorization for agent workloads.' maturity: - value: 76 - note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects the amount of established external practice visible in the note.' + value: 78 + note: 'MCP has broad framework and vendor adoption, dated specifications, official SDKs and registry work, and Linux Foundation governance, although transport evolution and authorization guidance are still moving.' novelty: - value: 62 - note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 55 + note: 'MCP newly standardizes model-facing tools, resources, prompts, capability negotiation, and sampling across vendors, while deliberately borrowing JSON-RPC, OAuth, and Language Server Protocol patterns.' actionability: - value: 66 - note: 'Initial review of Model Context Protocol (MCP) — Agent-to-Tool Interoperability: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 88 + note: 'CF can directly prototype a Streamable HTTP MCP server as a bound multi-instance app, validate audience-bound OAuth tokens through UAA, and document why stdio and session affinity do not fit that service model.' --- diff --git a/research/microsoft-agent-framework.md b/research/microsoft-agent-framework.md index c2c231e..cebe059 100644 --- a/research/microsoft-agent-framework.md +++ b/research/microsoft-agent-framework.md @@ -16,17 +16,17 @@ sources: - https://github.com/diagridio/python-ai ratings: platform-impact: - value: 66 - note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 55 + note: 'CF can host MAF applications but offers no native agent packaging, graph checkpoint service, or interchangeable durability substrate comparable to Durable Task or the Diagrid Dapr integration.' maturity: - value: 76 - note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects the amount of established external practice visible in the note.' - novelty: value: 62 - note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects how distinct or emerging the approach appears in the current landscape.' + note: 'MAF consolidates mature AutoGen and Semantic Kernel lineage with Python and .NET implementations, but Go is public preview and durable execution lives in separate first- and third-party extensions.' + novelty: + value: 48 + note: 'Agents, harnesses, explicit workflow graphs, MCP, A2A, and checkpointing form a broad modern combination, but each builds on familiar SDK, workflow, and protocol patterns.' actionability: - value: 58 - note: 'Initial review of Microsoft Agent Framework — Unified SDK for Agents and Multi-Agent Workflows: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 62 + note: 'A CF trial can package a declarative MAF agent and compare Azure Durable Task with Dapr-backed durability, but the note leaves the target substrate and cross-language scope unresolved.' --- diff --git a/research/open-agent-auth.md b/research/open-agent-auth.md index 060b2b7..783bba1 100644 --- a/research/open-agent-auth.md +++ b/research/open-agent-auth.md @@ -11,16 +11,16 @@ sources: ratings: platform-impact: value: 70 - note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: its subject and tags indicate how broadly the capability could affect an agentic platform.' + note: 'UAA authenticates users and applications, but CF lacks operation-specific tokens that cryptographically bind a human, agent workload, requested action, and request-level audit context.' maturity: - value: 68 - note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects the amount of established external practice visible in the note.' + value: 35 + note: 'Alibaba provides a concrete implementation, but AOAT is only an IETF draft-02 from March 2026 and the note offers little evidence of independent adoption or production operation.' novelty: - value: 70 - note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 75 + note: 'Cryptographically binding user delegation, agent workload identity, and semantic operation details into one authorization token is emerging, despite its foundation in OIDC, OAuth PAR, and WIMSE.' actionability: - value: 66 - note: 'Initial review of Open Agent Auth — IETF Agent Operation Authorization: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 68 + note: 'A bounded UAA investigation can issue and validate an AOAT-like token for one destructive agent tool call, though draft churn and the required workload-identity mapping remain open.' --- diff --git a/research/openai-agents-sdk.md b/research/openai-agents-sdk.md index 83ca7a2..85b5de1 100644 --- a/research/openai-agents-sdk.md +++ b/research/openai-agents-sdk.md @@ -21,17 +21,17 @@ sources: - https://github.com/openai/swarm ratings: platform-impact: - value: 66 - note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 48 + note: 'CF readily hosts the library-only agent loop, while the meaningful gap is optional platform support for sessions, tracing, MCP connectivity, and external durable execution rather than a missing runtime requirement.' maturity: - value: 76 - note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects the amount of established external practice visible in the note.' + value: 80 + note: 'The MIT Python SDK is widely adopted, has a JavaScript counterpart and comprehensive tools, handoffs, guardrails, sessions, MCP, and tracing support, with Temporal and Dapr integrations for durability.' novelty: - value: 62 - note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 35 + note: 'Its small set of agents, tools, handoffs, guardrails, sessions, and tracing intentionally favors conventional composable library primitives over a novel graph or hosting architecture.' actionability: - value: 58 - note: 'Initial review of OpenAI Agents SDK — Minimal Primitives, Provider-Agnostic Agent Loop: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 65 + note: 'The DaprSession bridge enables a focused CF session-store trial, but choosing whether CF should supply durability, tracing, or only ordinary bindings requires additional platform scoping.' --- diff --git a/research/opentelemetry-genai.md b/research/opentelemetry-genai.md index e2b0466..d879d8d 100644 --- a/research/opentelemetry-genai.md +++ b/research/opentelemetry-genai.md @@ -9,17 +9,17 @@ sources: - https://github.com/open-telemetry/semantic-conventions-genai ratings: platform-impact: - value: 66 - note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 58 + note: 'CF already transports application telemetry through Loggregator, but lacks standard platform treatment for model, token, agent, tool, and MCP spans and metrics.' maturity: - value: 76 - note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects the amount of established external practice visible in the note.' + value: 48 + note: 'The conventions cover major model vendors, agent operations, MCP, spans, events, and metrics within OpenTelemetry, but the document explicitly records Development status rather than stable standardization.' novelty: - value: 62 - note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 40 + note: 'Token usage, time-to-first-chunk, planning, and tool-call semantics adapt established tracing and metrics conventions to GenAI rather than introducing a new observability architecture.' actionability: - value: 58 - note: 'Initial review of OpenTelemetry Semantic Conventions for GenAI: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 82 + note: 'CF can instrument one model-and-tool request with the named gen_ai attributes and metrics, propagate its trace through platform routing, and test Loggregator export without designing a new protocol.' --- diff --git a/research/orleans.md b/research/orleans.md index 38da181..49710ad 100644 --- a/research/orleans.md +++ b/research/orleans.md @@ -22,17 +22,17 @@ sources: - https://github.com/managedcode/dotPilot ratings: platform-impact: - value: 78 - note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: its subject and tags indicate how broadly the capability could affect an agentic platform." + value: 62 + note: 'CF lacks a virtual-actor runtime providing stable logical identities, transparent activation, a distributed directory, per-actor persistence, streams, and cross-actor transactions.' maturity: - value: 76 - note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects the amount of established external practice visible in the note." + value: 92 + note: 'Orleans is a long-running .NET Foundation project proven in Halo cloud services, remains active through v10.2.x, and offers mature clustering, persistence, streaming, transactions, and versioning.' novelty: - value: 62 - note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects how distinct or emerging the approach appears in the current landscape." + value: 28 + note: 'Orleans pioneered the virtual-actor model, but transparent activation and location, actor persistence, and single-threaded grains are now established architecture inherited by systems such as Dapr.' actionability: - value: 66 - note: "Initial review of Microsoft Orleans — the Virtual Actor Model Dapr's Actors Building Block Came From: this score reflects how readily the material could guide a focused experiment or follow-up." + value: 52 + note: 'The note suggests comparing grains with durable agent identities, but a CF experiment must first resolve .NET-only coupling, silo membership, storage bindings, and the absence of official agent-framework integration.' --- diff --git a/research/tanzu-platform-ai.md b/research/tanzu-platform-ai.md index fd4ad3d..6af30a7 100644 --- a/research/tanzu-platform-ai.md +++ b/research/tanzu-platform-ai.md @@ -13,17 +13,17 @@ sources: - https://blogs.vmware.com/tanzu/scalable-agentic-applications-with-model-context-protocol-mcp/ ratings: platform-impact: - value: 78 - note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 75 + note: 'Commercial Tanzu adds model brokering, journaling, and agent-specific isolation around CF, demonstrating that the open-source platform lacks a substantial integrated AI and agent operations layer.' maturity: - value: 76 - note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects the amount of established external practice visible in the note.' - novelty: value: 62 - note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects how distinct or emerging the approach appears in the current landscape.' + note: 'The BOSH-managed GenAI tile and broker build on production Tanzu and CF machinery, while Agent Foundations was only announced in April 2026 and has less demonstrated operational evidence.' + novelty: + value: 45 + note: 'Model services through a tile and broker reuse established BOSH and service-binding patterns; agent journaling and secure-by-default isolation are newer additions but parallel other managed agent platforms.' actionability: - value: 66 - note: 'Initial review of VMware Tanzu Platform — GenAI Tile and Agent Foundations Built on Cloud Foundry: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 88 + note: 'Because the product is built on CF, the note directly supports separating reusable broker, binding, journaling, buildpack, secrets, and networking patterns from proprietary Agent Foundations components.' --- diff --git a/research/temporal.md b/research/temporal.md index 9852bc9..1500e3c 100644 --- a/research/temporal.md +++ b/research/temporal.md @@ -18,17 +18,17 @@ sources: - https://temporal.io/cloud ratings: platform-impact: - value: 78 - note: 'Initial review of Temporal — Durable Execution as a Standalone Service: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 72 + note: 'CF can run stateless Temporal workers but has no durable-execution service providing event histories, deterministic replay, task queues, signals, queries, and resumable agent loops.' maturity: - value: 76 - note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects the amount of established external practice visible in the note.' + value: 92 + note: 'Temporal is a production-proven successor to Cadence with 22k-plus stars, a managed cloud, multiple persistence backends, and official durable-agent integrations for OpenAI, ADK, and LangGraph.' novelty: - value: 62 - note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 45 + note: 'Append-only event history and deterministic replay are distinctive relative to checkpoint and actor models, but they are established durable-workflow techniques rather than new agent architecture.' actionability: - value: 66 - note: 'Initial review of Temporal — Durable Execution as a Standalone Service: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 78 + note: 'A bound Temporal service with CF workers can concretely test restart recovery, Continue-As-New, namespace isolation, and mTLS, although operating the clustered persistence tier remains a significant question.' --- diff --git a/research/toolhive.md b/research/toolhive.md index 620111e..ac42c3b 100644 --- a/research/toolhive.md +++ b/research/toolhive.md @@ -12,17 +12,17 @@ sources: - https://docs.stacklok.com/toolhive/concepts/auth-framework ratings: platform-impact: - value: 58 - note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 62 + note: 'Diego, UAA, and service brokers offer related primitives, but CF lacks an MCP-specific runtime and gateway combining per-server isolation, delegated credentials, Cedar authorization, registry governance, and audits.' maturity: - value: 76 - note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects the amount of established external practice visible in the note.' + value: 55 + note: 'ToolHive ships a CLI, desktop UI, gateway, registry, and Kubernetes operator with OIDC, audit, OTel, and Prometheus support, but the note gives limited evidence of broad production adoption.' novelty: - value: 62 - note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 55 + note: 'Centralizing MCP OAuth complexity, backend credential separation, Cedar policy, and isolated server containers is a useful new package of established gateway, policy, and container techniques.' actionability: - value: 58 - note: 'Initial review of ToolHive — Secure Runtime and Gateway for MCP Servers: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 70 + note: 'A focused comparison can deploy one MCP server in Diego, front it with UAA and deny-by-default policy, and test whether a curated service marketplace can reproduce ToolHive registry governance.' --- diff --git a/research/vercel-ai-sdk.md b/research/vercel-ai-sdk.md index c75ea70..46150ec 100644 --- a/research/vercel-ai-sdk.md +++ b/research/vercel-ai-sdk.md @@ -16,17 +16,17 @@ sources: - https://www.anthropic.com/research/building-effective-agents ratings: platform-impact: - value: 66 - note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: its subject and tags indicate how broadly the capability could affect an agentic platform.' + value: 48 + note: 'CF can already host the stateless TypeScript SDK; its main gap is an optional bindable session, memory, or workflow service for the state and durability the SDK intentionally leaves external.' maturity: - value: 76 - note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects the amount of established external practice visible in the note.' + value: 88 + note: 'The Apache-licensed SDK records more than 78 million monthly downloads and supports major JavaScript frameworks, providers, agents, MCP, telemetry, and documented external memory providers.' novelty: - value: 62 - note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects how distinct or emerging the approach appears in the current landscape.' + value: 38 + note: 'Provider abstraction, tool loops, stateless application code, external memory, and Anthropic-derived workflow patterns are familiar techniques assembled into an unusually popular TypeScript API.' actionability: - value: 58 - note: 'Initial review of Vercel AI SDK — Stateless, Provider-Agnostic Agent Building for TypeScript: this score reflects how readily the material could guide a focused experiment or follow-up.' + value: 78 + note: 'A Node.js buildpack sample can bind an external Memory Provider and OTel exporter, then test the shipped MCP tool-drift detector as a concrete CF security recommendation.' --- diff --git a/research/vertex-agent-engine.md b/research/vertex-agent-engine.md index f8f383e..e4ba6df 100644 --- a/research/vertex-agent-engine.md +++ b/research/vertex-agent-engine.md @@ -16,17 +16,17 @@ sources: - https://cloud.google.com/gemini-enterprise-agent-platform/agents ratings: platform-impact: - value: 78 - note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: its subject and tags indicate how broadly the capability could affect an agentic platform." + value: 85 + note: 'CF lacks the integrated managed-agent lifecycle shown by Agent Runtime, Sessions, Memory Bank, separate untrusted Sandboxes, SPIFFE identity, policy gateways, tracing, evaluation, and optimization.' maturity: - value: 76 - note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects the amount of established external practice visible in the note." + value: 78 + note: 'Google operates a fully managed, framework-agnostic runtime supporting ADK, LangGraph, AG2, LlamaIndex, and custom templates, while A2A deployment and some surrounding capabilities remain preview or unevenly governed.' novelty: - value: 62 - note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects how distinct or emerging the approach appears in the current landscape." + value: 58 + note: 'The trusted-runtime plus separate untrusted-sandbox split and memory-to-evaluation lifecycle are a newer agent-platform combination, though containers, SPIFFE, gateways, OTel, and managed memory are known patterns.' actionability: - value: 66 - note: "Initial review of Vertex AI Agent Engine (Gemini Enterprise Agent Platform) — Google's Managed Agent Runtime: this score reflects how readily the material could guide a focused experiment or follow-up." + value: 55 + note: 'The note supplies a strong reference architecture, but CF must still choose among broad experiments in workload identity, sandbox separation, policy gateways, memory, and evaluation rather than one defined implementation step.' --- diff --git a/research/wasmcloud.md b/research/wasmcloud.md index 55c085e..5bfb73b 100644 --- a/research/wasmcloud.md +++ b/research/wasmcloud.md @@ -14,17 +14,17 @@ sources: - https://wasmcloud.com/blog/2025-01-15-running-distributed-ml-and-ai-workloads-with-wasmcloud ratings: platform-impact: - value: 78 - note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: its subject and tags indicate how broadly the capability could affect an agentic platform." + value: 48 + note: 'CF lacks deny-by-default WIT capability contracts, but its general container hosting already covers far more workloads; wasmCloud requires WASI components and is not a direct replacement for buildpack applications.' maturity: - value: 76 - note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects the amount of established external practice visible in the note." + value: 65 + note: 'wasmCloud is a CNCF Incubating project with multi-organization maintainers, but its v1 architecture is no longer maintained and the early-2026 v2 Kubernetes rearchitecture leaves compatibility and operational questions.' novelty: - value: 62 - note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects how distinct or emerging the approach appears in the current landscape." + value: 70 + note: 'Language-neutral WIT imports as enforceable deny-by-default capabilities and swappable in-process providers offer an unconventional alternative to container and sidecar security models.' actionability: - value: 66 - note: "Initial review of wasmCloud — a WebAssembly-native alternative to Dapr's sidecar model: this score reflects how readily the material could guide a focused experiment or follow-up." + value: 35 + note: 'The document flags capability security as inspiration but finds no clear CF mapping; WASI support for Python and Node agent dependencies, v2 clustering, and a representative MCP workload all need discovery first.' --- diff --git a/scripts/summarize_ratings.py b/scripts/summarize_ratings.py new file mode 100644 index 0000000..c2d3808 --- /dev/null +++ b/scripts/summarize_ratings.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Summarize and validate rating distributions across all notes.""" + +from __future__ import annotations + +import argparse +import statistics +import sys +from itertools import combinations + +if __package__: + from scripts.generate_research_map import load_notes +else: + from generate_research_map import load_notes + + +REQUIRED_RATINGS = ("platform-impact", "maturity", "novelty", "actionability") +MATRICES = { + "platform-impact-maturity": ("platform-impact", "maturity"), + "platform-impact-novelty": ("platform-impact", "novelty"), + "platform-impact-actionability": ("platform-impact", "actionability"), + "novelty-actionability": ("actionability", "novelty"), + "maturity-novelty": ("maturity", "novelty"), + "maturity-actionability": ("maturity", "actionability"), +} + + +def summarize_ratings(notes) -> dict: + values = {name: [] for name in REQUIRED_RATINGS} + rows = [] + for note in notes: + ratings = note.metadata.get("ratings", {}) + row = { + name: rating["value"] + for name, rating in ratings.items() + if name in values + and isinstance(rating, dict) + and isinstance(rating.get("value"), (int, float)) + } + rows.append(row) + for name, value in row.items(): + values[name].append(value) + + rating_summaries = {} + for name, observed in values.items(): + if not observed: + continue + quartiles = ( + statistics.quantiles(observed, n=4, method="inclusive") + if len(observed) > 1 + else (observed[0], observed[0], observed[0]) + ) + distinct = len(set(observed)) + rating_summaries[name] = { + "count": len(observed), + "minimum": min(observed), + "maximum": max(observed), + "median": statistics.median(observed), + "first_quartile": quartiles[0], + "third_quartile": quartiles[2], + "distinct": distinct, + "duplicates": len(observed) - distinct, + } + + correlations = {} + for first, second in combinations(REQUIRED_RATINGS, 2): + paired = [(row[first], row[second]) for row in rows if first in row and second in row] + key = f"{first}:{second}" + try: + correlations[key] = statistics.correlation( + [pair[0] for pair in paired], [pair[1] for pair in paired] + ) + except statistics.StatisticsError: + correlations[key] = None + + matrices = {} + for matrix, (vertical, horizontal) in MATRICES.items(): + quadrants = {"low-low": 0, "low-high": 0, "high-low": 0, "high-high": 0} + for row in rows: + if vertical in row and horizontal in row: + vertical_side = "low" if row[vertical] <= 50 else "high" + horizontal_side = "low" if row[horizontal] <= 50 else "high" + quadrants[f"{vertical_side}-{horizontal_side}"] += 1 + matrices[matrix] = quadrants + + return { + "count": len(rows), + "ratings": rating_summaries, + "correlations": correlations, + "matrices": matrices, + } + + +def validate_summary(summary: dict) -> None: + ratings = summary["ratings"] + for name in REQUIRED_RATINGS: + if name not in ratings or ratings[name]["count"] != summary["count"]: + raise ValueError(f"missing required rating: {name}") + for name in REQUIRED_RATINGS: + rating = ratings[name] + if rating["distinct"] < 5: + raise ValueError(f"rating '{name}' has fewer than 5 distinct values") + if rating["maximum"] <= 50: + raise ValueError(f"rating '{name}' values are strictly below 50") + if rating["minimum"] >= 50: + raise ValueError(f"rating '{name}' values are strictly above 50") + + +def print_summary(summary: dict) -> None: + for name, rating in summary["ratings"].items(): + print( + f"{name}: count={rating['count']} min={rating['minimum']} " + f"max={rating['maximum']} median={rating['median']} " + f"q1={rating['first_quartile']} q3={rating['third_quartile']} " + f"distinct={rating['distinct']} duplicates={rating['duplicates']}" + ) + print("correlations:") + for pair, correlation in summary["correlations"].items(): + value = "undefined" if correlation is None else f"{correlation:.3f}" + print(f" {pair}: {value}") + print("matrix quadrants (vertical-horizontal):") + for matrix, quadrants in summary["matrices"].items(): + counts = " ".join(f"{name}={count}" for name, count in quadrants.items()) + print(f" {matrix}: {counts}") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + try: + summary = summarize_ratings(load_notes()) + print_summary(summary) + if args.check: + validate_summary(summary) + except (OSError, ValueError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 3925fd1..6ede409 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -1,5 +1,6 @@ import pathlib import unittest +from types import SimpleNamespace from scripts.generate_research_map import ( derive_position, @@ -12,12 +13,125 @@ parse_note, validate_ratings, ) +from scripts.summarize_ratings import summarize_ratings, validate_summary PLOT = {"x": {"rating": "maturity"}, "y": {"rating": "platform-impact"}} +REQUIRED_RATINGS = ("platform-impact", "maturity", "novelty", "actionability") + + +def rating_notes(values): + return [ + SimpleNamespace(metadata={"ratings": ratings}) + for ratings in ( + { + name: {"value": value, "note": "reason"} + for name, value in zip(REQUIRED_RATINGS, row) + } + for row in values + ) + ] class ResearchMapTests(unittest.TestCase): + def test_summarize_ratings_reports_distribution_correlations_and_quadrants(self): + notes = rating_notes( + [ + (10, 90, 10, 10), + (30, 70, 30, 70), + (50, 50, 50, 50), + (70, 30, 70, 30), + (90, 10, 90, 90), + ] + ) + + summary = summarize_ratings(notes) + + self.assertEqual( + summary["ratings"]["platform-impact"], + { + "count": 5, + "minimum": 10, + "maximum": 90, + "median": 50, + "first_quartile": 30.0, + "third_quartile": 70.0, + "distinct": 5, + "duplicates": 0, + }, + ) + self.assertEqual(summary["correlations"]["platform-impact:maturity"], -1.0) + self.assertEqual(summary["correlations"]["platform-impact:novelty"], 1.0) + self.assertEqual(len(summary["correlations"]), 6) + self.assertEqual( + summary["matrices"]["platform-impact-maturity"], + {"low-low": 1, "low-high": 2, "high-low": 2, "high-high": 0}, + ) + self.assertEqual(len(summary["matrices"]), 6) + + def test_summarize_ratings_reports_duplicate_count(self): + summary = summarize_ratings( + rating_notes([(10, 10, 10, 10), (10, 10, 10, 10), (20, 20, 20, 20)]) + ) + + self.assertEqual(summary["ratings"]["maturity"]["duplicates"], 1) + + def test_validate_summary_checks_all_missing_ratings_before_sparse_distribution(self): + notes = rating_notes([(50, 50, 50, 50)]) + notes[0].metadata["ratings"].pop("novelty") + + summary = summarize_ratings(notes) + + with self.assertRaisesRegex(ValueError, "missing required rating.*novelty"): + validate_summary(summary) + + def test_validate_summary_checks_all_malformed_ratings_before_sparse_distribution(self): + notes = rating_notes([(50, 50, 50, 50), (60, 60, 60, 60)]) + notes[0].metadata["ratings"]["novelty"] = {} + + summary = summarize_ratings(notes) + + with self.assertRaisesRegex(ValueError, "missing required rating.*novelty"): + validate_summary(summary) + + def test_validate_summary_rejects_missing_required_rating(self): + notes = rating_notes([(value, value, value, value) for value in (10, 30, 50, 70, 90)]) + notes[0].metadata["ratings"].pop("novelty") + summary = summarize_ratings(notes) + with self.assertRaisesRegex(ValueError, "missing required rating.*novelty"): + validate_summary(summary) + + def test_validate_summary_rejects_fewer_than_five_distinct_values(self): + summary = summarize_ratings( + rating_notes([(10, 10, 10, 10), (20, 20, 20, 20)] * 3) + ) + with self.assertRaisesRegex(ValueError, "fewer than 5 distinct values"): + validate_summary(summary) + + def test_validate_summary_rejects_values_strictly_on_one_side_of_midpoint(self): + for values, side in ( + ((10, 20, 30, 40, 45), "below"), + ((55, 60, 70, 80, 90), "above"), + ): + with self.subTest(side=side): + summary = summarize_ratings( + rating_notes([(value, value, value, value) for value in values]) + ) + with self.assertRaisesRegex(ValueError, f"strictly {side} 50"): + validate_summary(summary) + + def test_validate_summary_rejects_boundary_without_values_on_both_sides(self): + for values, side in ( + ((10, 20, 30, 40, 50), "below"), + ((50, 60, 70, 80, 90), "above"), + ): + with self.subTest(side=side): + summary = summarize_ratings( + rating_notes([(value, value, value, value) for value in values]) + ) + with self.assertRaisesRegex(ValueError, f"strictly {side} 50"): + validate_summary(summary) + def test_group_payload_combines_only_exact_positions(self): payload = [ {"id": "a", "position": {"x": 50, "y": 40}}, @@ -83,10 +197,9 @@ def test_github_url_uses_canonical_repository_path(self): def test_all_current_notes_have_initial_ratings_and_justifications(self): required = {"platform-impact", "maturity", "novelty", "actionability"} notes = load_notes() - self.assertEqual(len(notes), 41) for note in notes: ratings = note.metadata["ratings"] - self.assertTrue(required.issubset(ratings), note.path) + self.assertEqual(set(ratings), required, note.path) for name in required: self.assertIsInstance(ratings[name]["value"], int) self.assertIn(ratings[name]["value"], range(101)) @@ -97,7 +210,7 @@ def test_generated_html_contains_markers_dialog_and_source_links(self): self.assertGreater(html.count('class="marker '), 0) self.assertIn('', html) self.assertIn("Read the full Markdown note on GitHub", html) - self.assertIn("Initial working-group ratings", html) + self.assertIn("Recalibrated working-group ratings", html) def test_generated_html_contains_one_matrix_for_each_configured_plot(self): html = generate_html(load_notes(), load_plots()) From 049405ea5446577552fc871dd9165cb3fa9ce959 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 15:24:06 +0200 Subject: [PATCH 12/41] docs: rate Domyn Swarm research --- generated/research-map.html | 6 +- research/domyn-swarm.md | 194 ++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 research/domyn-swarm.md diff --git a/generated/research-map.html b/generated/research-map.html index a64661b..f4f71d0 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -10,10 +10,10 @@ .unplaced { margin-top:56px; border-top:1px solid #304640; padding-top:18px; } .unplaced summary { color:#9eb4ac; cursor:pointer; } .unplaced ul { list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; } .unplaced li { display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; } .note-kind,.picker-kind { display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; } .note-kind.research,.picker-kind.research { color:#bff7df; background:#245744; } .note-kind.idea,.picker-kind.idea { color:#ffe0ad; background:#654522; } a { color:#8ee3bf; } dialog { max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; } dialog::backdrop { background:#020505bb; } button { font:inherit; } .close { float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; } .tag { color:#9eb4ac; margin-right:8px; } .rating { border-top:1px solid #304640; padding:12px 0; } .rating strong { color:#8ee3bf; } .picker-item { display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; } .picker-item:hover,.picker-item:focus-visible { background:#2c4940; border-color:#8ee3bf; } .picker-item small { color:#b4c8c0; } .picker-kind { margin-right:8px; } @media(max-width:600px) { body { padding:24px 14px; } .map { height:720px; margin-left:58px; } .axis-y { left:-40px; } }

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    -

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    +

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    +
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    - \ No newline at end of file diff --git a/research/domyn-swarm.md b/research/domyn-swarm.md new file mode 100644 index 0000000..30e198d --- /dev/null +++ b/research/domyn-swarm.md @@ -0,0 +1,194 @@ +--- +title: "Domyn Swarm — HPC-Native Batch Inference Orchestration for vLLM" +author: Ruben Koster (@rkoster) +date: 2026-08-19 +tags: [runtime-lifecycle, orchestration, observability-governance] +status: draft +sources: + - https://github.com/igeniusai/domyn-swarm +ratings: + platform-impact: + value: 75 + note: 'CF has Diego/CAPI workload lifecycle primitives, but no integrated equivalent for provisioning GPU inference endpoints across heterogeneous schedulers, supervising vLLM replicas, and reconciling durable batch-job state with resumable checkpoints.' + maturity: + value: 50 + note: 'Version 0.29.0 has a substantial implementation with migrations, health supervision, retries, checkpointing, and rapid releases through 2026, but it is classified Alpha, is not on PyPI, has 23 stars and one fork, and shows only internal Domyn dogfooding rather than broad production evidence.' + novelty: + value: 40 + note: 'The ServingBackend/ComputeBackend split applies familiar adapter and deployment-lifecycle patterns to HPC inference; watchdog supervision, SQLite reconciliation, and sharded Parquet checkpoints are a useful combination, but each is established technology rather than a new architecture.' + actionability: + value: 80 + note: 'The two small backend protocols define a bounded CF spike: implement serving and compute adapters for CAPI/Diego plus a GPU scheduler, then exercise endpoint lifecycle, job-status reconciliation, watchdog failure recovery, and Parquet resume behavior.' + +--- + +## Summary + +Domyn Swarm (`igeniusai/domyn-swarm`, Apache-2.0) is a CLI + Python library that stands up +vLLM OpenAI-compatible serving endpoints on **Slurm** or **DGX Cloud Lepton**, then runs +high-throughput batch inference jobs (DataFrame-in/DataFrame-out, or arbitrary scripts) +against them with retries, checkpointing, and process-level health supervision. This +analysis is based on a local checkout (commit `4824560`, tagged `v0.29.0-11-g4824560`, +2026-07-17) rather than the truncated GitHub web view — the codebase is considerably more +sophisticated than the public README alone suggests: it includes a SQLite-backed state/job +store with Alembic migrations, a dedicated watchdog/collector process pair for per-replica +health monitoring and auto-restart, and pluggable Parquet checkpoint stores with resumable, +shard-based writes. It is HPC batch-inference tooling, not a general agent framework — no +tool-calling, planning, or multi-agent orchestration concepts appear anywhere in the codebase. + +## Key findings + +**Core abstraction: two-protocol backend split** +- `src/domyn_swarm/platform/protocols.py` defines two `Protocol` interfaces that everything + else implements: `ServingBackend` (`create_or_update`, `wait_ready`, `delete`, + `ensure_ready`, `status`) manages the life of an inference endpoint; `ComputeBackend` + (`submit`, `wait`, `cancel`, `probe`, plus `default_python`/`default_image`/ + `default_resources`/`default_env` via a `DefaultComputeMixin`) manages the life of a job + that targets that endpoint. Both use opaque `ServingHandle`/`JobHandle` value objects and + standardized `ServingPhase`/`JobStatus` enums so callers never touch platform-specific + types. +- `Deployment` (`deploy/deployment.py`) is a thin composition of one `ServingBackend` + one + `ComputeBackend`: `up()` creates+waits for the endpoint, `run()` submits a job against it, + `down()` tears it down. This is a clean, minimal pattern for decoupling "where inference + runs" from "how batch jobs are scheduled against it" — deliberately reusable beyond Slurm. +- Two backend pairs are implemented today: **Slurm** (`backends/serving/slurm.py` + + `backends/compute/slurm.py`, using `srun` inside a load-balanced allocation) and + **DGX Cloud Lepton** (`backends/serving/lepton.py` + `backends/compute/lepton.py`, via the + `leptonai` Python SDK, an optional extra). Adding a new platform means implementing the two + protocols, not touching the orchestration core. + +**Orchestrator: `DomynLLMSwarm`** +- `core/swarm.py` defines `DomynLLMSwarm`, a Pydantic `BaseModel` used as a context manager + (`__enter__`/`__exit__` bring the endpoint up/down). It owns: job submission + (`submit_job`, `submit_script`), job lifecycle (`wait_job`, `cancel_job`, + `refresh_job_status`), local persistence (`_persist`, `from_state` — a swarm can be + rehydrated later from a saved name), and `status()`. +- Every job submission is tracked in a local SQLite state DB: `_record_job_submission` / + `_update_job_submission` persist name, command, resources, kind, status, external ID + (Slurm job/step ID or Lepton job ID), and log paths — i.e. Domyn Swarm keeps its own + durable job audit trail independent of the underlying scheduler, and `refresh_job_status` + can re-probe the backend to reconcile it. +- `create_swarm_pool` (used in `examples/api/swarm_launch.py`) launches multiple + `DomynLLMSwarm` instances concurrently (e.g. two independent replicas/configs), and jobs + can be submitted `detach=True` to run as background child processes, with the caller + later `waitpid`-ing on the returned PIDs — a simple fan-out pattern for parallel swarms. + +**Config: auto-computed resource allocation** +- `config/swarm.py`'s `DomynLLMSwarmConfig` (Pydantic model) has a + `validate_resource_allocations` model validator that derives `nodes`, `cpus_per_task`, + and `replicas_per_node` from `replicas`, `gpus_per_replica`, and `gpus_per_node` when not + given explicitly — e.g. `nodes = ceil(replicas / replicas_per_node)` or, for multi-GPU + multi-node replicas, `ceil((replicas * gpus_per_replica) / gpus_per_node)`. This is the + concrete mechanism behind the "just write gpus_per_replica/replicas in YAML" quickstart + experience. +- `config/plan.py`'s `PlanBuilder` normalizes this into a `DeploymentPlan` + (serving+compute backend instances plus per-backend specs) and `DeploymentContext` + (normalized fields shared across serving and compute) — a single place where + backend-specific defaults (default container image, default Python interpreter, default + resources/env) get resolved before either backend is touched. + +**Jobs: `SwarmJob` abstract base + checkpointed execution** +- `jobs/api/base.py` defines `SwarmJob(abc.ABC)`. User code implements one method: + `async def transform_items(items: list[Any]) -> list[Any]` (pure, order-preserving). A + `transform_streaming` variant supports checkpoint-as-you-go without retaining all outputs + in memory. The constructor takes ~20 parameters covering input/output column naming, + concurrency (`max_concurrency`), `retries`, `timeout`, `checkpoint_interval`, an + `OutputJoinMode` (e.g. `APPEND`), and a pluggable `data_backend`. +- `jobs/base.py` (the module named in the original README) is now a **deprecated + compatibility shim** re-exporting from `jobs/api/base.py` with a `DeprecationWarning` — + the legacy `transform(df)`-based job shape has been fully replaced by the + `transform_items(items)` contract. +- Checkpointing is a separate, swappable concern (`checkpoint/store.py`): a + `CheckpointStore[T]` protocol (`prepare`, `flush`, `finalize`) with two implementations — + `ParquetShardStore` (writes monotonically-named Parquet shards to local or cloud URIs via + `fsspec`, tracks already-completed IDs to support resume, and merges shards on + `finalize()`) and `InMemoryStore` (no disk I/O, for tests/small jobs). +- Data backends are pluggable via a registry (`data/backends/registry.py`): pandas + (default), optional Polars, optional Ray — so the same `SwarmJob` can run over different + DataFrame engines depending on scale. + +**Runtime health: watchdog + collector** +- `runtime/watchdog.py` runs as a per-replica supervisor process: it spawns the actual vLLM + child process, polls its HTTP health endpoint (`_check_http`) and, for Ray-backed + multi-node replicas, probes Ray cluster health and expected worker/tensor-parallel + capacity (`_ray_cluster_ok`, `_ray_capacity_ok`). A hardcoded `RAY_FATAL_EXIT_CODE = 190` + distinguishes non-retryable Ray failures from transient ones (`_should_restart` decides + whether to respawn based on exit code). +- Replica state (`ReplicaState` enum) and failures are reported via `send_status()` to a + separate **collector** process, described in `AGENTS.md` as "single writer to + `watchdog.db`" — i.e. watchdogs never write the SQLite DB directly, avoiding + multi-writer contention; only the collector does, and `domyn-swarm status` reads from it. + `build_fail_reason` classifies failures from log tails into a human-readable reason plus + a `retryable` boolean. +- This is a real, if minimal, self-healing mechanism for long-running Slurm-allocated vLLM + replicas — not just "start it and hope," which matters on HPC clusters where a node/GPU + fault shouldn't require a human to notice and manually resubmit. + +**State & CLI** +- Two SQLite databases: a global `swarm.db` (`${DOMYN_SWARM_HOME:-~/.domyn_swarm}/swarm.db`) + for swarm/job records, and a per-swarm `watchdog.db` under + `.../swarms//` for replica health. Schema changes go through Alembic + migrations with an auto-upgrade step that runs on every CLI invocation (skippable via + `DOMYN_SWARM_SKIP_DB_UPGRADE=1`), guarded by a threading lock (per changelog) to avoid + concurrent-upgrade races. +- The CLI (`cli/main.py`, Typer) uses a `LazyGroup` that defers importing heavy subcommand + modules (job management, swarm lifecycle) until actually invoked, plus lazy proxies + (`_LazyDomynLLMSwarm`, `_LazySwarmStateManager`, `_LazyLogger`) — purely a startup-latency + optimization (per recent changelog entries: "defer heavy imports off the swarm-load + path"), notable mainly as evidence of active performance-focused maintenance. +- Commands include `up`/`down` (swarm lifecycle), `status` (table via Rich TUI or stable + JSON via `-o json`), and a `job` subcommand group (`submit`, `submit-script`, `status`, + `cancel`, `list` — added incrementally through v0.26–v0.29 per CHANGELOG.md, including + job persistence with external-ID tracking and idempotent cancellation). + +**Maturity signals** +- `pyproject.toml`: version `0.29.0`, `Development Status :: 3 - Alpha`, Python + `>=3.10,<3.14`, Apache-2.0. Two named maintainers with `@domyn.com` addresses (Federico + D'Ambrosio, Alessandro Rognoni) — a small, identifiable internal team, not a broad + open-source community project (1 fork, 23 stars at time of writing). +- CHANGELOG.md shows fast, incremental delivery: Ray backend support, Polars backend, + stable sharding strategies (`id` vs `index` mode), sharded/resumable checkpoint stores, + the watchdog/collector health system, and full job CRUD with JSON status output were all + added across versions v0.25.0–v0.29.0 (Jan–Jun 2026) — consistent with a tool under active + internal dogfooding at Domyn rather than a one-off open-source drop. +- Still not published to PyPI as of this checkout (per AGENTS.md/README); installed via + `uv`/`pip` directly from the GitHub repo at a pinned tag. + +## CF relevance + +The `ServingBackend` / `ComputeBackend` protocol split, composed by a single `Deployment` +object, is a directly reusable pattern for any platform wanting to decouple "stand up an +inference/agent endpoint" from "schedule work against it" across heterogeneous compute +(here: Slurm vs. Lepton; for CF, potentially Diego/CAPI vs. some GPU-scheduling backend). +Three other pieces are worth studying as concrete, load-bearing reference implementations +rather than abstract patterns: (1) the **watchdog/collector split** — one writer to a local +health-status store, workers only report — is a simple, robust pattern for supervising +long-running GPU workloads without database contention; (2) the **resumable, sharded +Parquet checkpoint store** with monotonic shard naming and "already-done ID" tracking is a +concrete answer to "how do you make a long batch job restart-safe"; (3) **local SQLite job +persistence independent of the underlying scheduler**, with a `refresh_job_status` +reconciliation path, is a lightweight pattern for a platform to keep its own durable +job/audit record without depending entirely on the backend's own state (relevant to +audit/observability-governance concerns raised in the broader agentic-workload research). +Note the scope limit: none of this is agent orchestration (no planning, tool use, or +multi-agent coordination) — it is HPC batch-inference plumbing, so its relevance is to the +"run the model reliably at scale" layer, not the "agent decides what to do" layer. + +## Open questions + +- How does the watchdog/collector health system behave across a full node failure (not + just process crash) — does Slurm's own requeue interact with domyn-swarm's restart + logic, or can they conflict (e.g. double-restart)? +- The `ParquetShardStore` resume logic depends on stable ID columns across runs — what + happens if the input DataFrame's row order or ID scheme changes between a failed run and + its resume attempt (partial-shard consistency wasn't verified in this pass)? +- Is there a production user of the `ray` data-backend / Ray-backed multi-node serving path + outside Domyn's own Colosseum cluster, or is Ray support Slurm-specific tooling that + wouldn't transfer to a non-HPC scheduler? +- The two named maintainers and Apache-2.0 license suggest Domyn intends this as a genuine + reusable open-source tool (vs. a marketing artifact) — is there any public roadmap or + issue tracker activity indicating outside contributions, or is it effectively + single-vendor maintained? +- How (if at all) does Domyn Swarm relate to the proprietary "Platform" product marketed on + domyn.com for building/orchestrating AI Agents — is Swarm the inference substrate + underneath that product, a separate internal tool, or unrelated? From bddad79c7dd194b27f7f26138ac630281b4cb0c0 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 15:27:37 +0200 Subject: [PATCH 13/41] fix: label recalibrated map ratings --- generated/research-map.html | 2 +- scripts/generate_research_map.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/generated/research-map.html b/generated/research-map.html index f4f71d0..c7d99cf 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -10,7 +10,7 @@ .unplaced { margin-top:56px; border-top:1px solid #304640; padding-top:18px; } .unplaced summary { color:#9eb4ac; cursor:pointer; } .unplaced ul { list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; } .unplaced li { display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; } .note-kind,.picker-kind { display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; } .note-kind.research,.picker-kind.research { color:#bff7df; background:#245744; } .note-kind.idea,.picker-kind.idea { color:#ffe0ad; background:#654522; } a { color:#8ee3bf; } dialog { max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; } dialog::backdrop { background:#020505bb; } button { font:inherit; } .close { float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; } .tag { color:#9eb4ac; margin-right:8px; } .rating { border-top:1px solid #304640; padding:12px 0; } .rating strong { color:#8ee3bf; } .picker-item { display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; } .picker-item:hover,.picker-item:focus-visible { background:#2c4940; border-color:#8ee3bf; } .picker-item small { color:#b4c8c0; } .picker-kind { margin-right:8px; } @media(max-width:600px) { body { padding:24px 14px; } .map { height:720px; margin-left:58px; } .axis-y { left:-40px; } }

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    -

    Initial working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    +

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    ''' +document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const items=plots[m.dataset.plot].filter(n=>n.position&&`${{n.position.x}}:${{n.position.y}}`===m.dataset.cluster.split(':').slice(1).join(':'));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 9f0c2a7..347c662 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -322,6 +322,38 @@ def test_generated_html_contains_accessible_matrix_tabs_and_panels(self): html, ) + def test_generated_html_embeds_primitive_membership_once_and_stable_control_ids(self): + primitives = load_primitives() + html = generate_html(load_notes(), load_plots(), primitives) + + self.assertEqual(html.count("const primitiveMemberships="), 1) + self.assertIn( + '"ideas/durable-tasks-for-cf.md": {"durable-addressable-execution": "core"}', + html, + ) + for primitive in primitives: + self.assertIn(f'id="primitive-{primitive["id"]}"', html) + + def test_generated_html_persistently_refreshes_marker_highlighting(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn("let selectedPrimitive=null", html) + self.assertIn("function selectPrimitive", html) + self.assertIn("function clearPrimitiveSelection", html) + self.assertIn("function refreshMarkers", html) + self.assertIn("aria-pressed',String", html) + self.assertIn("classList.toggle('related'", html) + self.assertIn("classList.toggle('dimmed'", html) + self.assertNotIn("m.disabled", html) + self.assertIn("--selected-accent", html) + + def test_generated_html_shows_selected_primitive_relationship_in_note_detail(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn("function relationshipBadge", html) + self.assertIn('class="primitive-badge"', html) + self.assertIn("relationship==='core'?'Core':'Supporting'", html) + def test_generated_html_has_one_dialog_close_button(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertEqual(html.count('class="close"'), 1) From 510bf0eccb9c51972eeeff2b266bdac471e9c51b Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 16:22:08 +0200 Subject: [PATCH 19/41] fix: preserve focus visibility on dimmed markers --- scripts/generate_research_map.py | 2 +- tests/test_generate_research_map.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index 4b81c83..1da719e 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -267,7 +267,7 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str body {{ max-width:1200px; margin:0 auto; padding:36px 24px; }} h1 {{ font-size:clamp(2rem,5vw,4rem); margin:0 0 8px; }} .intro {{ color:#9eb4ac; max-width:760px; line-height:1.5; }} .map {{ position:relative; height:620px; margin:34px 42px 20px 90px; border-left:1px solid #668078; border-bottom:1px solid #668078; background:linear-gradient(90deg,transparent 49.9%,#243a35 50%,transparent 50.1%),linear-gradient(0deg,transparent 49.9%,#243a35 50%,transparent 50.1%); }} .primitive-heading {{ margin-top:44px; }} .primitive-actions {{ display:flex; justify-content:flex-end; margin-bottom:12px; }} .show-all {{ color:#bff7df; background:#1b302b; border:1px solid #46675c; border-radius:999px; padding:8px 16px; cursor:pointer; }} .show-all:hover,.show-all:focus-visible {{ border-color:#8ee3bf; background:#24433a; }} .primitive-grid {{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; }} .primitive-card {{ min-width:0; background:#15211e; border:1px solid #304640; border-top:3px solid var(--primitive-accent); border-radius:12px; overflow:hidden; }} .primitive-select {{ display:flex; flex-direction:column; gap:12px; width:100%; min-height:100%; padding:20px; color:inherit; background:#172723; border:0; text-align:left; cursor:pointer; }} .primitive-select:hover,.primitive-select:focus-visible,.primitive-select[aria-pressed="true"] {{ background:#203630; box-shadow:inset 0 0 0 1px var(--primitive-accent); }} .primitive-title {{ color:var(--primitive-accent); font-size:1.15rem; font-weight:800; line-height:1.25; }} .strategic-decision {{ margin-top:auto; color:#c8d8d2; }} .primitive-details {{ border-top:1px solid #304640; padding:14px 20px 18px; }} .primitive-details summary {{ color:var(--primitive-accent); cursor:pointer; font-weight:700; }} .primitive-details dl {{ margin-bottom:0; }} .primitive-details dt {{ margin-top:14px; color:#b4c8c0; font-weight:700; }} .primitive-details dd {{ margin:4px 0 0; color:#d5e3de; line-height:1.45; }} .primitive-links {{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }} .primitive-links ul {{ margin:8px 0 0; padding-left:18px; }} .primitive-links li {{ margin:6px 0; }} .matrix-tabs {{ display:flex; gap:8px; margin-top:44px; padding:4px; overflow-x:auto; scrollbar-color:#46675c #15211e; }} .matrix-tabs [role="tab"] {{ flex:0 0 auto; color:#b4c8c0; background:#172723; border:1px solid #304640; border-radius:8px; padding:10px 14px; cursor:pointer; }} .matrix-tabs [role="tab"]:hover,.matrix-tabs [role="tab"]:focus-visible {{ color:#e7f1ed; border-color:#668078; background:#203630; }} .matrix-tabs [aria-selected="true"] {{ color:#10201b; background:#8ee3bf; border-color:#8ee3bf; font-weight:800; }} .matrix[hidden] {{ display:none; }} .matrix {{ margin-top:28px; }} .axis-x,.axis-y {{ position:absolute; color:#9eb4ac; font-size:.75rem; letter-spacing:.08em; text-transform:uppercase; }} .axis-x {{ left:0; right:0; bottom:-34px; text-align:center; }} .axis-y {{ writing-mode:vertical-rl; transform:rotate(180deg) translateY(50%); left:-52px; top:50%; text-align:center; white-space:nowrap; }} -.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; transition:opacity .15s,box-shadow .15s,border-color .15s; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .marker.related {{ border-color:var(--selected-accent); box-shadow:0 0 0 4px color-mix(in srgb,var(--selected-accent) 35%,transparent); }} .marker.dimmed {{ opacity:.22; }} +.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; transition:opacity .15s,box-shadow .15s,border-color .15s; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .marker.related {{ border-color:var(--selected-accent); box-shadow:0 0 0 4px color-mix(in srgb,var(--selected-accent) 35%,transparent); }} .marker.dimmed {{ opacity:.22; }} .marker.dimmed:focus-visible {{ opacity:1; outline:3px solid #fff; outline-offset:3px; }} .legend {{ display:flex; gap:22px; color:#b4c8c0; font-size:.9rem; }} .legend span::before {{ content:""; display:inline-block; width:11px; height:11px; margin-right:7px; background:#64c5a0; border-radius:50%; }} .legend .idea-key::before {{ background:#e6a85b; border-radius:0; transform:rotate(45deg); }} .unplaced {{ margin-top:56px; border-top:1px solid #304640; padding-top:18px; }} .unplaced summary {{ color:#9eb4ac; cursor:pointer; }} .unplaced ul {{ list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; }} .unplaced li {{ display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; }} .note-kind,.picker-kind {{ display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; }} .note-kind.research,.picker-kind.research {{ color:#bff7df; background:#245744; }} .note-kind.idea,.picker-kind.idea {{ color:#ffe0ad; background:#654522; }} a {{ color:#8ee3bf; }} dialog {{ max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; }} dialog::backdrop {{ background:#020505bb; }} button {{ font:inherit; }} .close {{ float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; }} .tag {{ color:#9eb4ac; margin-right:8px; }} .primitive-badge {{ display:inline-block; margin-left:8px; padding:3px 8px; color:#101619; background:var(--selected-accent); border-radius:999px; font-size:.72rem; font-weight:800; text-transform:uppercase; }} .rating {{ border-top:1px solid #304640; padding:12px 0; }} .rating strong {{ color:#8ee3bf; }} .picker-item {{ display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; }} .picker-item:hover,.picker-item:focus-visible {{ background:#2c4940; border-color:#8ee3bf; }} .picker-item small {{ color:#b4c8c0; }} .picker-kind {{ margin-right:8px; }} @media(max-width:800px) {{ .primitive-grid {{ grid-template-columns:1fr; }} .primitive-select {{ min-height:0; }} }} diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 347c662..c04b14a 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -1,4 +1,5 @@ import pathlib +import re import unittest from types import SimpleNamespace @@ -344,8 +345,21 @@ def test_generated_html_persistently_refreshes_marker_highlighting(self): self.assertIn("aria-pressed',String", html) self.assertIn("classList.toggle('related'", html) self.assertIn("classList.toggle('dimmed'", html) - self.assertNotIn("m.disabled", html) self.assertIn("--selected-accent", html) + marker_buttons = re.findall(r'' ) else: - cluster_id = f"{plot_id}:{p['x']}:{p['y']}" + cluster_id = f"{plot_id}-cluster-{len(markers)}" + note_ids = html.escape(json.dumps([item["id"] for item in group]), quote=True) markers.append( - f'' + f'' ) for item in payload: if item["position"] is None: @@ -267,7 +268,7 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str body {{ max-width:1200px; margin:0 auto; padding:36px 24px; }} h1 {{ font-size:clamp(2rem,5vw,4rem); margin:0 0 8px; }} .intro {{ color:#9eb4ac; max-width:760px; line-height:1.5; }} .map {{ position:relative; height:620px; margin:34px 42px 20px 90px; border-left:1px solid #668078; border-bottom:1px solid #668078; background:linear-gradient(90deg,transparent 49.9%,#243a35 50%,transparent 50.1%),linear-gradient(0deg,transparent 49.9%,#243a35 50%,transparent 50.1%); }} .primitive-heading {{ margin-top:44px; }} .primitive-actions {{ display:flex; justify-content:flex-end; margin-bottom:12px; }} .show-all {{ color:#bff7df; background:#1b302b; border:1px solid #46675c; border-radius:999px; padding:8px 16px; cursor:pointer; }} .show-all:hover,.show-all:focus-visible {{ border-color:#8ee3bf; background:#24433a; }} .primitive-grid {{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; }} .primitive-card {{ min-width:0; background:#15211e; border:1px solid #304640; border-top:3px solid var(--primitive-accent); border-radius:12px; overflow:hidden; }} .primitive-select {{ display:flex; flex-direction:column; gap:12px; width:100%; min-height:100%; padding:20px; color:inherit; background:#172723; border:0; text-align:left; cursor:pointer; }} .primitive-select:hover,.primitive-select:focus-visible,.primitive-select[aria-pressed="true"] {{ background:#203630; box-shadow:inset 0 0 0 1px var(--primitive-accent); }} .primitive-title {{ color:var(--primitive-accent); font-size:1.15rem; font-weight:800; line-height:1.25; }} .strategic-decision {{ margin-top:auto; color:#c8d8d2; }} .primitive-details {{ border-top:1px solid #304640; padding:14px 20px 18px; }} .primitive-details summary {{ color:var(--primitive-accent); cursor:pointer; font-weight:700; }} .primitive-details dl {{ margin-bottom:0; }} .primitive-details dt {{ margin-top:14px; color:#b4c8c0; font-weight:700; }} .primitive-details dd {{ margin:4px 0 0; color:#d5e3de; line-height:1.45; }} .primitive-links {{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }} .primitive-links ul {{ margin:8px 0 0; padding-left:18px; }} .primitive-links li {{ margin:6px 0; }} .matrix-tabs {{ display:flex; gap:8px; margin-top:44px; padding:4px; overflow-x:auto; scrollbar-color:#46675c #15211e; }} .matrix-tabs [role="tab"] {{ flex:0 0 auto; color:#b4c8c0; background:#172723; border:1px solid #304640; border-radius:8px; padding:10px 14px; cursor:pointer; }} .matrix-tabs [role="tab"]:hover,.matrix-tabs [role="tab"]:focus-visible {{ color:#e7f1ed; border-color:#668078; background:#203630; }} .matrix-tabs [aria-selected="true"] {{ color:#10201b; background:#8ee3bf; border-color:#8ee3bf; font-weight:800; }} .matrix[hidden] {{ display:none; }} .matrix {{ margin-top:28px; }} .axis-x,.axis-y {{ position:absolute; color:#9eb4ac; font-size:.75rem; letter-spacing:.08em; text-transform:uppercase; }} .axis-x {{ left:0; right:0; bottom:-34px; text-align:center; }} .axis-y {{ writing-mode:vertical-rl; transform:rotate(180deg) translateY(50%); left:-52px; top:50%; text-align:center; white-space:nowrap; }} -.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; transition:opacity .15s,box-shadow .15s,border-color .15s; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .marker.related {{ border-color:var(--selected-accent); box-shadow:0 0 0 4px color-mix(in srgb,var(--selected-accent) 35%,transparent); }} .marker.dimmed {{ opacity:.22; }} .marker.dimmed:focus-visible {{ opacity:1; outline:3px solid #fff; outline-offset:3px; }} +.marker {{ position:absolute; transform:translate(-50%,50%); width:18px; height:18px; border:2px solid #d9fff0; cursor:pointer; transition:opacity .15s,box-shadow .15s,border-color .15s; }} .marker.research {{ border-radius:50%; background:#64c5a0; }} .marker.idea {{ transform:translate(-50%,50%) rotate(45deg); background:#e6a85b; }} .marker.cluster {{ transform:translate(-50%,50%); width:30px; height:30px; border-radius:50%; background:#d9fff0; color:#10201b; font-weight:800; }} .marker.related {{ border-color:var(--selected-accent); box-shadow:0 0 0 4px color-mix(in srgb,var(--selected-accent) 35%,transparent); }} .marker.cluster.related {{ background:var(--selected-accent); }} .marker.dimmed {{ opacity:.22; }} .marker.dimmed:focus-visible {{ opacity:1; outline:3px solid #fff; outline-offset:3px; }} .legend {{ display:flex; gap:22px; color:#b4c8c0; font-size:.9rem; }} .legend span::before {{ content:""; display:inline-block; width:11px; height:11px; margin-right:7px; background:#64c5a0; border-radius:50%; }} .legend .idea-key::before {{ background:#e6a85b; border-radius:0; transform:rotate(45deg); }} .unplaced {{ margin-top:56px; border-top:1px solid #304640; padding-top:18px; }} .unplaced summary {{ color:#9eb4ac; cursor:pointer; }} .unplaced ul {{ list-style:none; margin:14px 0 0; padding:0; display:grid; gap:8px; }} .unplaced li {{ display:flex; align-items:center; gap:10px; background:#172320; border:1px solid #304640; border-radius:8px; padding:10px 12px; }} .note-kind,.picker-kind {{ display:inline-flex; align-items:center; flex:0 0 auto; border-radius:999px; padding:3px 8px; font-size:.68rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; }} .note-kind.research,.picker-kind.research {{ color:#bff7df; background:#245744; }} .note-kind.idea,.picker-kind.idea {{ color:#ffe0ad; background:#654522; }} a {{ color:#8ee3bf; }} dialog {{ max-width:620px; width:calc(100% - 48px); color:#e7f1ed; background:#172320; border:1px solid #668078; border-radius:14px; padding:26px; font:inherit; }} dialog::backdrop {{ background:#020505bb; }} button {{ font:inherit; }} .close {{ float:right; background:#243a35; color:inherit; border:1px solid #668078; border-radius:6px; padding:2px 8px; font-size:1.2rem; line-height:1.2; cursor:pointer; }} .tag {{ color:#9eb4ac; margin-right:8px; }} .primitive-badge {{ display:inline-block; margin-left:8px; padding:3px 8px; color:#101619; background:var(--selected-accent); border-radius:999px; font-size:.72rem; font-weight:800; text-transform:uppercase; }} .rating {{ border-top:1px solid #304640; padding:12px 0; }} .rating strong {{ color:#8ee3bf; }} .picker-item {{ display:block; width:100%; margin:8px 0; padding:12px 14px; text-align:left; color:inherit; background:#20322e; border:1px solid #46675c; border-radius:8px; cursor:pointer; }} .picker-item:hover,.picker-item:focus-visible {{ background:#2c4940; border-color:#8ee3bf; }} .picker-item small {{ color:#b4c8c0; }} .picker-kind {{ margin-right:8px; }} @media(max-width:800px) {{ .primitive-grid {{ grid-template-columns:1fr; }} .primitive-select {{ min-height:0; }} }} @@ -277,14 +278,14 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str
    ResearchIdea

    Candidate platform primitives

    {''.join(cards)}
    {''.join(tabs)}
    {''.join(matrices)}
    ''' +function showCluster(items){{const isRelated=note=>Boolean(primitiveMemberships[note.id]?.[selectedPrimitive]);const ordered=selectedPrimitive?[...items.filter(isRelated),...items.filter(note=>!isRelated(note))]:[...items];content.innerHTML=`

    ${{ordered.length}} notes at this position

    Select a note

    ${{ordered.map((note,i)=>``).join('')}}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(ordered[Number(b.dataset.index)]))}} +document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const noteIds=JSON.parse(m.dataset.noteIds);const items=noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index c04b14a..028f5a3 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -1,3 +1,5 @@ +import html as html_lib +import json import pathlib import re import unittest @@ -48,6 +50,41 @@ def rating_notes(values): ] +def mixed_cluster_fixture(): + paths = [pathlib.Path(f"research/cluster-{index}.md") for index in range(5)] + notes = [ + SimpleNamespace( + path=path, + kind="research", + title=f"Cluster note {index}", + summary=f"Summary {index}", + metadata={ + "ratings": { + "maturity": {"value": 42, "note": "reason"}, + "platform-impact": {"value": 37, "note": "reason"}, + } + }, + ) + for index, path in enumerate(paths) + ] + plot = { + "title": "Mixed cluster", + "x": {"rating": "maturity", "label": "Maturity", "low": "Low", "high": "High"}, + "y": { + "rating": "platform-impact", + "label": "Platform impact", + "low": "Low", + "high": "High", + }, + } + primitive = { + **PRIMITIVE, + "core": [paths[1].as_posix()], + "supporting": [paths[3].as_posix()], + } + return notes, {"mixed:plot": plot}, [primitive] + + class ResearchMapTests(unittest.TestCase): def test_load_primitives_loads_the_three_approved_primitives(self): primitives = load_primitives() @@ -378,6 +415,31 @@ def test_generated_html_contains_cluster_marker_and_picker(self): self.assertIn('function showCluster', html) self.assertIn('picker-item', html) + def test_mixed_cluster_embeds_ordered_note_ids_and_updates_selected_count(self): + notes, plots, primitives = mixed_cluster_fixture() + + html = generate_html(notes, plots, primitives) + + marker = re.search(r'', html).group(0) + note_ids = [note.path.as_posix() for note in notes] + encoded_ids = html_lib.escape(json.dumps(note_ids), quote=True) + self.assertIn(f'data-note-ids="{encoded_ids}"', marker) + self.assertIn("const relatedCount=noteIds.filter", html) + self.assertIn("m.textContent=relatedCount?`${relatedCount}/${noteIds.length}`:String(noteIds.length)", html) + self.assertIn("m.classList.toggle('related',relatedCount>0)", html) + self.assertIn("m.classList.toggle('dimmed',Boolean(selectedPrimitive&&!relatedCount))", html) + + def test_mixed_cluster_picker_sorts_a_copy_related_first_and_labels_relationships(self): + notes, plots, primitives = mixed_cluster_fixture() + + html = generate_html(notes, plots, primitives) + + self.assertIn("const ordered=selectedPrimitive?[...items.filter(isRelated),...items.filter(note=>!isRelated(note))]:[...items]", html) + self.assertIn("${relationshipBadge(note)}", html) + self.assertIn("const noteIds=JSON.parse(m.dataset.noteIds)", html) + self.assertIn("noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id))", html) + self.assertNotIn("m.dataset.cluster.split", html) + def test_generated_html_wires_singletons_and_close_button(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertIn("show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))", html) From a1fd27e8fd7953bb3bc3888f892e2421cb50f1cd Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 16:52:43 +0200 Subject: [PATCH 21/41] fix: update mixed cluster accessible count --- scripts/generate_research_map.py | 2 +- tests/test_generate_research_map.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index 3d95e52..8ec05c7 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -278,7 +278,7 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str
    ResearchIdea

    Candidate platform primitives

    {''.join(cards)}
    {''.join(tabs)}
    {''.join(matrices)}
    ''' + function show(note){{const tags=(note.tags||[]).map(t=>`#${{t}}`).join('');const ratings=Object.entries(note.ratings||{{}}).map(([name,r])=>`
    ${{name}}: ${{r.value}}/100
    ${{r.note}}
    `).join('');content.innerHTML=`

    ${{note.kind}}${{relationshipBadge(note)}}

    ${{note.title}}

    ${{note.summary}}

    ${{tags}}

    ${{ratings}}

    Read the full Markdown note on GitHub

    `;dialog.showModal()}} + function showCluster(items){{const isRelated=note=>Boolean(primitiveMemberships[note.id]?.[selectedPrimitive]);const ordered=selectedPrimitive?[...items.filter(isRelated),...items.filter(note=>!isRelated(note))]:[...items];content.innerHTML=`

    ${{ordered.length}} notes at this position

    Select a note

    ${{ordered.map((note,i)=>``).join('')}}`;dialog.showModal();content.querySelectorAll('.picker-item').forEach(b=>b.onclick=()=>show(ordered[Number(b.dataset.index)]))}} + function activateTab(index){{tabs.forEach((tab,i)=>{{const selected=i===index;tab.setAttribute('aria-selected',String(selected));tab.tabIndex=selected?0:-1;panels[i].hidden=!selected}});tabs[index].focus();updateMarkers()}} + function handleTabKey(event,index){{let next;switch(event.key){{case 'ArrowLeft':next=(index-1+tabs.length)%tabs.length;break;case 'ArrowRight':next=(index+1)%tabs.length;break;case 'Home':next=0;break;case 'End':next=tabs.length-1;break;default:return}}event.preventDefault();activateTab(next)}} + const tabs=[...document.querySelectorAll('[role="tab"]')],panels=tabs.map(tab=>document.querySelector(`#${{tab.getAttribute('aria-controls')}}`));tabs.forEach((tab,index)=>{{tab.onclick=()=>activateTab(index);tab.onkeydown=event=>handleTabKey(event,index)}});document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const noteIds=JSON.parse(m.dataset.noteIds);const items=noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});dialog.addEventListener('keydown',e=>{{if(e.key==='Escape')dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 8086185..831fb44 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -2,6 +2,7 @@ import json import pathlib import re +import subprocess import unittest from types import SimpleNamespace @@ -348,10 +349,11 @@ def test_generated_html_contains_accessible_matrix_tabs_and_panels(self): self.assertEqual(html.count('role="tab" aria-selected="false"'), 5) for index, plot_id in enumerate(plots): selected = "true" if index == 0 else "false" + tab_index = "0" if index == 0 else "-1" hidden = "" if index == 0 else " hidden" self.assertIn( f'id="tab-{plot_id}" role="tab" aria-selected="{selected}" ' - f'aria-controls="panel-{plot_id}"', + f'tabindex="{tab_index}" aria-controls="panel-{plot_id}"', html, ) self.assertIn( @@ -360,6 +362,30 @@ def test_generated_html_contains_accessible_matrix_tabs_and_panels(self): html, ) + def test_generated_html_activates_tabs_for_clicks_and_keyboard_navigation(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn("function activateTab(index)", html) + self.assertIn("tab.setAttribute('aria-selected',String(selected))", html) + self.assertIn("tab.tabIndex=selected?0:-1", html) + self.assertIn("panels[i].hidden=!selected", html) + self.assertIn("tabs[index].focus()", html) + self.assertIn("tab.onclick=()=>activateTab(index)", html) + self.assertIn("case 'ArrowLeft':next=(index-1+tabs.length)%tabs.length;break", html) + self.assertIn("case 'ArrowRight':next=(index+1)%tabs.length;break", html) + self.assertIn("case 'Home':next=0;break", html) + self.assertIn("case 'End':next=tabs.length-1;break", html) + self.assertIn("event.preventDefault();activateTab(next)", html) + + def test_tab_activation_preserves_primitive_selection_and_updates_markers(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + activate_tab = html.split("function activateTab(index)", 1)[1].split( + "function handleTabKey", 1 + )[0] + + self.assertNotIn("selectedPrimitive=", activate_tab) + self.assertIn("updateMarkers()", activate_tab) + def test_generated_html_embeds_primitive_membership_once_and_stable_control_ids(self): primitives = load_primitives() html = generate_html(load_notes(), load_plots(), primitives) @@ -378,7 +404,7 @@ def test_generated_html_persistently_refreshes_marker_highlighting(self): self.assertIn("let selectedPrimitive=null", html) self.assertIn("function selectPrimitive", html) self.assertIn("function clearPrimitiveSelection", html) - self.assertIn("function refreshMarkers", html) + self.assertIn("function updateMarkers", html) self.assertIn("aria-pressed',String", html) self.assertIn("classList.toggle('related'", html) self.assertIn("classList.toggle('dimmed'", html) @@ -456,6 +482,25 @@ def test_generated_html_wires_singletons_and_close_button(self): self.assertIn("show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))", html) self.assertIn("document.querySelector('.close').onclick=()=>dialog.close()", html) + def test_generated_html_retains_dialog_dismissal_interactions(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn("if(e.target===dialog)dialog.close()", html) + self.assertIn("if(e.key==='Escape')dialog.close()", html) + + def test_generated_javascript_has_valid_syntax(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + script = re.search(r"", html, re.DOTALL).group(1) + + result = subprocess.run( + ["node", "--check"], + input=script, + text=True, + capture_output=True, + check=False, + ) + self.assertEqual(result.returncode, 0, result.stderr) + def test_generated_html_styles_note_lists_and_picker_items(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertIn(".unplaced li", html) From db304addbb200afd5d1026b6ad6e6eb5db9f29f0 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 17:11:34 +0200 Subject: [PATCH 23/41] fix: remove Node test dependency --- tests/test_generate_research_map.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 831fb44..27bc7dc 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -2,7 +2,6 @@ import json import pathlib import re -import subprocess import unittest from types import SimpleNamespace @@ -488,19 +487,6 @@ def test_generated_html_retains_dialog_dismissal_interactions(self): self.assertIn("if(e.target===dialog)dialog.close()", html) self.assertIn("if(e.key==='Escape')dialog.close()", html) - def test_generated_javascript_has_valid_syntax(self): - html = generate_html(load_notes(), load_plots(), load_primitives()) - script = re.search(r"", html, re.DOTALL).group(1) - - result = subprocess.run( - ["node", "--check"], - input=script, - text=True, - capture_output=True, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - def test_generated_html_styles_note_lists_and_picker_items(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertIn(".unplaced li", html) From 6a79a0bb31e207b72a030da78b72a7a8cb88205d Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 17:15:55 +0200 Subject: [PATCH 24/41] chore: refresh platform primitives workshop view --- generated/research-map.html | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/generated/research-map.html b/generated/research-map.html index c7d99cf..41bc2cb 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -1,19 +1,33 @@ -Maturity x Actionability

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Novelty

    Familiar < Novelty > EmergingLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Platform Impact x Actionability

    Exploratory < Actionability > Ready to actLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.

    Novelty x Actionability

    Familiar < Novelty > EmergingExploratory < Actionability > Ready to act
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Novelty

    Familiar < Novelty > EmergingEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.

    Maturity x Actionability

    Exploratory < Actionability > Ready to actEmerging < Maturity > Established
    Unplaced notes (0)
    • All notes are placed.
    +
    ResearchIdea

    Candidate platform primitives

    + +
    Gap, experiments, and evidence
    Current CF gap
    CF tasks are tied to one compute attempt and lack stable execution identity, checkpoints, suspend/resume, durable events, timers, and retry policy.
    Candidate POC
    Run a checkpointing task under a stable execution ID, suspend it after persisting state, then resume it on replacement compute through events, timers, and bounded retries.
    Candidate RFC scope
    Define execution identity, lifecycle states, checkpoint handoff, event and timer delivery, retry semantics, observability, and the boundary between CF lifecycle ownership and bound state stores.
    + +
    Gap, experiments, and evidence
    Current CF gap
    CF issues workload identity certificates but does not exchange them for scoped tool authority, keep third-party credentials out of workloads, mediate off-platform access, or record delegation-aware audit events.
    Candidate POC
    Exchange a Diego instance identity certificate for a short-lived scoped token, invoke one allowed tool through a credential proxy and egress mediator, deny another, and emit attributable audit events.
    Candidate RFC scope
    Define workload token exchange, authority and delegation claims, credential brokering, outbound mediation and policy enforcement, audit events, revocation, and integration boundaries for UAA, routing, and service brokers.
    + +
    Gap, experiments, and evidence
    Current CF gap
    CF can stage apps and run ephemeral tasks but cannot cheaply compose a reusable environment with per-session workspace state, select stronger isolation, constrain session networking, or resume the session lifecycle.
    Candidate POC
    Start two isolated sessions from one content-addressed staged environment, attach separate mutable workspaces, apply per-session egress policy, stop one session, and resume it on fresh compute.
    Candidate RFC scope
    Define environment and workspace references, session identity and lifecycle, isolation classes, network policy, workspace persistence and cleanup, scheduling, quotas, and compatibility with existing CF staging and task APIs.

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.
    - \ No newline at end of file + \ No newline at end of file From 3c3e0a8f7b0c465fa950610aea28187287f189ea Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 17:24:03 +0200 Subject: [PATCH 25/41] fix: harden research map dialogs --- scripts/generate_research_map.py | 14 +++-- tests/test_generate_research_map.py | 83 +++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index 428e95c..a553ac8 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -181,6 +181,8 @@ def validate_primitives(primitives: object, known_paths: set[str]) -> list[dict] if path not in known_paths: raise ValueError(f"primitive '{primitive_id}' references unknown note path: {path}") + if len(primitives) != 3: + raise ValueError("primitive configuration must contain exactly 3 initial primitives") return primitives @@ -241,7 +243,7 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str if len(group) == 1: item = group[0] markers.append( - f'' + f'' ) else: cluster_id = f"{plot_id}-cluster-{len(markers)}" @@ -252,7 +254,8 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str for item in payload: if item["position"] is None: label = "Research" if item["kind"] == "research" else "Idea" - unplaced.append(f'
  • {label}{html.escape(item["title"])}
  • ') + escaped_kind = html.escape(item["kind"]) + unplaced.append(f'
  • {label}{html.escape(item["title"])}
  • ') title = html.escape(plot["title"]) x = plot["x"] y = plot["y"] @@ -277,15 +280,16 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    ResearchIdea

    Candidate platform primitives

    {''.join(cards)}
    {''.join(tabs)}
    {''.join(matrices)}
    -
    +
    ''' diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 27bc7dc..6df0a7c 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -101,8 +101,16 @@ def test_load_primitives_loads_the_three_approved_primitives(self): def test_validate_primitives_preserves_core_and_supporting_order(self): primitive = {**PRIMITIVE, "core": ["b.md", "a.md"], "supporting": ["d.md", "c.md"]} + primitives = [ + primitive, + {**PRIMITIVE, "id": "second"}, + {**PRIMITIVE, "id": "third"}, + ] - validated = validate_primitives([primitive], {"a.md", "b.md", "c.md", "d.md"}) + validated = validate_primitives( + primitives, + {"a.md", "b.md", "c.md", "d.md", *PRIMITIVE["core"], *PRIMITIVE["supporting"]}, + ) self.assertEqual(validated[0]["core"], ["b.md", "a.md"]) self.assertEqual(validated[0]["supporting"], ["d.md", "c.md"]) @@ -111,6 +119,13 @@ def test_validate_primitives_rejects_non_list_configuration(self): with self.assertRaisesRegex(ValueError, "non-empty list"): validate_primitives({"primitives": [PRIMITIVE]}, set(PRIMITIVE["core"] + PRIMITIVE["supporting"])) + def test_validate_primitives_requires_exactly_three_initial_primitives(self): + primitives = load_primitives()[:2] + known_paths = {path for primitive in primitives for path in primitive["core"] + primitive["supporting"]} + + with self.assertRaisesRegex(ValueError, "exactly 3"): + validate_primitives(primitives, known_paths) + def test_validate_primitives_rejects_blank_required_fields(self): for field in ("id", "title", "proposition", "cf_gap", "strategic_decision", "poc", "rfc_scope"): with self.subTest(field=field): @@ -309,10 +324,72 @@ def test_all_current_notes_have_initial_ratings_and_justifications(self): def test_generated_html_contains_markers_dialog_and_source_links(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertGreater(html.count('class="marker '), 0) - self.assertIn('', html) + self.assertIn('", 1) + self.assertNotIn(attack, static_markup) + self.assertNotIn("${note.title}", script) + self.assertNotIn("${note.summary}", script) + self.assertNotIn("${r.note}", script) + + def test_generated_dialog_has_a_stable_accessible_name_in_both_states(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn('', html) + self.assertEqual(html.count('

    '), 2) + def test_generated_html_contains_one_matrix_for_each_configured_plot(self): html = generate_html(load_notes(), load_plots(), load_primitives()) for plot in load_plots().values(): @@ -505,7 +582,7 @@ def test_unplaced_list_distinguishes_ideas_and_research(self): def test_picker_items_distinguish_ideas_and_research(self): html = generate_html(load_notes(), load_plots(), load_primitives()) - self.assertIn('class="picker-kind ${note.kind}"', html) + self.assertIn('class="picker-kind ${escapeHtml(note.kind)}"', html) self.assertIn('.picker-kind.research', html) self.assertIn('.picker-kind.idea', html) From 7d2736ba8769bfb175ac5647973a0f1ea036bbf6 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 2 Sep 2026 17:24:06 +0200 Subject: [PATCH 26/41] chore: refresh hardened research map --- generated/research-map.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/generated/research-map.html b/generated/research-map.html index 41bc2cb..bbf3777 100644 --- a/generated/research-map.html +++ b/generated/research-map.html @@ -19,15 +19,16 @@
    Gap, experiments, and evidence
    Current CF gap
    CF issues workload identity certificates but does not exchange them for scoped tool authority, keep third-party credentials out of workloads, mediate off-platform access, or record delegation-aware audit events.
    Candidate POC
    Exchange a Diego instance identity certificate for a short-lived scoped token, invoke one allowed tool through a credential proxy and egress mediator, deny another, and emit attributable audit events.
    Candidate RFC scope
    Define workload token exchange, authority and delegation claims, credential brokering, outbound mediation and policy enforcement, audit events, revocation, and integration boundaries for UAA, routing, and service brokers.
    Gap, experiments, and evidence
    Current CF gap
    CF can stage apps and run ephemeral tasks but cannot cheaply compose a reusable environment with per-session workspace state, select stronger isolation, constrain session networking, or resume the session lifecycle.
    Candidate POC
    Start two isolated sessions from one content-addressed staged environment, attach separate mutable workspaces, apply per-session egress policy, stop one session, and resume it on fresh compute.
    Candidate RFC scope
    Define environment and workspace references, session identity and lifecycle, isolation classes, network policy, workspace persistence and cleanup, scheduling, quotas, and compatibility with existing CF staging and task APIs.

    Platform Impact x Maturity

    Emerging < Maturity > EstablishedLocal concern < Platform Impact > Platform-wide concern
    Unplaced notes (0)
    • All notes are placed.
    -
    +
    \ No newline at end of file From 001111b49b91a853969ab8b7c6ceb00ef94663e0 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 09:32:16 +0200 Subject: [PATCH 27/41] docs: specify workshop focus use cases --- ...6-09-03-workshop-focus-use-cases-design.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-03-workshop-focus-use-cases-design.md diff --git a/docs/superpowers/specs/2026-09-03-workshop-focus-use-cases-design.md b/docs/superpowers/specs/2026-09-03-workshop-focus-use-cases-design.md new file mode 100644 index 0000000..06d665f --- /dev/null +++ b/docs/superpowers/specs/2026-09-03-workshop-focus-use-cases-design.md @@ -0,0 +1,93 @@ +# Workshop Focus Use Cases Design + +## Purpose + +Extend the generated research map to present the working group's workshop outcomes. The page will lead with two selected focus use cases, connect them to three candidate platform primitives, and let participants inspect the research and ideas supporting either layer or their intersection. + +Use cases describe desired workload outcomes. Platform primitives describe reusable Cloud Foundry capabilities that may support those outcomes. They remain separate curated layers over the same evidence corpus. + +## Workshop framing + +Replace language that presents the page as workshop input. The heading and introduction state that the page summarizes workshop results: two focus use cases, three candidate platform primitives, and the collected evidence behind them. + +The visual hierarchy is: + +1. Workshop outcome heading and summary. +2. Two focus use-case cards. +3. Three candidate platform-primitive cards. +4. Six tabbed rating matrices. + +## Focus use cases + +### CF-hosted coding harnesses + +App developers run coding harnesses and agents, such as OpenCode, inside Cloud Foundry. A session owns a mutable repository workspace and performs edit/test feedback loops. The untrusted harness submits a candidate artifact to a narrow trusted deployment broker. The broker validates target, policy, provenance, and approval before creating conventional CF packages, builds, deployments, and revisions. + +The harness never receives unrestricted CAPI, Git, model, or package-registry credentials. Relevant concerns include persistent workspaces, reusable toolchains, stronger isolation, mediated egress, deployment approvals, stale-base detection, provenance, rollback, and audit. + +### User-facing agentic applications + +Developers deploy CF applications that use agent frameworks to perform agentic tasks for their users. The routed application authenticates users, creates durable sessions/executions, invokes authorized tools, pauses for approvals, and may use isolated workers for generated code. + +Relevant concerns include durable execution identity, user delegation, tool authorization, memory/state services, framework-neutral suspension, multi-tenant quotas, scaling, SLOs, telemetry, and optional sandbox lifecycle. + +## Content model + +Add `scripts/focus_use_cases.yaml`. Each use case contains: + +- Stable unique ID and title. +- Workshop outcome statement. +- Primary actor and beneficiary. +- End-to-end lifecycle. +- Authority boundary. +- Unique capabilities. +- Failure domain. +- Candidate POC. +- RFC decisions unlocked. +- Ordered core and supporting note paths. +- Applicability for each platform primitive: `core`, `conditional`, or `supporting`. + +The initial IDs are `cf-hosted-coding-harnesses` and `user-facing-agentic-applications`. + +The generator validates exactly two use cases, required non-empty fields, unique IDs, non-empty core memberships, no duplicate note memberships within a use case, known note paths, known primitive IDs, and applicability values from the allowed set. + +Membership is curated rather than inferred from tags. A note may belong to both use cases. + +## Layout + +Render the two use-case cards above the three platform-primitive cards. A use-case card shows its title, workshop outcome, and candidate POC. Expandable content shows actors, lifecycle, authority boundary, failure domain, unique capabilities, RFC decisions, primitive applicability, and linked core/supporting notes. + +The existing primitive cards and tabbed matrices remain below. Mobile layouts stack cards and retain the horizontally scrollable matrix tab strip and tall active matrix canvas. + +## Selection model + +Maintain two independent states: `selectedUseCase` and `selectedPrimitive`. + +- With only a use case selected, a note matches when it belongs to that use case. +- With only a primitive selected, a note matches when it belongs to that primitive. +- With both selected, a note matches only when it belongs to both. +- With neither selected, all notes use their normal display. + +Each layer has its own **Show all** control. Clicking an already selected card clears only that layer. Cards expose state through `aria-pressed`. + +When a selected intersection contains no directly linked notes, display a clear status message instead of silently dimming every marker. + +## Matrix and cluster behavior + +Matching individual markers retain full saturation and use selection accents. Non-matching markers dim but remain interactive and keyboard reachable. + +Cluster markers display `matching/total` while either layer is selected. Cluster pickers sort matching notes first while preserving source order within matching and non-matching groups. All notes remain accessible. Clearing both layers restores total-only cluster counts and original ordering. + +Note and cluster-picker dialogs display use-case and primitive membership badges relevant to the current selection. + +Matrix tab state and keyboard behavior remain unchanged when selections change. + +## Safety and accessibility + +All dynamic note and configuration content remains escaped before insertion into dialog markup. Use-case cards, primitive cards, Show-all controls, tabs, markers, and picker items remain keyboard operable. Dialogs retain stable accessible names. Dimmed markers regain full visibility on focus. + +## Verification + +Tests cover focus-use-case configuration validation, exactly-two enforcement, note/primitive references, applicability values, workshop-result wording, card ordering, card content, independent selection/toggle behavior, intersection matching, empty-intersection status, mixed-cluster matching counts, related-first picker ordering, dialog badges, `aria-pressed`, matrix-tab regression behavior, XSS escaping, responsive structural classes, and all existing interactions. + +Regenerate `generated/research-map.html`. Existing note validation, rating safeguards, primitive validation, generated-artifact freshness, and whitespace checks must continue to pass. From 79f47ad3adb7cf623e99565be2673617cd6ab776 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 09:36:00 +0200 Subject: [PATCH 28/41] docs: add workshop use-case implementation plan --- .../2026-09-03-workshop-focus-use-cases.md | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-03-workshop-focus-use-cases.md diff --git a/docs/superpowers/plans/2026-09-03-workshop-focus-use-cases.md b/docs/superpowers/plans/2026-09-03-workshop-focus-use-cases.md new file mode 100644 index 0000000..2636bc2 --- /dev/null +++ b/docs/superpowers/plans/2026-09-03-workshop-focus-use-cases.md @@ -0,0 +1,228 @@ +# Workshop Focus Use Cases Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Present two workshop-selected focus use cases above the platform primitives and let users highlight evidence by use case, primitive, or their intersection. + +**Architecture:** A curated YAML file defines use-case narratives, note memberships, and primitive applicability. The generator validates and embeds this layer alongside existing primitive membership. The static page maintains independent use-case and primitive state and applies one matching predicate to markers, clusters, pickers, and dialogs. + +**Tech Stack:** Python 3.12, PyYAML, unittest, generated vanilla HTML/CSS/JavaScript. + +--- + +## Files and Responsibilities + +- Create `scripts/focus_use_cases.yaml`: two workshop use cases, narratives, primitive applicability, and note memberships. +- Modify `scripts/generate_research_map.py`: use-case loading/validation, workshop-result content, cards, independent selection state, intersection filtering, status, counts, ordering, and badges. +- Modify `tests/test_generate_research_map.py`: validation, content, interaction, accessibility, escaping, and regression coverage. +- Modify `generated/research-map.html`: regenerated static workshop-results artifact. + +### Task 1: Define and validate focus-use-case configuration + +**Files:** +- Create: `scripts/focus_use_cases.yaml` +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing validation tests** + +Add tests for `validate_focus_use_cases(use_cases, known_paths, primitive_ids)` covering exactly two entries, required string/list fields, unique IDs, non-empty core membership, duplicate core/supporting paths, unknown note paths, unknown primitive IDs, and applicability outside `core`, `conditional`, or `supporting`. Assert valid order is preserved. + +- [ ] **Step 2: Run focused tests and verify failure** + +Run: `devbox run -- python -m unittest tests.test_generate_research_map.ResearchMapTests.test_validate_focus_use_cases_rejects_unknown_note` + +Expected: FAIL because use-case loading/validation does not exist. + +- [ ] **Step 3: Create the curated YAML** + +Define exactly: + +- `cf-hosted-coding-harnesses` +- `user-facing-agentic-applications` + +Include all approved fields and evidence memberships from the design. Represent primitive applicability as a mapping from the three existing primitive IDs to `core`, `conditional`, or `supporting`. + +- [ ] **Step 4: Implement loading and validation** + +Add `FOCUS_USE_CASES_PATH`, `load_focus_use_cases()`, and `validate_focus_use_cases()`. Validate against note paths and validated primitive IDs without reordering configured lists. + +- [ ] **Step 5: Verify and commit** + +Run: `devbox run -- python -m unittest discover -s tests && devbox run validate` + +Expected: PASS. + +Run: `git add scripts/focus_use_cases.yaml scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "feat: define workshop focus use cases"` + +### Task 2: Reframe and render workshop outcome cards + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing content and order tests** + +Assert the page says it summarizes workshop results rather than seeding a workshop; two use-case cards occur before three primitive cards and before tabs; use-case buttons have `aria-pressed="false"`; and each card contains outcome, POC, expandable lifecycle/authority/failure/capabilities/RFC/applicability/evidence content. + +- [ ] **Step 2: Render the outcome hierarchy** + +Render a workshop-results heading and summary, use-case section/cards, primitive section/cards, then tabbed matrices. Add a use-case **Show all** control separate from the primitive control. + +- [ ] **Step 3: Style use-case cards** + +Reuse typography and card language while visually distinguishing outcomes from primitives. Stack responsively above primitive cards and matrix tabs; avoid browser-default controls. + +- [ ] **Step 4: Escape configuration content and preserve canonical links** + +Escape all YAML-derived fields in static markup and any dynamic dialog templates. Render linked notes using existing canonical GitHub URLs. + +- [ ] **Step 5: Verify and commit** + +Run: `devbox run -- python -m unittest discover -s tests` + +Expected: PASS. + +Run: `git add scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "feat: present workshop focus outcomes"` + +### Task 3: Add independent use-case selection + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing state-contract tests** + +Assert generated data contains use-case membership once and JavaScript defines `selectedUseCase`, `selectedPrimitive`, toggle/clear handlers for each layer, and one `noteMatchesSelection(noteId)` predicate. + +- [ ] **Step 2: Embed use-case membership** + +Build note-to-use-case relationship metadata (`core` or `supporting`) once, alongside primitive membership. Avoid duplicating complete note records. + +- [ ] **Step 3: Implement independent card controls** + +Use-case clicks toggle only `selectedUseCase`; primitive clicks continue to toggle only `selectedPrimitive`. Each Show-all control clears its own layer. Update `aria-pressed` independently. + +- [ ] **Step 4: Implement matching semantics** + +`noteMatchesSelection` returns use-case membership, primitive membership, their intersection, or true when neither is selected. Apply matching/dimmed classes without disabling markers. + +- [ ] **Step 5: Verify and commit** + +Run: `devbox run -- python -m unittest discover -s tests` + +Expected: PASS. + +Run: `git add scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "feat: filter evidence by workshop use case"` + +### Task 4: Update cluster counts, ordering, and empty state + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing intersection-cluster tests** + +Use fixtures where a cluster has notes matching only the use case, only the primitive, both, and neither. Assert `matching/total` uses the current predicate, all notes remain in the picker, matching notes sort first stably, and clearing restores total counts/order. + +- [ ] **Step 2: Reuse one matching predicate** + +Replace primitive-only cluster logic with `noteMatchesSelection`. Update visible text and accessible labels consistently for use-case-only, primitive-only, and intersection states. + +- [ ] **Step 3: Implement empty-intersection status** + +Add a live or status region below filter cards. When both layers are selected and no note matches, show “No directly linked evidence for this use-case and primitive combination.” Clear it in other states or when matches exist. + +- [ ] **Step 4: Update picker ordering and badges** + +Sort a copy by current match status without mutating plot data. Show use-case and primitive core/supporting badges relevant to active selections. Preserve all entries and escaping. + +- [ ] **Step 5: Verify and commit** + +Run: `devbox run -- python -m unittest discover -s tests` + +Expected: PASS. + +Run: `git add scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "feat: combine use-case and primitive evidence"` + +### Task 5: Update details and protect accessibility regressions + +**Files:** +- Modify: `scripts/generate_research_map.py` +- Modify: `tests/test_generate_research_map.py` + +- [ ] **Step 1: Write failing detail-badge tests** + +Assert individual dialogs show active use-case and primitive relationship badges when applicable, omit them otherwise, retain one close control and stable accessible name, and escape titles/summaries/tags/ratings/configuration text. + +- [ ] **Step 2: Implement dialog membership badges** + +Render selected use-case and primitive badges through escaped helper output. Keep core/supporting labels distinct and preserve canonical Markdown links. + +- [ ] **Step 3: Protect existing keyboard and visual behavior** + +Retain matrix Arrow/Home/End behavior, focus-visible restoration on dimmed markers, independent `aria-pressed` states, interactive unrelated markers, responsive card/tab classes, and backdrop/Escape dialog behavior. + +- [ ] **Step 4: Run full regression suite** + +Run: `devbox run -- python -m unittest discover -s tests` + +Expected: PASS without Node or browser runtime dependencies. + +- [ ] **Step 5: Commit interaction completion** + +Run: `git add scripts/generate_research_map.py tests/test_generate_research_map.py && git commit -m "fix: preserve accessible workshop filtering"` + +### Task 6: Regenerate and verify the workshop-results artifact + +**Files:** +- Modify: `generated/research-map.html` + +- [ ] **Step 1: Regenerate the page** + +Run: `devbox run map` + +Expected: generated HTML contains exactly two use-case cards above three primitive cards and six tabbed matrices. + +- [ ] **Step 2: Run complete verification** + +Run: `devbox run -- python -m unittest discover -s tests && devbox run validate && devbox run -- python scripts/summarize_ratings.py --check && devbox run -- python scripts/generate_research_map.py --check && git diff --check` + +Expected: all commands exit 0. + +- [ ] **Step 3: Inspect generated contracts** + +Confirm workshop-result wording, card ordering, independent controls, intersection predicate, empty status, cluster count/accessibility updates, picker ordering, dialog badges, tab behavior, XSS escaping, and exactly two/three/six use-case/primitive/tab counts. + +- [ ] **Step 4: Commit the artifact** + +Run: `git add generated/research-map.html && git commit -m "chore: refresh workshop focus use cases"` + +### Task 7: Final review and PR handoff + +**Files:** +- Review: `scripts/focus_use_cases.yaml` +- Review: `scripts/platform_primitives.yaml` +- Review: `scripts/generate_research_map.py` +- Review: `tests/test_generate_research_map.py` +- Review: `generated/research-map.html` + +- [ ] **Step 1: Run fresh verification** + +Run: `devbox run -- python -m unittest discover -s tests && devbox run validate && devbox run -- python scripts/summarize_ratings.py --check && devbox run -- python scripts/generate_research_map.py --check && git diff --check` + +Expected: all commands exit 0. + +- [ ] **Step 2: Complete spec and quality reviews** + +Review against `docs/superpowers/specs/2026-09-03-workshop-focus-use-cases-design.md`, then inspect configuration clarity, interaction correctness, accessibility, escaping, data size, and regressions. Fix every finding and re-review. + +- [ ] **Step 3: Inspect branch scope** + +Run: `git status --short --branch && git diff origin/feature/research-clustering-map...HEAD --stat` + +Confirm only use-case configuration, generator/tests, generated artifact, and approved documentation are included. Leave unrelated Chromium, `.superpowers/`, and Nix files untouched. + +- [ ] **Step 4: Request push confirmation** + +Present results and verification evidence. Push to PR #42 only after explicit user approval. From 06f1648775c2232776a6c4f7d530f1dfd9590fd2 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 09:50:28 +0200 Subject: [PATCH 29/41] feat: define workshop focus use cases --- scripts/focus_use_cases.yaml | 77 +++++++++++++++++ scripts/generate_research_map.py | 83 ++++++++++++++++++ tests/test_generate_research_map.py | 125 ++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 scripts/focus_use_cases.yaml diff --git a/scripts/focus_use_cases.yaml b/scripts/focus_use_cases.yaml new file mode 100644 index 0000000..dc8fd56 --- /dev/null +++ b/scripts/focus_use_cases.yaml @@ -0,0 +1,77 @@ +- id: cf-hosted-coding-harnesses + title: CF-hosted coding harnesses + workshop_outcome: Define the minimum Cloud Foundry platform contract for safely hosting coding harnesses with durable, isolated sessions. + primary_actor: A platform customer deploying a coding harness on Cloud Foundry. + beneficiary: A developer delegating repository changes and code execution to the harness. + lifecycle: Stage a reusable environment, create an isolated session and workspace, execute tools, suspend idle compute, resume on replacement compute, and destroy the session. + authority_boundary: The harness may change only its assigned workspace and invoke explicitly authorized tools and network destinations; the platform enforces isolation and scoped credentials. + unique_capabilities: + - Reuse an immutable staged environment across isolated sessions without sharing mutable workspace state. + - Suspend and resume a session while retaining its workspace and stable identity. + - Select stronger workload isolation and session-specific network policy for generated code. + failure_domain: A harness or sandbox failure must remain within one session, preserve recoverable workspace state, and never expose another session or foundation credential. + poc: Start two isolated coding sessions from one content-addressed staged environment, give each a separate workspace and egress policy, then suspend and resume one on fresh compute. + rfc_decisions: + - Whether CF should expose a first-class session resource or compose package, droplet, task, volume, networking, and isolation APIs. + - How session identity, workspace persistence, lifecycle transitions, quotas, cleanup, and failure recovery are represented. + - Which isolation classes and network-policy controls operators can offer and developers can select. + core: + - ideas/per-session-sandboxes.md + - ideas/staged-sandbox-environments.md + - ideas/stronger-workload-isolation-for-agents.md + - ideas/localhost-only-egress-for-agents.md + - research/k8s-agent-sandbox.md + - research/firecracker-microvm.md + supporting: + - research/anthropic-managed-agents.md + - research/azure-hosted-agents.md + - research/heroku-ai-platform.md + - research/toolhive.md + - ideas/credential-less-agent-processes.md + primitive_applicability: + durable-addressable-execution: supporting + attested-workload-authority: core + session-scoped-isolated-execution: core + +- id: user-facing-agentic-applications + title: User-facing agentic applications + workshop_outcome: Define the platform contract for routed CF applications that run durable, authorized agent workflows on behalf of end users. + primary_actor: An application developer deploying a multi-tenant agentic application to Cloud Foundry. + beneficiary: An authenticated application user delegating a long-running task while retaining approval and audit control. + lifecycle: Authenticate a user, create a durable execution, invoke authorized tools, checkpoint state, pause for approval or failure, resume on replaceable compute, and complete or cancel with an audit trail. + authority_boundary: The application acts with both workload identity and explicitly delegated user authority; tool access is scoped, auditable, revocable, and unavailable as ambient process credentials. + unique_capabilities: + - Address and resume framework-neutral agent executions independently of an application instance. + - Delegate user authority to specific tools without placing durable credentials in the agent process. + - Pause for human approval and recover from process or provider failure without repeating committed effects. + failure_domain: One tenant's execution, tool failure, or compromised prompt must not leak authority or state across users, and replacement compute must resume without duplicating external effects. + poc: Run a routed invoice agent that checkpoints progress, pauses for user approval, survives instance replacement, and calls one tool with a short-lived user-delegated token. + rfc_decisions: + - Whether CF owns durable execution identity, events, timers, retries, suspension, and cancellation or integrates an external engine. + - How workload identity, user delegation, tool authorization, token exchange, audit, and revocation fit CF APIs and UAA. + - Which state, quota, scaling, telemetry, and optional isolated-worker contracts remain framework-neutral platform responsibilities. + core: + - ideas/durable-tasks-for-cf.md + - ideas/agent-failure-checkpointing.md + - ideas/agent-identity-and-tool-authorization.md + - ideas/credential-less-agent-processes.md + - research/temporal.md + - research/langgraph.md + - research/open-agent-auth.md + supporting: + - ideas/dapr-durable-execution-on-cf.md + - research/dapr-agents.md + - research/hatchet.md + - research/letta.md + - research/llamaindex.md + - research/microsoft-agent-framework.md + - research/openai-agents-sdk.md + - research/opentelemetry-genai.md + - research/mcp-protocol.md + - research/vercel-ai-sdk.md + - ideas/per-session-sandboxes.md + - research/k8s-agent-sandbox.md + primitive_applicability: + durable-addressable-execution: core + attested-workload-authority: core + session-scoped-isolated-execution: conditional diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index a553ac8..a2763c4 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -16,6 +16,7 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent PLOTS_PATH = ROOT / "scripts" / "research_map_plots.yaml" PRIMITIVES_PATH = ROOT / "scripts" / "platform_primitives.yaml" +FOCUS_USE_CASES_PATH = ROOT / "scripts" / "focus_use_cases.yaml" OUTPUT_PATH = ROOT / "generated" / "research-map.html" GITHUB_BASE = "https://github.com/cloudfoundry/agentic-runtime-notes/blob/main" FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL) @@ -192,6 +193,88 @@ def load_primitives() -> list[dict]: return validate_primitives(primitives, known_paths) +def validate_focus_use_cases( + use_cases: object, known_paths: set[str], primitive_ids: set[str] +) -> list[dict]: + if not isinstance(use_cases, list) or len(use_cases) != 2: + raise ValueError("focus use case configuration must contain exactly 2 entries") + + required_ids = { + "cf-hosted-coding-harnesses", + "user-facing-agentic-applications", + } + string_fields = ( + "id", + "title", + "workshop_outcome", + "primary_actor", + "beneficiary", + "lifecycle", + "authority_boundary", + "failure_domain", + "poc", + ) + list_fields = ("unique_capabilities", "rfc_decisions") + allowed_applicability = {"core", "conditional", "supporting"} + seen_ids = set() + for use_case in use_cases: + if not isinstance(use_case, dict): + raise ValueError("each focus use case must be a mapping") + for field in string_fields: + if not isinstance(use_case.get(field), str) or not use_case[field].strip(): + raise ValueError(f"focus use case requires a non-empty '{field}'") + for field in list_fields: + values = use_case.get(field) + if ( + not isinstance(values, list) + or not values + or any(not isinstance(value, str) or not value.strip() for value in values) + ): + raise ValueError(f"focus use case requires a non-empty '{field}' list of strings") + + use_case_id = use_case["id"] + if use_case_id in seen_ids: + raise ValueError(f"duplicate focus use case id: {use_case_id}") + seen_ids.add(use_case_id) + + core = use_case.get("core") + supporting = use_case.get("supporting") + if not isinstance(core, list) or not core: + raise ValueError(f"focus use case '{use_case_id}' requires non-empty core membership") + if not isinstance(supporting, list): + raise ValueError(f"focus use case '{use_case_id}' supporting membership must be a list") + memberships = core + supporting + if any(not isinstance(path, str) or not path.strip() for path in memberships): + raise ValueError(f"focus use case '{use_case_id}' note paths must be non-empty strings") + if len(memberships) != len(set(memberships)): + raise ValueError(f"focus use case '{use_case_id}' has a duplicate note path") + for path in memberships: + if path not in known_paths: + raise ValueError(f"focus use case '{use_case_id}' references unknown note path: {path}") + + applicability = use_case.get("primitive_applicability") + if not isinstance(applicability, dict) or set(applicability) != primitive_ids: + raise ValueError( + f"focus use case '{use_case_id}' primitive applicability must contain exactly the known primitive ids" + ) + for primitive_id, value in applicability.items(): + if value not in allowed_applicability: + raise ValueError( + f"focus use case '{use_case_id}' has invalid applicability '{value}' for primitive '{primitive_id}'" + ) + + if seen_ids != required_ids: + raise ValueError("focus use case configuration must contain the approved focus use case ids") + return use_cases + + +def load_focus_use_cases() -> list[dict]: + use_cases = yaml.safe_load(FOCUS_USE_CASES_PATH.read_text(encoding="utf-8")) + known_paths = {note.path.as_posix() for note in load_notes()} + primitive_ids = {primitive["id"] for primitive in load_primitives()} + return validate_focus_use_cases(use_cases, known_paths, primitive_ids) + + def note_payload(note: Note, plot: dict) -> dict: ratings = note.metadata["ratings"] return { diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 6df0a7c..a82d879 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -6,16 +6,19 @@ from types import SimpleNamespace from scripts.generate_research_map import ( + FOCUS_USE_CASES_PATH, PRIMITIVES_PATH, derive_position, extract_summary, github_url, generate_html, group_payload, + load_focus_use_cases, load_primitives, load_plots, load_notes, parse_note, + validate_focus_use_cases, validate_primitives, validate_ratings, ) @@ -35,6 +38,38 @@ "core": ["ideas/durable-tasks-for-cf.md", "research/temporal.md"], "supporting": ["research/dapr-agents.md"], } +PRIMITIVE_IDS = { + "durable-addressable-execution", + "attested-workload-authority", + "session-scoped-isolated-execution", +} +FOCUS_USE_CASE = { + "id": "cf-hosted-coding-harnesses", + "title": "CF-hosted coding harnesses", + "workshop_outcome": "Determine the minimum CF platform contract for hosted coding harnesses.", + "primary_actor": "A developer deploying a coding harness to Cloud Foundry.", + "beneficiary": "A software team using the harness to change a repository.", + "lifecycle": "Stage an environment, start a session, execute tools, suspend it, and resume it.", + "authority_boundary": "The harness delegates only scoped repository and tool access.", + "unique_capabilities": ["Reusable staged environments", "Resumable isolated sessions"], + "failure_domain": "A failed sandbox must not lose the session workspace or affect another session.", + "poc": "Run and resume two isolated coding sessions from one staged environment.", + "rfc_decisions": ["Session resource and lifecycle", "Workspace and network policy"], + "core": ["ideas/per-session-sandboxes.md", "research/k8s-agent-sandbox.md"], + "supporting": ["research/firecracker-microvm.md"], + "primitive_applicability": { + "durable-addressable-execution": "supporting", + "attested-workload-authority": "core", + "session-scoped-isolated-execution": "core", + }, +} + + +def focus_use_cases(first=FOCUS_USE_CASE): + return [ + first, + {**FOCUS_USE_CASE, "id": "user-facing-agentic-applications"}, + ] def rating_notes(values): @@ -86,6 +121,96 @@ def mixed_cluster_fixture(): class ResearchMapTests(unittest.TestCase): + def test_load_focus_use_cases_loads_the_two_approved_use_cases(self): + use_cases = load_focus_use_cases() + + self.assertEqual(FOCUS_USE_CASES_PATH.name, "focus_use_cases.yaml") + self.assertEqual( + [use_case["id"] for use_case in use_cases], + ["cf-hosted-coding-harnesses", "user-facing-agentic-applications"], + ) + + def test_validate_focus_use_cases_preserves_use_case_and_membership_order(self): + first = { + **FOCUS_USE_CASE, + "core": ["b.md", "a.md"], + "supporting": ["d.md", "c.md"], + } + + validated = validate_focus_use_cases( + focus_use_cases(first), + {"a.md", "b.md", "c.md", "d.md", *FOCUS_USE_CASE["core"], *FOCUS_USE_CASE["supporting"]}, + PRIMITIVE_IDS, + ) + + self.assertEqual([item["id"] for item in validated], [first["id"], "user-facing-agentic-applications"]) + self.assertEqual(validated[0]["core"], ["b.md", "a.md"]) + self.assertEqual(validated[0]["supporting"], ["d.md", "c.md"]) + + def test_validate_focus_use_cases_requires_exactly_two_approved_ids(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + with self.assertRaisesRegex(ValueError, "exactly 2"): + validate_focus_use_cases([FOCUS_USE_CASE], known_paths, PRIMITIVE_IDS) + with self.assertRaisesRegex(ValueError, "approved focus use case ids"): + validate_focus_use_cases( + focus_use_cases({**FOCUS_USE_CASE, "id": "another-use-case"}), known_paths, PRIMITIVE_IDS + ) + + def test_validate_focus_use_cases_rejects_invalid_required_fields(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + string_fields = ( + "id", "title", "workshop_outcome", "primary_actor", "beneficiary", "lifecycle", + "authority_boundary", "failure_domain", "poc", + ) + list_fields = ("unique_capabilities", "rfc_decisions") + for field in string_fields: + with self.subTest(field=field), self.assertRaisesRegex(ValueError, f"non-empty '{field}'"): + validate_focus_use_cases(focus_use_cases({**FOCUS_USE_CASE, field: " "}), known_paths, PRIMITIVE_IDS) + for field in list_fields: + with self.subTest(field=field), self.assertRaisesRegex(ValueError, f"non-empty '{field}'"): + validate_focus_use_cases(focus_use_cases({**FOCUS_USE_CASE, field: []}), known_paths, PRIMITIVE_IDS) + + def test_validate_focus_use_cases_rejects_duplicate_ids(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + with self.assertRaisesRegex(ValueError, "duplicate focus use case id"): + validate_focus_use_cases([FOCUS_USE_CASE, dict(FOCUS_USE_CASE)], known_paths, PRIMITIVE_IDS) + + def test_validate_focus_use_cases_rejects_empty_core_membership(self): + with self.assertRaisesRegex(ValueError, "non-empty core"): + validate_focus_use_cases(focus_use_cases({**FOCUS_USE_CASE, "core": []}), set(FOCUS_USE_CASE["supporting"]), PRIMITIVE_IDS) + + def test_validate_focus_use_cases_rejects_duplicate_membership(self): + use_case = {**FOCUS_USE_CASE, "supporting": [FOCUS_USE_CASE["core"][0]]} + with self.assertRaisesRegex(ValueError, "duplicate note path"): + validate_focus_use_cases(focus_use_cases(use_case), set(FOCUS_USE_CASE["core"]), PRIMITIVE_IDS) + + def test_validate_focus_use_cases_rejects_unknown_note(self): + known_paths = {FOCUS_USE_CASE["core"][0], *FOCUS_USE_CASE["supporting"]} + with self.assertRaisesRegex(ValueError, "unknown note path.*research/k8s-agent-sandbox.md"): + validate_focus_use_cases(focus_use_cases(), known_paths, PRIMITIVE_IDS) + + def test_validate_focus_use_cases_rejects_unknown_or_missing_primitive_ids(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + applicability = {**FOCUS_USE_CASE["primitive_applicability"]} + applicability.pop("attested-workload-authority") + applicability["unknown-primitive"] = "core" + with self.assertRaisesRegex(ValueError, "primitive applicability must contain exactly"): + validate_focus_use_cases( + focus_use_cases({**FOCUS_USE_CASE, "primitive_applicability": applicability}), + known_paths, + PRIMITIVE_IDS, + ) + + def test_validate_focus_use_cases_rejects_invalid_applicability(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + applicability = {**FOCUS_USE_CASE["primitive_applicability"], "attested-workload-authority": "optional"} + with self.assertRaisesRegex(ValueError, "invalid applicability.*optional"): + validate_focus_use_cases( + focus_use_cases({**FOCUS_USE_CASE, "primitive_applicability": applicability}), + known_paths, + PRIMITIVE_IDS, + ) + def test_load_primitives_loads_the_three_approved_primitives(self): primitives = load_primitives() From 94a0ffe3d75660967bfafe9c81aa095aaf4d13e7 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 09:54:18 +0200 Subject: [PATCH 30/41] fix: clarify coding harness deployment boundary --- scripts/focus_use_cases.yaml | 23 +++++++++++----------- tests/test_generate_research_map.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/scripts/focus_use_cases.yaml b/scripts/focus_use_cases.yaml index dc8fd56..aeabbd2 100644 --- a/scripts/focus_use_cases.yaml +++ b/scripts/focus_use_cases.yaml @@ -1,20 +1,21 @@ - id: cf-hosted-coding-harnesses title: CF-hosted coding harnesses - workshop_outcome: Define the minimum Cloud Foundry platform contract for safely hosting coding harnesses with durable, isolated sessions. + workshop_outcome: Define the minimum CF contract from isolated mutable edit/test through candidate artifact submission to a narrow trusted deployment broker. primary_actor: A platform customer deploying a coding harness on Cloud Foundry. beneficiary: A developer delegating repository changes and code execution to the harness. - lifecycle: Stage a reusable environment, create an isolated session and workspace, execute tools, suspend idle compute, resume on replacement compute, and destroy the session. - authority_boundary: The harness may change only its assigned workspace and invoke explicitly authorized tools and network destinations; the platform enforces isolation and scoped credentials. + lifecycle: Stage a reusable environment; create an isolated mutable edit/test session; submit a candidate artifact; let the broker validate target, policy, provenance, and approval; then create conventional CF package, build, deployment, and revision resources, verify, audit, and clean up. + authority_boundary: The harness gets restricted Git, model, and package-registry credentials but no CAPI deployment authority; only the narrow trusted deployment broker holds restricted CAPI credentials and may promote an approved artifact to its validated target. unique_capabilities: - - Reuse an immutable staged environment across isolated sessions without sharing mutable workspace state. - - Suspend and resume a session while retaining its workspace and stable identity. - - Select stronger workload isolation and session-specific network policy for generated code. - failure_domain: A harness or sandbox failure must remain within one session, preserve recoverable workspace state, and never expose another session or foundation credential. - poc: Start two isolated coding sessions from one content-addressed staged environment, give each a separate workspace and egress policy, then suspend and resume one on fresh compute. + - Reuse an immutable staged environment while retaining isolated mutable workspace state across edit/test turns. + - Produce a provenance-bound candidate artifact without granting the harness deployment authority. + - Validate target, policy, provenance, and approval in a narrow broker before conventional CF deployment and revision creation. + failure_domain: Session failure stays isolated and recoverable; stale-base or concurrent submissions fail safely; broker failure cannot broaden authority, and deployment supports rollback and audit. + poc: Edit and test in an isolated session, submit a provenance-bound candidate artifact, then have a narrow broker validate target, policy, provenance, and approval and create conventional CF package, build, deployment, and revision resources with rollback and audit. rfc_decisions: - - Whether CF should expose a first-class session resource or compose package, droplet, task, volume, networking, and isolation APIs. - - How session identity, workspace persistence, lifecycle transitions, quotas, cleanup, and failure recovery are represented. - - Which isolation classes and network-policy controls operators can offer and developers can select. + - How session identity, mutable workspace lifecycle, isolation, networking, stale-base detection, and concurrency are represented. + - What candidate artifact, provenance, target, policy, and approval contract the trusted deployment broker validates. + - How restricted CAPI, Git, model, and package-registry credentials are issued, audited, revoked, and kept out of the wrong trust domain. + - How conventional CF package, build, deployment, and revision creation exposes verification, rollback, and audit outcomes. core: - ideas/per-session-sandboxes.md - ideas/staged-sandbox-environments.md diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index a82d879..6e8ece0 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -130,6 +130,36 @@ def test_load_focus_use_cases_loads_the_two_approved_use_cases(self): ["cf-hosted-coding-harnesses", "user-facing-agentic-applications"], ) + def test_coding_harness_use_case_covers_edit_to_trusted_deployment_lifecycle(self): + use_case = load_focus_use_cases()[0] + narrative = " ".join( + str(use_case[field]) + for field in ( + "workshop_outcome", + "lifecycle", + "authority_boundary", + "unique_capabilities", + "failure_domain", + "poc", + "rfc_decisions", + ) + ).lower() + + for required_phrase in ( + "mutable edit/test", + "candidate artifact", + "trusted deployment broker", + "target, policy, provenance, and approval", + "package, build, deployment, and revision", + "capi, git, model, and package-registry credentials", + "stale-base", + "concurrency", + "rollback", + "audit", + ): + with self.subTest(required_phrase=required_phrase): + self.assertIn(required_phrase, narrative) + def test_validate_focus_use_cases_preserves_use_case_and_membership_order(self): first = { **FOCUS_USE_CASE, From 703bbbbf46e47248bacafa215a8af66d44febc98 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 10:03:15 +0200 Subject: [PATCH 31/41] feat: present workshop focus outcomes --- scripts/generate_research_map.py | 43 +++++++++++---- tests/test_generate_research_map.py | 82 +++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index a2763c4..be96eb8 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -291,7 +291,11 @@ def note_payload(note: Note, plot: dict) -> dict: } -def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str: +def generate_html( + notes: list[Note], plots: dict, primitives: list[dict], use_cases: list[dict] | None = None +) -> str: + if use_cases is None: + use_cases = load_focus_use_cases() plot_payloads = {plot_id: [note_payload(note, plot) for note in notes] for plot_id, plot in plots.items()} data = json.dumps(plot_payloads, ensure_ascii=True).replace(" str primitive_memberships.setdefault(path, {})[primitive["id"]] = relationship membership_data = json.dumps(primitive_memberships, ensure_ascii=True).replace("{html.escape(note_titles.get(path, pathlib.Path(path).stem.replace("-", " ").title()))}' + for path in use_case[relationship] + ) + evidence.append(f'

    {relationship.title()}

      {links}
    ') + capabilities = "".join(f'
  • {html.escape(value)}
  • ' for value in use_case["unique_capabilities"]) + decisions = "".join(f'
  • {html.escape(value)}
  • ' for value in use_case["rfc_decisions"]) + applicability = "".join( + f'
  • {html.escape(primitive_titles.get(primitive_id, primitive_id))}: {html.escape(value.title())}
  • ' + for primitive_id, value in use_case["primitive_applicability"].items() + ) + use_case_cards.append(f'''
    + +
    Actors, lifecycle, decisions, and evidence
    Primary actor
    {html.escape(use_case["primary_actor"])}
    Beneficiary
    {html.escape(use_case["beneficiary"])}
    Lifecycle
    {html.escape(use_case["lifecycle"])}
    Authority boundary
    {html.escape(use_case["authority_boundary"])}
    Failure domain
    {html.escape(use_case["failure_domain"])}

    Unique capabilities

      {capabilities}

    RFC decisions

      {decisions}

    Primitive applicability

      {applicability}
    ''') accents = ("#68d5ac", "#f1b866", "#8eb8ff") cards = [] for primitive, accent in zip(primitives, accents): @@ -350,19 +374,20 @@ def generate_html(notes: list[Note], plots: dict, primitives: list[dict]) -> str matrices.append(f'''

    {title}

    {''.join(markers)}{html.escape(x["low"])} < {html.escape(x["label"])} > {html.escape(x["high"])}{html.escape(y["low"])} < {html.escape(y["label"])} > {html.escape(y["high"])}
    Unplaced notes ({len(unplaced)})
      {''.join(unplaced) or '
    • All notes are placed.
    • '}
    ''') return f''' -Research and Ideas Landscape

    Agentic Runtime Working Group - provisional workshop view

    Research and Ideas Landscape

    -

    Recalibrated working-group ratings, shown to seed discussion rather than establish a permanent taxonomy. Click a note for its summary, rating justifications, and source.

    -
    ResearchIdea

    Candidate platform primitives

    {''.join(cards)}
    {''.join(tabs)}
    {''.join(matrices)}
    +@media(max-width:800px) {{ .use-case-grid,.primitive-grid {{ grid-template-columns:1fr; }} .use-case-select,.primitive-select {{ min-height:0; }} }} +@media(max-width:600px) {{ body {{ padding:24px 14px; }} .use-case-links,.primitive-links {{ grid-template-columns:1fr; gap:0; }} .matrix-tabs {{ margin-left:-14px; margin-right:-14px; padding-left:14px; padding-right:14px; }} .map {{ height:720px; margin-left:58px; }} .axis-y {{ left:-40px; }} }} +

    Agentic Runtime Working Group

    Workshop results

    +

    This page summarizes the workshop results: two focus use cases, three candidate platform primitives, and the collected evidence behind them.

    +

    Focus use cases

    {''.join(use_case_cards)}

    Candidate platform primitives

    {''.join(cards)}
    ResearchIdea
    {''.join(tabs)}
    {''.join(matrices)}
    ''' + const tabs=[...document.querySelectorAll('[role="tab"]')],panels=tabs.map(tab=>document.querySelector(`#${{tab.getAttribute('aria-controls')}}`));tabs.forEach((tab,index)=>{{tab.onclick=()=>activateTab(index);tab.onkeydown=event=>handleTabKey(event,index)}});document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.primitive-show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const noteIds=JSON.parse(m.dataset.noteIds);const items=noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});dialog.addEventListener('keydown',e=>{{if(e.key==='Escape')dialog.close()}});''' def main() -> int: @@ -383,7 +408,7 @@ def main() -> int: parser.add_argument("--check", action="store_true") args = parser.parse_args() try: - output = generate_html(load_notes(), load_plots(), load_primitives()) + output = generate_html(load_notes(), load_plots(), load_primitives(), load_focus_use_cases()) except (OSError, ValueError, yaml.YAMLError) as exc: print(f"error: {exc}", file=sys.stderr) return 1 diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 6e8ece0..39b44e6 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -481,7 +481,60 @@ def test_generated_html_contains_markers_dialog_and_source_links(self): self.assertGreater(html.count('class="marker '), 0) self.assertIn('{applicability.title()}', html) + for path in use_case["core"] + use_case["supporting"]: + self.assertIn(f'href="{github_url(pathlib.Path(path))}"', html) + + def test_generated_html_styles_use_cases_responsively_with_dark_card_language(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn(".use-case-grid { display:grid", html) + self.assertIn(".use-case-card { min-width:0; background:#15211e", html) + self.assertIn(".use-case-details summary", html) + self.assertIn("@media(max-width:800px) { .use-case-grid", html) def test_generated_html_escapes_all_dynamic_dialog_content(self): html = generate_html(load_notes(), load_plots(), load_primitives()) @@ -531,7 +584,23 @@ def test_hostile_dynamic_content_is_not_emitted_as_raw_executable_markup(self): "title": "Hostile", "x": {"rating": "maturity", "label": "Maturity", "low": "Low", "high": "High"}, "y": {"rating": "platform-impact", "label": "Impact", "low": "Low", "high": "High"}, - }}, [primitive]) + }}, [primitive], focus_use_cases({ + **FOCUS_USE_CASE, + "id": attack, + "title": attack, + "workshop_outcome": attack, + "primary_actor": attack, + "beneficiary": attack, + "lifecycle": attack, + "authority_boundary": attack, + "unique_capabilities": [attack], + "failure_domain": attack, + "poc": attack, + "rfc_decisions": [attack], + "core": [note.path.as_posix()], + "supporting": [], + "primitive_applicability": {attack: attack}, + })) static_markup, script = generated.split("''' + const tabs=[...document.querySelectorAll('[role="tab"]')],panels=tabs.map(tab=>document.querySelector(`#${{tab.getAttribute('aria-controls')}}`));tabs.forEach((tab,index)=>{{tab.onclick=()=>activateTab(index);tab.onkeydown=event=>handleTabKey(event,index)}});document.querySelectorAll('.use-case-select').forEach(button=>button.onclick=()=>selectUseCase(button));document.querySelector('.use-case-show-all').onclick=clearUseCaseSelection;document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.primitive-show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{{if(m.dataset.cluster){{const noteIds=JSON.parse(m.dataset.noteIds);const items=noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id));showCluster(items)}}else{{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}}}});dialog.addEventListener('click',e=>{{if(e.target===dialog)dialog.close()}});dialog.addEventListener('keydown',e=>{{if(e.key==='Escape')dialog.close()}});''' def main() -> int: diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 39b44e6..67a37b4 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -701,10 +701,66 @@ def test_generated_html_embeds_primitive_membership_once_and_stable_control_ids( for primitive in primitives: self.assertIn(f'id="primitive-{primitive["id"]}"', html) + def test_generated_html_embeds_use_case_membership_once(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertEqual(html.count("const useCaseMemberships="), 1) + self.assertIn( + '"ideas/staged-sandbox-environments.md": {"cf-hosted-coding-harnesses": "core"}', + html, + ) + + def test_generated_html_manages_use_case_and_primitive_selection_independently(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertIn("let selectedUseCase=null,selectedPrimitive=null", html) + for function_name in ( + "selectUseCase", + "clearUseCaseSelection", + "selectPrimitive", + "clearPrimitiveSelection", + "updateUseCaseControls", + "updatePrimitiveControls", + ): + self.assertIn(f"function {function_name}", html) + + select_use_case = html.split("function selectUseCase", 1)[1].split( + "function clearUseCaseSelection", 1 + )[0] + select_primitive = html.split("function selectPrimitive", 1)[1].split( + "function clearPrimitiveSelection", 1 + )[0] + self.assertNotIn("selectedPrimitive=", select_use_case) + self.assertNotIn("selectedUseCase=", select_primitive) + self.assertIn("button.dataset.useCase===selectedUseCase", html) + self.assertIn("button.dataset.primitive===selectedPrimitive", html) + self.assertIn("document.querySelector('.use-case-show-all').onclick=clearUseCaseSelection", html) + self.assertIn("document.querySelector('.primitive-show-all').onclick=clearPrimitiveSelection", html) + + def test_generated_html_matches_evidence_against_both_independent_selections(self): + html = generate_html(load_notes(), load_plots(), load_primitives()) + + self.assertEqual(html.count("function noteMatchesSelection(noteId)"), 1) + predicate = html.split("function noteMatchesSelection(noteId)", 1)[1].split( + "function updateMarkers", 1 + )[0] + self.assertIn("!selectedUseCase||useCaseMemberships[noteId]?.[selectedUseCase]", predicate) + self.assertIn("!selectedPrimitive||primitiveMemberships[noteId]?.[selectedPrimitive]", predicate) + self.assertIn("return Boolean(matchesUseCase&&matchesPrimitive)", predicate) + self.assertIn("const related=noteMatchesSelection(m.dataset.id)", html) + self.assertIn("noteIds.filter(noteMatchesSelection)", html) + self.assertIn("classList.toggle('related'", html) + self.assertIn("classList.toggle('dimmed'", html) + marker_buttons = re.findall(r'
    {''.join(use_case_cards)}

    Candidate platform primitives

    {''.join(cards)}
    ResearchIdea
    {''.join(tabs)}
    {''.join(matrices)} +

    Focus use cases

    {''.join(use_case_cards)}

    Candidate platform primitives

    {''.join(cards)}

    ResearchIdea
    {''.join(tabs)}
    {''.join(matrices)}
    -''' diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 67a37b4..518607d 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -115,9 +115,14 @@ def mixed_cluster_fixture(): primitive = { **PRIMITIVE, "core": [paths[1].as_posix()], - "supporting": [paths[3].as_posix()], + "supporting": [paths[2].as_posix(), paths[4].as_posix()], } - return notes, {"mixed:plot": plot}, [primitive] + use_case = { + **FOCUS_USE_CASE, + "core": [paths[0].as_posix(), paths[2].as_posix()], + "supporting": [paths[4].as_posix()], + } + return notes, {"mixed:plot": plot}, [primitive], focus_use_cases(use_case) class ResearchMapTests(unittest.TestCase): @@ -801,41 +806,78 @@ def test_generated_html_contains_cluster_marker_and_picker(self): self.assertIn('picker-item', html) def test_mixed_cluster_embeds_ordered_note_ids_and_updates_selected_count(self): - notes, plots, primitives = mixed_cluster_fixture() + notes, plots, primitives, use_cases = mixed_cluster_fixture() - html = generate_html(notes, plots, primitives) + html = generate_html(notes, plots, primitives, use_cases) marker = re.search(r'', html).group(0) note_ids = [note.path.as_posix() for note in notes] encoded_ids = html_lib.escape(json.dumps(note_ids), quote=True) self.assertIn(f'data-note-ids="{encoded_ids}"', marker) - self.assertIn("const relatedCount=hasSelection?noteIds.filter(noteMatchesSelection).length:0", html) - self.assertIn("m.textContent=relatedCount?`${relatedCount}/${noteIds.length}`:String(noteIds.length)", html) - self.assertIn("m.classList.toggle('related',relatedCount>0)", html) - self.assertIn("m.classList.toggle('dimmed',Boolean(hasSelection&&!relatedCount))", html) + self.assertIn("const matchingCount=hasSelection?noteIds.filter(noteMatchesSelection).length:0", html) + self.assertIn("m.textContent=hasSelection?`${matchingCount}/${noteIds.length}`:String(noteIds.length)", html) + self.assertIn("m.classList.toggle('related',matchingCount>0)", html) + self.assertIn("m.classList.toggle('dimmed',Boolean(hasSelection&&!matchingCount))", html) def test_mixed_cluster_updates_and_restores_accessible_count(self): - notes, plots, primitives = mixed_cluster_fixture() + notes, plots, primitives, use_cases = mixed_cluster_fixture() - html = generate_html(notes, plots, primitives) + html = generate_html(notes, plots, primitives, use_cases) self.assertIn('aria-label="5 notes at this position"', html) self.assertIn( - "m.setAttribute('aria-label',relatedCount?`${relatedCount} of ${noteIds.length} related notes at this position`:`${noteIds.length} notes at this position`)", + "m.setAttribute('aria-label',hasSelection?`${matchingCount} of ${noteIds.length} matching notes at this position`:`${noteIds.length} notes at this position`)", html, ) - def test_mixed_cluster_picker_sorts_a_copy_related_first_and_labels_relationships(self): - notes, plots, primitives = mixed_cluster_fixture() + def test_mixed_cluster_fixture_covers_each_combined_match_category(self): + notes, _, primitives, use_cases = mixed_cluster_fixture() + + primitive_paths = set(primitives[0]["core"] + primitives[0]["supporting"]) + use_case_paths = set(use_cases[0]["core"] + use_cases[0]["supporting"]) + categories = [] + for note in notes: + path = note.path.as_posix() + categories.append((path in use_case_paths, path in primitive_paths)) + + self.assertEqual( + categories, + [(True, False), (False, True), (True, True), (False, False), (True, True)], + ) + + def test_mixed_cluster_picker_sorts_a_copy_by_combined_match_and_labels_both_relationships(self): + notes, plots, primitives, use_cases = mixed_cluster_fixture() - html = generate_html(notes, plots, primitives) + html = generate_html(notes, plots, primitives, use_cases) - self.assertIn("const ordered=selectedPrimitive?[...items.filter(isRelated),...items.filter(note=>!isRelated(note))]:[...items]", html) - self.assertIn("${relationshipBadge(note)}", html) + self.assertIn("const ordered=hasSelection?[...items.filter(isRelated),...items.filter(note=>!isRelated(note))]:[...items]", html) + self.assertIn("const isRelated=note=>noteMatchesSelection(note.id)", html) + self.assertIn("${relationshipBadges(note)}", html) + self.assertIn("useCaseMemberships[note.id]?.[selectedUseCase]", html) + self.assertIn("primitiveMemberships[note.id]?.[selectedPrimitive]", html) + self.assertIn("escapeHtml(label)", html) self.assertIn("const noteIds=JSON.parse(m.dataset.noteIds)", html) self.assertIn("noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id))", html) self.assertNotIn("m.dataset.cluster.split", html) + def test_combined_empty_selection_announces_exact_status_and_clear_restores_it(self): + notes, plots, primitives, use_cases = mixed_cluster_fixture() + + html = generate_html(notes, plots, primitives, use_cases) + + self.assertIn('

    ', html) + self.assertIn( + "filterStatus.textContent=selectedUseCase&&selectedPrimitive&&!hasMatches?'No directly linked evidence for this use-case and primitive combination.':''", + html, + ) + self.assertIn( + "const hasMatches=Object.values(plots).some(notes=>notes.some(note=>noteMatchesSelection(note.id)))", + html, + ) + for function_name in ("clearUseCaseSelection", "clearPrimitiveSelection"): + function = html.split(f"function {function_name}", 1)[1].split("function ", 1)[0] + self.assertIn("updateMarkers()", function) + def test_generated_html_wires_singletons_and_close_button(self): html = generate_html(load_notes(), load_plots(), load_primitives()) self.assertIn("show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))", html) From 050e8421211138740321e12f9bd29dae4c64084f Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 10:27:17 +0200 Subject: [PATCH 34/41] fix: preserve accessible workshop filtering --- scripts/generate_research_map.py | 8 +++-- tests/test_generate_research_map.py | 55 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index e9b97df..f8ccb4d 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -312,6 +312,10 @@ def generate_html( use_case_membership_data = json.dumps(use_case_memberships, ensure_ascii=True).replace("This page summarizes the workshop results: two focus use cases, three candidate platform primitives, and the collected evidence behind them.

    Focus use cases

    {''.join(use_case_cards)}

    Candidate platform primitives

    {''.join(cards)}

    ResearchIdea
    {''.join(tabs)}
    {''.join(matrices)}
    - \ No newline at end of file + const tabs=[...document.querySelectorAll('[role="tab"]')],panels=tabs.map(tab=>document.querySelector(`#${tab.getAttribute('aria-controls')}`));tabs.forEach((tab,index)=>{tab.onclick=()=>activateTab(index);tab.onkeydown=event=>handleTabKey(event,index)});document.querySelectorAll('.use-case-select').forEach(button=>button.onclick=()=>selectUseCase(button));document.querySelector('.use-case-show-all').onclick=clearUseCaseSelection;document.querySelectorAll('.primitive-select').forEach(button=>button.onclick=()=>selectPrimitive(button));document.querySelector('.primitive-show-all').onclick=clearPrimitiveSelection;document.querySelectorAll('.marker').forEach(m=>m.onclick=()=>{if(m.dataset.cluster){const noteIds=JSON.parse(m.dataset.noteIds);const items=noteIds.map(id=>plots[m.dataset.plot].find(note=>note.id===id));showCluster(items)}else{show(plots[m.dataset.plot].find(n=>n.id===m.dataset.id))}});dialog.addEventListener('click',e=>{if(e.target===dialog)dialog.close()});dialog.addEventListener('keydown',e=>{if(e.key==='Escape')dialog.close()}); \ No newline at end of file From c4d226a53bb7eb0f161500fa048f99f97bf453ec Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 10:40:09 +0200 Subject: [PATCH 36/41] fix: validate focus applicability types --- scripts/generate_research_map.py | 4 ++++ tests/test_generate_research_map.py | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/scripts/generate_research_map.py b/scripts/generate_research_map.py index f8ccb4d..5e23eae 100644 --- a/scripts/generate_research_map.py +++ b/scripts/generate_research_map.py @@ -258,6 +258,10 @@ def validate_focus_use_cases( f"focus use case '{use_case_id}' primitive applicability must contain exactly the known primitive ids" ) for primitive_id, value in applicability.items(): + if not isinstance(value, str): + raise ValueError( + f"focus use case '{use_case_id}' applicability for primitive '{primitive_id}' must be a string" + ) if value not in allowed_applicability: raise ValueError( f"focus use case '{use_case_id}' has invalid applicability '{value}' for primitive '{primitive_id}'" diff --git a/tests/test_generate_research_map.py b/tests/test_generate_research_map.py index 3df9a4b..bd83c32 100644 --- a/tests/test_generate_research_map.py +++ b/tests/test_generate_research_map.py @@ -246,6 +246,19 @@ def test_validate_focus_use_cases_rejects_invalid_applicability(self): PRIMITIVE_IDS, ) + def test_validate_focus_use_cases_rejects_non_string_applicability(self): + known_paths = set(FOCUS_USE_CASE["core"] + FOCUS_USE_CASE["supporting"]) + applicability = {**FOCUS_USE_CASE["primitive_applicability"], "attested-workload-authority": ["core"]} + with self.assertRaisesRegex( + ValueError, + "applicability for primitive 'attested-workload-authority' must be a string", + ): + validate_focus_use_cases( + focus_use_cases({**FOCUS_USE_CASE, "primitive_applicability": applicability}), + known_paths, + PRIMITIVE_IDS, + ) + def test_load_primitives_loads_the_three_approved_primitives(self): primitives = load_primitives() From 053465a82af1f4d64982814fdbba5627f5ca25c0 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 12:13:50 +0200 Subject: [PATCH 37/41] docs: specify map validation and Pages deployment --- .../2026-09-03-map-validation-pages-design.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-03-map-validation-pages-design.md diff --git a/docs/superpowers/specs/2026-09-03-map-validation-pages-design.md b/docs/superpowers/specs/2026-09-03-map-validation-pages-design.md new file mode 100644 index 0000000..80ab8b7 --- /dev/null +++ b/docs/superpowers/specs/2026-09-03-map-validation-pages-design.md @@ -0,0 +1,64 @@ +# Map Validation and GitHub Pages Design + +## Purpose + +Guarantee that the checked-in research map is synchronized with its Markdown notes, ratings, plot configuration, platform primitives, focus use cases, and generator. Publish that reviewed artifact through GitHub Pages after changes merge to `main`. + +## Validation workflow + +Extend `.github/workflows/lint.yml` while retaining note validation, rating-distribution safeguards, and generator tests. + +The map verification steps are: + +1. Run `python scripts/generate_research_map.py`, which validates all generator inputs and writes `generated/research-map.html`. +2. Run `git diff --exit-code -- generated/research-map.html`. + +Generator failures catch missing or invalid ratings, malformed primitive/use-case configuration, and unknown note references. The Git diff catches contributors changing any input without checking in the regenerated HTML. + +The step name must clearly state the invariant, such as **Verify generated map is checked in**. Workflow path filters continue to cover `research/**`, `ideas/**`, `scripts/**`, `generated/**`, `tests/**`, and relevant workflow files. + +## Pages workflow + +Add `.github/workflows/pages.yml`, named **Deploy research map to Pages**. + +It triggers only on relevant pushes to `main`; it has no pull-request trigger. Relevant paths include all map inputs, the generated artifact, tests, and the Pages workflow itself. + +Use these minimal permissions: + +- `contents: read` +- `pages: write` +- `id-token: write` + +Use a `pages` concurrency group and cancel an in-progress older deployment when a newer relevant `main` push arrives. + +## Build and deployment + +The Pages workflow checks out the repository, configures Python 3.12, installs PyYAML, and runs: + +1. Note validation. +2. Rating-distribution safeguards. +3. Generator tests. +4. Normal map generation. +5. `git diff --exit-code -- generated/research-map.html`. + +After successful validation, create a staging directory containing the verified file as `index.html`. Upload that directory with the official Pages artifact action and deploy it with the official Pages deployment action through the `github-pages` environment. Expose the deployment URL through the environment metadata. + +The deployment publishes the checked-in artifact after proving it is byte-for-byte identical to fresh generation. It must not publish a different, unreviewed generated result. + +## Repository setup reminder + +The current repository does not yet have GitHub Pages configured. Add this reminder to PR #42's description: + +1. After merging, open repository **Settings -> Pages**. +2. Under **Build and deployment**, select **GitHub Actions** as the source. +3. Confirm **Deploy research map to Pages** succeeds on `main`. +4. Verify the published Pages URL. +5. Optionally add the published URL to the repository description and README in a follow-up. + +The workflow is added in this PR and remains dormant until a relevant push reaches `main`. + +## Verification + +Tests or workflow-oriented checks must verify trigger branches and paths, minimal permissions, concurrency, validation commands, normal generation followed by generated-file diff checking, artifact staging as `index.html`, official Pages actions, and the `github-pages` environment. + +Run the complete local test and validation suite and simulate the stale-artifact failure by changing a generator input or generated artifact, confirming the diff step fails, then regenerating and confirming it passes. From b5497e82fbe74c991ee6611a7ee7d3a2dee0e2df Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 12:19:54 +0200 Subject: [PATCH 38/41] docs: add map validation and Pages plan --- .../plans/2026-09-03-map-validation-pages.md | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-03-map-validation-pages.md diff --git a/docs/superpowers/plans/2026-09-03-map-validation-pages.md b/docs/superpowers/plans/2026-09-03-map-validation-pages.md new file mode 100644 index 0000000..3cc016a --- /dev/null +++ b/docs/superpowers/plans/2026-09-03-map-validation-pages.md @@ -0,0 +1,197 @@ +# Map Validation and GitHub Pages Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fail CI when the checked-in research map differs from fresh generation and publish the verified HTML through GitHub Pages after relevant merges to `main`. + +**Architecture:** The existing validation workflow will run the generator in write mode and use Git to prove the checked-in artifact remains unchanged. A separate main-only Pages workflow repeats repository validation, stages the verified artifact as `index.html`, uploads it with the official Pages artifact action, and deploys through the `github-pages` environment. + +**Tech Stack:** GitHub Actions, Python 3.12, PyYAML, Git, official GitHub Pages actions. + +--- + +## Files and Responsibilities + +- Modify `.github/workflows/lint.yml`: regenerate the map and fail on a generated artifact diff. +- Create `.github/workflows/pages.yml`: validate and deploy the checked-in map on relevant `main` pushes. +- Create `tests/test_workflows.py`: parse workflow YAML and verify triggers, permissions, validation commands, Pages actions, and staging behavior. +- Update PR #42 description: add post-merge GitHub Pages setup reminder after explicit posting approval. + +### Task 1: Test and extend generated-map validation + +**Files:** +- Create: `tests/test_workflows.py` +- Modify: `.github/workflows/lint.yml` + +- [ ] **Step 1: Write failing validation-workflow tests** + +Load `.github/workflows/lint.yml` with `yaml.safe_load` and assert the validation job contains, in order: + +```text +python .github/scripts/validate_notes.py +python scripts/summarize_ratings.py --check +python -m unittest discover -s tests +python scripts/generate_research_map.py +git diff --exit-code -- generated/research-map.html +``` + +Also assert pull-request and `main` push path filters cover `research/**`, `ideas/**`, `scripts/**`, `generated/**`, `tests/**`, and both relevant workflow files. + +- [ ] **Step 2: Run the focused test and verify failure** + +Run: `python -m unittest tests.test_workflows` + +Expected: FAIL because validation currently runs generator `--check` instead of normal generation followed by Git diff. + +- [ ] **Step 3: Update validation workflow** + +Replace the current final map check with two explicit steps: + +```yaml +- name: Generate research map + run: python scripts/generate_research_map.py + +- name: Verify generated map is checked in + run: git diff --exit-code -- generated/research-map.html +``` + +Add `.github/workflows/pages.yml` to validation path filters so changes to deployment validation are tested. + +- [ ] **Step 4: Run validation tests and repository checks** + +Run: `python -m unittest tests.test_workflows && python -m unittest discover -s tests && python .github/scripts/validate_notes.py && python scripts/summarize_ratings.py --check` + +Expected: PASS. + +- [ ] **Step 5: Commit validation changes** + +Run: `git add .github/workflows/lint.yml tests/test_workflows.py && git commit -m "ci: verify generated research map is committed"` + +### Task 2: Prove stale-artifact detection + +**Files:** +- Test: `generated/research-map.html` +- Test: one temporary generator input change that is restored after verification + +- [ ] **Step 1: Verify a clean generation produces no diff** + +Run: `python scripts/generate_research_map.py && git diff --exit-code -- generated/research-map.html` + +Expected: PASS with no generated artifact diff. + +- [ ] **Step 2: Create a temporary stale artifact** + +Use `apply_patch` to add a harmless temporary comment to `generated/research-map.html`, then run: + +`python scripts/generate_research_map.py && git diff --exit-code -- generated/research-map.html` + +Expected: generation restores the canonical content, and Git diff exits non-zero because the committed artifact would differ from the temporary working-tree baseline only if the checked-in output is stale. To test stale source input instead, temporarily alter a copy or reversible generator text label, run generation and confirm Git diff fails. + +- [ ] **Step 3: Restore only through canonical generation** + +Undo the temporary generator-input change using `apply_patch`, then run `python scripts/generate_research_map.py`. Do not use destructive Git checkout/reset commands. + +- [ ] **Step 4: Confirm clean state** + +Run: `git diff --exit-code -- generated/research-map.html && git diff --check` + +Expected: PASS. + +### Task 3: Add the main-only GitHub Pages workflow + +**Files:** +- Create: `.github/workflows/pages.yml` +- Modify: `tests/test_workflows.py` + +- [ ] **Step 1: Write failing Pages workflow tests** + +Assert `.github/workflows/pages.yml` has: + +- Name `Deploy research map to Pages`. +- Push-only trigger for `main` and no pull-request trigger. +- Relevant path filters for notes, scripts/config, generated map, tests, and the Pages workflow. +- Permissions `contents: read`, `pages: write`, `id-token: write`. +- Concurrency group `pages` with `cancel-in-progress: true`. +- Validation, generation, and generated-file diff commands. +- `actions/configure-pages`, `actions/upload-pages-artifact`, and `actions/deploy-pages` official actions. +- A `github-pages` environment exposing `${{ steps.deployment.outputs.page_url }}`. + +- [ ] **Step 2: Run focused tests and verify failure** + +Run: `python -m unittest tests.test_workflows` + +Expected: FAIL because `pages.yml` does not exist. + +- [ ] **Step 3: Implement the Pages workflow** + +Create a validation/build job that checks out, sets up Python 3.12, installs `pyyaml>=6`, runs the same validation sequence, creates `_site`, and copies `generated/research-map.html` to `_site/index.html`. Configure Pages and upload `_site`. + +Create a deployment job depending on the build job, using `environment.name: github-pages`, `environment.url: ${{ steps.deployment.outputs.page_url }}`, and `actions/deploy-pages` with step ID `deployment`. + +- [ ] **Step 4: Run workflow and repository tests** + +Run: `python -m unittest discover -s tests && python .github/scripts/validate_notes.py && python scripts/summarize_ratings.py --check && python scripts/generate_research_map.py && git diff --exit-code -- generated/research-map.html && git diff --check` + +Expected: PASS. + +- [ ] **Step 5: Commit Pages workflow** + +Run: `git add .github/workflows/pages.yml tests/test_workflows.py && git commit -m "ci: deploy research map to GitHub Pages"` + +### Task 4: Final workflow review and verification + +**Files:** +- Review: `.github/workflows/lint.yml` +- Review: `.github/workflows/pages.yml` +- Review: `tests/test_workflows.py` + +- [ ] **Step 1: Validate workflow YAML and exact commands** + +Run the workflow tests and inspect parsed values for YAML's `on` key behavior. Ensure tests account for PyYAML interpreting YAML 1.1 booleans if necessary without weakening assertions. + +- [ ] **Step 2: Run the full local verification suite** + +Run: `python -m unittest discover -s tests && python .github/scripts/validate_notes.py && python scripts/summarize_ratings.py --check && python scripts/generate_research_map.py && git diff --exit-code -- generated/research-map.html && git diff --check` + +Expected: all commands exit 0. + +- [ ] **Step 3: Review permissions and deployment boundaries** + +Confirm the validation workflow has no write permissions, the Pages workflow grants only the three approved permissions, deploys only from `main`, and publishes only `_site/index.html` derived from the verified checked-in artifact. + +- [ ] **Step 4: Inspect branch scope** + +Run: `git status --short --branch && git diff origin/feature/research-clustering-map...HEAD --stat` + +Confirm only workflow files, workflow tests, and approved documentation are included. Leave unrelated local Chromium, `.superpowers/`, Nix, and temporary files untouched. + +### Task 5: Update PR description and handoff + +**Files:** +- External: PR #42 description + +- [ ] **Step 1: Prepare the PR reminder text** + +Append: + +```markdown +## Post-merge GitHub Pages setup + +- [ ] Open repository **Settings -> Pages**. +- [ ] Under **Build and deployment**, select **GitHub Actions** as the source. +- [ ] Confirm **Deploy research map to Pages** succeeds on `main`. +- [ ] Verify the published Pages URL. +- [ ] Optionally add the URL to the repository description and README. +``` + +- [ ] **Step 2: Request grouped approval** + +Present the final verification evidence and exact description update. Ask once for permission to push commits and update PR #42's description. + +- [ ] **Step 3: Push and update only after approval** + +Run `git push`, then use `gh pr edit 42 --body-file ` or equivalent while preserving the existing PR body and appending the reminder exactly once. + +- [ ] **Step 4: Verify remote state** + +Confirm PR #42 remains draft, points to `feature/research-clustering-map`, contains the setup reminder, and reports the expected validation check after push. From 2fc4eac960e68f88413f088804f6e7d2bca5c508 Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 12:24:47 +0200 Subject: [PATCH 39/41] ci: verify generated research map is committed --- .github/workflows/lint.yml | 9 ++++-- tests/test_workflows.py | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/test_workflows.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9b73b96..2e503a2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,6 +10,7 @@ on: - "generated/**" - "tests/**" - ".github/workflows/lint.yml" + - ".github/workflows/pages.yml" push: branches: [main] paths: @@ -20,6 +21,7 @@ on: - "generated/**" - "tests/**" - ".github/workflows/lint.yml" + - ".github/workflows/pages.yml" jobs: validate: @@ -45,5 +47,8 @@ jobs: - name: Test map generator run: python -m unittest discover -s tests - - name: Check generated map - run: python scripts/generate_research_map.py --check + - name: Generate research map + run: python scripts/generate_research_map.py + + - name: Verify generated map is checked in + run: git diff --exit-code -- generated/research-map.html diff --git a/tests/test_workflows.py b/tests/test_workflows.py new file mode 100644 index 0000000..e9d566a --- /dev/null +++ b/tests/test_workflows.py @@ -0,0 +1,57 @@ +from pathlib import Path +import unittest + +import yaml + + +ROOT = Path(__file__).resolve().parents[1] + + +class LintWorkflowTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + with (ROOT / ".github/workflows/lint.yml").open(encoding="utf-8") as workflow_file: + cls.workflow = yaml.safe_load(workflow_file) + + def test_validation_commands_run_in_order(self): + run_steps = [ + step for step in self.workflow["jobs"]["validate"]["steps"] if "run" in step + ] + commands = [step["run"] for step in run_steps] + + self.assertEqual( + commands, + [ + 'pip install "pyyaml>=6"', + "python .github/scripts/validate_notes.py", + "python scripts/summarize_ratings.py --check", + "python -m unittest discover -s tests", + "python scripts/generate_research_map.py", + "git diff --exit-code -- generated/research-map.html", + ], + ) + self.assertEqual( + [step["name"] for step in run_steps[-2:]], + ["Generate research map", "Verify generated map is checked in"], + ) + + def test_path_filters_cover_validation_inputs_and_workflows(self): + triggers = self.workflow.get("on", self.workflow.get(True)) + required_paths = { + "research/**", + "ideas/**", + "scripts/**", + "generated/**", + "tests/**", + ".github/workflows/lint.yml", + ".github/workflows/pages.yml", + } + + self.assertEqual(triggers["push"]["branches"], ["main"]) + for event in ("pull_request", "push"): + with self.subTest(event=event): + self.assertTrue(required_paths.issubset(triggers[event]["paths"])) + + +if __name__ == "__main__": + unittest.main() From 102a1862316c875eb2bd96b8353da3642a76362a Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 12:31:03 +0200 Subject: [PATCH 40/41] ci: deploy research map to Pages --- .github/workflows/pages.yml | 77 +++++++++++++++++++++++++++++++++++ tests/test_workflows.py | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..7f3ead2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,77 @@ +name: Deploy research map to Pages + +on: + push: + branches: [main] + paths: + - "research/**" + - "ideas/**" + - "scripts/**" + - "generated/**" + - "tests/**" + - ".github/workflows/pages.yml" + - ".github/workflows/lint.yml" + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: pip install "pyyaml>=6" + + - name: Validate notes and ideas + run: python .github/scripts/validate_notes.py + + - name: Check rating distributions + run: python scripts/summarize_ratings.py --check + + - name: Test map generator + run: python -m unittest discover -s tests + + - name: Generate research map + run: python scripts/generate_research_map.py + + - name: Verify generated map is checked in + run: git diff --exit-code -- generated/research-map.html + + - name: Create Pages site + run: mkdir _site + + - name: Copy research map to site index + run: cp generated/research-map.html _site/index.html + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/tests/test_workflows.py b/tests/test_workflows.py index e9d566a..c89e4e8 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -53,5 +53,85 @@ def test_path_filters_cover_validation_inputs_and_workflows(self): self.assertTrue(required_paths.issubset(triggers[event]["paths"])) +class PagesWorkflowTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + with (ROOT / ".github/workflows/pages.yml").open(encoding="utf-8") as workflow_file: + cls.workflow = yaml.safe_load(workflow_file) + + def test_deploys_generated_map_from_main(self): + triggers = self.workflow.get("on", self.workflow.get(True)) + + self.assertEqual(self.workflow["name"], "Deploy research map to Pages") + self.assertEqual(set(triggers), {"push"}) + self.assertEqual(triggers["push"]["branches"], ["main"]) + self.assertEqual( + set(triggers["push"]["paths"]), + { + "research/**", + "ideas/**", + "scripts/**", + "generated/**", + "tests/**", + ".github/workflows/pages.yml", + ".github/workflows/lint.yml", + }, + ) + self.assertEqual( + self.workflow["permissions"], + {"contents": "read", "pages": "write", "id-token": "write"}, + ) + self.assertEqual( + self.workflow["concurrency"], + {"group": "pages", "cancel-in-progress": True}, + ) + + def test_build_validates_and_packages_generated_map(self): + steps = self.workflow["jobs"]["build"]["steps"] + + self.assertEqual(self.workflow["jobs"]["build"]["runs-on"], "ubuntu-latest") + self.assertEqual(steps[0]["uses"], "actions/checkout@v4") + self.assertEqual(steps[1]["uses"], "actions/setup-python@v5") + self.assertEqual(steps[1]["with"]["python-version"], "3.12") + self.assertEqual( + [step["run"] for step in steps if "run" in step], + [ + 'pip install "pyyaml>=6"', + "python .github/scripts/validate_notes.py", + "python scripts/summarize_ratings.py --check", + "python -m unittest discover -s tests", + "python scripts/generate_research_map.py", + "git diff --exit-code -- generated/research-map.html", + "mkdir _site", + "cp generated/research-map.html _site/index.html", + ], + ) + self.assertEqual(steps[-2]["uses"], "actions/configure-pages@v5") + self.assertEqual(steps[-1]["uses"], "actions/upload-pages-artifact@v3") + self.assertEqual(steps[-1]["with"]["path"], "_site") + + def test_deploy_uses_pages_environment(self): + deploy = self.workflow["jobs"]["deploy"] + + self.assertEqual(deploy["needs"], "build") + self.assertEqual( + deploy["environment"], + { + "name": "github-pages", + "url": "${{ steps.deployment.outputs.page_url }}", + }, + ) + self.assertEqual( + deploy["steps"], + [ + { + "name": "Deploy to GitHub Pages", + "id": "deployment", + "uses": "actions/deploy-pages@v4", + } + ], + ) + + if __name__ == "__main__": unittest.main() From 058ce2a6ccb6e6c194e502e77d86dc6d2f1ef2cb Mon Sep 17 00:00:00 2001 From: rkoster Date: Thu, 3 Sep 2026 12:37:08 +0200 Subject: [PATCH 41/41] fix: harden GitHub workflow dependencies --- .github/workflows/lint.yml | 9 +++-- .github/workflows/pages.yml | 19 +++++----- tests/test_workflows.py | 69 ++++++++++++++++++++++++------------- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2e503a2..a957fd2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,20 +23,23 @@ on: - ".github/workflows/lint.yml" - ".github/workflows/pages.yml" +permissions: + contents: read + jobs: validate: runs-on: ubuntu-latest steps: - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Install dependencies - run: pip install "pyyaml>=6" + run: pip install "pyyaml==6.0.2" - name: Validate notes and ideas run: python .github/scripts/validate_notes.py diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 7f3ead2..c176d02 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -14,8 +14,6 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: pages @@ -24,17 +22,19 @@ concurrency: jobs: build: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Install dependencies - run: pip install "pyyaml>=6" + run: pip install "pyyaml==6.0.2" - name: Validate notes and ideas run: python .github/scripts/validate_notes.py @@ -58,20 +58,23 @@ jobs: run: cp generated/research-map.html _site/index.html - name: Configure Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: path: _site deploy: needs: build runs-on: ubuntu-latest + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/tests/test_workflows.py b/tests/test_workflows.py index c89e4e8..d8318c4 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -5,6 +5,33 @@ ROOT = Path(__file__).resolve().parents[1] +VALIDATION_COMMANDS = [ + 'pip install "pyyaml==6.0.2"', + "python .github/scripts/validate_notes.py", + "python scripts/summarize_ratings.py --check", + "python -m unittest discover -s tests", + "python scripts/generate_research_map.py", + "git diff --exit-code -- generated/research-map.html", +] +ACTION_PINS = { + "actions/checkout": ("11d5960a326750d5838078e36cf38b85af677262", "v4.4.0"), + "actions/setup-python": ("a26af69be951a213d495a4c3e4e4022e16d87065", "v5.6.0"), + "actions/configure-pages": ("983d7736d9b0ae728b81ab479565c72886d7745b", "v5.0.0"), + "actions/upload-pages-artifact": ("56afc609e74202658d3ffba0e8f6dda462b719fa", "v3.0.1"), + "actions/deploy-pages": ("d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e", "v4.0.5"), +} + + +def assert_actions_are_pinned(test_case, workflow_path, workflow): + workflow_text = workflow_path.read_text(encoding="utf-8") + for job in workflow["jobs"].values(): + for step in job.get("steps", []): + if "uses" not in step: + continue + action, reference = step["uses"].split("@", 1) + sha, version = ACTION_PINS[action] + test_case.assertEqual(reference, sha) + test_case.assertIn(f"uses: {action}@{sha} # {version}", workflow_text) class LintWorkflowTests(unittest.TestCase): @@ -12,6 +39,7 @@ class LintWorkflowTests(unittest.TestCase): def setUpClass(cls): with (ROOT / ".github/workflows/lint.yml").open(encoding="utf-8") as workflow_file: cls.workflow = yaml.safe_load(workflow_file) + cls.workflow_path = ROOT / ".github/workflows/lint.yml" def test_validation_commands_run_in_order(self): run_steps = [ @@ -21,20 +49,17 @@ def test_validation_commands_run_in_order(self): self.assertEqual( commands, - [ - 'pip install "pyyaml>=6"', - "python .github/scripts/validate_notes.py", - "python scripts/summarize_ratings.py --check", - "python -m unittest discover -s tests", - "python scripts/generate_research_map.py", - "git diff --exit-code -- generated/research-map.html", - ], + VALIDATION_COMMANDS, ) self.assertEqual( [step["name"] for step in run_steps[-2:]], ["Generate research map", "Verify generated map is checked in"], ) + def test_uses_read_only_permissions_and_immutable_action_pins(self): + self.assertEqual(self.workflow["permissions"], {"contents": "read"}) + assert_actions_are_pinned(self, self.workflow_path, self.workflow) + def test_path_filters_cover_validation_inputs_and_workflows(self): triggers = self.workflow.get("on", self.workflow.get(True)) required_paths = { @@ -58,6 +83,7 @@ class PagesWorkflowTests(unittest.TestCase): def setUpClass(cls): with (ROOT / ".github/workflows/pages.yml").open(encoding="utf-8") as workflow_file: cls.workflow = yaml.safe_load(workflow_file) + cls.workflow_path = ROOT / ".github/workflows/pages.yml" def test_deploys_generated_map_from_main(self): triggers = self.workflow.get("on", self.workflow.get(True)) @@ -79,7 +105,7 @@ def test_deploys_generated_map_from_main(self): ) self.assertEqual( self.workflow["permissions"], - {"contents": "read", "pages": "write", "id-token": "write"}, + {"contents": "read"}, ) self.assertEqual( self.workflow["concurrency"], @@ -90,30 +116,22 @@ def test_build_validates_and_packages_generated_map(self): steps = self.workflow["jobs"]["build"]["steps"] self.assertEqual(self.workflow["jobs"]["build"]["runs-on"], "ubuntu-latest") - self.assertEqual(steps[0]["uses"], "actions/checkout@v4") - self.assertEqual(steps[1]["uses"], "actions/setup-python@v5") + self.assertEqual(self.workflow["jobs"]["build"]["permissions"], {"contents": "read"}) self.assertEqual(steps[1]["with"]["python-version"], "3.12") self.assertEqual( [step["run"] for step in steps if "run" in step], - [ - 'pip install "pyyaml>=6"', - "python .github/scripts/validate_notes.py", - "python scripts/summarize_ratings.py --check", - "python -m unittest discover -s tests", - "python scripts/generate_research_map.py", - "git diff --exit-code -- generated/research-map.html", - "mkdir _site", - "cp generated/research-map.html _site/index.html", - ], + VALIDATION_COMMANDS + + ["mkdir _site", "cp generated/research-map.html _site/index.html"], ) - self.assertEqual(steps[-2]["uses"], "actions/configure-pages@v5") - self.assertEqual(steps[-1]["uses"], "actions/upload-pages-artifact@v3") self.assertEqual(steps[-1]["with"]["path"], "_site") def test_deploy_uses_pages_environment(self): deploy = self.workflow["jobs"]["deploy"] self.assertEqual(deploy["needs"], "build") + self.assertEqual( + deploy["permissions"], {"pages": "write", "id-token": "write"} + ) self.assertEqual( deploy["environment"], { @@ -127,11 +145,14 @@ def test_deploy_uses_pages_environment(self): { "name": "Deploy to GitHub Pages", "id": "deployment", - "uses": "actions/deploy-pages@v4", + "uses": f"actions/deploy-pages@{ACTION_PINS['actions/deploy-pages'][0]}", } ], ) + def test_uses_immutable_action_pins(self): + assert_actions_are_pinned(self, self.workflow_path, self.workflow) + if __name__ == "__main__": unittest.main()