4.0. Typing
Why does type safety matter more for an agent?
Agent code combines non-deterministic model output with deterministic side effects. Strings arrive from prompts, environments, MCP, A2A, SQLite, and providers. Static types catch incorrect composition; runtime validation decides whether external data is safe to use.
Types do not prove that a model answer is correct. They prevent malformed data from becoming an unexamined incident, service, action target, or audit record.
Where are the trusted boundaries?
class Incident(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str = Field(pattern=r"^INC-\d+$")
service: str = Field(pattern=r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
severity: Severity
status: IncidentStatus
opened_at: str = Field(min_length=1)
summary: str = Field(min_length=1)
Settingsparses environment variables into paths, booleans, bounded ports, protocols, and call budgets.- Domain models parse database rows and forbid unexpected columns.
- Enum fields reject unknown lifecycle values.
- Normalizers reject model-controlled ids/slugs before SQL or filesystem access.
- Tool results use plain JSON-compatible structures after validation.
How is the code checked statically?
The repository uses ty against Python 3.13:
Do not silence an error with Any or an ignore unless the external library boundary genuinely cannot be expressed. Keep any necessary ignore narrow and explain why runtime compatibility is still safe.
Which invariants deserve enums or validated types?
Use a type when an invalid value should be unrepresentable after parsing: incident status, severity, service status, incident ids, slugs, A2A protocol, ports, and max model calls. Do not introduce wrapper types for strings that have no meaningful invariant.
What happens when the model violates the schema?
Something explicit. triage_report_agent (2.3. Instructions) promises a validated TriageReport; the programmatic path in report.py decides what a violation means. parse_triage_report lets every real violation raise ValidationError, and request_triage_report retries once with the validation errors fed back, then degrades to prose:
second = await generate(retry_prompt)
try:
return parse_triage_report(second)
except ValidationError:
_SCHEMA_FAILURES.add(1)
logger.warning("Triage report for %s failed schema validation twice; degrading to prose", incident_id)
return second
The return type is TriageReport | str, so the caller must handle both outcomes — never a silent crash and never a silently-wrong object (extra="forbid" rejects a plausible-looking report with an invented field). Each degradation increments the agentops.triage_report.schema_failures counter, so a rising violation rate is a dashboard signal of model quality, not a hidden branch. The policy is deterministic to test offline with a fake model:
The separate model-backed checkpoint exercises the actual ADK output_schema entry point without changing the conversational agent:
triage-report.evalset.json requires the incident, log, and runbook reads in order. A completed run has therefore crossed both boundaries: ADK accepted the final response as a TriageReport, and the trajectory stayed grounded in the fixed seed.
What is the typing checkpoint?
Introduce one malformed database row or environment value in a test and confirm it fails at the boundary with context rather than propagating as a later tool error.