landmarkArchitecture Decisions

Key architectural decisions in the Amigo API design and their rationale.

Key architectural decisions in the Amigo AI API design and their rationale.

ADR-001: NDJSON over SSE for Streaming

Status: Accepted

Context: Conversation creation and interaction endpoints stream multiple events back to the client. We needed to choose between Server-Sent Events (SSE), WebSocket, and Newline-Delimited JSON (NDJSON) for the HTTP streaming protocol.

Decision: Use NDJSON (newline-delimited JSON) for all streaming HTTP endpoints.

Rationale:

Factor
NDJSON
SSE
WebSocket

Parsing complexity

Parse each line as JSON

Event ID management, retry logic, type field parsing

Full duplex protocol, framing

Client support

Works with any HTTP client

Requires EventSource API or polyfill

Requires WebSocket API

Content types

Standard application/x-ndjson

text/event-stream

Binary frames

Proxy compatibility

Passes through standard HTTP proxies

Some proxies buffer SSE streams

Requires upgrade

Error handling

Standard HTTP status codes

Limited to stream reconnection

Close codes

Binary data

Base64 in JSON fields

Text only

Native binary support

Key advantages for our use case:

  • Simplicity: Each line is a self-contained JSON object. No event ID tracking or reconnection logic needed.

  • Language agnostic: Works with any language that can read lines from an HTTP response body.

  • Discriminated unions: Each event has a type field that maps directly to a typed discriminated union in both SDKs.

  • Progressive parsing: Clients can process events as they arrive without buffering the entire response.

Consequences:

  • Clients must handle newline-delimited parsing (both SDKs provide this via async generators)

  • No built-in reconnection semantics (clients implement retry via the SDK retry middleware)

  • WebSocket is used separately for the real-time voice endpoint where bidirectional streaming is required


ADR-002: JWT over Session Tokens

Status: Accepted

Context: The API needs an authentication mechanism that works across multiple services, supports token refresh, and can be verified without database lookups.

Decision: Use JWT (JSON Web Token) bearer tokens obtained via API key exchange.

Authentication flow:

  1. Client exchanges API key + API key ID for a JWT via POST /user/signin_with_api_key

  2. JWT is sent as Authorization: Bearer {token} on subsequent requests

  3. SDKs proactively refresh tokens 5 minutes before expiry

Rationale:

Factor
JWT
Session Tokens
API Key Direct

Stateless verification

Yes (signature check)

No (DB lookup)

No (DB lookup)

Cross-service

Yes (shared signing key)

No (service-specific)

Yes

Expiration

Built-in exp claim

Manual tracking

Never expires

Revocation

Requires token blacklist

Delete from DB

Requires rotation

Payload

Standard claims (sub, org, exp)

Opaque identifier

Opaque identifier

Token size

Larger (~1KB)

Small (~32 bytes)

Small (~64 bytes)

Key advantages:

  • Stateless: API servers verify tokens without hitting a session store, enabling horizontal scaling.

  • Standard claims: sub (user ID), org (organization), exp (expiration) are standard JWT fields that any JWT library can validate.

  • Proactive refresh: The 5-minute refresh window in both SDKs ensures uninterrupted operation without 401 retry overhead.

  • Separation of concerns: Long-lived API keys never travel over the wire after initial exchange. Short-lived JWTs limit exposure if intercepted.

Consequences:

  • Tokens are larger than session IDs, increasing request overhead slightly

  • SDKs must implement token refresh logic (both do, with race condition protection via locks/shared promises)

  • Token revocation requires additional infrastructure (not currently needed for API key-based auth)


ADR-003: service_hierarchical_state_machine Naming

Status: Accepted

Context: The platform documentation refers to "Context Graphs" - the state-based architecture that structures agent reasoning and conversation flow. The API needs a field name for this entity.

Decision: Use service_hierarchical_state_machine as the API resource name.

Rationale:

The name was chosen to be technically precise about what the entity represents:

  • service: Scoped to a service (the deployment unit that combines agent + state machine + tools)

  • hierarchical: States can be nested hierarchically (parent states containing child states)

  • state_machine: The fundamental computational model - a finite set of states with defined transitions

Why not context_graph?

Concern
Decision

Disambiguation

"Graph" is overloaded - could be confused with knowledge graphs, graph databases, or dependency graphs. "State machine" is unambiguous in computer science.

Implementation fidelity

The implementation IS a hierarchical state machine. Exit conditions define transitions. States have types (Action, Decision, Reflection, ToolCall).

API stability

Technical names are less likely to change than marketing names. The platform docs can rename "Context Graph" without breaking the API.

Self-documenting

A developer seeing service_hierarchical_state_machine in a payload immediately understands the data structure without needing to read documentation.

Consequences:

  • Verbose field name increases payload sizes slightly

  • Developers must learn the mapping between platform terminology ("Context Graph") and API names

  • The terminology mapping page bridges this gap (see Terminology Mapping)

See also: Terminology Mapping for the complete mapping between platform concepts and API names.

Last updated

Was this helpful?