Skip to content

feat(channel): let an agent hold a conversation on a chat platform - #527

Merged
yordis merged 56 commits into
mainfrom
telegram
Aug 5, 2026
Merged

feat(channel): let an agent hold a conversation on a chat platform#527
yordis merged 56 commits into
mainfrom
telegram

Conversation

@yordis

@yordis yordis commented Aug 4, 2026

Copy link
Copy Markdown
Member
  • The agent platform had no human-facing surface: every path to an agent started at a machine webhook, so there was no way for a person to simply talk to one.
  • Telegram is the first surface, but a person, a chat, and an agent session are named channel-neutrally on purpose, so adding a second platform does not mean relearning who is talking to whom.
  • A long-lived chat needs a way to start over without abandoning the chat itself, which is why resetting a session belongs to the surface rather than to an operator.
  • An update larger than the NATS max payload reaches the stream as an empty body with claim headers, and nothing on the consuming side redeemed it, so real messages were being destroyed while the log blamed the parser.

yordis added 13 commits August 1, 2026 18:45
…astructure

Squashes the telegram branch history into one commit so it lands as a single reviewable unit rather than 113 incremental commits accumulated during development.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
A conversation outlives the ephemeral session behind it, so a user needs a way
to abandon accumulated context without losing their binding to an agent.
Rotating on any prompt failure also discarded live conversations that were
merely unreachable for a moment.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The data model and the architecture doc both call this axis a channel, while
only the crate and three type names said chat. That read the boundary as
chat-app-specific when it covers email, SMS, and push just as well, and it
gave no signal about the surfaces that genuinely do not belong.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…surface

Every crate directory in the workspace shares one vocabulary with the crates
inside it, and chat/ became the sole exception the moment the shared crate
stopped being called chat. A half-finished rename reads worse than none: the
crate doc described its own sibling as a channel binary named chat-bridge.

Deriving the durable consumer identity from the crate name meant any future
rename could silently reset a deployment's position in the stream, and a first
run replayed history the agent had no business answering.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…space does

Conversation ids are KV keys, so a random v4 threw away the creation
ordering every other durable id in the workspace gets for free. The v4
here was the last one outside the correlation-token cases where ordering
genuinely does not matter.

Taking the generator as an argument keeps the crate honest about the
clock discipline it already documents one field away.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Internal notes were only staying out of the index because of a
machine-local global ignore, so the repo did not protect itself on a
fresh clone or in CI. Ignoring .dockerignore was the inverse mistake:
build context config belongs in version control.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The eager claim-check the routing design assumed was never built, and it
forced every turn to pay for media the agent may never read while pushing
platform credentials toward a component whose value is being generic.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…t uses it

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
… of identity

The group semantics were only discoverable by reading the pipeline, and the
one place they surprise a reader is authorization: linking a room authorizes
everyone in it, present and future. The routing page was also unreachable from
the site nav, so none of this vocabulary was findable.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The document carried the shipped topology and a speculative one side by
side, both in the present tense, so a reader could not tell which one
runs. That ambiguity produced the same question twice about subjects
that do not exist. Rejected alternatives belong in the decision list,
not in a second diagram.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…k hole

The rewrite described streaming edit-in-place output that the Telegram
outbound surface has no method for, restating the kind of unverified
claim it was meant to remove. Separately, both this doc and ADR#0044
asserted the gateway carries no object-store dependency while its
publish path has claim-checked since before either was written, which
hid a consumer that drops oversized updates and blames the parser.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
An update over the NATS max payload arrives with an empty body and claim
headers, so the bridge read it as unparseable and acked it: the only copy
of a real user message was destroyed silently, and the log blamed the
parser. A redeem failure now leaves the message for redelivery, because
the bytes exist and the failure is recoverable.

The consumer is bound to the bucket it reads so a wrong bucket reports
itself instead of masquerading as data loss, and the bucket default moved
next to the headers it travels with so the two sides cannot drift apart.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 04:50
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New long-running bridge holds the bot token, mutates conversational KV, and drives ACP sessions with complex redelivery/session-repair behavior; claim resolution is security- and correctness-sensitive but covered by extensive integration tests.

Overview
Introduces a human-facing chat path for agents: a new channel-bridge-telegram worker consumes the gateway’s raw TELEGRAM JetStream stream, normalizes updates into channel-neutral InboundEvents, resolves endpoints → principals → conversations in JetStream KV, and prompts agents via AgentPort (AcpPort over acp-nats). Replies are buffered from ACP session notifications and sent with the Telegram Bot API (chunked at 4096 UTF-16 units); /new / /reset rotate sessions without dropping the conversation.

Shared logic lives in trogon-channel: endpoint/principal/conversation types, ChannelStore, command triggers, and the AgentPort contract (session create/prompt/release with cautious “session lost” repair). The pipeline redeems claim-check bodies before parsing (fixing oversized updates that previously looked empty), seeds principals from env, and leaves failed turns unacked for redelivery while acking unrecoverable drops.

Docs add Multi-Channel Agent Routing, glossary entries, and ADR#0044 (dedicated downloader + channel_media KV for inbound media—designed, not implemented; attachments stay empty in the parser). Workspace gains teloxide and minor .gitignore entries.

Reviewed by Cursor Bugbot for commit 15fc0d2. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds a channel-neutral Rust routing library, a Telegram ACP bridge, JetStream-backed channel and claim storage, runtime integration, and documentation for multi-channel routing and inbound media handling.

Changes

Channel-neutral contracts

Layer / File(s) Summary
Routing contracts and persistence
rsworkspace/crates/channel/trogon-channel/...
Adds validated endpoints, principals, events, commands, conversations, agent sessions, render commands, and race-safe JetStream KV conversation bindings.
Claim-bucket transport
rsworkspace/crates/platform/trogon-nats/..., rsworkspace/crates/platform/trogon-gateway/...
Adds typed claim buckets and bindings, validates claim headers, centralizes JetStream error classification, and updates gateway wiring and tests.

Telegram channel bridge

Layer / File(s) Summary
Telegram adapters
rsworkspace/crates/channel/channel-bridge-telegram/...
Adds environment configuration, Telegram update parsing, outbound messaging, UTF-16-safe rendering, ACP session lifecycle handling, and adapter tests.
Bridge runtime and pipeline
rsworkspace/crates/channel/channel-bridge-telegram/src/main.rs, rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs, rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline/tests.rs
Adds durable-consumer startup, principal seeding, authorization, conversation routing, session repair, response delivery, redelivery, and acknowledgment behavior.

Documentation and integration

Layer / File(s) Summary
Architecture and operational records
docs/architecture/..., docs/adr/..., docs/glossary/..., docs/.vitepress/...
Adds multi-channel routing documentation, ADR#0044, glossary entries, and navigation links.
Workspace and service integration
rsworkspace/Cargo.toml, rsworkspace/crates/platform/trogon-telemetry/..., .gitignore
Registers channel crates and Telegram dependencies, adds the Telegram telemetry service name, and expands local ignore rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Telegram
  participant Gateway
  participant JetStream
  participant TelegramBridge
  participant ChannelStore
  participant ACPAgent
  Telegram->>Gateway: Publish inbound update
  Gateway->>JetStream: Store claim and publish message
  TelegramBridge->>JetStream: Consume inbound message
  TelegramBridge->>ChannelStore: Resolve principal and conversation
  TelegramBridge->>ACPAgent: Create or prompt session
  ACPAgent-->>TelegramBridge: Stream response chunks
  TelegramBridge->>Telegram: Send chunked response
  TelegramBridge->>JetStream: Acknowledge message
Loading

Possibly related PRs

Suggested reviewers: copilot

Poem

A rabbit routes messages through the moonlit stream,
Telegram hops where channel-neutral contracts gleam.
Claims find their buckets, sessions gently start,
ACP carries prompts from bridge to agent heart.
With durable hops and UTF-16 replies,
The routing burrow grows beneath the skies.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enabling agents to hold conversations on chat platforms through a channel bridge.
Description check ✅ Passed The description explains the Telegram chat bridge, channel-neutral modeling, session reset behavior, and oversized NATS update handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch telegram

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs
Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs
Comment thread devops/docker/compose/compose.yml Outdated
Comment thread devops/docker/compose/compose.yml Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a human-facing “channel” surface (Telegram first) so a person can hold a long-lived conversation with an agent, including session reset semantics at the surface layer, and fixes claim-check redemption so oversized NATS messages are not silently destroyed.

Changes:

  • Introduces the channel-neutral routing/identity/state crate (trogon-channel) plus a Telegram bridge binary (channel-bridge-telegram) that consumes raw gateway updates, routes to an agent via ACP, and renders replies back to Telegram.
  • Adds a consumer-side claim-check abstraction (ClaimResolver) and a protocol-level default claim bucket constant to ensure oversized messages are redeemed before parsing.
  • Documents the multi-channel routing architecture and adds container/build wiring for the new bridge service.

Reviewed changes

Copilot reviewed 43 out of 45 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rsworkspace/crates/platform/trogon-telemetry/src/service_name.rs Adds ServiceName::ChannelBridgeTelegram for telemetry attribution.
rsworkspace/crates/platform/trogon-telemetry/src/service_name/tests.rs Extends service-name string/display tests for the new service.
rsworkspace/crates/platform/trogon-nats/src/jetstream/object_store.rs Adds NatsObjectStore::bind for opening an existing object-store bucket without provisioning.
rsworkspace/crates/platform/trogon-nats/src/jetstream/mod.rs Re-exports DEFAULT_CLAIM_BUCKET and new ClaimResolver.
rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check.rs Introduces ClaimResolver and bucket-mismatch error to make claim redemption safer for consumers.
rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check/integration_tests.rs Adds integration tests covering claim resolution and bucket mismatch behavior.
rsworkspace/crates/platform/trogon-nats/src/constants.rs Adds DEFAULT_CLAIM_BUCKET as the protocol-level default bucket name.
rsworkspace/crates/platform/trogon-gateway/src/constants.rs Removes gateway-local claim bucket constant in favor of DEFAULT_CLAIM_BUCKET.
rsworkspace/crates/platform/trogon-gateway/src/main.rs Updates gateway claim-check bucket provisioning/publishing to use DEFAULT_CLAIM_BUCKET.
rsworkspace/crates/channel/trogon-channel/Cargo.toml New crate manifest for channel-neutral routing/state types.
rsworkspace/crates/channel/trogon-channel/src/lib.rs Declares channel-neutral modules and re-exports the public API surface.
rsworkspace/crates/channel/trogon-channel/src/agent_port.rs Defines the protocol-neutral AgentPort trait and session lifecycle types.
rsworkspace/crates/channel/trogon-channel/src/command.rs Implements bridge-owned command parsing (/new, /reset, configurable).
rsworkspace/crates/channel/trogon-channel/src/conversation.rs Defines conversation/session identity and durable conversation record.
rsworkspace/crates/channel/trogon-channel/src/endpoint.rs Defines endpoint/principal identity with safe-token validation and tests.
rsworkspace/crates/channel/trogon-channel/src/event.rs Defines the channel-neutral inbound event and attachment handle types.
rsworkspace/crates/channel/trogon-channel/src/render.rs Defines the channel-neutral render command vocabulary.
rsworkspace/crates/channel/trogon-channel/src/store.rs Implements JetStream KV-backed registries for principals, endpoints, bindings, and conversations.
rsworkspace/crates/channel/channel-bridge-telegram/Cargo.toml New Telegram bridge binary manifest.
rsworkspace/crates/channel/channel-bridge-telegram/src/main.rs Bridge entrypoint: config, NATS/JetStream wiring, claim resolver binding, consumer loop, ACP client tasks.
rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs Env-driven bridge configuration (stream names, claim bucket, triggers, seed principals, ACP config).
rsworkspace/crates/channel/channel-bridge-telegram/src/parse.rs Normalizes Telegram Update into channel-neutral InboundEvent.
rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs End-to-end message handling: redeem claim-checked bodies, authorize, manage sessions, prompt agent, render reply.
rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline_tests.rs End-to-end tests using testcontainers NATS to validate routing, resets, and claim-check redemption behavior.
rsworkspace/crates/channel/channel-bridge-telegram/src/outbound.rs Defines the outbound Telegram API seam used by the pipeline.
rsworkspace/crates/channel/channel-bridge-telegram/src/render.rs Buffers streamed ACP session notifications and chunks long text to Telegram’s size limit.
rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs Implements AgentPort over ACP, including session capability-gating and prompt metadata shaping.
rsworkspace/Cargo.toml Adds workspace members/deps for trogon-channel, channel-bridge-telegram, and teloxide.
docs/architecture/multi-channel-agent-routing.md New architecture doc describing the multi-channel routing model and operational characteristics.
docs/glossary/index.md Adds “Channels and conversations” glossary section and entries list.
docs/glossary/channel.md Defines “Channel” glossary entry.
docs/glossary/endpoint.md Defines “Endpoint” glossary entry.
docs/glossary/principal.md Defines “Principal” glossary entry.
docs/glossary/conversation.md Defines “Conversation” glossary entry.
docs/glossary/binding.md Defines “Binding” glossary entry.
docs/adr/index.md Adds ADR#0044 to ADR index.
docs/adr/0044-inbound-media-fetch-out-of-band.md New ADR draft specifying out-of-band inbound media download design.
docs/.vitepress/helpers.ts Adds glossary section ordering for channels/conversations.
docs/.vitepress/config.mts Adds nav link for the multi-channel routing architecture doc.
devops/docker/compose/services/channel-bridge-telegram/Dockerfile Adds Docker build for the Telegram bridge binary.
devops/docker/compose/compose.yml Adds channel-bridge-telegram service to compose profile wiring.
.github/workflows/canary-container-images.yml Includes channel-bridge-telegram in canary container build matrix.
.github/canary-container-services.json Adds canary container service metadata for channel-bridge-telegram.
.gitignore Adds ignores for editor backups, logs, and NATS creds.
Suppressed comments (1)

