This commit is contained in:
a2a-platform
2026-07-18 11:56:09 +00:00
commit b9b0abea2b
8 changed files with 346 additions and 0 deletions

189
.a2a/agent.dsl.json Normal file
View File

@@ -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"
}
}
}
}

27
a2a.yaml Normal file
View File

@@ -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

29
agent.py Normal file
View File

@@ -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"}

17
frontend/app.js Normal file
View File

@@ -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;
}
});

36
frontend/index.html Normal file
View File

@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="A live A2A Cloud self-healing agent demonstration." />
<title>Self-Healing Agent · A2A Cloud</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<main>
<div class="eyebrow"><span></span> LIVE PLATFORM DEMO</div>
<h1>Software that repairs<br /><em>its own runtime failure.</em></h1>
<p class="lede">
This agent opts in through <code>a2a.yaml</code>. 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.
</p>
<section class="card">
<div class="card-head">
<div><small>SELF-HEALING POLICY</small><strong>Enabled</strong></div>
<div class="pulse">READY</div>
</div>
<div class="policy">
<div><span>Trigger</span><b>1 internal failure</b></div>
<div><span>Cooldown</span><b>5 minutes</b></div>
<div><span>Daily ceiling</span><b>2 repairs</b></div>
<div><span>Promotion gate</span><b>Tests + exact SHA live</b></div>
</div>
<button id="trigger">Trigger controlled failure</button>
<pre id="result">Waiting for a test run.</pre>
</section>
<p class="foot">Expected input and 4xx errors never mutate source. Credentials and user inputs stay out of repair evidence.</p>
</main>
<script type="module" src="./app.js"></script>
</body>
</html>

24
frontend/styles.css Normal file
View File

@@ -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; } }

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
a2a-pack==0.1.113
pytest>=8
pytest-asyncio>=0.23

21
tests/test_agent.py Normal file
View File

@@ -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",
}