Skip to content

docs(pylon): document the Pylon datasource for the Ruby agent - #30

Open
christophebrun-forest wants to merge 3 commits into
mainfrom
docs/pylon-ruby-datasource
Open

docs(pylon): document the Pylon datasource for the Ruby agent#30
christophebrun-forest wants to merge 3 commits into
mainfrom
docs/pylon-ruby-datasource

Conversation

@christophebrun-forest

@christophebrun-forest christophebrun-forest commented Sep 1, 2026

Copy link
Copy Markdown
Member

Why

agent-ruby#369 merged forest_admin_datasource_pylon (released in agent-ruby 1.41.0). Nothing in the docs mentioned it, so the gem shipped with its package README as the only place a customer could learn how to configure it — or, more to the point, what the connector refuses to do and why.

Pylon is a ticketing API, not a database, so most of the page is that second half: several things Forest asks for have no equivalent, and the datasource answers a 400 naming the reason rather than something that looks right and is not. That is the behaviour a customer meets first and has no way to guess.

What changed

Two new pages, mirroring how the Zendesk connector is documented (datasource page + plugins page):

get-started/connect/data-sources/pylon.mdx

  • Installation, and the full option table with defaults — base_url, both timeouts, retry_policy, rate_limiter: nil, the boot_* trio.
  • The five collections with their read/write matrix, the ten relations, and the two relations Pylon's shape does not allow (team membership, account owner).
  • The messages thread column: field-by-field shape, the lazy fetch, the 10-row cap, and why a capped row is nil rather than [].
  • Custom fields: the type mapping, the slug as column name, select-by-slug, the is_read_only flag rule, and the boot-time degradation.
  • Capabilities: a filter allow-list per collection (in tabs), the id short-circuit and MAX_ID_LOOKUPS, the absence of any sort parameter, the cursor pagination caps, why only PylonUser / PylonTeam aggregate — and exactly — the write budget and the per-collection create-only / update-only fields.
  • Rate limits and retries, with the DEFAULT_MAX_INTERVAL: 12 trade stated and the snippet to choose the other way; then the error-class table and logging.

product/process/advanced-concepts/plugins/pylon.mdx

  • CreateIssueWithNotification: options, form fields, the two-page templates wizard, the refusals at registration, and what the operator reads back.
  • CloseIssue: options, the one-action-per-scope registration and its name-collision refusal, which scope bounds it, and the 20-issue batch cap.

Both pages are Ruby-only, with a <Warning> saying so, rather than tabbed: there is no Node.js Pylon connector, so a Node.js tab would have nothing in it.

Also: docs.json nav for both pages, and a Pylon entry in the "Available datasources" list of get-started/connect/data-sources/overview.mdx.

How it was written

Every figure, option name and default is transcribed from the merged code at cf20168a, not from the PR description: the package README, configuration.rb, retry_policy.rb, rate_limits.rb, the five collections with their schema_definition / api_filters, operator_maps.rb, base_collection.rb / fetch_all_collection.rb / writes.rb, custom_fields_introspector.rb, cursor_walker.rb, and both plugins.

Two places where that mattered:

  • The CloseIssue scope caveat is the corrected reading, the one b6bd085b landed. Mounted on PylonIssue, a scope bounds exactly what the action closes — the ids are resolved through the host collection first, and the agent intersects the operator's scope into that filter. The caveat only applies to the host-collection form, where the id column is the authority. The earlier wording had it backwards, and telling an operator a scope buys them nothing when it buys them the whole selection is the one error worth not copying into the docs.
  • The batch cap counts issues named, not records selected — a hundred host records naming ten issues is a batch of ten, duplicates collapsed. Same for MAX_ID_LOOKUPS, which bounds a page rather than a selection (a93474b1), so a wider selection is read a page at a time rather than truncated.

Three limitations the PR filed on Linear rather than fixing are not documented as behaviour, since they are open tickets: EXT-21 (a non-positive page limit), EXT-22 (a nested and carrying an id — the page does state that this shape is refused, which is what EXT-22 would change) and EXT-23 (an aggregation applying an operation the column type cannot take).

Note