rsworkspace/crates/channel/trogon-channel/src/store.rs:46

  • ensure_bucket currently falls back to creating the bucket on any get_key_value error. That means timeouts/permission errors (or other JetStream failures) get misinterpreted as "missing bucket" and may lead to an attempted create (which can update existing resources). It should only create on a definite not-found, and otherwise return an error (e.g. ChannelStoreError::OpenBucket).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs
Comment thread rsworkspace/crates/channel/trogon-channel/src/store.rs
The branch had fallen behind far enough that a dependency bump on main and the
branch's own added crates were resolving the lockfile differently. Taking main's
resolution now keeps the review focused on the channel work instead of on a
lockfile argument.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 05:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 45 changed files in this pull request and generated no new comments.

Suppressed comments (3)

rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline_tests.rs:489

  • This uses error.to_string().contains(...) to assert on an error message. The workspace’s ERROR_STRING_COMPARISON lint flags string probes derived from Error::to_string(). If you want to keep a message assertion in this test, format the error via Display instead of calling to_string().
    let error = pipeline.handle_message(&msg).await.expect_err("must not be acked");
    assert!(error.to_string().contains("failed to redeem claim-checked update"));

rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check/integration_tests.rs:361

  • This assertion probes ClaimResolveError’s Display text via error.to_string().contains(...). The workspace has a deny-by-default lint (ERROR_STRING_COMPARISON) that flags string probes on values derived from Error::to_string(), and the check is redundant with the typed BucketMismatch match above.
    rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs:97
  • map_err(|e| anyhow!("... {e}")) stringifies the underlying claim-resolution error, which discards its typed context and nudges callers/tests toward message matching. Since anyhow::Context is already in scope, prefer .context(...) so the original error remains available as the source.
        let body = self
            .claims
            .resolve(msg.headers.as_ref(), msg.payload.clone())
            .await
            .map_err(|e| anyhow::anyhow!("failed to redeem claim-checked update: {e}"))?;

yordis added 2 commits August 4, 2026 01:49
…session

