Skip to content

Redesign sati as plain Java client for v3 protocol#42

Open
namtzigla wants to merge 8 commits intomasterfrom
v3-plain-java-redesign
Open

Redesign sati as plain Java client for v3 protocol#42
namtzigla wants to merge 8 commits intomasterfrom
v3-plain-java-redesign

Conversation

@namtzigla
Copy link
Copy Markdown
Member

Remove all Micronaut dependencies. The library is now plain Java 21 with only gRPC, protobuf, and SLF4J.

Architecture:

  • ExileClient: builder-based entry point, composes WorkStream + services
  • ExileConfig: immutable connection config (certs, endpoint)
  • WorkStreamClient: single bidirectional gRPC stream implementing the v3 WorkStream protocol (replaces GateClientJobQueue, GateClientEventStream, and SubmitJobResults)
  • JobHandler interface: integrations implement to process jobs, return results directly (no more calling gateClient.submitJobResults internally)
  • EventHandler interface: integrations implement to handle events
  • Domain service clients: thin wrappers around AgentService, CallService, RecordingService, ScrubListService, ConfigService gRPC stubs

Removed:

  • Micronaut framework (context, core, serde, http, validation, aot)
  • GateClientAbstract, GateClientJobQueue, GateClientEventStream, GateClientPollEvents, GateClientJobStream (all replaced by WorkStreamClient)
  • PluginInterface god interface (replaced by JobHandler + EventHandler)
  • 20+ model wrapper classes (use proto types directly)
  • Config/DiagnosticsService (replaced by ExileConfig/ConfigService)
  • StructuredLogger/LogCategory (use SLF4J directly)
  • Jackson, Reactor, HikariCP, Jakarta, SnakeYAML dependencies
  • demo module (was Micronaut app with copy-pasted controllers)

What integrations need to change:

  • Implement JobHandler (return results) instead of PluginInterface (void)
  • Implement EventHandler instead of PluginInterface event methods
  • Use ExileClient.builder() instead of manually creating GateClient + Plugin + GateClientJobQueue + GateClientEventStream
  • Use proto types directly instead of model wrappers
  • Use domain service clients (client.agents(), client.calls()) instead of GateClient for unary RPCs

namtzigla and others added 8 commits April 7, 2026 21:22
Remove all Micronaut dependencies. The library is now plain Java 21
with only gRPC, protobuf, and SLF4J.

Architecture:
- ExileClient: builder-based entry point, composes WorkStream + services
- ExileConfig: immutable connection config (certs, endpoint)
- WorkStreamClient: single bidirectional gRPC stream implementing the
  v3 WorkStream protocol (replaces GateClientJobQueue, GateClientEventStream,
  and SubmitJobResults)
- JobHandler interface: integrations implement to process jobs, return
  results directly (no more calling gateClient.submitJobResults internally)
- EventHandler interface: integrations implement to handle events
- Domain service clients: thin wrappers around AgentService, CallService,
  RecordingService, ScrubListService, ConfigService gRPC stubs

Removed:
- Micronaut framework (context, core, serde, http, validation, aot)
- GateClientAbstract, GateClientJobQueue, GateClientEventStream,
  GateClientPollEvents, GateClientJobStream (all replaced by WorkStreamClient)
- PluginInterface god interface (replaced by JobHandler + EventHandler)
- 20+ model wrapper classes (use proto types directly)
- Config/DiagnosticsService (replaced by ExileConfig/ConfigService)
- StructuredLogger/LogCategory (use SLF4J directly)
- Jackson, Reactor, HikariCP, Jakarta, SnakeYAML dependencies
- demo module (was Micronaut app with copy-pasted controllers)

What integrations need to change:
- Implement JobHandler (return results) instead of PluginInterface (void)
- Implement EventHandler instead of PluginInterface event methods
- Use ExileClient.builder() instead of manually creating GateClient +
  Plugin + GateClientJobQueue + GateClientEventStream
- Use proto types directly instead of model wrappers
- Use domain service clients (client.agents(), client.calls()) instead
  of GateClient for unary RPCs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No protobuf generated interfaces are exposed in the public API.
Integrations only see plain Java records, enums, and java.time types.

Added:
- model/ package: Pool, Record, Field, Filter, Agent, Skill, CallType,
  AgentState, TaskData, Page<T> — all Java records
- model/event/ package: AgentCallEvent, TelephonyResultEvent,
  AgentResponseEvent, TransferInstanceEvent, CallRecordingEvent,
  TaskEvent — Java records with java.time.Duration/Instant fields
- internal/ProtoConverter: bidirectional conversion between proto types
  and the Java model. Only class that touches generated proto classes.
- service/ServiceFactory: creates service instances with package-private
  constructors so ManagedChannel doesn't leak