Add documentation for Pylon datasource and plugins

  • Adds a new page for the Pylon datasource covering installation, configuration, collections, and capabilities like filters, sorting, and pagination
  • Adds a new page for Pylon action plugins covering usage, forms, email-templates wizard, outcomes, and batch behavior
  • Updates docs.json and overview.mdx with navigation links to the new pages

Macroscope summarized d727d68.

The `forest_admin_datasource_pylon` gem landed in agent-ruby#369: five
collections over the Pylon API, cursor pagination, per-endpoint rate
limiting, custom-field introspection, CRUD writes and two action plugins.

Two pages, mirroring how the Zendesk connector is documented:

- `get-started/connect/data-sources/pylon` — installation, the
  configuration options and their defaults, the collections and their
  relations, the embedded conversation thread, custom-field mapping, then
  what the API cannot do and what the datasource does about it: the filter
  allow-list per collection, the primary-key short-circuit and its caps,
  the absence of any sort parameter, why only the two fetch-all
  collections aggregate, the write budget, the rate-limit / retry trade
  and the error classes.
- `product/process/advanced-concepts/plugins/pylon` — `CloseIssue` and
  `CreateIssueWithNotification`: options, form fields, the wizard, which
  scope bounds a close, and the batch cap.

Ruby-only pages rather than tabbed ones: there is no Node.js Pylon
connector, so a `Node.js` tab would have nothing to say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
forest 🟢 Ready View Preview Sep 1, 2026, 2:46 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment on lines +23 to +31
@agent.collection :Customer do |collection|
collection.use(
ForestAdminDatasourcePylon::Plugins::CreateIssueWithNotification,
datasource: pylon_datasource,
sender_email: 'support@acme.com'
)
end

@agent.collection :Order do |collection|

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High plugins/pylon.mdx:23

The snippet calls collection on the uninitialized @agent, so copying it raises a NoMethodError and registers neither plugin action. The documented factory setup stores the agent in @create_agent; use that receiver for both collection declarations.

-@agent.collection :Customer do |collection|
+@create_agent.collection :Customer do |collection|
   collection.use(
     ForestAdminDatasourcePylon::Plugins::CreateIssueWithNotification,
     datasource: pylon_datasource,
     sender_email: 'support@acme.com'
   )
 end
 
-@agent.collection :Order do |collection|
+@create_agent.collection :Order do |collection|
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around lines 23-31:

The snippet calls `collection` on the uninitialized `@agent`, so copying it raises a `NoMethodError` and registers neither plugin action. The documented factory setup stores the agent in `@create_agent`; use that receiver for both collection declarations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b79d271, but not the way the suggestion proposes — the diagnosis was off by one line.

@agent.collection is a valid call: AgentFactory includes ForestAdminDatasourceCustomizer::DSL::DatasourceHelpers, which defines collection(name, &block) as a wrapper over customize_collection. So the receiver is not the problem.

The actual defect was on line 21, which called add_datasource and threw away its return value, leaving @agent unbound two lines later. add_datasource returns self, so binding it is the whole fix:

@agent = ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(pylon_datasource, {})

Switching to @create_agent instead would have made this the only plugins page using that name — plugins/zendesk.mdx and plugins/active-storage.mdx both use @agent. @create_agent is the datasource-page convention, which is why data-sources/pylon.mdx uses it: there the snippet shows the whole CreateAgent.setup! body, where that is the real local name.

The Pylon connector is only available for Ruby (gem `forest_admin_datasource_pylon`).
</Warning>

Both plugins require the [Pylon datasource](/get-started/connect/data-sources/pylon) to be registered on your back-end: they need the `Datasource` instance to reach the Pylon API client.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium plugins/pylon.mdx:12

The page incorrectly tells users that both plugins require the Datasource to be registered with the agent, even though the actions use only the supplied datasource: object and its client; users who only need these actions are therefore steered into unnecessary datasource registration. Document that a constructed Datasource passed via datasource: is sufficient.

- Both plugins require the [Pylon datasource](/get-started/connect/data-sources/pylon) to be registered on your back-end: they need the `Datasource` instance to reach the Pylon API client.
+ Both plugins require a constructed `Datasource` instance passed via the `datasource:` option so they can reach the Pylon API client; they do not require that datasource to be registered with the agent.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around line 12:

The page incorrectly tells users that both plugins require the `Datasource` to be registered with the agent, even though the actions use only the supplied `datasource:` object and its `client`; users who only need these actions are therefore steered into unnecessary datasource registration. Document that a constructed `Datasource` passed via `datasource:` is sufficient.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct on the substance, and deliberately left as is in this PR.

Both plugins reach Pylon through options[:datasource].client only — CreateIssueWithNotification#create_issue and CloseIssue#apply_state — and never look the datasource up on the agent. A Datasource.new(api_key: ...) that is never passed to add_datasource builds its Client and is enough for both actions, so "must be registered" does overstate it.

The reason it stays: that sentence is copied verbatim from the Ruby tab of plugins/zendesk.mdx, where the same thing is true of CreateTicketWithNotification / CloseTicket. Relaxing it on the Pylon page alone would leave the two connectors documented as having different requirements when they have the same one, which is more confusing than the overstatement.

Worth its own PR that fixes both pages together. One thing to weigh there: constructing a Pylon Datasource runs the custom-field introspection (three GET /custom-fields calls in front of the Rails boot), so an unregistered instance kept only to feed the plugins pays that cost for a schema nobody reads — which is a reason to keep recommending registration even once the wording no longer requires it.

| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `datasource` | **Required.** The `ForestAdminDatasourcePylon::Datasource` instance. |
| `action_name` | Overrides the action label. Defaults to `'Create Pylon issue and notify'`. |
| `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, `whatsapp` or `internal`. An unknown value is refused at registration. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium plugins/pylon.mdx:68

The destination table says internal delivers the first message through a channel, but destination: 'internal' omits destination_metadata so the message remains internal and the requester is not contacted. Update the description to reflect this behavior.

Suggested change
| `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, `whatsapp` or `internal`. An unknown value is refused at registration. |
| `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, or `whatsapp`. Use `internal` to keep the first message internal without contacting the requester. An unknown value is refused at registration. |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around line 68:

The `destination` table says `internal` delivers the first message through a channel, but `destination: 'internal'` omits `destination_metadata` so the message remains internal and the requester is not contacted. Update the description to reflect this behavior.

Review of #30 against the merged code at cf20168a. Five places where the
pages said something the package does not do.

- `GET /me` was introduced as "the health check", which reads as a boot-time
  validation of the token. `Client#me` is called nowhere: the only boot call is
  `GET /custom-fields`, which degrades to the native schema on failure. A wrong
  api_key therefore boots the agent and fails on the first list. Say that, and
  point at `client.me` as the check to run yourself.
- "Two relations Pylon's shape does not allow" covered one reason for two
  relations. Team membership is a shape limitation; `PylonAccount.owner_id` is
  not — the embedder would resolve it like any other key, and it is left a plain
  column because nothing in the panel asks for it yet. Split the two.
- The plugins snippet called `add_datasource` without binding its return, then
  used `@agent` on the next line. Bind it. `@agent.collection` is valid —
  `AgentFactory` includes `DSL::DatasourceHelpers` — and is what the Zendesk
  plugins page already uses, so only the assignment was missing.
- The `destination` row listed `internal` among the channels the first message
  is delivered through. It is the absence of a delivery: `Payload#build` omits
  `destination_metadata` entirely for it. The page had this right two paragraphs
  above and wrong in the table.
- The filter tables list no `NOT_EQUAL`, because `operator_maps.rb` deliberately
  declares only `not_in` — the toolkit republishes the other spelling from it.
  The tables transcribe the wire maps, so the UI offers a filter the page reads
  as unavailable. One note under the tabs, next to the presence rewrite it
  shares a mechanism with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to b79d271, which overcorrected: it said nothing calls Pylon at
boot and that the token is checked by the first request needing it. The
custom-field introspection does call Pylon at boot and does carry the token
-- `fetch_custom_fields` wraps `must_succeed` in `best_effort(default: nil)`,
so a 401 there returns nil, logs a warning and leaves the agent on the native
schema. The call happens; it just does not validate.

Worth the precision because it is where the operator looks: a wrong key leaves
a custom-fields warning in the boot log, which the Note further down already
describes, and nothing else until the first list.

Co-Authored-By: Claude Opus 5 (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