Skip to content

Allow authenticated message sending over REST - #106

Merged
timkpaine merged 1 commit into
Point72:mainfrom
timkpaine:feature/sendable-messages-out
Oct 7, 2026
Merged

timkpaine merged 1 commit into
Point72:mainfrom
timkpaine:feature/sendable-messages-out

Conversation

@timkpaine

Copy link
Copy Markdown
Member

What this adds

allow_send_messages, off by default, makes messages_out a send channel so an authenticated caller can ask the bot to deliver a message:

curl -X POST http://localhost:8000/api/v1/send/messages_out \
  -H 'Content-Type: application/json' -H "token: $CSP_BOT_API_KEY" \
  -d '[{"content": "<messageML>deploy finished</messageML>",
        "backend": "symphony", "channel": {"id": "..."}}]'

csp-bot already serves its channels over csp-gateway's REST API, but messages_out was read-only, so a job had no way in.

Two fixes were needed for a posted message to arrive

The publish loop read the wrong edge. It filtered the edge built locally from command responses rather than the channel, so anything else feeding messages_out never reached a backend. It now reads the channel, which the gateway merges with the send adapter after every module has connected.

This is not a cycle, which was the main thing I wanted to be sure of: subscribe() is a source and publish() a sink, so nothing here flows back into what composes the channel. Confirmed by building and running the graph rather than reasoning about it.

The backend filter ignored the field a caller would set. _filter_messages_for_backend only looked at metadata["backend"], which inbound tagging writes. A message posted over REST sets chatom's own backend field, so it was accepted by the send endpoint with a 200 and then silently dropped — the worst failure mode. The filter now falls back to that field, with metadata still taking precedence.

That is the same class of bug TestMetadataPropagation already documents, where a command returned a Message with backend set but no metadata. That was fixed at the producer; this fixes it at the filter, so any future producer is covered. I updated that class's docstring, which claimed the filter looks at metadata and not msg.backend.

Why it is off by default

Turning it on makes the bot relay anything that can reach its API. MountAPIKeyMiddleware is mounted by default but rejects nothing until it has a key, so the documentation pairs the two settings.

Verification

Against a live Symphony pod:

  • POST /api/v1/send/messages_out with no token → 403
  • with a token → 200, and the message appears in the room
  • a structured message built with MessageBuilder and rendered to MessageML arrives with its <h3>, <table>, <b>, and <td> intact

Six new tests cover metadata routing, field routing, metadata winning over the field, a message naming no backend going nowhere, and the opt-in defaulting off. I checked the field-routing test fails without the filter change.

Suite: 288 passed, up from 282. The twelve failures are ModuleNotFoundError: No module named 'claude_agent_sdk' in my environment and are identical on an unmodified checkout. make lint passes.

Note

The REST surface for messages_in and messages_out returns 500 on csp-gateway as released, because its response path calls type_adapter() and chatom's Message is a plain pydantic model. Point72/csp-gateway#378 fixes that, and this feature needs it to be useful. Worth merging that first.

csp-bot already serves its channels over csp-gateway's REST API, but
messages_out was read-only, so a job had no way to ask the bot to
deliver a message. allow_send_messages, off by default, registers it as
a send channel so POST /api/v1/send/messages_out works.

Two things had to change for a posted message to arrive:

The publish loop filtered the edge built locally from command responses,
not the channel, so anything else feeding messages_out never reached a
backend. It now reads the channel, which the gateway merges with the
send adapter after every module has connected. This is not a cycle:
subscribe() is a source and publish() a sink, so nothing flows back into
what composes the channel. Verified by running the graph.

_filter_messages_for_backend only looked at metadata["backend"], which
is written by inbound tagging. A caller posting a message sets chatom's
backend field instead, so the message was accepted and then silently
dropped. The filter now falls back to that field, with metadata still
taking precedence. This is the same class of bug as the one
TestMetadataPropagation covers, fixed at the filter rather than at each
producer.

Off by default because it turns the bot into a relay for anything that
can reach its API. The documentation pairs it with configuring an API
key, since MountAPIKeyMiddleware only rejects requests once it has one.

Verified against a Symphony pod: an authenticated POST carrying
MessageML built by chatom arrived in the room with its headings, table,
and bold intact.

Signed-off-by: Tim Paine <[email protected]>
@timkpaine
timkpaine requested a review from ptomecek as a code owner October 7, 2026 03:18
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.18919% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.68%. Comparing base (18bfd2b) to head (8f2eabc).

Files with missing lines Patch % Lines
csp_bot/bot.py 20.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #106      +/-   ##
==========================================
+ Coverage   84.56%   84.68%   +0.11%     
==========================================
  Files          44       44              
  Lines        4569     4604      +35     
  Branches      337      338       +1     
==========================================
+ Hits         3864     3899      +35     
+ Misses        600      598       -2     
- Partials      105      107       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timkpaine
timkpaine merged commit 9259759 into Point72:main Oct 7, 2026
4 checks passed
@timkpaine timkpaine changed the title Let an authenticated caller send messages over REST Allow authenticated message sending over REST Oct 8, 2026
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