Changed:
- JobHandler: methods take/return plain Java types (List<Pool>,
  Page<Record>, Map<String,Object>) instead of proto messages
- EventHandler: methods take event records instead of proto messages
- All 5 service clients: public methods use Java types only,
  constructors are package-private
- WorkStreamClient: converts between proto and model at the boundary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New public type StreamStatus with Phase enum tracking the stream
lifecycle: IDLE → CONNECTING → REGISTERING → ACTIVE → RECONNECTING → CLOSED.

Includes: clientId, connectedSince, lastDisconnect, lastError,
inflight count, completedTotal, failedTotal, reconnectAttempts.

Accessible via ExileClient.streamStatus() — returns a snapshot.
Uses isHealthy() convenience method for simple health checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New module that eliminates the 400-500 line ConfigChangeWatcher
copy-pasted across finvi, capone, latitude, and debtnet.

Components:

- ConfigParser: reads the Base64-encoded JSON config file
  (com.tcn.exiles.sati.config.cfg) and produces an ExileConfig.
  No Jackson dependency — minimal built-in JSON parser.

- ConfigFileWatcher: watches /workdir/config for file changes
  using directory-watcher. Fires Listener callbacks on
  create/modify/delete. Handles directory discovery and creation.

- CertificateRotator: checks certificate expiration and rotates
  via the gate config service. Writes rotated cert back to the
  config file (triggering watcher reload).

- ExileClientManager: single-tenant lifecycle manager. Watches
  config file, creates/destroys ExileClient on changes, detects
  org changes, schedules cert rotation. One builder replaces the
  entire ConfigChangeWatcher + manual bean wiring.

- MultiTenantManager: multi-tenant lifecycle manager for
  velosidy-like deployments. Polls a tenant provider, reconciles
  desired vs actual tenants, creates/destroys ExileClients.

Dependencies: sati-core + directory-watcher (single external dep).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The api() dependency configuration requires the java-library plugin.
Both modules were missing it, causing build failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace pre-built buf.build Maven artifacts with local code generation
using the build.buf gradle plugin. Proto stubs are now generated from
source at build time instead of depending on published artifacts.

Changes:
- Add build.buf plugin (v0.11.0) to core module
- Add buf.yaml, buf.gen.yaml, buf.lock for code generation config
- Copy v3 proto files into core/proto/ (source of truth)
- Pin protoc java plugin to v28.3 and grpc java to v1.68.1
- Add java_multiple_files=true to all proto files (generates top-level
  classes instead of nested inner classes)
- Bump protobuf-java to 4.28.3 (matches remote plugin output)
- Move grpc-netty-shaded from runtimeOnly to implementation
  (ChannelFactory needs compile-time access)
- Rename model.Record to model.DataRecord (avoids collision with
  java.lang.Record)
- Fix WorkItem.task oneof field name collision (task→exile_task)
- Remove exileapi Maven version pins from gradle.properties
- Apply spotless and buf format fixes

The build now generates stubs from proto source, so sati can build
independently of whether exileapi v3 is published to buf.build BSR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 tests across 7 test classes, all passing.

core module (53 tests):
- BackoffTest: exponential growth, jitter bounds, cap at 30s, reset
- ProtoConverterTest: round-trip conversion for Duration, Timestamp,
  CallType, AgentState, Pool (all statuses), DataRecord, Field,
  Filter (all operators), Struct/Map (nested, lists, nulls),
  TaskData, Agent (with/without connected party), Skill, and all
  event types (AgentCall, TelephonyResult, CallRecording, Task)
- ModelTest: Page.hasMore(), record equality, enum value counts
- StreamStatusTest: isHealthy() for all phases
- ExileConfigTest: builder, default port, null validation

config module (9 tests):
- ConfigParserTest: Base64 encoded, raw JSON, missing port, missing
  endpoint, missing certs, garbage input, empty input, trailing
  newline, escaped newlines in certificate values

Added grpc-testing and grpc-inprocess test dependencies to core.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace local buf proto generation with pre-built artifacts from
buf.build BSR, now that exileapi v3 is merged to master.

Changes:
- Remove build.buf gradle plugin, local proto files, buf.yaml/gen/lock
- Add exileapi version pins in gradle.properties pointing to the
  published v3 artifacts (commit 461190882f3b)
- Update all Java imports from old sub-packages (tcnapi.exile.types.v3,
  tcnapi.exile.worker.v3, etc.) to flat package with BSR prefix
  (build.buf.gen.tcnapi.exile.v3)
- Service files use FQN for proto types to avoid ambiguity with
  identically-named model classes (Pool, Agent, Filter, etc.)
- Move buf Maven repo declaration to root allprojects block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant