Data contracts are the stable spine of SYRIS. Implementations can change; contracts evolve slowly and deliberately. Breaking changes require an ADR and a migration plan.
All schemas are Pydantic v2. Fields use the format: name: Type — notes.
Referenced by Task.error, Step.error, and ToolResult.error.
ErrorDetail:
code: str — Machine-readable error code (e.g. "tool.timeout",
"scope.violation", "gate.denied")
message: str — Human-readable description
retryable: bool — Whether the caller should retry
details: dict | None — Optional structured context; MUST NOT contain secrets
MessageEvent:
event_id: UUID — Generated at ingestion; stable identity
schema_version: str — "1.0"; bump for breaking changes
occurred_at: datetime — Source timestamp (UTC)
ingested_at: datetime — Set by normaliser; used for latency metrics
source:
channel: ChannelEnum — email | sms | webhook | ha_event |
scheduler | rule_engine | watcher | vision
connector_id: str — Which integration instance
thread_id: str | None — Native thread/conversation ID
message_id: str | None — Native message ID for dedup
actor:
actor_type: ActorType — user | system | integration
actor_id: str — Stable identity string
content:
text: str | None
structured: dict — Parsed payload or intent hints
attachment_refs: list[str] — IDs of stored attachments; never inline bytes
(Renamed from attachments in original spec)
links: list[str]
context:
timezone: str — IANA timezone string
locale: str — BCP-47
device_id: str | None
correlation:
trace_id: UUID — Created at ingestion; NEVER changes
parent_event_id: UUID | None — Set when triggered by another event
dedupe_key: str | None — Derived by normaliser from stable hash
security:
sensitivity: Sensitivity — low | med | high
redaction_policy_id: str
RoutingDecision:
trace_id: UUID
event_id: UUID
decided_at: datetime
execution_mode: ExecutionMode — fast | task | gated | sandbox
match:
matched_fastpath: str | None — e.g. "timer.set"
matched_rule_ids: list[str]
used_llm: bool
intent: str | None — Normalised intent label
confidence: float | None — None for deterministic matches
required_scopes: list[str]
risk_level: RiskLevel — low | medium | high | critical
gates: list[GateSpec]
notes: str | None — Human-readable routing explanation
GateSpec
gate_type: GateType — approval | dryrun | scope_check
reason: str
expires_in_seconds: int
Task:
task_id: UUID
created_at: datetime
updated_at: datetime
status: TaskStatus — pending | running | paused | succeeded |
failed | canceled
trigger_event_id: UUID
trace_id: UUID
current_step_id: UUID | None
autonomy_level_at_start: AutonomyLevel — Snapshot; never changes mid-task
labels: list[str]
next_wake_time: datetime | None
cancel_reason: str | None
error: ErrorDetail | None
Step:
step_id: UUID
task_id: UUID
name: str
status: StepStatus — pending | running | succeeded | failed | paused
attempt: int — 0-indexed
max_attempts: int
retry_policy: RetryPolicy
input: dict — Immutable at creation
checkpoint: dict — Mutable; written mid-step for resumability
output: dict | None — Written on success
idempotency_key: str | None — Required for effectful steps
started_at: datetime | None
ended_at: datetime | None
error: ErrorDetail | None
RetryPolicy:
strategy: RetryStrategy — exponential | fixed | none
base_delay_ms: int
max_delay_ms: int
jitter: bool — Always true for exponential
ToolCall:
tool_call_id: UUID
created_at: datetime
trace_id: UUID
task_id: UUID | None — None for fast-lane calls
step_id: UUID | None
event_id: UUID | None
connector_id: str
tool_name: str
action: str
request_hash: str — SHA-256 of redacted request
idempotency_key: str — REQUIRED; no exceptions
status: ToolCallStatus — attempted | succeeded | failed | unknown
latency_ms: int | None
risk_level: RiskLevel
autonomy_level: AutonomyLevel
ToolResult:
tool_call_id: UUID
status: ResultStatus — succeeded | failed
response_hash: str — SHA-256; payload stored in artifact store
error: ErrorDetail | None
resolved_at: datetime
AuditEvent:
audit_id: UUID
timestamp: datetime — UTC; set by AuditWriter, never by caller
trace_id: UUID — Required; positional argument to AuditWriter
stage: PipelineStage — normalize | route | execute | tool_call |
gate | operator | scheduler | watcher | rule
refs:
event_id: UUID | None
task_id: UUID | None
step_id: UUID | None
tool_call_id: UUID | None
approval_id: UUID | None
type: str — e.g. "tool_call.succeeded"
summary: str — Human-readable; indexed for search
outcome: AuditOutcome — success | failure | suppressed | info
latency_ms: int | None
tool_name: str | None
connector_id: str | None
risk_level: RiskLevel | None
autonomy_level: AutonomyLevel | None
payload_ref: str | None — Artifact store ID; None if no payload
(Added; inline payloads not stored)
Approval:
approval_id: UUID
created_at: datetime
expires_at: datetime
status: ApprovalStatus — pending | approved | denied | expired
trace_id: UUID
refs:
event_id: UUID | None
task_id: UUID | None
step_id: UUID | None
risk_level: RiskLevel
autonomy_level: AutonomyLevel — Snapshot at gate decision time
what: dict — Exact serialised action payload
why: str — Human-readable gate reason (Added)
how_to_approve: str — e.g. "POST /approvals/{id}/approve" (Added)
decision:
by: str | None — "operator"
at: datetime | None
reason: str | None
ChannelEnum: email | sms | webhook | ha_event | scheduler | rule_engine | watcher | vision
ActorType: user | system | integration
Sensitivity: low | med | high
RiskLevel: low | medium | high | critical
AutonomyLevel: A0 | A1 | A2 | A3 | A4
TaskStatus: pending | running | paused | succeeded | failed | canceled
StepStatus: pending | running | succeeded | failed | paused
ToolCallStatus: attempted | succeeded | failed | unknown
ResultStatus: succeeded | failed
ApprovalStatus: pending | approved | denied | expired
AuditOutcome: success | failure | suppressed | info
PipelineStage: normalize | route | execute | tool_call | gate | operator |
scheduler | watcher | rule
ExecutionMode: fast | task | gated | sandbox
GateType: approval | dryrun | scope_check
GateAction: ALLOW | CONFIRM | PREVIEW | HARD_BLOCK
HealthStatus: healthy | degraded | unavailable | down
RetryStrategy: exponential | fixed | none
ProviderType: native | mcp