Skip to content
Open
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
65 changes: 65 additions & 0 deletions .github/workflows/doc-codeblocks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Issue #1759 - execute documentation code blocks in CI (md-babel-py).
#
# Policy (matches issue author):
# - Behavior and fence flags (`skip`, `expected-error`, sessions, etc.) follow
# docs/agents/docs/codeblocks.md.
# - If a block is broken and the fix is not obvious, contributors may mark the
# fence with `skip` (or `expected-error` where appropriate) and fix later;
# until then, this job fails on execution errors so drift is visible.
# - For now this job only verifies that covered code blocks execute in CI.
# A follow-up can re-enable auto-committing refreshed <!--Result:--> /
# generated assets back onto the PR branch.
name: doc-codeblocks
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'docs/**'
- 'README.md'
- 'bin/run-doc-codeblocks'
- '.github/workflows/doc-codeblocks.yml'
- 'pyproject.toml'
- 'uv.lock'

permissions:
contents: read
packages: read
pull-requests: read

jobs:
md-babel:
Comment thread
bogwi marked this conversation as resolved.
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
timeout-minutes: 60
runs-on: [self-hosted, Linux]
container:
image: ghcr.io/dimensionalos/ros-dev:dev

steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
clean: false
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ github.token }}

- name: Fix permissions
run: |
git config --global --add safe.directory '*'
git clean -ffdx -e .venv

- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Python dependencies
run: |
uv sync --all-extras --no-extra dds --frozen
uv pip install matplotlib httpx