No protocol carries a distinct "no such session" code, so the code that
rejects an unknown session id is also the code an agent uses to reject a
prompt it simply will not answer. Betting the conversation's session
pointer on that guess meant an ordinary refusal rotated the conversation
onto a fresh session and failed anyway, leaving the user talking to an
agent that had lost the thread.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
"Unset" reaches a process as the empty string more often than as an
absent variable: Compose renders it that way, and so does a Kubernetes
secret reference to a missing key. Reading that as configured meant the
bridge started, reported nothing wrong, and then failed on its first Bot
API call with nothing at startup to point at the cause.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 4, 2026 05:49
The reference linter requires every ADR mention to be a markdown link, so
a bare one fails the branch on a formatting rule rather than on anything
about the design.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs
Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs
Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/render.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 46 changed files in this pull request and generated 3 comments.

Suppressed comments (2)

rsworkspace/crates/channel/trogon-channel/src/store.rs:45

  • ensure_bucket currently creates the bucket whenever get_key_value returns Err, which can mask real JetStream failures (e.g. auth/network issues) as "bucket missing" and then report the create error instead of the original cause. It should only create when the get failed specifically because the bucket does not exist, and otherwise surface the get error.
    rsworkspace/crates/channel/trogon-channel/src/store.rs:13
  • ensure_bucket currently treats any get_key_value error as "bucket is missing" and goes on to create it, but ChannelStoreError has no variant to represent an "open existing bucket" failure. Adding a dedicated GetBucket variant makes non-not-found failures reportable and lets ensure_bucket distinguish missing buckets from real JetStream errors (permission/network/etc.).

Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs
The service was left naming a Dockerfile the tree no longer had, so the profile
that exists to run this bridge locally could not bring it up at all.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 03:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
rsworkspace/crates/channel/trogon-channel/src/store.rs (1)

381-392: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Consider a revision-checked write for the session pointer.

update_conversation uses put, so the last writer wins. create_conversation deliberately uses create and update with a revision to avoid exactly this. If two bridge replicas process redelivered messages for the same conversation, one current_session write can overwrite the other. The overwritten session is then held at the agent with nothing pointing at it, which is the condition ReleaseReason::Replaced exists to avoid.

If a single-writer-per-conversation deployment is guaranteed, record that assumption here instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rsworkspace/crates/channel/trogon-channel/src/store.rs` around lines 381 -
392, Update update_conversation to perform a revision-checked conditional write
for current_session updates, reusing the same create/update revision mechanism
as create_conversation so concurrent replica writes cannot overwrite each other;
otherwise explicitly document the guaranteed single-writer-per-conversation
assumption at this method.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/0044-inbound-media-fetch-out-of-band.md`:
- Around line 181-185: Before documenting the readiness and terminal-record
invariants in this ADR, add a durable retry or reconciliation path for writing
terminal failed channel_media records, including cases where the downloader
acknowledges or stops after final delivery and where the initial KV write fails.
Ensure failed-record persistence completes or is retried independently of
JetStream redelivery so channel_media is not left absent.

In `@rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check.rs`:
- Around line 142-147: Correct the documentation for UnnamableBucket to state
that the header was present but contained a value that is not a valid bucket
name, rather than saying it lacked a bucket name. Leave resolve’s absent-header
handling and the existing error text unchanged.

---

Nitpick comments:
In `@rsworkspace/crates/channel/trogon-channel/src/store.rs`:
- Around line 381-392: Update update_conversation to perform a revision-checked
conditional write for current_session updates, reusing the same create/update
revision mechanism as create_conversation so concurrent replica writes cannot
overwrite each other; otherwise explicitly document the guaranteed
single-writer-per-conversation assumption at this method.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6cfd2a1-267e-446e-b8be-630b68f9919e

📥 Commits

Reviewing files that changed from the base of the PR and between 6858891 and 05e335a.

⛔ Files ignored due to path filters (1)
  • rsworkspace/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • .gitignore
  • devops/docker/compose/services/channel-bridge-telegram/Dockerfile
  • docs/adr/0044-inbound-media-fetch-out-of-band.md
  • docs/architecture/multi-channel-agent-routing.md
  • rsworkspace/crates/channel/channel-bridge-telegram/Cargo.toml
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port_tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/config_tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/agent_port.rs
  • rsworkspace/crates/channel/trogon-channel/src/agent_port_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/conversation_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/event.rs
  • rsworkspace/crates/channel/trogon-channel/src/event_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/lib.rs
  • rsworkspace/crates/channel/trogon-channel/src/store.rs
  • rsworkspace/crates/channel/trogon-channel/src/store_tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_bucket.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check/integration_tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/mod.rs
