surf-agentic-base 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/CITATION.cff +1 -1
- {surf_agentic_base-0.3.2/src/surf_agentic_base.egg-info → surf_agentic_base-0.3.4}/PKG-INFO +5 -3
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/README.md +4 -2
- surf_agentic_base-0.3.4/docs/architecture/blocks.md +96 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/boundaries.md +7 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/picture.md +52 -41
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/reuse-ledger.md +1 -1
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/schemas/OutcomeRunFacet.json +3 -2
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/mkdocs.yml +1 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/outcomes.py +11 -2
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/provenance/__init__.py +4 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/provenance/emit.py +38 -9
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4/src/surf_agentic_base.egg-info}/PKG-INFO +5 -3
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/SOURCES.txt +1 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/scm_file_list.json +1 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/scm_version.json +2 -2
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/test_outcomes.py +16 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/provenance/test_emit.py +48 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.dockerignore +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/CODEOWNERS +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/allowed_signers +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/dependabot.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/release.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/renovate.json5 +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/workflows/ci.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/workflows/label.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/workflows/release.yml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.gitignore +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.markdownlint-cli2.jsonc +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.pre-commit-config.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/CODE_OF_CONDUCT.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/CONTRIBUTING.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/Dockerfile +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/Justfile +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/LICENSE +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/SECURITY.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/START_HERE.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/.helmignore +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/Chart.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/NOTES.txt +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/_helpers.tpl +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/deployment.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/hpa.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/ingress.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/networkpolicy.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/poddisruptionbudget.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/prometheusrule.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/service.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/serviceaccount.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/servicemonitor.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/templates/tests/test-connection.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/charts/app/values.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/ENGINEERING.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/OBSERVABILITY.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/compliance.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/deployment-overlay.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/from-agentic-env.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/go-live-on-sdp.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/kubernetes.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/layering.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/move-plan.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/operational-traps.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/picture.svg +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/process.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/architecture/proposals.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/decisions.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/include-readme.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/docs/index.md +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/overlay.cfg +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/pyproject.toml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/scripts/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/scripts/assert_no_permitted_failures.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/scripts/check_wheel_imports.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/scripts/overlay.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/setup.cfg +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/client.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/code_policy/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/code_policy/policy.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/config.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/db.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/epochs.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/integrity.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/run_record.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/domain/validity.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/hpc/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/hpc/clusters.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/hpc/job_result.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/hpc/profiles/lumi.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/hpc/profiles/snellius.yaml +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/limits.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/llm/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/llm/health.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/llm/resilience.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/main.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/mcp/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/mcp/server.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/observability/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/observability/conventions.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/observability/tracing.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/provenance/mlflow_export.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/py.typed +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/recording.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/routers/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/routers/health.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/routers/runs.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/security/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/security/netsec.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/tools/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/tools/types.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/utils/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/agentic_base/utils/logging.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/dependency_links.txt +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/requires.txt +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/src/surf_agentic_base.egg-info/top_level.txt +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/client/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/client/test_client.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/code_policy/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/code_policy/test_policy.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/conftest.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/test_epochs.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/test_integrity.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/test_run_record.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/domain/test_validity.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/hpc/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/hpc/test_clusters.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/hpc/test_job_result.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/lessons/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/lessons/test_a_null_from_a_mechanism_that_never_fired.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/llm/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/llm/test_health.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/llm/test_resilience.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/mcp/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/mcp/test_server.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/observability/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/observability/test_conventions.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/observability/test_recipes.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/observability/test_tracing.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/provenance/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/provenance/test_mlflow_export.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/recording/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/recording/test_recording.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/routers/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/routers/test_health.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/routers/test_runs.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/security/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/security/test_netsec.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_chart.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_check_wheel_imports.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_config.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_limits.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_overlay_contract.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_permitted_failures.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_portable_surface.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_process.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/test_reuse_ledger.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/tools/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/tools/test_types.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/utils/__init__.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/tests/utils/test_logging.py +0 -0
- {surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: surf-agentic-base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Agentic base layer: run provenance, comparison validity, and shared safety primitives.
|
|
5
5
|
Author: SURF
|
|
6
6
|
License-Expression: EUPL-1.2
|
|
@@ -59,8 +59,10 @@ Dynamic: license-file
|
|
|
59
59
|
[](pyproject.toml)
|
|
60
60
|
[](CITATION.cff)
|
|
61
61
|
|
|
62
|
-
The agentic base layer for SURF: **
|
|
63
|
-
primitives an agent needs
|
|
62
|
+
The agentic base layer for SURF: **the record and referee for agent runs, the security
|
|
63
|
+
primitives an agent needs on a shared platform, and the engineering standard that keeps both
|
|
64
|
+
honest**. It is the contracts layer under a set of capability blocks, one per SURF system, that
|
|
65
|
+
live in their own packages; see `docs/architecture/blocks.md`.
|
|
64
66
|
|
|
65
67
|
It is built on the SURF Developer Platform golden path and adopts the common stack wherever the
|
|
66
68
|
common stack has an answer. It contains only the parts we could not find anywhere else.
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
[](pyproject.toml)
|
|
6
6
|
[](CITATION.cff)
|
|
7
7
|
|
|
8
|
-
The agentic base layer for SURF: **
|
|
9
|
-
primitives an agent needs
|
|
8
|
+
The agentic base layer for SURF: **the record and referee for agent runs, the security
|
|
9
|
+
primitives an agent needs on a shared platform, and the engineering standard that keeps both
|
|
10
|
+
honest**. It is the contracts layer under a set of capability blocks, one per SURF system, that
|
|
11
|
+
live in their own packages; see `docs/architecture/blocks.md`.
|
|
10
12
|
|
|
11
13
|
It is built on the SURF Developer Platform golden path and adopts the common stack wherever the
|
|
12
14
|
common stack has an answer. It contains only the parts we could not find anywhere else.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Blocks: the common capabilities SURF exposes to agents, and where each one lives
|
|
2
|
+
|
|
3
|
+
SURF intends to offer a set of common building blocks, tools and skills, that an agent can use,
|
|
4
|
+
internally and externally. This page says what a block is, which blocks the existing systems
|
|
5
|
+
imply, how a block composes with others and is adapted by a downstream, and where this
|
|
6
|
+
repository stops. It was checked on 2026-09-13 against the internal Confluence (the NL AI
|
|
7
|
+
Factory architecture, its high-level MLOps design, the design memo of July 2026, the user
|
|
8
|
+
interviews, the SURF Developer Platform overview, the AI-at-SURF product list) and against the
|
|
9
|
+
code that exists in agentic-env, willma2 and the AI4Science prototype.
|
|
10
|
+
|
|
11
|
+
## Three layers
|
|
12
|
+
|
|
13
|
+
| layer | what it is | who owns it |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **contracts** | what every block stands on: the tool contract, the recording seam, the curated-surface mechanism, the security primitives, the span vocabulary, the run record and the referee. No block lives here | this repository |
|
|
16
|
+
| **blocks** | one per system. An MCP server, a Python client for code that does not want a model in the loop, and the skills that say how to use the system well. Each its own package, each with the owner of the system behind it | the system's team |
|
|
17
|
+
| **catalogue** | where a block is found. Backstage on the SURF Developer Platform internally, the MCP Registry externally; each block ships its own catalogue entry | the platform |
|
|
18
|
+
|
|
19
|
+
The reason the base is not a block, and no block is in the base: a block imports the contracts,
|
|
20
|
+
and a package that imports something must not also contain it. That is also why the scheduler
|
|
21
|
+
client is not here, whatever the temptation; `boundaries.md` says this repository must not
|
|
22
|
+
submit jobs, and a block that does belongs with the people who run the scheduler.
|
|
23
|
+
|
|
24
|
+
## The blocks the existing systems imply
|
|
25
|
+
|
|
26
|
+
The first list had six. Checking it against what SURF runs and what the AI Factory names as its
|
|
27
|
+
common denominators, identity and access, a data plane with object and POSIX tiers, accounting
|
|
28
|
+
and quota, a shared GPU pool, a common home for artifacts, uniform observability, and tenant
|
|
29
|
+
isolation, adds three and sharpens two.
|
|
30
|
+
|
|
31
|
+
| block | wraps | what exists today | status |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| **hpc** | Slurm on Snellius, LUMI and the AI Factory; a served model on it | agentic-env's slurm_companion, 51 tools, two curated profiles, an SSH backend; three separate slurmrestd clients in willma2, the AI4Science prototype and a stub in `python_slurm_wrapper` | the most duplicated capability at SURF and the one to consolidate, in its own package, once slurmrestd's availability is settled with the operators |
|
|
34
|
+
| **inference** | Willma, the AI Hub back office | an OpenAI-compatible endpoint and nothing published as a block; the model catalogue, serve requests and the Whisper transcription that Research Cloud items already call | a block, because every other block's agent needs a model and the catalogue is the thing to expose |
|
|
35
|
+
| **knowledge** | Confluence today; the SURF knowledge base and the education search portals tomorrow | agentic-env's confluence product, 20 tools, already served over MCP with a curated surface; a separate team is building an MCP server for edusources.nl | the cheapest first extraction, and the proof that two teams' MCP servers can share one catalogue |
|
|
36
|
+
| **software** | EasyBuild and EESSI | agentic-env's easybuild product, 10 tools, with a sandboxed validation backend | ready to extract |
|
|
37
|
+
| **data** | the object stores (Swift, LUMI-O, MinIO on the platform), the POSIX tiers, dCache, iRODS and Yoda, Research Drive, the AI Factory's dataset-as-a-service | the AI4Science prototype's dataset vocabulary; agentic-env's staging scripts for LUMI-O; nothing agent-facing | a block, and the largest gap: an agent that cannot find, stage or cite data does not do research |
|
|
38
|
+
| **artifacts** | the container registry, a model registry, dataset versions and checkpoints | GitLab and Harbor registries on the platform; MLflow named as the registry in the AI Factory design; content-addressed artifacts in agentic-env's lineage | missing. The GPT-NL interview asked for exactly this: a shared versioned store and a common registry |
|
|
39
|
+
| **identity** | SURFconext and SRAM: who you are, which project you belong to, what you may touch | every block needs it and none carries it; the platform's tenancy model | not a block an agent calls; the thing every block's surface is scoped by. Named so it is not forgotten |
|
|
40
|
+
| **accounting** | GPU-seconds, storage, and energy per tenant and per run | Slurm and EAR accounting on Snellius; the AI Factory asks for accounting "the same way however the work was launched"; the run record carries joules beside tokens | a small read-only block, and the one that answers the question a funder asks first |
|
|
41
|
+
| **runs** | the record and the referee | this repository's service and its four-tool server | exists |
|
|
42
|
+
|
|
43
|
+
Two of the nine are not agent tools at all. Identity is what scopes every surface, and
|
|
44
|
+
accounting is what the platform bills on; they appear because a design that leaves them implicit
|
|
45
|
+
gets them wrong, which the AI Factory memo says in its own words.
|
|
46
|
+
|
|
47
|
+
## What makes a block composable
|
|
48
|
+
|
|
49
|
+
- **It speaks MCP and nothing private.** Any agent, any framework, any vendor's client, connects
|
|
50
|
+
to it. That is the entire composition mechanism, and the field settled it in 2025.
|
|
51
|
+
- **One tool name per capability across backends.** `submit_job` is the same name on Snellius,
|
|
52
|
+
LUMI and the AI Factory, so telemetry aggregates and a skill written for one transfers to the
|
|
53
|
+
others. Decision D1.
|
|
54
|
+
- **Surfaces are declared subsets with reasons.** agentic-env's profiles already do this:
|
|
55
|
+
`hpc-ops` is read-only, `hpc-serve` adds serving, and the full surface stays in-process because
|
|
56
|
+
a remote shell as the credential owner is not a thing to publish. Internal and external
|
|
57
|
+
exposure are one block with a different profile and different authentication, never two
|
|
58
|
+
codebases.
|
|
59
|
+
- **Every block records through the same seam and speaks the same span vocabulary.** A call
|
|
60
|
+
through the knowledge block and a call through the hpc block land in one trace and one run
|
|
61
|
+
record, which is what lets the referee judge what an agent did across blocks.
|
|
62
|
+
- **A block's profile maps onto the platform's isolation tier.** The AI Factory distinguishes a
|
|
63
|
+
community tier, a virtualised tier and an isolated tier for sensitive data. A block published
|
|
64
|
+
into the isolated tier exposes less, not the same surface behind a stricter login.
|
|
65
|
+
|
|
66
|
+
## What makes a block derivable
|
|
67
|
+
|
|
68
|
+
- **Site facts live in configuration, never in code.** Cluster profiles, registry hosts,
|
|
69
|
+
endpoints, tenant names. The deployment-overlay contract enforces this for the base and applies
|
|
70
|
+
to a block unchanged: a downstream takes the hpc block and overlays its own cluster profile.
|
|
71
|
+
- **Skills are files next to the block, in the open SKILL.md format**, with the anatomy
|
|
72
|
+
agentic-env already requires: steps, the rationalisations table, red flags, a verification
|
|
73
|
+
checklist. A skill for another cluster differs in the profile it names and nothing else.
|
|
74
|
+
- **A template repository derived from this one's standard.** The guards, the ledger, the release
|
|
75
|
+
path, signed tags and attested wheels. A new block starts from the template and inherits the
|
|
76
|
+
discipline without inheriting any code.
|
|
77
|
+
|
|
78
|
+
## What the internal documentation does not yet say
|
|
79
|
+
|
|
80
|
+
Nothing on the internal Confluence describes an agent-facing layer: no page on MCP, on skills, or
|
|
81
|
+
on agents as a workload, apart from one product line about an MCP server for the education
|
|
82
|
+
portals. The AI Factory's own architecture notes list train, fine-tune and infer as the three
|
|
83
|
+
verbs and have no box for an agent run, which is the gap the design memo of July 2026 calls the
|
|
84
|
+
common denominators without naming agents. This page is therefore the first written statement of
|
|
85
|
+
the layer, and it should move to Confluence once the block owners have read it.
|
|
86
|
+
|
|
87
|
+
## Order of work
|
|
88
|
+
|
|
89
|
+
1. Extract the **knowledge** block first. Smallest, already over MCP, needs no scheduler
|
|
90
|
+
decision, and proves the extraction loop and the template on something that cannot break a
|
|
91
|
+
cluster.
|
|
92
|
+
2. Settle slurmrestd availability with the Snellius operators, then start the **hpc** block from
|
|
93
|
+
the `python_slurm_wrapper` stub with willma2's token handling and the AI4Science schemas.
|
|
94
|
+
3. Define the **data** block's surface with the data teams before writing it; find, stage and
|
|
95
|
+
cite are the three verbs an agent needs, and the systems behind them are theirs.
|
|
96
|
+
4. Leave **identity** and **accounting** as named constraints until a block needs them for real.
|
|
@@ -72,6 +72,13 @@ No model serving. That is Willma.
|
|
|
72
72
|
No job submission. That is AI4Science, and the seam between the two is the first piece of work
|
|
73
73
|
worth doing together.
|
|
74
74
|
|
|
75
|
+
## The blocks are outside, by construction
|
|
76
|
+
|
|
77
|
+
The capability blocks an agent calls, hpc, inference, knowledge, data, software, artifacts, are
|
|
78
|
+
each their own package with the owner of the system behind them (`blocks.md`). They import this
|
|
79
|
+
repository's contracts; nothing here imports a block. A scheduler client in particular does not
|
|
80
|
+
belong here, because this repository must not submit jobs.
|
|
81
|
+
|
|
75
82
|
## Split this repository when, and not before
|
|
76
83
|
|
|
77
84
|
One package becomes two when someone outside SURF wants the library and does not want the service,
|
|
@@ -10,34 +10,35 @@ The mermaid block at the bottom needs a mermaid-capable viewer.
|
|
|
10
10
|
APPLICATIONS people's own work, each brings its own agent
|
|
11
11
|
┌──────────────────┬──────────────────────┬────────────────────┐
|
|
12
12
|
│ agentic-env │ pipeline triage │ other SURF teams │
|
|
13
|
-
│ experiments │ a new SURF project │
|
|
13
|
+
│ experiments │ a new SURF project │ and their agents │
|
|
14
14
|
└──────────────────┴──────────────────────┴────────────────────┘
|
|
15
15
|
│
|
|
16
16
|
CONTROL PLANE │ who may run what, and where
|
|
17
17
|
┌───────────────────────────▼────────────────────────────────┐
|
|
18
|
-
│ AI4Science
|
|
18
|
+
│ AI4Science today; the AI Factory's MLOps and meta-scheduler │
|
|
19
19
|
└───────────────────────────┬────────────────────────────────┘
|
|
20
20
|
│
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
│ runs
|
|
24
|
-
│ (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
BLOCKS │ one per system, curated surface, own package, own owner
|
|
22
|
+
┌───────┬──────────┬──────────┬──────────┬──────┬──────────┬──────────┐
|
|
23
|
+
│ runs │ hpc │inference │knowledge │ data │ software │ artifacts│
|
|
24
|
+
│ (here)│ │ │ │ │ │ │
|
|
25
|
+
└───┬───┴────┬─────┴────┬─────┴────┬─────┴──┬───┴────┬─────┴────┬─────┘
|
|
26
|
+
│ scoped by IDENTITY (SURFconext, SRAM) · billed by ACCOUNTING │
|
|
27
|
+
│ │ │ │ │ │
|
|
28
|
+
CONTRACTS │ THIS REPOSITORY │ │ │ │
|
|
29
|
+
┌───────────▼────────────▼──────────▼──────────▼────────▼──────────▼────┐
|
|
30
|
+
│ tool contract │ recording seam │ curated surfaces │ security │ spans │
|
|
31
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
32
|
+
│ run record · label provenance · validity · epochs · emitted standards │
|
|
33
|
+
└────────────────────────────────────────────────────────────────────────┘
|
|
33
34
|
|
|
34
35
|
ALREADY EXIST, not ours to rebuild
|
|
35
|
-
|
|
36
|
-
│ Willma
|
|
37
|
-
│ serves
|
|
38
|
-
│ models
|
|
39
|
-
|
|
40
|
-
each is published as exactly one
|
|
36
|
+
┌──────────┬───────────────┬────────────┬───────────┬──────────────────┬──────────────┐
|
|
37
|
+
│ Willma │ Slurm │ Confluence │ EasyBuild │ object stores, │ registries │
|
|
38
|
+
│ serves │ Snellius,LUMI │ edusources │ EESSI │ dCache, iRODS, │ GitLab, │
|
|
39
|
+
│ models │ AI Factory │ │ │ Yoda, Res. Drive │ Harbor,MLflow│
|
|
40
|
+
└──────────┴───────────────┴────────────┴───────────┴──────────────────┴──────────────┘
|
|
41
|
+
each is published as exactly one block above; see blocks.md
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
## Reading it in one paragraph
|
|
@@ -46,8 +47,11 @@ The systems at the bottom already exist and are not ours to rebuild. Each is pub
|
|
|
46
47
|
one capability with a curated, read-only-by-default surface. Those capabilities all stand on the
|
|
47
48
|
same contracts, which is what this repository is. Applications sit on top and bring their own agent.
|
|
48
49
|
|
|
49
|
-
The only
|
|
50
|
-
|
|
50
|
+
The only block with no existing system behind it is **runs**, which is why the record lives here
|
|
51
|
+
and the rest do not. Two things on the picture are not blocks an agent calls: identity scopes
|
|
52
|
+
every surface and accounting bills every run, and both are named because a design that leaves
|
|
53
|
+
them implicit gets them wrong. The full list, with what exists behind each block and the order
|
|
54
|
+
to build them, is in `blocks.md`.
|
|
51
55
|
|
|
52
56
|
## What "moving things" would actually mean
|
|
53
57
|
|
|
@@ -87,39 +91,46 @@ flowchart TB
|
|
|
87
91
|
OTH[other SURF teams]
|
|
88
92
|
end
|
|
89
93
|
subgraph ctrl[Control plane - who may run what and where]
|
|
90
|
-
AI4[AI4Science]
|
|
94
|
+
AI4[AI4Science today, the AI Factory MLOps tomorrow]
|
|
91
95
|
end
|
|
92
|
-
subgraph
|
|
96
|
+
subgraph blocks[Blocks - one per system, curated surface, own owner]
|
|
93
97
|
R[runs]
|
|
94
98
|
H[hpc]
|
|
99
|
+
I[inference]
|
|
100
|
+
K[knowledge]
|
|
95
101
|
D[data]
|
|
96
102
|
S[software]
|
|
97
|
-
|
|
103
|
+
A[artifacts]
|
|
104
|
+
end
|
|
105
|
+
subgraph scope[Every block is scoped and billed]
|
|
106
|
+
ID[identity: SURFconext, SRAM]
|
|
107
|
+
AC[accounting: GPU-seconds, storage, joules]
|
|
98
108
|
end
|
|
99
|
-
subgraph base[
|
|
109
|
+
subgraph base[Contracts - this repository]
|
|
100
110
|
C1[tool contract]
|
|
101
|
-
C2[
|
|
102
|
-
C3[
|
|
103
|
-
C4[run record
|
|
104
|
-
C6[provenance emission: PROV, OpenLineage, RO-Crate]
|
|
105
|
-
C5[primitives]
|
|
111
|
+
C2[recording seam and curated surfaces]
|
|
112
|
+
C3[security primitives and span vocabulary]
|
|
113
|
+
C4[run record, validity, epochs, emitted standards]
|
|
106
114
|
end
|
|
107
115
|
subgraph sys[Systems that already exist]
|
|
108
116
|
W[Willma]
|
|
109
117
|
SL[Slurm clusters]
|
|
110
|
-
CF[Confluence]
|
|
111
|
-
EB[EasyBuild]
|
|
118
|
+
CF[Confluence and edusources]
|
|
119
|
+
EB[EasyBuild and EESSI]
|
|
120
|
+
ST[object stores, dCache, iRODS, Yoda, Research Drive]
|
|
121
|
+
RG[GitLab, Harbor, MLflow registries]
|
|
112
122
|
end
|
|
113
|
-
AE -->
|
|
114
|
-
MW -->
|
|
115
|
-
OTH -->
|
|
116
|
-
AI4 -->
|
|
117
|
-
|
|
123
|
+
AE --> blocks
|
|
124
|
+
MW --> blocks
|
|
125
|
+
OTH --> blocks
|
|
126
|
+
AI4 --> blocks
|
|
127
|
+
blocks --> base
|
|
128
|
+
blocks -.-> scope
|
|
118
129
|
H --> SL
|
|
119
|
-
|
|
130
|
+
I --> W
|
|
120
131
|
K --> CF
|
|
121
132
|
S --> EB
|
|
122
|
-
D -->
|
|
133
|
+
D --> ST
|
|
134
|
+
A --> RG
|
|
123
135
|
R --> C4
|
|
124
|
-
R --> C6
|
|
125
136
|
```
|
|
@@ -34,7 +34,7 @@ already supported, and already have an owner.
|
|
|
34
34
|
| secrets | SDP secret management, per its guide | ADOPT. It is the fix for secrets-in-job-scripts |
|
|
35
35
|
| identity and collaboration groups | SURFconext, SRAM | ADOPT |
|
|
36
36
|
| shared model inference | Willma (`research/hpml/willma`, the AI Hub back office) | ADOPT where it serves the model needed |
|
|
37
|
-
| Slurm access from services | `snellius/pyslurm
|
|
37
|
+
| Slurm access from services | slurmrestd, the scheduler's own REST API with a published OpenAPI specification, plus a thin site wrapper. Surveyed 2026-09-13: three hand-written REST clients exist at SURF (willma2, the AI4Science prototype, the `hpml-llms/python_slurm_wrapper` stub) and one SSH backend (agentic-env); `snellius/pyslurm` and `SOIL/slurm-bridge`, named here earlier, could not be found on the GitLab | ADOPT the REST API and generate the client from its specification; the wrapper is one package in the **hpc block**, not this repository (`blocks.md`) |
|
|
38
38
|
| software environments on HPC | EasyBuild (`easybuild-surf`), EESSI | ADOPT |
|
|
39
39
|
|
|
40
40
|
## External, where SURF has no internal equivalent
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"allOf": [{ "$ref": "https://openlineage.io/spec/2-0-2/OpenLineage.json#/$defs/RunFacet" }],
|
|
8
8
|
"properties": {
|
|
9
9
|
"resolved": { "type": ["boolean", "null"], "description": "The outcome, or null when no label has been attached." },
|
|
10
|
-
"labelSource": { "type": "string", "enum": ["unlabelled", "self_reported", "convenience_verifier", "official_harness", "human"], "description": "Who decided the outcome." },
|
|
10
|
+
"labelSource": { "type": "string", "enum": ["unlabelled", "self_reported", "convenience_verifier", "official_harness", "benchmark_grader", "human"], "description": "Who decided the outcome." },
|
|
11
11
|
"authority": { "type": "string", "enum": ["none", "diagnostic", "authoritative"], "description": "Whether the label may be cited, independent of its modality." },
|
|
12
12
|
"degraded": { "type": "boolean", "description": "True when the scoring instrument was not working and returned its fail-open default." },
|
|
13
13
|
"instrument": { "type": "string" },
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"promptTokens": { "type": "integer" },
|
|
19
19
|
"completionTokens": { "type": "integer" },
|
|
20
20
|
"elapsedMs": { "type": "number" },
|
|
21
|
-
"joules": { "type": "number", "description": "Physical energy, kept apart from tokens and elapsed time because each prices a different thing." }
|
|
21
|
+
"joules": { "type": "number", "description": "Physical energy, kept apart from tokens and elapsed time because each prices a different thing." },
|
|
22
|
+
"sourceRunId": { "type": "string", "description": "The producer's own run id. The event's runId is this when it is a UUID and a uuid5 derived from it otherwise." }
|
|
22
23
|
},
|
|
23
24
|
"required": ["labelSource", "authority", "degraded"]
|
|
24
25
|
}
|
|
@@ -11,6 +11,7 @@ nav:
|
|
|
11
11
|
- Observability: OBSERVABILITY.md
|
|
12
12
|
- Architecture:
|
|
13
13
|
- The picture: architecture/picture.md
|
|
14
|
+
- Blocks and layers: architecture/blocks.md
|
|
14
15
|
- Layering: architecture/layering.md
|
|
15
16
|
- Boundaries: architecture/boundaries.md
|
|
16
17
|
- Move plan: architecture/move-plan.md
|
|
@@ -52,12 +52,19 @@ class LabelSource(str, enum.Enum):
|
|
|
52
52
|
contrast."""
|
|
53
53
|
|
|
54
54
|
OFFICIAL_HARNESS = "official_harness"
|
|
55
|
-
"""The benchmark's own authoritative scorer."""
|
|
55
|
+
"""The benchmark's own authoritative scorer, published as such: the SWE-bench harness."""
|
|
56
|
+
|
|
57
|
+
BENCHMARK_GRADER = "benchmark_grader"
|
|
58
|
+
"""A benchmark's own grader where the benchmark ships no separate harness: a tau environment
|
|
59
|
+
reward, a Terminal-Bench grader, an ARE validator. Authoritative for that benchmark, and
|
|
60
|
+
named apart from the harness because a reader must not assume the SWE-bench calibration."""
|
|
56
61
|
|
|
57
62
|
HUMAN = "human"
|
|
58
63
|
|
|
59
64
|
|
|
60
|
-
CITABLE_LABEL_SOURCES = frozenset(
|
|
65
|
+
CITABLE_LABEL_SOURCES = frozenset(
|
|
66
|
+
{LabelSource.OFFICIAL_HARNESS, LabelSource.BENCHMARK_GRADER, LabelSource.HUMAN}
|
|
67
|
+
)
|
|
61
68
|
"""Sources whose labels may be reported as results. Everything else is a diagnostic."""
|
|
62
69
|
|
|
63
70
|
|
|
@@ -81,6 +88,7 @@ _AUTHORITY: dict[LabelSource, LabelAuthority] = {
|
|
|
81
88
|
LabelSource.SELF_REPORTED: LabelAuthority.DIAGNOSTIC,
|
|
82
89
|
LabelSource.CONVENIENCE_VERIFIER: LabelAuthority.DIAGNOSTIC,
|
|
83
90
|
LabelSource.OFFICIAL_HARNESS: LabelAuthority.AUTHORITATIVE,
|
|
91
|
+
LabelSource.BENCHMARK_GRADER: LabelAuthority.AUTHORITATIVE,
|
|
84
92
|
LabelSource.HUMAN: LabelAuthority.AUTHORITATIVE,
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -89,6 +97,7 @@ _MLFLOW_SOURCE_TYPE: dict[LabelSource, str] = {
|
|
|
89
97
|
LabelSource.SELF_REPORTED: "LLM_JUDGE",
|
|
90
98
|
LabelSource.CONVENIENCE_VERIFIER: "CODE",
|
|
91
99
|
LabelSource.OFFICIAL_HARNESS: "CODE",
|
|
100
|
+
LabelSource.BENCHMARK_GRADER: "CODE",
|
|
92
101
|
LabelSource.HUMAN: "HUMAN",
|
|
93
102
|
}
|
|
94
103
|
|
|
@@ -11,6 +11,8 @@ schema file per format under ``docs/schemas``.
|
|
|
11
11
|
from agentic_base.provenance.emit import (
|
|
12
12
|
OUTCOME_FACET_SCHEMA,
|
|
13
13
|
PROCESS_RUN_CRATE_PROFILE,
|
|
14
|
+
build_process_run_crate,
|
|
15
|
+
openlineage_run_id,
|
|
14
16
|
to_openlineage,
|
|
15
17
|
to_process_run_crate,
|
|
16
18
|
to_prov,
|
|
@@ -19,6 +21,8 @@ from agentic_base.provenance.emit import (
|
|
|
19
21
|
__all__ = [
|
|
20
22
|
"OUTCOME_FACET_SCHEMA",
|
|
21
23
|
"PROCESS_RUN_CRATE_PROFILE",
|
|
24
|
+
"build_process_run_crate",
|
|
25
|
+
"openlineage_run_id",
|
|
22
26
|
"to_openlineage",
|
|
23
27
|
"to_process_run_crate",
|
|
24
28
|
"to_prov",
|
|
@@ -4,6 +4,7 @@ without the ``provenance`` extra and a caller without it gets one sentence namin
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
+
import uuid
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import TYPE_CHECKING, Any
|
|
@@ -13,12 +14,28 @@ from agentic_base.domain.outcomes import RunRecordCreate, authority_of
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from openlineage.client.event_v2 import RunEvent
|
|
15
16
|
from prov.model import ProvDocument
|
|
17
|
+
from rocrate.rocrate import ROCrate
|
|
16
18
|
|
|
17
19
|
NAMESPACE = "https://github.com/saradamian/agentic-base/ns#"
|
|
18
20
|
PRODUCER = "https://github.com/saradamian/agentic-base"
|
|
19
21
|
OUTCOME_FACET_SCHEMA = "https://github.com/saradamian/agentic-base/blob/main/docs/schemas/OutcomeRunFacet.json"
|
|
20
22
|
PROCESS_RUN_CRATE_PROFILE = "https://w3id.org/ro/wfrun/process/0.5"
|
|
21
23
|
EXTRA = "provenance"
|
|
24
|
+
RUN_ID_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, PRODUCER)
|
|
25
|
+
"""Namespace for deriving an OpenLineage run UUID from a run id that is not one."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def openlineage_run_id(run_id: str) -> str:
|
|
29
|
+
"""The UUID OpenLineage requires for a run, derived when *run_id* is not one.
|
|
30
|
+
|
|
31
|
+
PROV and RO-Crate take any string; OpenLineage's ``Run.runId`` must be a UUID and the
|
|
32
|
+
library fails on anything else. A consumer keying runs by an integer or a slug gets one
|
|
33
|
+
stable rule here rather than three private ones, and the original id travels in the facet.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
return str(uuid.UUID(run_id))
|
|
37
|
+
except ValueError:
|
|
38
|
+
return str(uuid.uuid5(RUN_ID_NAMESPACE, run_id))
|
|
22
39
|
|
|
23
40
|
|
|
24
41
|
def _need(module: str) -> None:
|
|
@@ -143,6 +160,7 @@ def to_openlineage(run: RunRecordCreate, run_id: str, created_at: datetime) -> R
|
|
|
143
160
|
completionTokens: int = 0
|
|
144
161
|
elapsedMs: float = 0.0
|
|
145
162
|
joules: float = 0.0
|
|
163
|
+
sourceRunId: str = ""
|
|
146
164
|
|
|
147
165
|
@staticmethod
|
|
148
166
|
def _get_schema() -> str:
|
|
@@ -164,6 +182,7 @@ def to_openlineage(run: RunRecordCreate, run_id: str, created_at: datetime) -> R
|
|
|
164
182
|
completionTokens=run.completion_tokens,
|
|
165
183
|
elapsedMs=run.elapsed_ms,
|
|
166
184
|
joules=run.joules,
|
|
185
|
+
sourceRunId=run_id,
|
|
167
186
|
),
|
|
168
187
|
}
|
|
169
188
|
versions = run.component_versions
|
|
@@ -182,21 +201,24 @@ def to_openlineage(run: RunRecordCreate, run_id: str, created_at: datetime) -> R
|
|
|
182
201
|
eventTime=_iso(created_at),
|
|
183
202
|
producer=PRODUCER,
|
|
184
203
|
eventType=state,
|
|
185
|
-
run=Run(runId=run_id, facets=facets),
|
|
204
|
+
run=Run(runId=openlineage_run_id(run_id), facets=facets),
|
|
186
205
|
job=Job(namespace=run.tenant, name=run.arm or "(unset)"),
|
|
187
206
|
inputs=inputs,
|
|
188
207
|
outputs=[],
|
|
189
208
|
)
|
|
190
209
|
|
|
191
210
|
|
|
192
|
-
def
|
|
193
|
-
run: RunRecordCreate, run_id: str, created_at: datetime
|
|
194
|
-
) ->
|
|
195
|
-
"""
|
|
211
|
+
def build_process_run_crate(
|
|
212
|
+
run: RunRecordCreate, run_id: str, created_at: datetime
|
|
213
|
+
) -> ROCrate:
|
|
214
|
+
"""The Process Run Crate as a library object, not yet written anywhere.
|
|
196
215
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
Returned unwritten so a consumer can add its own entities, the files a run produced, the
|
|
217
|
+
tool activities inside it, a Slurm job, before serialising, the way :func:`to_prov` and
|
|
218
|
+
:func:`to_openlineage` already hand back a document to extend. The run is a
|
|
219
|
+
``CreateAction`` whose instrument is the software that ran it and whose result is the
|
|
220
|
+
outcome with its provenance as ``PropertyValue`` entities, which is how the profile says to
|
|
221
|
+
attach facts the vocabulary does not name.
|
|
200
222
|
"""
|
|
201
223
|
try:
|
|
202
224
|
from rocrate.model import ContextEntity
|
|
@@ -268,5 +290,12 @@ def to_process_run_crate(
|
|
|
268
290
|
action["object"] = objects
|
|
269
291
|
if results:
|
|
270
292
|
action["result"] = results
|
|
271
|
-
crate
|
|
293
|
+
return crate
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def to_process_run_crate(
|
|
297
|
+
run: RunRecordCreate, run_id: str, created_at: datetime, out_dir: Path
|
|
298
|
+
) -> Path:
|
|
299
|
+
""":func:`build_process_run_crate`, written to *out_dir*. Returns the metadata file's path."""
|
|
300
|
+
build_process_run_crate(run, run_id, created_at).write(out_dir)
|
|
272
301
|
return Path(out_dir) / "ro-crate-metadata.json"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: surf-agentic-base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Agentic base layer: run provenance, comparison validity, and shared safety primitives.
|
|
5
5
|
Author: SURF
|
|
6
6
|
License-Expression: EUPL-1.2
|
|
@@ -59,8 +59,10 @@ Dynamic: license-file
|
|
|
59
59
|
[](pyproject.toml)
|
|
60
60
|
[](CITATION.cff)
|
|
61
61
|
|
|
62
|
-
The agentic base layer for SURF: **
|
|
63
|
-
primitives an agent needs
|
|
62
|
+
The agentic base layer for SURF: **the record and referee for agent runs, the security
|
|
63
|
+
primitives an agent needs on a shared platform, and the engineering standard that keeps both
|
|
64
|
+
honest**. It is the contracts layer under a set of capability blocks, one per SURF system, that
|
|
65
|
+
live in their own packages; see `docs/architecture/blocks.md`.
|
|
64
66
|
|
|
65
67
|
It is built on the SURF Developer Platform golden path and adopts the common stack wherever the
|
|
66
68
|
common stack has an answer. It contains only the parts we could not find anywhere else.
|
|
@@ -141,3 +141,19 @@ def test_tenant_and_code_revision_have_no_defaults() -> None:
|
|
|
141
141
|
RunRecordCreate(code_revision="abc123")
|
|
142
142
|
with pytest.raises(ValueError):
|
|
143
143
|
RunRecordCreate(tenant="hpml")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_a_benchmarks_own_grader_is_authoritative_and_citable() -> None:
|
|
147
|
+
"""tau, Terminal-Bench and ARE ship a grader and no separate harness; their verdict is the
|
|
148
|
+
benchmark's own, and a consumer must not have to file it as a convenience check."""
|
|
149
|
+
from agentic_base.domain.outcomes import (
|
|
150
|
+
CITABLE_LABEL_SOURCES,
|
|
151
|
+
LabelAuthority,
|
|
152
|
+
LabelSource,
|
|
153
|
+
authority_of,
|
|
154
|
+
mlflow_source_type,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
assert LabelSource.BENCHMARK_GRADER in CITABLE_LABEL_SOURCES
|
|
158
|
+
assert authority_of(LabelSource.BENCHMARK_GRADER) is LabelAuthority.AUTHORITATIVE
|
|
159
|
+
assert mlflow_source_type(LabelSource.BENCHMARK_GRADER) == "CODE"
|
|
@@ -204,3 +204,51 @@ def test_the_module_imports_without_the_extra_and_names_it_when_called(
|
|
|
204
204
|
|
|
205
205
|
with pytest.raises(ImportError, match=r"surf-agentic-base\[provenance\]"):
|
|
206
206
|
module.to_prov(_run(), "abc", CREATED)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_a_run_id_that_is_not_a_uuid_becomes_a_stable_uuid_and_travels_in_the_facet() -> (
|
|
210
|
+
None
|
|
211
|
+
):
|
|
212
|
+
from openlineage.client.serde import Serde
|
|
213
|
+
|
|
214
|
+
from agentic_base.provenance import openlineage_run_id
|
|
215
|
+
|
|
216
|
+
first = json.loads(Serde.to_json(to_openlineage(_run(), "journal-42", CREATED)))
|
|
217
|
+
second = json.loads(Serde.to_json(to_openlineage(_run(), "journal-42", CREATED)))
|
|
218
|
+
|
|
219
|
+
assert (
|
|
220
|
+
first["run"]["runId"]
|
|
221
|
+
== second["run"]["runId"]
|
|
222
|
+
== openlineage_run_id("journal-42")
|
|
223
|
+
)
|
|
224
|
+
assert first["run"]["runId"] != "journal-42"
|
|
225
|
+
assert first["run"]["facets"]["agenticBaseOutcome"]["sourceRunId"] == "journal-42"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_a_run_id_that_is_a_uuid_is_kept_as_is() -> None:
|
|
229
|
+
from agentic_base.provenance import openlineage_run_id
|
|
230
|
+
|
|
231
|
+
assert (
|
|
232
|
+
openlineage_run_id("C5A4B1E0-0000-4000-8000-000000000001")
|
|
233
|
+
== "c5a4b1e0-0000-4000-8000-000000000001"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_the_crate_can_be_extended_before_it_is_written(tmp_path) -> None:
|
|
238
|
+
"""A consumer folds its own entities, the files a run produced, onto the base's crate."""
|
|
239
|
+
from rocrate.rocrate import ROCrate
|
|
240
|
+
|
|
241
|
+
from agentic_base.provenance import build_process_run_crate
|
|
242
|
+
|
|
243
|
+
crate = build_process_run_crate(_run(), "abc", CREATED)
|
|
244
|
+
(tmp_path / "patch.diff").write_text("--- a\n+++ b\n")
|
|
245
|
+
produced = crate.add_file(tmp_path / "patch.diff", properties={"name": "the patch"})
|
|
246
|
+
action = crate.dereference("#abc")
|
|
247
|
+
action["result"] = [*action["result"], produced]
|
|
248
|
+
out = tmp_path / "crate"
|
|
249
|
+
crate.write(out)
|
|
250
|
+
|
|
251
|
+
reloaded = ROCrate(out)
|
|
252
|
+
results = [r["@id"] for r in reloaded.dereference("#abc")["result"]]
|
|
253
|
+
assert "patch.diff" in results
|
|
254
|
+
assert (out / "patch.diff").exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{surf_agentic_base-0.3.2 → surf_agentic_base-0.3.4}/.github/ISSUE_TEMPLATE/feature_request.yml
RENAMED
|
File without changes
|
|
File without changes
|