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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Review for this repo routes to the Agentic Runtime working-group leads.
# See: https://github.com/cloudfoundry/community/blob/main/toc/working-groups/agentic-runtime.md
* @beyhan @wayneeseguin @rkoster @itsouvalas
15 changes: 15 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Thanks for contributing a research note! Please confirm the checklist below. -->

## What is this note about?

<!-- One or two sentences. -->

## Checklist

- [ ] My note lives in `research/` and uses a lowercase kebab-case filename.
- [ ] It starts from `research/TEMPLATE.md` and has valid frontmatter
(`title`, `author`, `date`, `tags`, `status`, `sources`).
- [ ] It has the four sections: Summary, Key findings, CF relevance, Open questions.
- [ ] I added relevant `tags` (see the suggested vocabulary in `IDEATION.md`).
- [ ] Sources are linked.
- [ ] It's on-topic for agentic workloads on Cloud Foundry
173 changes: 173 additions & 0 deletions .github/scripts/validate_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""Validate research notes in research/ and ideas in ideas/.

For every research/*.md file except README.md and TEMPLATE.md this checks:
- the filename is lowercase kebab-case ending in .md
- a YAML frontmatter block is present and parses
- required frontmatter keys are present and well-typed
- the four required body section headings are present

Ideas are deliberately low-barrier. For every ideas/*.md file except README.md and
TEMPLATE.md this checks only:
- the filename is lowercase kebab-case ending in .md
- a title is present (a YAML 'title:' or a '# ' heading)

Exits non-zero (printing every problem) if any note or idea is invalid.
"""

from __future__ import annotations

import datetime as dt
import pathlib
import re
import sys

import yaml

RESEARCH_DIR = pathlib.Path("research")
IDEAS_DIR = pathlib.Path("ideas")
SKIP = {"README.md", "TEMPLATE.md"}

REQUIRED_KEYS = {
"title": str,
"author": str,
"date": object, # validated separately
"tags": list,
"status": str,
"sources": list,
}
ALLOWED_STATUS = {"draft", "reviewed"}
REQUIRED_SECTIONS = [
"## Summary",
"## Key findings",
"## CF relevance",
"## Open questions",
]
FILENAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*\.md$")
FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
H1_RE = re.compile(r"^#\s+\S", re.MULTILINE)


def validate_file(path: pathlib.Path) -> list[str]:
problems: list[str] = []
name = path.name

if not FILENAME_RE.match(name):
problems.append(
f"filename must be lowercase kebab-case ending in .md (got '{name}')"
)

text = path.read_text(encoding="utf-8")

match = FRONTMATTER_RE.match(text)
if not match:
problems.append("missing YAML frontmatter block (must start with '---' on line 1)")
return [f"{path}: {p}" for p in problems]

try:
meta = yaml.safe_load(match.group(1))
except yaml.YAMLError as exc:
problems.append(f"frontmatter is not valid YAML: {exc}")
return [f"{path}: {p}" for p in problems]

if not isinstance(meta, dict):
problems.append("frontmatter must be a YAML mapping")
return [f"{path}: {p}" for p in problems]

for key, expected_type in REQUIRED_KEYS.items():
value = meta.get(key)
if value in (None, "", [], {}):
problems.append(f"missing required frontmatter key '{key}'")
continue
if expected_type is not object and not isinstance(value, expected_type):
problems.append(f"frontmatter key '{key}' must be a {expected_type.__name__}")

status = meta.get("status")
if isinstance(status, str) and status not in ALLOWED_STATUS:
problems.append(
f"'status' must be one of {sorted(ALLOWED_STATUS)} (got '{status}')"
)

date_val = meta.get("date")
if date_val not in (None, "", [], {}):
if isinstance(date_val, dt.date):
pass # YAML already parsed a date
elif isinstance(date_val, str) and DATE_RE.match(date_val):
pass
else:
problems.append("'date' must be in YYYY-MM-DD format")

body = text[match.end():]
for section in REQUIRED_SECTIONS:
if not re.search(rf"^{re.escape(section)}\s*$", body, re.MULTILINE):
problems.append(f"missing required section heading '{section}'")

return [f"{path}: {p}" for p in problems]


def validate_idea(path: pathlib.Path) -> list[str]:
"""Light validation: kebab-case filename plus a title of some kind."""
problems: list[str] = []
name = path.name

if not FILENAME_RE.match(name):
problems.append(
f"filename must be lowercase kebab-case ending in .md (got '{name}')"
)

text = path.read_text(encoding="utf-8")

has_title = False
body = text
match = FRONTMATTER_RE.match(text)
if match:
body = text[match.end():]
try:
meta = yaml.safe_load(match.group(1))
except yaml.YAMLError as exc:
problems.append(f"frontmatter is not valid YAML: {exc}")
else:
if isinstance(meta, dict) and str(meta.get("title") or "").strip():
has_title = True

if not has_title and not H1_RE.search(body):
problems.append("idea needs a title (a YAML 'title:' or a '# ' heading)")

return [f"{path}: {p}" for p in problems]


def main() -> int:
if not RESEARCH_DIR.is_dir():
print(f"error: '{RESEARCH_DIR}/' directory not found", file=sys.stderr)
return 1

notes = sorted(p for p in RESEARCH_DIR.glob("*.md") if p.name not in SKIP)
ideas = (
sorted(p for p in IDEAS_DIR.glob("*.md") if p.name not in SKIP)
if IDEAS_DIR.is_dir()
else []
)

problems: list[str] = []
for note in notes:
problems.extend(validate_file(note))
for idea in ideas:
problems.extend(validate_idea(idea))

if problems:
print("Validation failed:\n")
for problem in problems:
print(f" - {problem}")
print(
f"\n{len(problems)} problem(s) across "
f"{len(notes)} research note(s) and {len(ideas)} idea(s)."
)
return 1

print(f"OK: {len(notes)} research note(s) and {len(ideas)} idea(s) valid.")
return 0


if __name__ == "__main__":
sys.exit(main())
34 changes: 34 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Validate notes and ideas

on:
pull_request:
paths:
- "research/**"
- "ideas/**"
- ".github/scripts/validate_notes.py"
- ".github/workflows/lint.yml"
push:
branches: [main]
paths:
- "research/**"
- "ideas/**"
- ".github/scripts/validate_notes.py"
- ".github/workflows/lint.yml"

jobs:
validate:
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
13 changes: 13 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Code of Conduct

The Cloud Foundry Agentic Runtime Working Group, like all Cloud Foundry community spaces,
operates under the **Cloud Foundry Foundation Code of Conduct**.

Please read it here: https://www.cloudfoundry.org/code-of-conduct/

By participating in this repository — through issues, pull requests, reviews, or any other
interaction — you agree to abide by its terms.

To report a concern, follow the reporting instructions in the linked Code of Conduct, or
reach a working-group lead in [#ai-wg](https://cloudfoundry.slack.com/archives/C0B214KJ1HA)
on the Cloud Foundry Slack.
77 changes: 77 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Contributing

Thanks for helping shape the future of agentic workloads on Cloud Foundry. During the
current **research phase** there are two ways to contribute, both via pull request:

- A quick **idea** in [`ideas/`](./ideas) — low bar, no sourcing required. A place for
sparks, so nothing gets turned away for not yet being a full research note.
- A sourced **research note** in [`research/`](./research) — structured and cited.

Ideas and research link together: an idea points to the research that backs it, and one
research note can back several ideas. An idea with enough research behind it can later become
a proposal.

For the why and the bigger picture, read [`IDEATION.md`](./IDEATION.md).

## Adding an idea

Copy [`ideas/TEMPLATE.md`](./ideas/TEMPLATE.md) to `ideas/my-idea.md`, jot down the spark,
and open a PR. The only hard rules are a kebab-case filename and a title — everything else
is optional. See [`ideas/README.md`](./ideas/README.md).

## Adding a research note

1. **Fork** this repository (or, if you're a working-group member with write access, create
a branch).
2. **Copy the template:**
```bash
cp research/TEMPLATE.md research/my-topic.md
```
3. **Fill it in.** Keep it short, sourced, and outward-looking (see
[`IDEATION.md`](./IDEATION.md) for what Phase 1 is after). See
[`research/README.md`](./research/README.md) for the frontmatter schema.
4. **Commit your note:**
```bash
git add research/my-topic.md
git commit -m "Add research note: my topic"
```
5. **Open a pull request.** A CI check validates your note's frontmatter and structure. A
working-group tech lead will give it a quick look and merge.

## Filename convention

- Lowercase **kebab-case**, ending in `.md`: `research/agent-frameworks.md`.
- If your topic collides with an existing note, add your GitHub handle as a suffix:
`research/agent-frameworks-rkoster.md`.

## Frontmatter

Every note starts with a YAML frontmatter block. Required keys: `title`, `author`, `date`,
`tags`, `status`, `sources`. The `cf_areas` key is optional. The full schema and field
descriptions live in [`research/README.md`](./research/README.md).

Tagging well matters — tags are how the workshop clusters notes into themes. See the
suggested (non-binding) vocabulary in
[`IDEATION.md`](./IDEATION.md#tagging-how-themes-will-emerge).

## Review & merge

Research notes are low-risk, so we optimize for throughput:

- For research notes, CI validates frontmatter, filename, and required sections. For
[`ideas/`](./ideas) it only checks the filename and that a title is present.
- Any working-group **tech lead** can merge once CI passes and the contribution is on-topic.
- Substantive debate about the *merits* of a contribution happens at the workshop, not as a
merge gate.

## Contributor License Agreement (CLA)

All committers to a Cloud Foundry Foundation project must sign a Contributor License
Agreement. You don't need to do anything up front: the **EasyCLA** bot comments on your
first pull request with a link to sign (individual or corporate). Once it's signed, the
CLA check goes green and your PR can be merged. You can also
[sign in to EasyCLA](https://corporate.v1.easycla.lfx.linuxfoundation.org/) ahead of time.

## Code of conduct

This project follows the [Cloud Foundry Code of Conduct](./CODE_OF_CONDUCT.md).
Loading
Loading