Edictum
Edictum ConsoleReference

SSE Events

Server-Sent Events for real-time updates to agents and dashboards. Two separate streams with different event types and payloads.

AI Assistance

Right page if: you are debugging SSE connections, building a custom SSE client, or need to know which events fire on which actions (agent stream vs dashboard stream). Wrong page if: you need the HTTP API endpoints -- see https://docs.edictum.ai/docs/console/reference/api. For connecting agents to the console, see https://docs.edictum.ai/docs/console/connecting-agents. Gotcha: there are two separate SSE streams (agent at /api/v1/stream, dashboard at /api/v1/stream/dashboard) with different event types and auth. The agent stream filters `contract_update` events by `bundle_name` if provided on connection.

Edictum Console uses Server-Sent Events (SSE) to push real-time updates to agents and dashboards. Two separate streams serve different audiences with different event types.

Agent Stream

GET /api/v1/stream?env={env}&bundle_name={name}&policy_version={hash}&tags={json}
Auth: API key (Authorization: Bearer edk_...)

Agents connect to this endpoint to receive contract updates, approval decisions, and assignment changes in real time.

Query Parameters

ParameterRequiredDescription
envYesEnvironment to subscribe to (e.g., production, staging)
bundle_nameNoFilter contract_update events to this bundle only. When omitted, all contract_update events for the tenant are forwarded.
policy_versionNoSHA-256 revision hash the agent is currently running. Used for drift detection on the fleet status endpoint.
tagsNoJSON-encoded agent tags (e.g., {"team": "platform"}). Used for assignment rule matching.

Authentication Error

Connecting without an Authorization header, or with an invalid or revoked API key, returns 401 Unauthorized. The connection is not established. This replaces the previous 422 behavior — any tooling that previously checked for 422 on auth failure should be updated to check for 401.

Environment Scope Enforcement

The env query parameter must match the API key's scoped environment. If they differ, the server rejects the connection immediately:

HTTP/1.1 403 Forbidden
{"detail": "API key is scoped to 'production', cannot subscribe to 'staging'."}

This check runs before any SSE headers are sent. Agents using a key for production cannot subscribe to staging events, and vice versa.

Connection Behavior

On connect, the stream endpoint:

  1. Validates the API key and resolves tenant_id + env
  2. Auto-registers the agent if not already known (creates an agent_registrations row)
  3. Resolves bundle assignment if bundle_name is not provided (explicit assignment -> rule match -> agent-provided). Rule matching is scoped to the agent's authenticated environment — agents in different environments receive independent, environment-filtered contract bundles.
  4. Subscribes the connection to PushManager._connections[env]
  5. Sends a keepalive comment (: keepalive) periodically

On disconnect, the connection is removed from the PushManager. Stale connections are cleaned up every 5 minutes.

Agent Events

contract_update

Fired when a bundle is deployed to the agent's environment.

event: contract_update
data: {
  "type": "contract_update",
  "bundle_name": "devops-agent",
  "version": 3,
  "revision_hash": "abc123def456...",
  "signature": "ed25519-hex-signature|null",
  "public_key": "ed25519-hex-public-key|null",
  "yaml_bytes": "base64-encoded-yaml-content"
}
FieldTypeDescription
typestringAlways "contract_update"
bundle_namestringName of the deployed bundle
versionintBundle version number
revision_hashstringSHA-256 hash of the YAML content
signaturestring|nullHex-encoded Ed25519 signature
public_keystring|nullHex-encoded Ed25519 public key for verification
yaml_bytesstringBase64-encoded YAML bundle content

The SDK decodes yaml_bytes from base64 and passes it to Edictum.reload() for atomic contract swap. If bundle_name was provided on connection, only updates matching that bundle are forwarded.

approval_decided

Fired when a human approves or denies a pending approval request for this agent.

event: approval_decided
data: {
  "type": "approval_decided",
  "approval_id": "uuid",
  "status": "approved",
  "decided_by": "admin@example.com"
}
FieldTypeDescription
typestringAlways "approval_decided"
approval_idstringUUID of the approval request
statusstring"approved" or "denied"
decided_bystringEmail or identifier of the decision-maker

Note: decision_reason is not included in the SSE payload. To get the full decision details, fetch GET /api/v1/approvals/{approval_id}.

assignment_changed

Fired when an agent's bundle assignment changes (via bulk assign or rule update).

event: assignment_changed
data: {
  "type": "assignment_changed",
  "agent_id": "my-agent",
  "bundle_name": "new-bundle",
  "source": "explicit"
}
FieldTypeDescription
typestringAlways "assignment_changed"
agent_idstringThe affected agent
bundle_namestring|nullNew bundle assignment (null if unassigned)
sourcestringAssignment source: "explicit", "rule", or "none"

Dashboard Stream

GET /api/v1/stream/dashboard
Auth: Session cookie (HttpOnly)

