Architecture

One persisted event path for web and Channels.

The Runtime assigns a Thread before its run. IntelligenceAgentRunner writes the AG-UI event stream. Learning derives bounded snapshots from those events after the exact run ends.

Runtime path

Assignment happens on the normal run boundary

Web or Channel runOne Runtime receives the Thread, run, agent, and user identity.
→
Learning hooklearning.containerId resolves once to a stable ID or no assignment.
→
Thread create / lockapp-api checks Project ownership and binds an unassigned Thread once.
→
IntelligenceAgentRunnerThe same runner persists canonical AG-UI events for both surfaces.
A matching ID is idempotent. A missing or foreign-Project ID returns 404. A different ID on an assigned Thread returns 409.

Learning path

Derived snapshots never replace the source

Terminal agent_runThe harvester finds a completed assigned run in Postgres.
→
Exact event reductionOnly events tied to that agent_run become the compact Learning input.
→
S3 + PostgresObject storage holds derived bytes; scoped rows hold identity and digests.
→
Queue + Jobpg-boss and a scoped lock launch one isolated Learning attempt with the configured schema and pool size.
Flue returns cited Insights and Skill changes. One database transaction writes provenance and the final run state.

Hard boundaries

What blocks cross-talk and duplicate work

Scope stays explicit.

Container, run, snapshot, lock, queue payload, Insight, and Skill paths carry Organization, Project, and Container identity.

Thread assignment cannot move.

The API and database accept the first valid assignment, allow the same ID again, and reject a change.

The run input freezes once.

The first attempt chooses at most 500 unconsumed snapshots, stores a manifest plus digest, and reuses it on retry.

One Container learns at a time.

A scoped database lock remains held until the Kubernetes Job ends, including across reconciler replicas.

One failed attempt cannot retry siblings.

Each queue callback settles one job. Local worker concurrency keeps unrelated Containers moving.

Producer failures can recover.

A failed lazy connection clears its cache before any run is created. If queue send rejects, only the creator’s exact queued run fails, so a later request can create a new run.