💤 Files with no reviewable changes (1)
  • rsworkspace/crates/channel/channel-bridge-telegram/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (10)
  • devops/docker/compose/services/channel-bridge-telegram/Dockerfile
  • rsworkspace/crates/channel/trogon-channel/src/conversation_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/lib.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port_tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/mod.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline_tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/event.rs
  • rsworkspace/crates/channel/trogon-channel/src/event_tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check/integration_tests.rs
  • .gitignore

Comment thread docs/adr/0044-inbound-media-fetch-out-of-band.md Outdated
Comment thread rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check.rs Outdated
yordis added 8 commits August 5, 2026 03:48
These crates were the last place where finding a module's tests meant following
a path attribute to a sibling file, instead of the layout every other crate in
the workspace already uses.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Nothing builds this image at pull request time and the canary workflow only
fires on a push to main, so a build that cannot succeed lands as a red main
rather than as a failed check. The Compose service goes with the file it built
from, since a profile naming a Dockerfile the tree does not have cannot come up
either.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The check had to be spelled as a constant, which meant widening the repo's own
rule about where constants may live in order to accept it, and being evaluated
at compile time it left the file reporting lines no test can reach. What it
guaranteed is one assertion wide.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The variant read as though the header had been left out, which is a different
deployment problem from the one it is raised for.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
The decision read as though every abandoned handle ends in a record, so a reader
could conclude that waiting on one is safe. Liveness rests on the reader's
deadline, and only the explanation an agent receives depends on the record.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
One of the two unwind paths could not be reached from any test, because the
create it follows performs that same read internally and cannot report a
conflict unless it already succeeded.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Nothing showed that an agent handing over an id too long for a subject token is
told which rule it broke, or that an id sitting exactly at the limit can still
address a session.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Matching a fragment of the message left the half that names the variable to edit
unasserted, which is the half an operator needs.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 07:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/adr/0044-inbound-media-fetch-out-of-band.md (1)

135-151: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not promise a terminal record after documenting cases that produce none.

