deploy
This commit is contained in:
189
.a2a/agent.dsl.json
Normal file
189
.a2a/agent.dsl.json
Normal 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
27
a2a.yaml
Normal 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
29
agent.py
Normal 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
17
frontend/app.js
Normal 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
36
frontend/index.html
Normal 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
24
frontend/styles.css
Normal 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
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
a2a-pack==0.1.113
|
||||
pytest>=8
|
||||
pytest-asyncio>=0.23
|
||||
21
tests/test_agent.py
Normal file
21
tests/test_agent.py
Normal 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",
|
||||
}
|
||||
Reference in New Issue
Block a user