From b9b0abea2b987dfc37f9ecafbd50e3f343e9ebde Mon Sep 17 00:00:00 2001 From: a2a-platform Date: Sat, 18 Jul 2026 11:56:09 +0000 Subject: [PATCH] deploy --- .a2a/agent.dsl.json | 189 ++++++++++++++++++++++++++++++++++++++++++++ a2a.yaml | 27 +++++++ agent.py | 29 +++++++ frontend/app.js | 17 ++++ frontend/index.html | 36 +++++++++ frontend/styles.css | 24 ++++++ requirements.txt | 3 + tests/test_agent.py | 21 +++++ 8 files changed, 346 insertions(+) create mode 100644 .a2a/agent.dsl.json create mode 100644 a2a.yaml create mode 100644 agent.py create mode 100644 frontend/app.js create mode 100644 frontend/index.html create mode 100644 frontend/styles.css create mode 100644 requirements.txt create mode 100644 tests/test_agent.py diff --git a/.a2a/agent.dsl.json b/.a2a/agent.dsl.json new file mode 100644 index 0000000..e348bc2 --- /dev/null +++ b/.a2a/agent.dsl.json @@ -0,0 +1,189 @@ +{ + "schema_version": "2026-06-04", + "language": "python", + "name": "self-healing-demo-v1", + "description": "A live demonstration of bounded, auditable source self-repair.", + "version": "0.1.0", + "entrypoint": { + "module": "agent", + "class_name": "SelfHealingDemo", + "function": null, + "command": [] + }, + "skills": [ + { + "name": "status", + "description": "Return the current self-healing demo state.", + "handler": "status", + "tags": [], + "scopes": [], + "stream": false, + "policy": { + "timeout_seconds": null, + "idempotent": false, + "max_retries": 0, + "cost_class": null, + "allow_scope_expansion": false, + "grant_mode": null, + "grant_allow_patterns": [], + "grant_deny_patterns": [], + "grant_outputs_prefix": null, + "grant_write_prefixes": [], + "grant_ttl_seconds": null, + "grant_run_timeout_seconds": null, + "grant_approval_timeout_seconds": null, + "grant_scope_approval_timeout_seconds": null + }, + "input_schema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + }, + "output_schema": { + "additionalProperties": true, + "type": "object" + } + }, + { + "name": "run", + "description": "Run the demo workflow, including its controlled production failure.", + "handler": "run", + "tags": [], + "scopes": [], + "stream": false, + "policy": { + "timeout_seconds": null, + "idempotent": false, + "max_retries": 0, + "cost_class": null, + "allow_scope_expansion": false, + "grant_mode": null, + "grant_allow_patterns": [], + "grant_deny_patterns": [], + "grant_outputs_prefix": null, + "grant_write_prefixes": [], + "grant_ttl_seconds": null, + "grant_run_timeout_seconds": null, + "grant_approval_timeout_seconds": null, + "grant_scope_approval_timeout_seconds": null + }, + "input_schema": { + "type": "object", + "properties": { + "scenario": { + "type": "string" + } + }, + "required": [], + "additionalProperties": false + }, + "output_schema": { + "additionalProperties": true, + "type": "object" + } + } + ], + "capabilities": { + "self_healing": { + "enabled": true, + "consecutive_failures": 1, + "window_seconds": 300, + "cooldown_seconds": 300, + "max_repairs_per_day": 2, + "max_turns": 30, + "deployment_timeout_seconds": 1800, + "require_tests": true + } + }, + "input_modes": [ + "application/json" + ], + "output_modes": [ + "application/json" + ], + "required_secrets": [], + "required_env": [], + "consumer_setup": { + "fields": [] + }, + "runtime": { + "lifecycle": "ephemeral", + "availability": "always_on", + "state": "none", + "sandbox": "microsandbox", + "resources": { + "cpu": "100m", + "memory": "256Mi", + "gpu": 0, + "max_runtime_seconds": 120 + }, + "concurrency": 1, + "egress": { + "allow_hosts": [], + "allow_internal_services": [], + "deny_internet_by_default": true + }, + "tools_used": [], + "llm_provisioning": "platform", + "pricing": { + "price_per_call_usd": 0.0, + "caller_pays_llm": false, + "notes": "" + }, + "wants_cp_jwt": false, + "platform_resources": { + "memory": null, + "databases": [], + "mailbox": null + }, + "endpoints": [], + "apt_packages": [] + }, + "template_lineage": null, + "meta_agent_manifest": null, + "state_schema": null, + "workspace_access": { + "enabled": false, + "max_files": 0, + "allowed_modes": [], + "require_reason": true, + "deny_patterns": [], + "require_human_approval": false, + "max_total_size_bytes": 104857600 + }, + "config_schema": { + "description": "Default config model when an agent declares no config.", + "properties": {}, + "title": "_EmptyConfig", + "type": "object" + }, + "auth": { + "model": "a2a_pack.auth.NoAuth", + "strategy": "public", + "principal_schema": { + "additionalProperties": false, + "description": "Public agent: no caller identity required.", + "properties": {}, + "title": "NoAuth", + "type": "object" + }, + "resolver": null, + "required": false + }, + "metadata": { + "source": "python-a2a-pack", + "project_manifest": { + "name": "self-healing-demo-v1", + "version": "0.1.0", + "entrypoint": "agent:SelfHealingDemo", + "frontend": { + "path": "frontend", + "build": "mkdir -p dist && cp index.html app.js styles.css dist/", + "dist": "dist", + "mount": "/", + "auth": "public" + } + } + } +} diff --git a/a2a.yaml b/a2a.yaml new file mode 100644 index 0000000..0741c45 --- /dev/null +++ b/a2a.yaml @@ -0,0 +1,27 @@ +name: self-healing-demo-v1 +description: A live demonstration of bounded, auditable source self-repair. +version: 0.1.0 +entrypoint: agent:SelfHealingDemo +expose: + public: true +self_healing: + enabled: true + consecutive_failures: 1 + window_seconds: 300 + cooldown_seconds: 300 + max_repairs_per_day: 2 + max_turns: 30 + deployment_timeout_seconds: 1800 + require_tests: true +runtime: + availability: always_on + resources: + cpu: 100m + memory: 256Mi + max_runtime_seconds: 120 +frontend: + path: frontend + build: mkdir -p dist && cp index.html app.js styles.css dist/ + dist: dist + mount: / + auth: public diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..72a859a --- /dev/null +++ b/agent.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from a2a_pack import A2AAgent, NoAuth, RunContext, skill + + +class SelfHealingDemo(A2AAgent): + name = "self-healing-demo-v1" + description = "A live demonstration of bounded, auditable source self-repair." + version = "0.1.0" + + @skill(description="Return the current self-healing demo state.") + async def status(self, ctx: RunContext[NoAuth]) -> dict[str, object]: + return { + "ok": True, + "agent": self.name, + "message": "The agent is online. Trigger the controlled failure once to exercise repair.", + } + + @skill(description="Run the demo workflow, including its controlled production failure.") + async def run( + self, + ctx: RunContext[NoAuth], + scenario: str = "normal", + ) -> dict[str, object]: + if scenario == "controlled-failure": + # Intentional smoke fixture: the regression test specifies the safe + # result. The production self-healing worker must repair this source. + raise RuntimeError("SELF_HEAL_SMOKE: controlled invoice total handler crashed") + return {"ok": True, "scenario": scenario, "result": "healthy"} diff --git a/frontend/app.js b/frontend/app.js new file mode 100644 index 0000000..7bb3e7a --- /dev/null +++ b/frontend/app.js @@ -0,0 +1,17 @@ +import { agent } from "/a2a-client.js"; + +const button = document.querySelector("#trigger"); +const result = document.querySelector("#result"); + +button.addEventListener("click", async () => { + button.disabled = true; + result.textContent = "Calling run(scenario=controlled-failure)…"; + try { + const response = await agent.call("run", { scenario: "controlled-failure" }); + result.textContent = JSON.stringify(response, null, 2); + } catch (error) { + result.textContent = `Failure observed. The bounded repair should now be queued.\n\n${error.message}`; + } finally { + button.disabled = false; + } +}); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..57145ac --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,36 @@ + + + + + + + Self-Healing Agent · A2A Cloud + + + +
+
LIVE PLATFORM DEMO
+

Software that repairs
its own runtime failure.

+

+ This agent opts in through a2a.yaml. A real 500 error creates a bounded repair job, + patches managed source, runs tests, and redeploys the exact commit—with the full trail in the app. +

+
+
+
SELF-HEALING POLICYEnabled
+
READY
+
+
+
Trigger1 internal failure
+
Cooldown5 minutes
+
Daily ceiling2 repairs
+
Promotion gateTests + exact SHA live
+
+ +
Waiting for a test run.
+
+

Expected input and 4xx errors never mutate source. Credentials and user inputs stay out of repair evidence.

+
+ + + diff --git a/frontend/styles.css b/frontend/styles.css new file mode 100644 index 0000000..7240ddc --- /dev/null +++ b/frontend/styles.css @@ -0,0 +1,24 @@ +:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, sans-serif; background: #090b10; color: #edf1f7; } +* { box-sizing: border-box; } +body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 78% 15%, #16362d 0, transparent 30%), linear-gradient(145deg, #090b10, #0d1218); } +main { width: min(920px, calc(100% - 40px)); margin: 0 auto; padding: 9vh 0 8vh; } +.eyebrow { color: #7f8b9c; font-size: 11px; letter-spacing: .2em; display: flex; align-items: center; gap: 10px; } +.eyebrow span { width: 8px; height: 8px; border-radius: 99px; background: #5dffb2; box-shadow: 0 0 22px #5dffb2; } +h1 { margin: 28px 0 22px; font-size: clamp(48px, 8vw, 88px); line-height: .98; letter-spacing: -.055em; font-weight: 580; } +h1 em { font-style: normal; color: #76efb5; } +.lede { max-width: 760px; color: #aab4c2; font-size: clamp(17px, 2.2vw, 21px); line-height: 1.58; } +code { color: #76efb5; } +.card { margin-top: 48px; padding: 28px; border: 1px solid #27323e; border-radius: 18px; background: rgba(14, 18, 25, .82); box-shadow: 0 26px 80px #0008; } +.card-head { display: flex; justify-content: space-between; gap: 20px; align-items: flex-start; } +.card-head small { display: block; color: #728094; letter-spacing: .14em; } +.card-head strong { display: block; margin-top: 8px; font-size: 28px; } +.pulse { color: #67f6ad; border: 1px solid #32634d; background: #10281f; border-radius: 99px; padding: 8px 12px; font: 11px ui-monospace, monospace; } +.policy { margin: 26px 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border: 1px solid #202a35; border-radius: 12px; overflow: hidden; } +.policy div { padding: 16px; border: 1px solid #202a35; } +.policy span { display: block; color: #728094; font-size: 11px; text-transform: uppercase; letter-spacing: .1em; } +.policy b { display: block; margin-top: 7px; font-size: 14px; } +button { width: 100%; border: 0; border-radius: 10px; padding: 15px 18px; background: #70efb2; color: #07120d; font: 700 14px ui-monospace, monospace; cursor: pointer; } +button:disabled { opacity: .5; cursor: wait; } +pre { min-height: 74px; margin: 14px 0 0; padding: 16px; border-radius: 10px; background: #080a0e; color: #96a4b7; white-space: pre-wrap; overflow: auto; font-size: 12px; } +.foot { color: #687588; font-size: 12px; margin-top: 20px; } +@media (max-width: 620px) { .policy { grid-template-columns: 1fr; } .card { padding: 20px; } } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bcb02e1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +a2a-pack==0.1.113 +pytest>=8 +pytest-asyncio>=0.23 diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 0000000..b8237f3 --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import pytest + +from a2a_pack import LocalRunContext, NoAuth + +from agent import SelfHealingDemo + + +@pytest.mark.asyncio +async def test_controlled_failure_has_a_safe_result() -> None: + agent = SelfHealingDemo() + ctx = LocalRunContext(auth=NoAuth()) + + result = await agent.run(ctx, scenario="controlled-failure") + + assert result == { + "ok": True, + "scenario": "controlled-failure", + "result": "recovered", + }