The ADR says the downloader writes before acknowledgement. It also says a KV outage or downloader crash can exhaust delivery attempts with no record. Therefore, “ends as a terminal record” and “Every handle ... leaves a terminal record” are not guaranteed. Add durable reconciliation, or qualify the invariant and consequence so the reader deadline is the only guaranteed fallback when the record write fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0044-inbound-media-fetch-out-of-band.md` around lines 135 - 151,
Revise the ADR’s terminal-record claims to distinguish the intended outcome from
the guaranteed invariant: remove or qualify statements such as “ends as a
terminal record” and “Every handle ... leaves a terminal record” to acknowledge
that KV failures or downloader crashes may leave no record after redelivery
exhaustion. State that the reader deadline is the guaranteed liveness fallback
when terminal-record persistence fails, and avoid promising durable
reconciliation unless it is actually implemented.
🧹 Nitpick comments (1)
rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline/tests.rs (1)

612-618: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the error variant so the test proves the claim failed.

Line 613 asserts only is_err(). This test seeds no principal, so several later stages would also fail, and the assertion would still pass if the claim resolved and authorization refused instead. The sibling tests already pin the variant at lines 871, 1117, and 1127. Pin PipelineError::Claim here for the same reason.

♻️ Proposed tightening
     let msg = next_message(&mut messages).await;
-    assert!(pipeline.handle_message(&msg).await.is_err(), "must not be acked");
+    let error = pipeline
+        .handle_message(&msg)
+        .await
+        .expect_err("an unredeemable claim must not be acked");
+    assert!(
+        matches!(error, PipelineError::Claim(_)),
+        "the claim failure must surface, got {error:?}"
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline/tests.rs`
around lines 612 - 618, Update the failure assertion in the test around
next_message and pipeline.handle_message to match the specific
PipelineError::Claim variant instead of only checking is_err(). Preserve the
existing “must not be acked” assertion and subsequent empty prompt/outbound
checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/0044-inbound-media-fetch-out-of-band.md`:
- Around line 196-201: Update the readiness lifecycle contract in the ADR by
choosing either persisted pending records at ingestion or absence-as-unresolved
with only ready and failed as explicit records. Apply the chosen model
consistently to the downloader lifecycle, reader deadline behavior, and agent
download tool, removing contradictory statements about absent records and
explicit readiness states.

---

Duplicate comments:
In `@docs/adr/0044-inbound-media-fetch-out-of-band.md`:
- Around line 135-151: Revise the ADR’s terminal-record claims to distinguish
the intended outcome from the guaranteed invariant: remove or qualify statements
such as “ends as a terminal record” and “Every handle ... leaves a terminal
record” to acknowledge that KV failures or downloader crashes may leave no
record after redelivery exhaustion. State that the reader deadline is the
guaranteed liveness fallback when terminal-record persistence fails, and avoid
promising durable reconciliation unless it is actually implemented.

---

Nitpick comments:
In `@rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline/tests.rs`:
- Around line 612-618: Update the failure assertion in the test around
next_message and pipeline.handle_message to match the specific
PipelineError::Claim variant instead of only checking is_err(). Preserve the
existing “must not be acked” assertion and subsequent empty prompt/outbound
checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 79aa5eac-afa4-4e63-b456-885ab1d256b3

📥 Commits

Reviewing files that changed from the base of the PR and between 05e335a and 3d47759.

📒 Files selected for processing (30)
  • docs/adr/0044-inbound-media-fetch-out-of-band.md
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port/tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/config/tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/parse.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/parse/tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline/tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/render.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/render/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/agent_port.rs
  • rsworkspace/crates/channel/trogon-channel/src/agent_port/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/command.rs
  • rsworkspace/crates/channel/trogon-channel/src/command/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/command_trigger_input.rs
  • rsworkspace/crates/channel/trogon-channel/src/command_trigger_input/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/conversation.rs
  • rsworkspace/crates/channel/trogon-channel/src/conversation/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/endpoint.rs
  • rsworkspace/crates/channel/trogon-channel/src/endpoint/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/event.rs
  • rsworkspace/crates/channel/trogon-channel/src/event/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/safe_token.rs
  • rsworkspace/crates/channel/trogon-channel/src/safe_token/tests.rs
  • rsworkspace/crates/channel/trogon-channel/src/store.rs
  • rsworkspace/crates/channel/trogon-channel/src/store/tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_bucket.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_bucket/tests.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_check.rs
🚧 Files skipped from review as they are similar to previous changes (13)
  • rsworkspace/crates/channel/channel-bridge-telegram/src/acp_port.rs
  • rsworkspace/crates/channel/trogon-channel/src/command_trigger_input.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/parse.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_bucket/tests.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/render.rs
  • rsworkspace/crates/channel/trogon-channel/src/agent_port.rs
  • rsworkspace/crates/channel/trogon-channel/src/endpoint.rs
  • rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs
  • rsworkspace/crates/channel/trogon-channel/src/safe_token.rs
  • rsworkspace/crates/platform/trogon-nats/src/jetstream/claim_bucket.rs
  • rsworkspace/crates/channel/trogon-channel/src/store.rs
  • rsworkspace/crates/channel/trogon-channel/src/event.rs
  • rsworkspace/crates/channel/trogon-channel/src/conversation.rs

Comment thread docs/adr/0044-inbound-media-fetch-out-of-band.md
Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/config.rs
yordis added 2 commits August 5, 2026 13:41
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 17:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/pipeline.rs
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 18:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 188716d. Configure here.

Comment thread rsworkspace/crates/channel/channel-bridge-telegram/src/parse.rs Outdated
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 22:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@yordis
yordis merged commit 072d98b into main Aug 5, 2026
15 checks passed
@yordis
yordis deleted the telegram branch August 5, 2026 23:38
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.

3 participants