Skip to content

Latest commit

 

History

History
84 lines (56 loc) · 7.1 KB

File metadata and controls

84 lines (56 loc) · 7.1 KB

OpenGameAgent Runtime Protocol v1

The OpenGameAgent Runtime Protocol is OpenGameAgent's optional, versioned boundary for a game engine, native client, sidecar, or self-hosted service. It is not part of OpenGameAgent.Kernel: in-process C# integrations can call GameAgentRuntime directly and omit every Runtime package.

Use it when a client must reconnect to a running agent, consume a canonical cross-language event stream, or issue race-safe control requests. The public packages are:

  • OpenGameAgent.Runtime.Protocol: transport-neutral DTOs, JSON codec, capability negotiation, and client reducer;
  • OpenGameAgent.Runtime.Hosting: in-process event projection and bounded replay journal;
  • OpenGameAgent.Client: typed HTTP/SSE client;
  • OpenGameAgent.Server: self-hostable HTTP/SSE implementation.

The normative schema, fixtures, dependency-free C++ DTOs, and generated TypeScript/Python SDKs live under protocol/runtime/v1. They are the supported interoperability boundary for remote and self-hosted services; consumers should depend on these public contracts instead of duplicating server-internal DTOs or agent-loop state.

The TypeScript and Python SDKs contain strict DTO guards, cursor parsing, the same client reducer, and clients for initialize, cursor pages, exact steer/interrupt, and resumable SSE. TypeScript has no runtime dependency and builds to JavaScript plus declarations; Python uses only the standard library. Both are generated from the checked-in v1 schema and embed its SHA-256. Verify generation and packed clean consumers with ./tools/Test-RuntimeProtocolSdks.ps1.

Versioned distribution

Runtime Protocol v1 is part of the 0.3.0-alpha.4 source line. Pin one exact source commit and use ProjectReference for OpenGameAgent.Runtime.Protocol, OpenGameAgent.Runtime.Hosting, and OpenGameAgent.Client. Hosting and Client keep their Protocol project dependency aligned.

The release pipeline generates RELEASE_MANIFEST.json, recording the package version, full source commit, supported Runtime Protocol versions, package IDs, asset sizes, and frozen SHA-256 hashes. SHA256SUMS.txt covers every release payload including that manifest; the checksum index itself is the detached verifier.

Coordinates and lifecycle

Every event has a monotonically increasing sequence, stable eventId, (sessionId, actorId, inputId), and optional runId, turn/turnId, and itemId/itemKind. Runs, turns, and items publish started, delta, and completed lifecycles. Message, tool, durable action, approval, interaction, artifact, delegation, plan, media, and status items share this envelope.

GameRuntimeReducer rejects mixed sessions, non-contiguous sequences, duplicate starts, completions without starts, and changes of run identity. A terminal run reconciles any still-open presentation item with an explicit item_interrupted before the terminal result. It never invents or repeats a durable game action.

Server endpoints

Endpoint Purpose
POST /runtime/v1/initialize Negotiate protocol version and capabilities
POST /runtime/v1/run/stream Start or reconnect to one idempotently identified input and receive SSE
POST /runtime/v1/events Read a bounded cursor page without opening a stream
POST /runtime/v1/control/steer Steer only the exact active run and turn
POST /runtime/v1/control/interrupt Interrupt and wait for the exact run to settle

The same identity-derived owner authorization and host-controlled audience projection used by the v1 server run, usage, transcript, approval, and durable action endpoints applies before Runtime state or runtime state is touched. A bounded body credential is accepted for local engine clients that cannot set headers; the host maps it to a principal, and ownership is still derived from that principal. Credentials are never stored in a transcript, event, journal, exception, or result.

The public C# client is GameRuntimeServerClient:

var client = new GameRuntimeServerClient(new GameRuntimeServerClientOptions(
    httpClient,
    new Uri("http://127.0.0.1:5157/")));

var negotiated = await client.InitializeAsync();
var cursor = await client.StreamAsync(
    input,
    requestId: "host-command-42",
    (value, cancellationToken) =>
    {
        engineQueue.Enqueue(value); // deliver on the engine's main thread later
        return default;
    });

The same (session, actor, input, requestId, inputJson) identifies a reconnect. Reusing an input with different request content fails closed.

Reconnect and reconciliation

SSE responses set id: to the canonical event ID. Persist the last fully applied ID and reconnect with Last-Event-ID. The server keeps running if one HTTP caller disconnects. It replays retained events without starting a second model run.

Retention is bounded. An unknown or expired cursor produces a gap event and requiresTranscriptReconciliation=true. Stop reducing incremental items, read the authorized durable transcript through ServerGameAgentClient.ReadTranscriptAsync, rebuild the presentation state, and then continue from the page's nextAfterSequence. That cursor represents the last scanned event and can be greater than the last visible event when audience projection removed private data.

A reconnect cannot authorize a repeated game mutation. Non-idempotent game tools still pass through DurableGameActionDispatcher, operationId, journal, authoritative receipt, and reconciliation. Runtime only replays observations of that lifecycle.

Exact control

Read the current runId and turn from the stream, then bind both into GameRuntimeControlRequest. A delayed request returns runMismatch, turnMismatch, controlClosed, or idle; it cannot steer or interrupt a newer run. Accepted interrupt does not report completion until the runtime lane has settled and emitted its terminal state.

Legacy uncoordinated TrySteer/TryAbort remains available for tightly coupled in-process code. Remote and delayed clients should always use exact coordinates.

Compatibility rules

  • Negotiate before using optional capabilities; never infer support from a server brand or version string.
  • Adding an optional capability or additive payload field does not redefine an existing lifecycle.
  • Changing required fields, enum meaning, cursor semantics, or lifecycle ordering requires a new protocol version.
  • JSON readers reject duplicate properties, use a maximum depth of 128, and enforce the documented character/page limits.
  • Event sequences stop at 9007199254740991, preserving exact integer identity in C#, C++, TypeScript, and Python.
  • payloadJson, inputJson, and messageJson contain one bounded canonical JSON value; they preserve the existing runtime wire contracts without nesting provider-specific objects into the Runtime schema.
  • Hidden reasoning, signatures, private messages, credentials, and private tool details are never part of a non-internal projection.

Run dotnet test tests/OpenGameAgent.Runtime.Protocol.Tests -c Release for fixture/schema checks and dotnet test tests/OpenGameAgent.Server.Tests -c Release for authorization, projection, replay, and exact-control conformance.