From 418695fed494857414838ba27ee13b238f95301f Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Sun, 11 Oct 2026 10:19:04 +0200 Subject: [PATCH 1/2] feat: versioned system agent template (`system-agent`, OCEAN P05) Ship a neutral system agent template as package data and expose it with `githubbot-core --json system-agent` (schema githubbot-core.system-agent/v1): version (= package version), sha256 of the canonical template, capability, effective permissions and a blueprint using the field names of the ellmos agent-studio blueprint payload. The template exists only where the package is installed, which is the capability binding the consumer needs. Policy refs and the model fallback chain are deliberately not part of the template yet; they depend on the consumer contract (BACH/ellmos-system-gui). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LYHP4Js5ggpzQ78JXWRe5E --- CHANGELOG.md | 7 ++++ README.md | 12 ++++++- README_de.md | 12 ++++++- llms.txt | 2 ++ pyproject.toml | 2 +- src/githubbot_core/cli.py | 15 +++++++++ src/githubbot_core/system_agent.json | 20 +++++++++++ tests/test_consumer_install.py | 6 +++- tests/test_system_agent.py | 50 ++++++++++++++++++++++++++++ 9 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 src/githubbot_core/system_agent.json create mode 100644 tests/test_system_agent.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a86b6ab..5e1b76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), [Semantic Vers ## [Unreleased] +### Hinzugefügt / Added + +- Befehl `system-agent`: versionierte, gehashte Systemagent-Vorlage (`githubbot-core.system-agent/v1`) + mit Agent-Studio-Blueprint-Feldern, nur bei installiertem Paket vorhanden / + `system-agent` command: versioned, hashed system agent template with agent-studio blueprint fields, + present only where the package is installed. + ## [0.1.0] - 2026-10-11 ### Hinzugefügt / Added diff --git a/README.md b/README.md index 599c8f3..082c6a0 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,9 @@ githubbot-core duplicates --input issues.json --threshold 0.8 # Pull-request summary (+ checks from installed plugins) githubbot-core review-pr --repo owner/repo --pr 12 -# Plugins +# Plugins and system agent template githubbot-core plugins +githubbot-core --json system-agent githubbot-core run [--plugin-arg ...] [--apply] ``` @@ -85,6 +86,15 @@ The Python API is the package itself: `githubbot_core.triage.IssueTriage`, `githubbot_core.actions.preview/apply`, `githubbot_core.health.RepoHealthCollector`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +### System agent template + +`githubbot-core --json system-agent` prints the neutral agent template shipped with the package +(schema `githubbot-core.system-agent/v1`): `version` (package version), `sha256` of the canonical +template, `capability`, the effective `permissions` and a `blueprint` with the field names of the +ellmos agent-studio blueprint payload (persona, role, contractus, governance, skills). The template +exists only where the package is installed. Consumers bind an agent to `version` + `sha256` and must +not overwrite an existing agent configuration when a newer template appears. + ## Configuration and permissions The packaged defaults (`githubbot_core/default_config.json`) are extended with `--config file.json` diff --git a/README_de.md b/README_de.md index ab7f822..677515b 100644 --- a/README_de.md +++ b/README_de.md @@ -62,8 +62,9 @@ githubbot-core duplicates --input issues.json --threshold 0.8 # Pull-Request-Zusammenfassung (+ Prüfungen installierter Plugins) githubbot-core review-pr --repo owner/repo --pr 12 -# Plugins +# Plugins und Systemagent-Vorlage githubbot-core plugins +githubbot-core --json system-agent githubbot-core run [--plugin-arg ...] [--apply] ``` @@ -86,6 +87,15 @@ Die Python-API ist das Paket selbst: `githubbot_core.triage.IssueTriage`, `githubbot_core.actions.preview/apply`, `githubbot_core.health.RepoHealthCollector`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +### Systemagent-Vorlage + +`githubbot-core --json system-agent` gibt die neutrale, mit dem Paket ausgelieferte Agentenvorlage aus +(Schema `githubbot-core.system-agent/v1`): `version` (Paketversion), `sha256` der kanonischen Vorlage, +`capability`, die wirksamen `permissions` und ein `blueprint` mit den Feldnamen des +ellmos-Agent-Studio-Blueprint-Payloads (Persona, Rolle, Contractus, Governance, Skills). Die Vorlage +existiert nur dort, wo das Paket installiert ist. Konsumenten binden einen Agenten an `version` + +`sha256` und überschreiben bei einer neueren Vorlage keine bestehende Agentenkonfiguration. + ## Konfiguration und Rechte Die mitgelieferten Standardwerte (`githubbot_core/default_config.json`) werden durch diff --git a/llms.txt b/llms.txt index 97b8bcc..fc0b8b6 100644 --- a/llms.txt +++ b/llms.txt @@ -13,6 +13,8 @@ Languages: en, de - Python API: `githubbot_core.triage`, `githubbot_core.actions`, `githubbot_core.health`, `githubbot_core.traffic`, `githubbot_core.stars`, `githubbot_core.pr_review`, `githubbot_core.plugins`, `githubbot_core.review_gate`, `githubbot_core.credentials`. +- System agent template: `githubbot-core --json system-agent` (schema + `githubbot-core.system-agent/v1`, version + sha256, agent-studio blueprint fields). - Plugin SPI v1: entry-point group `githubbot_core.plugins`, factory returning an object with `name`, `spi_version`, `describe()`, optional `plan(ctx)` and `pr_policy_checks(pr, files, diff)`. diff --git a/pyproject.toml b/pyproject.toml index 9ddfb17..10474d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ version = { attr = "githubbot_core.__version__" } where = ["src"] [tool.setuptools.package-data] -githubbot_core = ["default_config.json", "templates/*.yml"] +githubbot_core = ["default_config.json", "system_agent.json", "templates/*.yml"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/githubbot_core/cli.py b/src/githubbot_core/cli.py index a2e1438..9413de2 100644 --- a/src/githubbot_core/cli.py +++ b/src/githubbot_core/cli.py @@ -15,6 +15,7 @@ from __future__ import annotations import argparse +import hashlib import json import shutil import sys @@ -114,6 +115,18 @@ def cmd_plugins(args, config, transport) -> int: return EXIT_OK +def cmd_system_agent(args, config, transport) -> int: + template = json.loads(resources.files("githubbot_core").joinpath("system_agent.json").read_text(encoding="utf-8")) + canonical = json.dumps(template, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + data = {"schema": template["_meta"]["schema"], "id": template["id"], "version": __version__, + "sha256": hashlib.sha256(canonical.encode("utf-8")).hexdigest(), + "capability": {"package": "githubbot-core", "version": __version__, "spi_version": plugins.SPI_VERSION}, + "blueprint": template["blueprint"], "permissions": config.get("permissions", {})} + text = f"system agent {data['id']} {data['version']} sha256 {data['sha256']}" + _emit(args, "system-agent", True, "read", data, [], text) + return EXIT_OK + + def cmd_classify(args, config, transport) -> int: classifier = IssueClassifier(config) results = [classifier.classify(issue) for issue in _read_issues(Path(args.input))] @@ -240,6 +253,8 @@ def build_parser() -> argparse.ArgumentParser: sub.add_parser("info", help="version, contract and plugin overview").set_defaults(func=cmd_info) sub.add_parser("doctor", help="check gh CLI, credentials and permissions").set_defaults(func=cmd_doctor) sub.add_parser("plugins", help="list installed plugins").set_defaults(func=cmd_plugins) + sub.add_parser("system-agent", help="versioned system agent template of this package").set_defaults( + func=cmd_system_agent) p = sub.add_parser("classify", help="classify issues from a JSON file (offline)") p.add_argument("--input", required=True) diff --git a/src/githubbot_core/system_agent.json b/src/githubbot_core/system_agent.json new file mode 100644 index 0000000..2a45f9a --- /dev/null +++ b/src/githubbot_core/system_agent.json @@ -0,0 +1,20 @@ +{ + "_meta": { + "schema": "githubbot-core.system-agent/v1", + "description": "Neutral system agent template shipped with this package. It exists only where githubbot-core is installed; read it with `githubbot-core --json system-agent`. `blueprint` uses the field names of the ellmos agent-studio blueprint payload. Consumers may change persona, skills, governance and model; a newer template version must not silently overwrite an existing agent configuration." + }, + "id": "githubbot", + "blueprint": { + "name": "githubbot", + "title": "GitHub Maintenance Agent", + "kind": "agent", + "description": "Issue triage, duplicate detection, pull-request summaries and read-only health, traffic and star reports. Every write is a previewed plan that is applied only with explicit approval.", + "persona_role": "github-maintainer", + "persona_prompt": "You are the GitHub maintenance agent of githubbot-core. You triage issues, find duplicates, summarise pull requests and produce read-only health, traffic and star reports for the repositories assigned to you. Every write is first shown as a preview plan; you apply a plan only when a current, explicit approval names its plan_sha256, and you never widen the configured permissions. A review verdict is valid only for the exact head SHA it was made on. If a source cannot be read, report the error instead of treating it as empty.", + "skills": [], + "animus_type": "cli", + "modus": "casualis", + "contractus": {"turns": 15}, + "governance": {"profile": "read_only_research", "tool_whitelist": ["read_files", "search_content"]} + } +} diff --git a/tests/test_consumer_install.py b/tests/test_consumer_install.py index 63babed..e257214 100644 --- a/tests/test_consumer_install.py +++ b/tests/test_consumer_install.py @@ -44,7 +44,11 @@ def test_installed_package_cli_without_private_extension(tmp_path: Path) -> None envelope = json.loads(info.stdout) assert envelope["ok"] is True and envelope["data"]["plugins"] == [] - origin = _run([str(python), "-c", "import githubbot_core, sys; print(githubbot_core.__file__)"], cwd=work) + agent = _run([str(cli), "--json", "system-agent"], cwd=work) # template ships as package data + assert agent.returncode == 0, agent.stderr + assert json.loads(agent.stdout)["data"]["blueprint"]["name"] == "githubbot" + + origin =_run([str(python), "-c", "import githubbot_core, sys; print(githubbot_core.__file__)"], cwd=work) assert "site-packages" in origin.stdout, origin.stdout # not the source tree missing = _run([str(cli), "--json", "run", "private-extension"], cwd=work) diff --git a/tests/test_system_agent.py b/tests/test_system_agent.py new file mode 100644 index 0000000..b26385a --- /dev/null +++ b/tests/test_system_agent.py @@ -0,0 +1,50 @@ +"""System agent template: versioned, hashed, shaped like the agent-studio blueprint payload.""" +from __future__ import annotations + +import hashlib +import json +import re +from importlib import resources + +from githubbot_core import __version__, cli + +# Rules of the consuming agent-studio save/materialize path (ellmos BACH blueprint service). +NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$") +KINDS = {"agent", "role", "skill", "workflow", "service", "contractus"} +ANIMUS = {"api", "cli", "subscription", "local"} +EXECUTABLE_MODUS = {"casualis", "usus"} + + +def _system_agent(capsys, *extra: str) -> dict: + assert cli.main([*extra, "--json", "system-agent"]) == 0 + envelope = json.loads(capsys.readouterr().out) + assert envelope["ok"] is True and envelope["mode"] == "read" + return envelope["data"] + + +def test_template_identity_is_version_plus_canonical_hash(capsys): + data = _system_agent(capsys) + template = json.loads(resources.files("githubbot_core").joinpath("system_agent.json").read_text(encoding="utf-8")) + canonical = json.dumps(template, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + assert data["schema"] == "githubbot-core.system-agent/v1" + assert data["id"] == "githubbot" and data["version"] == __version__ + assert data["sha256"] == hashlib.sha256(canonical.encode("utf-8")).hexdigest() + assert data["capability"] == {"package": "githubbot-core", "version": __version__, "spi_version": 1} + + +def test_blueprint_is_accepted_by_the_agent_studio_payload_rules(capsys): + bp = _system_agent(capsys)["blueprint"] + assert NAME.fullmatch(bp["name"]) and bp["name"] == bp["name"].lower() + assert bp["kind"] in KINDS and bp["kind"] == "agent" + assert bp["persona_prompt"].strip() and len(bp["title"]) <= 120 + assert bp["animus_type"] in ANIMUS and bp["modus"] in EXECUTABLE_MODUS + assert isinstance(bp["skills"], list) and all(NAME.fullmatch(s) for s in bp["skills"]) + assert isinstance(bp["contractus"], dict) and isinstance(bp["governance"], dict) + assert "is_template" not in bp # system templates are registered by the consumer, never self-declared + + +def test_permissions_are_the_effective_narrowed_configuration(capsys, tmp_path): + layer = tmp_path / "narrow.json" + layer.write_text(json.dumps({"permissions": {"enabled": []}}), encoding="utf-8") + assert _system_agent(capsys)["permissions"]["enabled"] == ["issue.add_labels", "issue.comment"] + assert _system_agent(capsys, "--config", str(layer))["permissions"]["enabled"] == [] From eb12c2a1bd31b4e4d5021d71d50e929a6a16631a Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Sun, 11 Oct 2026 10:29:45 +0200 Subject: [PATCH 2/2] chore: release 0.2.0 (version bump, changelog, neutral wording) Review fixes by independent reviewer: version 0.1.0 -> 0.2.0 so tag and template version match; remove product name from template/docs; fix spacing typo in test. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01LYHP4Js5ggpzQ78JXWRe5E --- CHANGELOG.md | 2 ++ README.md | 4 ++-- README_de.md | 4 ++-- src/githubbot_core/__init__.py | 2 +- src/githubbot_core/system_agent.json | 2 +- tests/test_consumer_install.py | 2 +- tests/test_system_agent.py | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1b76f..fa0fa29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Format: [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), [Semantic Vers ## [Unreleased] +## [0.2.0] - 2026-10-11 + ### Hinzugefügt / Added - Befehl `system-agent`: versionierte, gehashte Systemagent-Vorlage (`githubbot-core.system-agent/v1`) diff --git a/README.md b/README.md index 082c6a0..04ce54e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ pull-request summaries, read-only health/traffic/star reports, a preview/apply c write, a plugin SPI and signed exact-SHA review receipts. Pure Python standard library, no runtime dependencies. -> Status: alpha (0.1.0). The CLI, the JSON result envelope and SPI version 1 are the contract; +> Status: alpha (0.2.0). The CLI, the JSON result envelope and SPI version 1 are the contract; > everything else may still change. ## Features @@ -91,7 +91,7 @@ The Python API is the package itself: `githubbot_core.triage.IssueTriage`, `githubbot-core --json system-agent` prints the neutral agent template shipped with the package (schema `githubbot-core.system-agent/v1`): `version` (package version), `sha256` of the canonical template, `capability`, the effective `permissions` and a `blueprint` with the field names of the -ellmos agent-studio blueprint payload (persona, role, contractus, governance, skills). The template +agent-studio blueprint payload (persona, role, contractus, governance, skills). The template exists only where the package is installed. Consumers bind an agent to `version` + `sha256` and must not overwrite an existing agent configuration when a newer template appears. diff --git a/README_de.md b/README_de.md index 677515b..2edb27f 100644 --- a/README_de.md +++ b/README_de.md @@ -7,7 +7,7 @@ Duplikaterkennung, Pull-Request-Zusammenfassungen, lesende Berichte zu Gesundhei Sternen, ein Vorschau-/Ausführungsvertrag für jede Schreibaktion, eine Plugin-Schnittstelle (SPI) und signierte Exact-SHA-Review-Belege. Reine Python-Standardbibliothek, keine Laufzeitabhängigkeiten. -> Status: Alpha (0.1.0). Verbindlich sind CLI, JSON-Ergebnishülle und SPI-Version 1; alles andere +> Status: Alpha (0.2.0). Verbindlich sind CLI, JSON-Ergebnishülle und SPI-Version 1; alles andere > kann sich noch ändern. ## Funktionen @@ -92,7 +92,7 @@ Die Python-API ist das Paket selbst: `githubbot_core.triage.IssueTriage`, `githubbot-core --json system-agent` gibt die neutrale, mit dem Paket ausgelieferte Agentenvorlage aus (Schema `githubbot-core.system-agent/v1`): `version` (Paketversion), `sha256` der kanonischen Vorlage, `capability`, die wirksamen `permissions` und ein `blueprint` mit den Feldnamen des -ellmos-Agent-Studio-Blueprint-Payloads (Persona, Rolle, Contractus, Governance, Skills). Die Vorlage +Agent-Studio-Blueprint-Payloads (Persona, Rolle, Contractus, Governance, Skills). Die Vorlage existiert nur dort, wo das Paket installiert ist. Konsumenten binden einen Agenten an `version` + `sha256` und überschreiben bei einer neueren Vorlage keine bestehende Agentenkonfiguration. diff --git a/src/githubbot_core/__init__.py b/src/githubbot_core/__init__.py index ddc8116..923bd0c 100644 --- a/src/githubbot_core/__init__.py +++ b/src/githubbot_core/__init__.py @@ -5,4 +5,4 @@ a plugin SPI and signed exact-SHA review receipts. Standard library only. """ -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/src/githubbot_core/system_agent.json b/src/githubbot_core/system_agent.json index 2a45f9a..a297fa7 100644 --- a/src/githubbot_core/system_agent.json +++ b/src/githubbot_core/system_agent.json @@ -1,7 +1,7 @@ { "_meta": { "schema": "githubbot-core.system-agent/v1", - "description": "Neutral system agent template shipped with this package. It exists only where githubbot-core is installed; read it with `githubbot-core --json system-agent`. `blueprint` uses the field names of the ellmos agent-studio blueprint payload. Consumers may change persona, skills, governance and model; a newer template version must not silently overwrite an existing agent configuration." + "description": "Neutral system agent template shipped with this package. It exists only where githubbot-core is installed; read it with `githubbot-core --json system-agent`. `blueprint` uses the field names of the agent-studio blueprint payload. Consumers may change persona, skills, governance and model; a newer template version must not silently overwrite an existing agent configuration." }, "id": "githubbot", "blueprint": { diff --git a/tests/test_consumer_install.py b/tests/test_consumer_install.py index e257214..46b6506 100644 --- a/tests/test_consumer_install.py +++ b/tests/test_consumer_install.py @@ -48,7 +48,7 @@ def test_installed_package_cli_without_private_extension(tmp_path: Path) -> None assert agent.returncode == 0, agent.stderr assert json.loads(agent.stdout)["data"]["blueprint"]["name"] == "githubbot" - origin =_run([str(python), "-c", "import githubbot_core, sys; print(githubbot_core.__file__)"], cwd=work) + origin = _run([str(python), "-c", "import githubbot_core, sys; print(githubbot_core.__file__)"], cwd=work) assert "site-packages" in origin.stdout, origin.stdout # not the source tree missing = _run([str(cli), "--json", "run", "private-extension"], cwd=work) diff --git a/tests/test_system_agent.py b/tests/test_system_agent.py index b26385a..835006f 100644 --- a/tests/test_system_agent.py +++ b/tests/test_system_agent.py @@ -8,7 +8,7 @@ from githubbot_core import __version__, cli -# Rules of the consuming agent-studio save/materialize path (ellmos BACH blueprint service). +# Rules of the consuming agent-studio save/materialize path (blueprint service). NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$") KINDS = {"agent", "role", "skill", "workflow", "service", "contractus"} ANIMUS = {"api", "cli", "subscription", "local"}