a2a-source-edit: write agent.py

This commit is contained in:
a2a-cloud
2026-07-19 19:57:51 +00:00
parent da0d8a9dba
commit 70992bbe85

626
agent.py
View File

@@ -1,189 +1,533 @@
"""production-proof-v116-high-utili-7255-2 agent. """People-team shared inbox triage and policy-grounded draft assistant."""
Starter stack:
- DeepAgents for tool-calling orchestration
- Caller-provided LLM credentials via ctx.llm
- A tiny model-call middleware hook you can replace with tracing,
routing, rate limits, or policy checks
"""
from __future__ import annotations from __future__ import annotations
import hashlib
import json import json
from pathlib import Path import os
from typing import Any import re
from datetime import UTC, datetime
from typing import Any, Literal
from pydantic import BaseModel from pydantic import BaseModel, Field
import a2a_pack as a2a import a2a_pack as a2a
from a2a_pack import ( from a2a_pack import (
A2AAgent, A2AAgent,
AccountAccess,
AgentDatabase,
AgentDatabaseEnv,
AgentDatabaseMigrations,
AgentPlatformResources,
InboundEmailPayload,
LLMProvisioning, LLMProvisioning,
{{ auth_type }}, PlatformUserAuth,
Pricing, Pricing,
Resources,
RunContext, RunContext,
WorkspaceAccess, WorkspaceAccess,
WorkspaceMode, WorkspaceMode,
) )
from a2a_pack.context import LLMCreds
DATABASE_NAME = "production-proof-v116-high-utili-7255-2-data"
MAX_INBOX_CHARS = 20_000
MAX_POLICY_CHARS = 12_000
MAX_MESSAGES = 12
class ProductionProofV116HighUtili72552Config(BaseModel): class ProductionProofV116HighUtili72552Config(BaseModel):
pass default_policy_text: str = Field(
default=(
"Benefits questions: be empathetic, explain that enrollment and eligibility "
"depend on plan documents, and route exceptions to People Ops.\n"
"Leave requests: acknowledge receipt, ask for dates if missing, and mention "
"manager/People approval before commitments.\n"
"Payroll or personal data: mark urgent, avoid exposing private data, and route "
"to the secure HRIS/payroll channel.\n"
"Employee relations or harassment: mark urgent, avoid investigation promises, "
"and escalate to the designated People partner."
),
max_length=MAX_POLICY_CHARS,
)
SYSTEM_PROMPT = """\ class DraftResult(BaseModel):
You are a compact tool-calling agent. to: str
subject: str
Use the text_stats tool when the user asks about text, counts, summaries, body: str
or anything where exact length/word numbers would help. Mention tool results approval_required: bool
briefly instead of dumping raw JSON. approval_reason: str
""" policy_basis: list[str]
RUNTIME_SKILLS_DIR = "production-proof-v116-high-utili-7255-2/.deepagents/skills/"
DEEPAGENTS_RECURSION_LIMIT = 500
class ProductionProofV116HighUtili72552(A2AAgent[ProductionProofV116HighUtili72552Config, {{ auth_type }}]): class QueueItem(BaseModel):
message_id: str
sender: str
subject: str
priority: Literal["urgent", "high", "normal", "low"]
category: str
reason: str
next_action: str
draft: DraftResult
class ArtifactDescriptor(BaseModel):
name: str
media_type: str
uri: str
size_bytes: int
content_text: str
class ReceiptDescriptor(BaseModel):
receipt_id: str
persisted: bool
persisted_at: str | None = None
message: str = ""
class TriageResult(BaseModel):
status: Literal["ok", "validation_error", "setup_required"]
summary: str
queue: list[QueueItem] = Field(default_factory=list)
approval_boundary: str
artifact: ArtifactDescriptor | None = None
receipt: ReceiptDescriptor | None = None
warnings: list[str] = Field(default_factory=list)
class ProductionProofV116HighUtili72552(
A2AAgent[ProductionProofV116HighUtili72552Config, PlatformUserAuth]
):
name = "production-proof-v116-high-utili-7255-2" name = "production-proof-v116-high-utili-7255-2"
description = "One-page email assistant for people teams that triages a shared inbox and drafts policy-grounded replies." description = (
"One-page email assistant for people teams that triages a shared inbox "
"and drafts policy-grounded replies for approval."
)
version = "0.1.0" version = "0.1.0"
config_model = ProductionProofV116HighUtili72552Config config_model = ProductionProofV116HighUtili72552Config
auth_model = {{ auth_type }} auth_model = PlatformUserAuth
# Hosted generated agents read the caller's saved LLM credential through
# ctx.llm. The platform may proxy that credential through LiteLLM, but agent
# code never reads provider keys, LiteLLM master keys, or OPENAI_API_KEY
# directly.
llm_provisioning = LLMProvisioning.PLATFORM llm_provisioning = LLMProvisioning.PLATFORM
account_access = AccountAccess(required=True, platform_skill_calls=3, after_trial="byok")
pricing = Pricing( pricing = Pricing(
price_per_call_usd=0.0, price_per_call_usd=0.0,
caller_pays_llm=True, caller_pays_llm=True,
notes="Starter agent uses the caller's saved LLM credential via ctx.llm.", notes="Includes three platform-funded skill calls per account, then requires BYOK.",
) )
resources = Resources(cpu="500m", memory="512Mi", max_runtime_seconds=300)
workspace_access = WorkspaceAccess.dynamic( workspace_access = WorkspaceAccess.dynamic(
max_files=64, max_files=32,
allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY), allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY),
require_reason=False, require_reason=False,
) )
tools_used = ("deepagents", "langchain") platform_resources = AgentPlatformResources(
databases=(
AgentDatabase(
name=DATABASE_NAME,
scope="user",
access_mode="read_write",
env=AgentDatabaseEnv(url="DATABASE_URL"),
migrations=AgentDatabaseMigrations(path="db/migrations"),
),
),
mailbox=True,
)
tools_used = ("postgres", "platform-mailbox", "mcp")
@a2a.tool(description="Ask the starter DeepAgent to answer with tool calls when useful") @a2a.tool(
async def ask(self, ctx: RunContext[{{ auth_type }}], prompt: str) -> str: description=(
creds = ctx.llm "Triage pasted shared-inbox email text and return a prioritized queue, "
await ctx.emit_progress(f"llm: {creds.model} via {creds.source}") "approval-ready drafts, a persisted execution receipt, and a downloadable JSON report."
if not creds.api_key: ),
return ( timeout_seconds=120,
"LLM key required. Add an LLM credential in Settings > LLM " cost_class="standard",
"credentials before running this agent; for local --invoke " grant_mode="read_write_overlay",
"runs set AGENT_LLM_KEY." grant_allow_patterns=("outputs/email-assistant/**",),
) grant_outputs_prefix="outputs/email-assistant/",
graph = self._build_deep_agent(ctx=ctx, creds=creds) grant_write_prefixes=("outputs/email-assistant/",),
state = await graph.ainvoke( )
{"messages": [{"role": "user", "content": prompt}]}, async def triage_people_inbox(
config={"recursion_limit": DEEPAGENTS_RECURSION_LIMIT},
)
await ctx.emit_progress("deepagent finished")
return _last_message_text(state)
def _build_deep_agent(
self, self,
*, ctx: RunContext[PlatformUserAuth],
ctx: RunContext[{{ auth_type }}], inbox_text: str = Field(..., min_length=20, max_length=MAX_INBOX_CHARS),
creds: LLMCreds, policy_text: str | None = Field(default=None, max_length=MAX_POLICY_CHARS),
) -> Any: max_messages: int = Field(default=6, ge=1, le=MAX_MESSAGES),
# Lazy imports keep `a2a card` usable before local dependencies are ) -> TriageResult:
# installed. `a2a deploy` installs requirements.txt during the build. """Primary product workflow used by the frontend and MCP clients."""
from a2a_pack.deepagents import create_a2a_deep_agent stable_tenant = _tenant_key(ctx)
from langchain.agents.middleware import wrap_model_call policy = (policy_text or self.config.default_policy_text).strip()
from langchain_core.tools import tool messages = _parse_inbox(inbox_text)[:max_messages]
if not messages:
@tool return TriageResult(
def text_stats(text: str) -> str: status="validation_error",
"""Return exact word, character, and line counts for text.""" summary="No messages could be parsed. Include sender, subject, and body text.",
words = [part for part in text.split() if part.strip()] approval_boundary=_approval_boundary(),
return json.dumps( warnings=["Use blocks with From:, Subject:, and Body: fields for best results."],
{
"characters": len(text),
"words": len(words),
"lines": len(text.splitlines()) or 1,
}
) )
@wrap_model_call queue = [_triage_message(message, policy) for message in messages]
async def log_model_call(request: Any, handler: Any) -> Any: queue.sort(key=lambda item: _priority_rank(item.priority))
messages = request.state.get("messages", []) summary = _summary(queue)
print( report = {
"[middleware] model_call " "generated_at": datetime.now(UTC).isoformat(),
f"model={creds.model} source={creds.source} messages={len(messages)}" "summary": summary,
) "approval_boundary": _approval_boundary(),
return await handler(request) "queue": [item.model_dump(mode="json") for item in queue],
}
report_bytes = json.dumps(report, indent=2, ensure_ascii=False).encode("utf-8")
artifact = await ctx.write_artifact(
"people-team-inbox-triage.json",
report_bytes,
"application/json",
)
await ctx.emit_artifact(artifact)
backend = ctx.workspace_backend() input_hash = hashlib.sha256(
skill_sources = _seed_runtime_skills(backend, ctx) json.dumps(
# create_a2a_deep_agent resolves provider:model strings with {"inbox_text": inbox_text, "policy_text": policy, "max_messages": max_messages},
# langchain.init_chat_model from ctx.llm, preserving LiteLLM routing, sort_keys=True,
# provider-specific extra body, and runtime model overrides. ).encode("utf-8")
return create_a2a_deep_agent( ).hexdigest()
ctx, receipt = await _persist_receipt(
creds=creds, tenant_key=stable_tenant,
backend=backend, skill_name="triage_people_inbox",
skills=skill_sources or None, input_hash=input_hash,
tools=[text_stats], result_summary={
middleware=[log_model_call], "message_count": len(queue),
system_prompt=SYSTEM_PROMPT, "urgent_count": sum(1 for item in queue if item.priority == "urgent"),
"artifact_name": artifact.name,
},
) )
return TriageResult(
status="ok",
summary=summary,
queue=queue,
approval_boundary=_approval_boundary(),
artifact=ArtifactDescriptor(
name=artifact.name,
media_type=artifact.mime_type,
uri=artifact.uri,
size_bytes=artifact.size_bytes,
content_text=report_bytes.decode("utf-8"),
),
receipt=receipt,
warnings=[] if receipt.persisted else [receipt.message],
)
def _runtime_skills_root(ctx: RunContext[Any]) -> str: @a2a.tool(
workspace = getattr(ctx, "_workspace", None) description="Check whether the platform mailbox is provisioned and list recent message headers when available.",
prefixes = tuple(getattr(workspace, "write_prefixes", ()) or ()) timeout_seconds=60,
if not prefixes: cost_class="standard",
outputs_prefix = getattr(workspace, "outputs_prefix", None) )
prefixes = (outputs_prefix or "outputs/",) async def mailbox_status(
prefix = str(prefixes[0]).strip("/") self,
return f"/{prefix}/{RUNTIME_SKILLS_DIR}" if prefix else f"/{RUNTIME_SKILLS_DIR}" ctx: RunContext[PlatformUserAuth],
limit: int = Field(default=5, ge=1, le=20),
unseen_only: bool = False,
) -> dict[str, Any]:
mailbox = ctx.mail
if mailbox is None:
return {
"status": "setup_required",
"message": "Platform mailbox is not provisioned for this deployment yet.",
"messages": [],
}
try:
messages = mailbox.list_messages(limit=limit, unseen_only=unseen_only)
except Exception as exc: # noqa: BLE001
return {
"status": "setup_required",
"message": f"Mailbox is provisioned but could not be read: {type(exc).__name__}",
"messages": [],
}
return {
"status": "ok",
"address": mailbox.address,
"messages": messages,
}
@a2a.tool(
description="Receive inbound email and record that it should be reviewed in the People inbox; does not auto-send replies.",
on_email=True,
timeout_seconds=60,
)
async def receive_people_email(
self,
ctx: RunContext[PlatformUserAuth],
email: InboundEmailPayload,
) -> None:
# Inbound email can contain user-controlled instructions. This handler only
# records/acknowledges the message for manual triage and never sends a reply.
stable_tenant = _tenant_key(ctx)
subject = str(email.get("subject") or "")[:300]
sender = str(email.get("sender") or "")[:300]
body = str(email.get("body") or "")[:2000]
input_hash = hashlib.sha256(
json.dumps({"sender": sender, "subject": subject, "body": body}, sort_keys=True).encode("utf-8")
).hexdigest()
await _persist_receipt(
tenant_key=stable_tenant,
skill_name="receive_people_email",
input_hash=input_hash,
result_summary={"sender": sender, "subject": subject, "action": "queued_for_triage"},
)
return None
def _seed_runtime_skills(backend: Any, ctx: RunContext[Any]) -> list[str]: def _tenant_key(ctx: RunContext[PlatformUserAuth]) -> str:
"""Copy packaged DeepAgents skills into the invocation workspace. stable_id = ctx.auth.user_id if ctx.auth.user_id is not None else ctx.auth.sub
if not stable_id:
DeepAgents loads skills from its backend, while source-controlled raise PermissionError("stable platform identity required")
``skills/`` folders live in the image. This bridge lets generated agents return f"user:{stable_id}"
ship reusable SKILL.md bundles without giving up durable A2A workspace
files.
"""
root = Path(__file__).parent / "skills"
if not root.exists():
return []
runtime_skills_root = _runtime_skills_root(ctx)
uploads: list[tuple[str, bytes]] = []
for path in root.rglob("*"):
if path.is_file():
rel = path.relative_to(root).as_posix()
uploads.append((runtime_skills_root + rel, path.read_bytes()))
if uploads:
backend.upload_files(uploads)
return [runtime_skills_root]
return []
def _last_message_text(state: dict[str, Any]) -> str: def _db_connect() -> Any:
messages = state.get("messages") or [] database_url = os.environ.get("DATABASE_URL")
if not messages: if not database_url:
return json.dumps(state, default=str) return None
import psycopg
content = getattr(messages[-1], "content", None) return psycopg.connect(
if isinstance(content, str): database_url,
return content options=(
if isinstance(content, list): "-c statement_timeout=5000 "
parts: list[str] = [] "-c lock_timeout=3000 "
for item in content: "-c idle_in_transaction_session_timeout=5000"
if isinstance(item, dict): ),
text = item.get("text") or item.get("content") )
if text:
parts.append(str(text))
elif item: async def _persist_receipt(
parts.append(str(item)) *,
return "\n".join(parts) if parts else json.dumps(content, default=str) tenant_key: str,
return str(content or messages[-1]) skill_name: str,
input_hash: str,
result_summary: dict[str, Any],
) -> ReceiptDescriptor:
receipt_id = hashlib.sha256(f"{tenant_key}:{skill_name}:{input_hash}".encode("utf-8")).hexdigest()[:24]
persisted_at = datetime.now(UTC).isoformat()
conn = _db_connect()
if conn is None:
return ReceiptDescriptor(
receipt_id=receipt_id,
persisted=False,
message="DATABASE_URL is not configured; receipt persistence will work on the managed deployment.",
)
try:
with conn:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO execution_receipts
(receipt_id, tenant_key, skill_name, input_hash, result_summary, persisted_at)
VALUES (%s, %s, %s, %s, %s::jsonb, %s)
ON CONFLICT (receipt_id) DO UPDATE SET
result_summary = EXCLUDED.result_summary,
persisted_at = EXCLUDED.persisted_at
""",
(
receipt_id,
tenant_key,
skill_name,
input_hash,
json.dumps(result_summary, sort_keys=True),
persisted_at,
),
)
except Exception as exc: # noqa: BLE001
return ReceiptDescriptor(
receipt_id=receipt_id,
persisted=False,
message=f"Receipt could not be persisted: {type(exc).__name__}",
)
finally:
try:
conn.close()
except Exception: # noqa: BLE001
pass
return ReceiptDescriptor(
receipt_id=receipt_id,
persisted=True,
persisted_at=persisted_at,
message="Production execution receipt persisted.",
)
def _parse_inbox(inbox_text: str) -> list[dict[str, str]]:
normalized = inbox_text.replace("\r\n", "\n").strip()
blocks = [block.strip() for block in re.split(r"\n\s*---+\s*\n", normalized) if block.strip()]
if len(blocks) == 1:
# Also split common pasted inbox format with repeated From: headers.
blocks = [b.strip() for b in re.split(r"(?=\n?From\s*:)", normalized) if b.strip()]
out: list[dict[str, str]] = []
for idx, block in enumerate(blocks, start=1):
sender = _field(block, "from") or _field(block, "sender") or "unknown@example.com"
subject = _field(block, "subject") or f"People inbox message {idx}"
body = _field(block, "body") or _strip_headers(block)
if len(body.strip()) < 8 and subject.startswith("People inbox message"):
continue
out.append(
{
"message_id": hashlib.sha1(f"{sender}|{subject}|{body}".encode("utf-8")).hexdigest()[:12],
"sender": sender[:200],
"subject": subject[:200],
"body": body[:4000],
}
)
return out
def _field(block: str, name: str) -> str:
pattern = rf"(?ims)^\s*{re.escape(name)}\s*:\s*(.*?)(?=^\s*(?:from|sender|subject|body)\s*:|\Z)"
match = re.search(pattern, block)
return re.sub(r"\s+", " ", match.group(1)).strip() if match else ""
def _strip_headers(block: str) -> str:
lines = []
for line in block.splitlines():
if re.match(r"^\s*(from|sender|subject)\s*:", line, flags=re.I):
continue
if re.match(r"^\s*body\s*:", line, flags=re.I):
line = re.sub(r"^\s*body\s*:\s*", "", line, flags=re.I)
lines.append(line)
return "\n".join(lines).strip()
def _triage_message(message: dict[str, str], policy_text: str) -> QueueItem:
text = f"{message['subject']}\n{message['body']}".lower()
category = "general_hr"
priority: Literal["urgent", "high", "normal", "low"] = "normal"
reason = "Routine people-team question."
next_action = "Review the draft, verify details, and send after approval."
policy_basis = _policy_basis(policy_text, ["general", "people", "policy"])
if any(term in text for term in ["harassment", "discrimination", "unsafe", "retaliation", "hostile"]):
category = "employee_relations"
priority = "urgent"
reason = "Potential employee-relations risk requires confidential escalation."
next_action = "Escalate to the designated People partner before replying."
policy_basis = _policy_basis(policy_text, ["relations", "harassment", "escalate", "urgent"])
elif any(term in text for term in ["payroll", "paycheck", "tax", "ssn", "bank", "personal data"]):
category = "payroll_privacy"
priority = "urgent"
reason = "Payroll or personal-data content should move to a secure channel."
next_action = "Route through payroll/HRIS and avoid requesting sensitive data by email."
policy_basis = _policy_basis(policy_text, ["payroll", "personal", "secure", "data"])
elif any(term in text for term in ["leave", "pto", "fmla", "bereavement", "sick"]):
category = "leave_request"
priority = "high"
reason = "Leave timing affects staffing and may require manager/People approval."
next_action = "Confirm dates and required approvals before committing."
policy_basis = _policy_basis(policy_text, ["leave", "approval", "manager"])
elif any(term in text for term in ["benefit", "insurance", "enrollment", "coverage", "dependent"]):
category = "benefits"
priority = "normal"
reason = "Benefits question can usually be answered with policy-grounded guidance."
next_action = "Send after verifying plan-document details."
policy_basis = _policy_basis(policy_text, ["benefits", "enrollment", "eligibility", "plan"])
elif any(term in text for term in ["thank", "resolved", "fyi", "newsletter"]):
priority = "low"
category = "low_touch"
reason = "Low-risk informational or resolved message."
next_action = "Archive or send a short acknowledgement if needed."
draft = _draft_for(message, category, priority, policy_basis, next_action)
return QueueItem(
message_id=message["message_id"],
sender=message["sender"],
subject=message["subject"],
priority=priority,
category=category,
reason=reason,
next_action=next_action,
draft=draft,
)
def _policy_basis(policy_text: str, keywords: list[str]) -> list[str]:
sentences = [part.strip() for part in re.split(r"(?<=[.!?])\s+|\n+", policy_text) if part.strip()]
matches = [s for s in sentences if any(k.lower() in s.lower() for k in keywords)]
selected = matches[:3] or sentences[:2]
return [item[:240] for item in selected]
def _draft_for(
message: dict[str, str],
category: str,
priority: str,
policy_basis: list[str],
next_action: str,
) -> DraftResult:
greeting = "Hi there,"
if "@" in message["sender"]:
local = message["sender"].split("@", 1)[0].split("<")[-1]
first = re.split(r"[._\-\s]", local.strip())[0].title()
if first:
greeting = f"Hi {first},"
subject = message["subject"] if message["subject"].lower().startswith("re:") else f"Re: {message['subject']}"
if category == "employee_relations":
body = (
f"{greeting}\n\nThank you for raising this. We take these concerns seriously and will route "
"this to the appropriate People partner for confidential review. Please avoid sending additional "
"sensitive details by email unless requested through the approved channel.\n\n"
"A People team member will follow up with next steps.\n\nBest,\nPeople Team"
)
reason = "Employee-relations matters require a human People partner before any commitment."
elif category == "payroll_privacy":
body = (
f"{greeting}\n\nThanks for reaching out. Because this may involve payroll or personal data, "
"we should handle it through the secure HRIS/payroll channel rather than email. "
"Please use the secure case path or wait for a People Ops teammate to confirm next steps.\n\n"
"Best,\nPeople Team"
)
reason = "Payroll/personal-data replies must be checked for privacy and channel safety."
elif category == "leave_request":
body = (
f"{greeting}\n\nThanks for the note. We can help review the leave request. Please confirm the "
"requested dates and any supporting details required by policy. Final approval may depend on "
"manager and People review, so we will confirm before making any commitment.\n\nBest,\nPeople Team"
)
reason = "Leave commitments need date verification and approval checks."
elif category == "benefits":
body = (
f"{greeting}\n\nThanks for your benefits question. Based on our policy guidance, eligibility "
"and enrollment details should be confirmed against the current plan documents. We can point you "
"to the right resource and will escalate any exception request to People Ops.\n\nBest,\nPeople Team"
)
reason = "Benefits guidance should be verified against plan documents before sending."
else:
body = (
f"{greeting}\n\nThanks for reaching out. We reviewed your note and the next step is: "
f"{next_action}\n\nBest,\nPeople Team"
)
reason = "People-team replies should be reviewed before sending from a shared inbox."
return DraftResult(
to=message["sender"],
subject=subject[:200],
body=body,
approval_required=True,
approval_reason=reason,
policy_basis=policy_basis,
)
def _priority_rank(priority: str) -> int:
return {"urgent": 0, "high": 1, "normal": 2, "low": 3}.get(priority, 9)
def _summary(queue: list[QueueItem]) -> str:
counts: dict[str, int] = {}
for item in queue:
counts[item.priority] = counts.get(item.priority, 0) + 1
parts = [f"{counts.get(name, 0)} {name}" for name in ["urgent", "high", "normal", "low"] if counts.get(name, 0)]
return f"Prioritized {len(queue)} people-inbox message(s): " + ", ".join(parts) + "."
def _approval_boundary() -> str:
return (
"Drafts are approval-ready but not auto-sent. A People teammate must verify facts, "
"policy citations, privacy constraints, and any commitment before sending."
)