- name: Remove pydrake stubs
run: |
find .venv/lib/*/site-packages/pydrake -name '*.pyi' -delete 2>/dev/null || true

- name: Execute documentation code blocks
run: ./bin/run-doc-codeblocks --ci --no-cache
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Dimensional is agent native -- "vibecode" your robots in natural language and bu

## Interactive Install

```sh
```sh skip
curl -fsSL https://raw.githubusercontent.com/dimensionalOS/dimos/main/scripts/install.sh | bash
```

Expand Down Expand Up @@ -222,7 +222,7 @@ dimos stop # Shut down

See below a simple robot connection module that sends streams of continuous `cmd_vel` to the robot and receives `color_image` to a simple `Listener` module. DimOS Modules are subsystems on a robot that communicate with other modules using standardized messages.

```py
```py skip
import threading, time, numpy as np
from dimos.core.coordination.blueprints import autoconnect
from dimos.core.core import rpc
Expand Down Expand Up @@ -271,7 +271,7 @@ Blueprints are instructions for how to construct and wire modules. We compose th
Blueprints can be composed, remapped, and have transports overridden if `autoconnect()` fails due to conflicting variable names or `In[]` and `Out[]` message types.

A blueprint example that connects the image stream from a robot to an MCP-backed LLM agent for reasoning and action execution.
```py
```py skip
from dimos.core.coordination.blueprints import autoconnect
from dimos.core.transport import LCMTransport
from dimos.msgs.sensor_msgs import Image
Expand Down Expand Up @@ -308,7 +308,7 @@ if __name__ == "__main__":

## Develop on DimOS

```sh
```sh skip
export GIT_LFS_SKIP_SMUDGE=1
git clone -b dev https://github.com/dimensionalOS/dimos.git
cd dimos
Expand Down
112 changes: 112 additions & 0 deletions bin/run-doc-codeblocks
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Execute md-babel-py on Markdown documentation (see docs/agents/docs/codeblocks.md).
# CI uses --ci (python, shell, Node only) so native diagram toolchains are not required on runners.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

LANG_FILTER="python,sh,node"
# md-babel-py 1.1.4+: isolated block subprocess limit (default upstream is 60).
USE_CACHE=1
declare -a USER_PATHS=()

usage() {
cat >&2 <<'EOF'
Usage: bin/run-doc-codeblocks [options] [path ...]

--ci Same as --lang python,sh,node (default; for CI runners).
Comment thread
bogwi marked this conversation as resolved.
--lang LIST Comma-separated languages for md-babel-py (default: python,sh,node).
--all-langs Run all block languages (requires local native tools; see codeblocks.md).
--no-cache Re-execute all blocks instead of reusing md-babel cache.

With no paths: runs on ./docs (recursive) and ./README.md when present.

Examples:
bin/run-doc-codeblocks --ci
bin/run-doc-codeblocks --all-langs docs/agents/docs/index.md
EOF
}

while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
exit 0
;;
--ci)
LANG_FILTER="python,sh,node"
shift
;;
--lang)
LANG_FILTER="${2:?--lang requires a value}"
shift 2
;;
--all-langs)
LANG_FILTER=""
shift
;;
--no-cache)
USE_CACHE=0
shift
;;
-*)
echo "Unknown option: $1" >&2
usage
exit 2
;;
*)
USER_PATHS+=("$1")
shift
;;
esac
done

resolve_md_babel() {
# Prefer project env so md-babel-py version matches pyproject (needs >= 1.1.4 for --execution-timeout).
if command -v uv &>/dev/null && [[ -f pyproject.toml ]]; then
MB=(uv run md-babel-py)
elif [[ -x .venv/bin/md-babel-py ]]; then
MB=(.venv/bin/md-babel-py)
elif [[ -x .venv/bin/python ]]; then
MB=(.venv/bin/python -m md_babel_py.cli)
elif command -v md-babel-py &>/dev/null; then
MB=(md-babel-py)
else
echo "Error: md-babel-py not found. Install project deps (e.g. uv sync --extra dev or uv sync --all-extras)." >&2
exit 1
fi
}

resolve_md_babel

run_one_target() {
local target=$1
local -a cmd=("${MB[@]}" run --execution-timeout 120)
if [[ "$USE_CACHE" -eq 0 ]]; then
cmd+=(--no-cache)
fi
if [[ -n "$LANG_FILTER" ]]; then
cmd+=(--lang "$LANG_FILTER")
fi
if [[ -d "$target" ]]; then
cmd+=("$target" --recursive)
else
cmd+=("$target")
fi
"${cmd[@]}"
}

if [[ ${#USER_PATHS[@]} -gt 0 ]]; then
for p in "${USER_PATHS[@]}"; do
if [[ -d "$p" ]] || [[ -f "$p" ]]; then
run_one_target "$p"
else
echo "Error: not a file or directory: $p" >&2
exit 1
fi
done
else
run_one_target ./docs
run_one_target README.md
fi
5 changes: 5 additions & 0 deletions docs/agents/docs/codeblocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Add flags after the language identifier:
| `skip` | Don't execute this block |
| `expected-error` | Block is expected to fail |

Use `skip` when a block would pull in **CUDA / GPU-only** stacks (for example perception models, `VoxelGridMapper` defaults, or imports that load torch with GPU expectations), or when it is **flaky in CI** (multi-module coordinators, timing-sensitive workers, pytest-style snippets that are not meant to run as a single script). Prefer `expected-error` only when the block is supposed to fail and you want to assert that failure.

## Examples

# md-babel-py
Expand Down Expand Up @@ -311,4 +313,7 @@ md-babel-py run document.md --lang python,sh

# Dry run - show what would execute
md-babel-py run document.md --dry-run

# Longer subprocess limit (default 60s); see upstream README for MD_BABEL_EXECUTION_TIMEOUT
md-babel-py run document.md --execution-timeout 120
```
2 changes: 1 addition & 1 deletion docs/agents/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ something/

Never add imports to `__init__.py` files. Re-exporting from `__init__.py` makes imports too wide and slow — importing one symbol pulls in the entire package tree.

```python
```python skip
# BAD — dimos/memory2/__init__.py
from dimos.memory2.store import Store, SqliteStore
from dimos.memory2.stream import Stream
Expand Down
10 changes: 5 additions & 5 deletions docs/agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def test_wiring() -> None:

When a resource is shared across multiple tests, use a pytest fixture with `yield` instead of repeating context managers in each test:

```python
```python skip
# GOOD - fixture handles lifecycle for all tests that use it
@pytest.fixture(scope="module")
def store() -> Iterator[SqliteStore]:
Expand All @@ -79,7 +79,7 @@ def test_search(store: SqliteStore) -> None:

Tests must be deterministic. If you don't know the state, the test is wrong.

```python
```python skip
# BAD - assertion may never execute
if hasattr(obj, "_disposables") and obj._disposables is not None:
assert obj._disposables.is_disposed
Expand All @@ -101,7 +101,7 @@ assert obj._disposables.is_disposed

Don't use `time.sleep()` to wait for async operations. Use `threading.Event` to synchronize emitter/receiver patterns.

```python
```python skip
# BAD - arbitrary sleep, fragile
module.start()
time.sleep(0.5)
Expand All @@ -122,7 +122,7 @@ assert received == [84]

Configuration fields on non-Pydantic classes should be private (underscore-prefixed) unless they are part of the public API.

```python
```python skip
# BAD
self.voxel_size = voxel_size
self.carve_columns = carve_columns
Expand All @@ -136,7 +136,7 @@ self._carve_columns = carve_columns

Avoid `# type: ignore` by using proper types:

```python
```python skip
# BAD
self.vbg = None # type: ignore[assignment]

Expand Down
Loading
Loading