Dashboard users connect to this endpoint to receive real-time updates across all environments for their tenant.

Dashboard Events

The dashboard stream forwards events matching this whitelist:

Event TypeTrigger
approval_createdAgent creates a new approval request
approval_decidedHuman approves or denies an approval
approval_timeoutPending approval expires
assignment_changedAgent bundle assignment modified
bundle_uploadedNew bundle version uploaded
composition_changedComposition created, updated, or deleted
contract_createdNew contract added to library
contract_updateBundle deployed to an environment
contract_updatedExisting contract version updated
event_createdNew audit event ingested
api_key_createdNew API key created
api_key_revokedAPI key revoked
bundle_deployedBundle deployed to environment
signing_key_rotatedEd25519 signing key rotated

approval_created

{
  "type": "approval_created",
  "approval_id": "uuid",
  "agent_id": "my-agent",
  "tool_name": "delete_user",
  "message": "Agent wants to delete user account"
}

Note: env, timeout_seconds, and contract_name are not included in the SSE payload. The SSE event provides minimal identifying info; fetch GET /api/v1/approvals/{approval_id} for full details.

approval_timeout

{
  "type": "approval_timeout",
  "approval_id": "uuid",
  "agent_id": "my-agent",
  "tool_name": "delete_user"
}

bundle_uploaded

{
  "type": "bundle_uploaded",
  "bundle_name": "devops-agent",
  "version": 3,
  "revision_hash": "abc123...",
  "uploaded_by": "user_123"
}

event_created

{
  "type": "event_created",
  "accepted": 3
}

Note: This is a per-batch summary, not per-event. The accepted field is the number of events ingested in the batch. Pushed to dashboard only (not to agents).


AI Assistant Stream

POST /api/ai/assist
Auth: Session cookie (HttpOnly)

The AI assistant endpoint streams responses as SSE. In addition to content_chunk events carrying streamed text, two events are emitted during agentic turns when the assistant invokes tools.

AI Assistant Events

tool_call_start

Emitted when the LLM invokes a tool.

{
  "type": "tool_call_start",
  "tool": "validate_contract",
  "id": "toolu_01Abc123"
}
FieldTypeDescription
typestringAlways "tool_call_start"
toolstringName of the tool being called (e.g., "validate_contract", "evaluate_contract")
idstringUnique identifier for this tool invocation; matches the corresponding tool_call_result

tool_call_result

Emitted when a tool invocation completes.

{
  "type": "tool_call_result",
  "id": "toolu_01Abc123",
  "tool": "validate_contract",
  "result": { "valid": true, "errors": [] },
  "duration_ms": 42
}
FieldTypeDescription
typestringAlways "tool_call_result"
idstringMatches the id from the corresponding tool_call_start
toolstringName of the tool that was called
resultobjectTool execution output (schema varies by tool)
duration_msintExecution time in milliseconds

These two events appear between content_chunk events during agentic turns. The UI uses them to render collapsible tool call indicators with status icons and duration.

Ollama does not emit these events. Ollama-backed responses are text-only; no tool calls are made and no tool_call_start or tool_call_result events are emitted.


PushManager Architecture

The PushManager is an in-process event dispatcher using asyncio queues. No external message broker is required.

PushManager
+-- _connections: dict[env, list[AgentConnection]]
|   Each AgentConnection holds:
|   +- queue: asyncio.Queue
|   +- env, tenant_id, agent_id
|   +- bundle_name, policy_version
|   +- connected_at, is_closed
|
+-- _dashboard_connections: dict[tenant_id, list[DashboardConnection]]
|   Each DashboardConnection holds:
|   +- queue: asyncio.Queue
|   +- tenant_id, connected_at

Dispatch Methods

MethodTargetFiltering
push_to_env(env, data, tenant_id)All agents in environmentTenant match. For contract_update events, additionally filters by bundle_name -- only agents subscribed to that bundle receive the event.
push_to_dashboard(tenant_id, data)All dashboard connections for tenantEvent type must be in the whitelist.
push_to_agent(agent_id, data, tenant_id)Specific agent across all environmentsExact agent_id + tenant_id match.

Connection Lifecycle

  1. Subscribe: Agent/dashboard connects, a new asyncio.Queue is created and added to the connection map
  2. Receive: The SSE endpoint reads from the queue in a loop, yielding events as SSE text
  3. Unsubscribe: On disconnect, the connection is removed from the map
  4. Cleanup: A background task runs every 5 minutes, removing connections that are closed or older than 1 hour

Event Format on the Wire

Events are sent as standard SSE with the event type derived from data["type"]:

event: contract_update
data: {"type": "contract_update", "bundle_name": "devops-agent", ...}

event: approval_created
data: {"type": "approval_created", "approval_id": "...", ...}

The event: line enables EventSource clients to listen for specific event types with addEventListener().

Last updated on

On this page