Skip to content

A fork cannot keep one of its public agents off the consumer chat route — the authorization seam is never asked about an agent there #817

Description

@JohnD-EE

What bit

A fork serves one of its agents through a surface of its own: a route that runs the fork's checks around every turn (a crisis check, a per-user spend ceiling, a turn record). That surface needs the agent to be visibility: 'public'. Setting it also opens the agent to Sunrise's general consumer chat routes:

  • POST /api/v1/chat/stream streams a turn with it for any signed-in user, by agentSlug.
  • GET /api/v1/chat/agents lists it.

A turn through chat/stream skips everything the fork's surface does. The fork has no way to close that door without editing the route.

Why the existing seams cannot express it

The shape carried downstream (reference implementation)

A generic registry, with no fork vocabulary in the platform files:

  • lib/orchestration/chat/consumer-exclusions.ts (new): excludeFromConsumerChat(slug), isExcludedFromConsumerChat(slug), and a test reset. It is stored on globalThis under a Symbol.for key, because the route and the boot seam can land in different module graphs (the same reason lib/framework/modules/registry.ts does this).
  • app/api/v1/chat/stream/route.ts: one import, and if (!agent) becomes if (!agent || isExcludedFromConsumerChat(agent.slug)). A refused slug gets the same 404, code and message as an unknown agent. The DB lookup still runs, so nothing about the agent's existence leaks. The refusal happens before the per-agent limiter and before streamChat.
  • app/api/v1/chat/agents/route.ts: one import. The query result is filtered with the same predicate, so the query itself is unchanged.
  • The fork registers from its boot seam.

Behaviour-neutral at rest: with nothing registered, both routes behave exactly as before, and a test asserts it. The downstream tests live in tests/unit/app/api/v1/chat/consumer-exclusions.test.ts (Lelañea): at rest vs registered, response-body equality with the unknown-agent answer, the listing omission, and the globalThis sharing across a module reset.

Reference implementation: human-centric-engineering/lelanea — PR link to follow in a comment.

Behaviour change we could not avoid

None at rest. With a registration, a registered agent is unreachable through the consumer routes by design. Admin chat and embed are untouched: they don't consult the registry.

Notes

  • A fork could reasonably also want the same exclusion on the embed route. We did not need that (embed tokens are admin-minted per agent), so we did not carry it.
  • The route file has moved on main since 0.12.0 (the invite-token refactor). Our edit touches only the if (!agent) line and the imports, so it should re-apply cleanly on top of that.

We are reporting the gap with our shape as a reference. We are not proposing to write the platform's fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    seamFork extension point; adds public surface, needs a VERSIONING.md entryupstream-gapGap surfaced building a fork; tracked for upstream fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions