Module: Parse::Agent::MetadataDSL::ClassMethods

Defined in:
lib/parse/agent/metadata_dsl.rb

Constant Summary collapse

AGENT_METHOD_PERMISSIONS =

Permission levels for agent methods (matches Parse::Agent permission levels)

%i[readonly write admin].freeze
WRITE_METHOD_PATTERNS =

Patterns that suggest a method performs write operations Used to warn developers who may have misclassified a method as readonly

[
  /save/i, /update/i, /delete/i, /destroy/i, /create/i, /remove/i,
  /insert/i, /upsert/i, /modify/i, /set/i, /clear/i, /reset/i,
  /add/i, /append/i, /push/i, /increment/i, /decrement/i,
].freeze

Instance Method Summary collapse

Instance Method Details

#agent_admin(method_name, description = nil) ⇒ Hash

Convenience method: mark a method as requiring admin permission

Examples:

agent_admin :reset_all_counts, "Reset all play counts to zero"

Parameters:

  • method_name (Symbol, String)

    the method to expose

  • description (String, nil) (defaults to: nil)

    optional description

Returns:

  • (Hash)

    the method metadata



542
543
544
# File 'lib/parse/agent/metadata_dsl.rb', line 542

def agent_admin(method_name, description = nil)
  agent_method(method_name, description, permission: :admin)
end

#agent_allow_collscan(value = nil) ⇒ Boolean

Opt this class out of the global COLLSCAN refusal check. Intended for small lookup tables (Roles, Config) where full scans are acceptable and an index is not needed.

Examples:

class AppConfig < Parse::Object
  agent_allow_collscan true
end

Parameters:

  • value (Boolean) (defaults to: nil)

    true to allow COLLSCANs for this class

Returns:

  • (Boolean)

    the current setting



379
380
381
382
# File 'lib/parse/agent/metadata_dsl.rb', line 379

def agent_allow_collscan(value = nil)
  return @agent_allow_collscan if value.nil?
  @agent_allow_collscan = value == true
end

#agent_allow_collscan?Boolean

Check whether COLLSCANs are explicitly permitted for this class.

Returns:

  • (Boolean)


386
387
388
# File 'lib/parse/agent/metadata_dsl.rb', line 386

def agent_allow_collscan?
  @agent_allow_collscan == true
end

#agent_can_call?(method_name, agent_permission) ⇒ Boolean

Check if an agent with given permission can call a specific method. Permission hierarchy: admin > write > readonly

Parameters:

  • method_name (Symbol, String)

    the method to check

  • agent_permission (Symbol)

    the agent’s permission level

Returns:

  • (Boolean)

    true if the agent can call this method



670
671
672
673
674
675
676
# File 'lib/parse/agent/metadata_dsl.rb', line 670

def agent_can_call?(method_name, agent_permission)
  method_info = agent_methods[method_name.to_sym]
  return false unless method_info

  required_permission = method_info[:permission] || :readonly
  permission_allows?(agent_permission, required_permission)
end

#agent_canonical_filter(filter = nil) ⇒ Hash?

Declare a canonical “valid state” filter for this class that the agent’s read tools (‘query_class`, `count_objects`, `aggregate`) apply BY DEFAULT to every call. Closes the silently-suspect- counts gap: when a class soft-deletes via `isRemoved`, hides rows via `on_timeline: false`, or has any other always-applied validity predicate, the canonical filter ensures an LLM that drops to raw aggregate doesn’t accidentally include the excluded rows.

The filter is a MongoDB-style match expression (the same shape ‘query_class`’s ‘where:` argument accepts). When applied:

- `query_class` / `count_objects`: merged with the caller's
  `where:` via top-level `$and` so caller constraints
  compose rather than override.
- `aggregate`: prepended as a `$match` stage at index 0
  (after tenant-scope injection).

Callers opt out per call with ‘apply_canonical_filter: false`. The filter is also surfaced via `get_schema` so an opt-out caller can reproduce it manually.

Examples:

class Capture < Parse::Object
  property :isRemoved, :boolean
  property :onTimeline, :boolean
  agent_canonical_filter "isRemoved" => { "$ne" => true },
                         "onTimeline" => true
end

Parameters:

  • filter (Hash, nil) (defaults to: nil)

    a where-style hash. Pass nil to read the current value.

Returns:

  • (Hash, nil)

    the filter, or nil when not declared.

Raises:

  • (ArgumentError)


345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/parse/agent/metadata_dsl.rb', line 345

def agent_canonical_filter(filter = nil)
  return @agent_canonical_filter if filter.nil?
  raise ArgumentError, "agent_canonical_filter expects a Hash, got #{filter.class}" unless filter.is_a?(Hash)
  # Validate at registration time so a developer misconfiguration
  # (e.g. `$where`, `$function`, or an internal-field key) fails at
  # app boot rather than silently bypassing PipelineValidator at
  # request time. The filter is treated like a permissive pipeline
  # node: server-side JS operators and internal-field keys are refused;
  # normal Mongo query operators ($ne, $gt, $exists, etc.) are allowed.
  begin
    Parse::PipelineSecurity.validate_filter!(filter)
  rescue Parse::PipelineSecurity::Error => e
    raise ArgumentError, "agent_canonical_filter rejected: #{e.message}"
  end
  @agent_canonical_filter = filter.transform_keys(&:to_s).freeze
end

#agent_canonical_filter_for_applyHash?

Read-only accessor for the canonical filter.

Returns:

  • (Hash, nil)

    the filter as String-keyed Hash, or nil



364
365
366
# File 'lib/parse/agent/metadata_dsl.rb', line 364

def agent_canonical_filter_for_apply
  @agent_canonical_filter
end

#agent_description(text = nil) ⇒ String?

Set or get the class-level description for agent context. This description helps LLMs understand what this class represents.

Examples:

Set a description

agent_description "A music track in the catalog"

Get the description

Song.agent_description # => "A music track in the catalog"

Parameters:

  • text (String, nil) (defaults to: nil)

    the description to set, or nil to get

Returns:

  • (String, nil)

    the current description



173
174
175
176
177
178
179
# File 'lib/parse/agent/metadata_dsl.rb', line 173

def agent_description(text = nil)
  if text
    @agent_description = text.to_s.freeze
  else
    @agent_description
  end
end

#agent_field_allowlistArray<Symbol>

Read-only accessor for the agent field allowlist.

Returns:

  • (Array<Symbol>)

    the allowlist (empty if not declared)



211
212
213
# File 'lib/parse/agent/metadata_dsl.rb', line 211

def agent_field_allowlist
  @agent_field_allowlist || []
end

#agent_fields(*names) ⇒ Array<Symbol>

Declare which fields are surfaced to agent tools for this class. When set, agent schema enrichment trims the field list down to this allowlist (plus the always-on ‘objectId`/`createdAt`/`updatedAt`), and agent query/fetch tools push the allowlist into the server-side `keys` projection unless the caller passed an explicit `keys:` override. Called without arguments, returns the current allowlist.

Examples:

Limit agent visibility to analytics-relevant fields

class Team < Parse::Object
  agent_fields :name, :status, :member_count, :owner
end

Parameters:

Returns:



199
200
201
202
203
204
205
206
207
# File 'lib/parse/agent/metadata_dsl.rb', line 199

def agent_fields(*names)
  return @agent_field_allowlist ||= [] if names.empty?
  @agent_field_allowlist = names.flatten.map(&:to_sym).freeze
  # If agent_join_fields was declared earlier in the class body, the
  # subset invariant must still hold once agent_fields lands. Re-check
  # so declaration order doesn't matter.
  assert_agent_join_fields_subset!
  @agent_field_allowlist
end

#agent_hidden(except: nil) ⇒ Boolean

Mark this class as hidden from agent tools. Hidden classes are filtered out of ‘get_all_schemas`, refused by `query_class` / `count_objects` / `get_object` / `get_objects` / `get_sample_objects` / `aggregate` / `explain_query` / `get_schema` with a sanitized `:permission_denied` error response, and excluded from the `RelationGraph` prompt diagram.

Unlike ‘agent_visible` (which is opt-in for diagram-walking only), `agent_hidden` is a hard access denial. Use it for classes that contain PII the agent must never touch — student SSN tables, internal billing records, password reset tokens, etc.

Records still exist in the database; only the agent surface is blocked. Direct application code (Parse::Object#query, Parse::MongoDB) is unaffected.

Examples:

Hide a PII class from every agent surface

class StudentSSN < Parse::Object
  parse_class "StudentSSN"
  property :student_name, :string
  property :ssn, :string
  agent_hidden
end

Parameters:

  • except (Symbol, nil) (defaults to: nil)

    when set to ‘:master_key`, session-bound agents refuse this class but master-key agents are allowed through. This is the “internal admin tooling can see it, user-facing agents never can” tier — intended for collections like `_Session` where a dev-MCP / customer-support tool may legitimately need read access but no end-user-bound agent ever should. The field-level `INTERNAL_FIELDS_DENYLIST` floor (sessionToken, _hashed_password, etc.) still applies, so even master-key reads cannot exfiltrate credential columns.

Returns:

  • (Boolean)

    true



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/parse/agent/metadata_dsl.rb', line 92

def agent_hidden(except: nil)
  @agent_hidden = true
  @agent_hidden_except = case except
                         when nil    then nil
                         when :master_key, "master_key" then :master_key
                         else
                           raise ArgumentError,
                                 "agent_hidden(except:) accepts only :master_key (got #{except.inspect})"
                         end
  Parse::Agent::MetadataRegistry.register_hidden_class(self, except: @agent_hidden_except)
  true
end

#agent_hidden?Boolean

Check if this class is hidden from agent tools.

Returns:

  • (Boolean)


150
151
152
# File 'lib/parse/agent/metadata_dsl.rb', line 150

def agent_hidden?
  @agent_hidden == true
end

#agent_hidden_exceptSymbol?

The exception scope a previous ‘agent_hidden(except: …)` declared, or nil when the class is unconditionally hidden / not hidden at all. Currently the only supported value is `:master_key`.

Returns:



158
159
160
# File 'lib/parse/agent/metadata_dsl.rb', line 158

def agent_hidden_except
  @agent_hidden_except
end

#agent_join_field_listArray<Symbol>

Read-only accessor for the agent join-projection list.

Returns:



278
279
280
# File 'lib/parse/agent/metadata_dsl.rb', line 278

def agent_join_field_list
  @agent_join_field_list || []
end

#agent_join_fields(*names) ⇒ Array<Symbol>

Declare a narrower projection used when this class shows up as an included pointer on another class’s query (‘query_class` / `get_object` / `get_objects` / `get_sample_objects` / `export_data` + `include:`). When the agent asks for `keys: [“user”, …] + include: [“user”]`, the SDK auto-rewrites `keys` to dotted paths (`user.firstName, user.email, …`) so the joined record is projected to exactly the fields listed here.

This sits one tier tighter than ‘agent_fields`. The direct-query allowlist is typically the full “what the agent may see” set; the join-projection list is the narrower “what’s interesting when I’m a foreign key” set. Example: ‘_User` may surface 18 fields on a direct query, but when it’s joined onto a ‘Membership` row the agent usually only needs `firstName`, `lastName`, `email`, `internalTag` — not the `teams[]` pointer array or the `iconImage` presigned URL.

**Subset invariant**: when both ‘agent_fields` and `agent_join_fields` are declared, every entry in `agent_join_fields` MUST also appear in `agent_fields`. The direct-query allowlist is the upper bound on what the agent ever sees; the join list can only tighten that, never widen it. Violations raise `ArgumentError` at class load time. Declaring `agent_join_fields` without `agent_fields` is allowed — it means “no direct-query allowlist, but on a join project to these only.”

When ‘agent_join_fields` is NOT declared, the auto-projection falls back to `agent_fields - agent_large_fields` (or, when only `agent_large_fields` is declared, to `field_map.keys - agent_large_fields`). Callers can always opt out per call by passing dotted-path keys (`keys: [“user.iconImage”]`), which signals explicit intent and suppresses auto-expansion for that pointer.

Examples:

class Membership < Parse::Object
  belongs_to :user
  property :title, :string
  property :active, :boolean
  # …
end

# In the _User reopen / customization:
class Parse::User
  agent_fields :first_name, :last_name, :email, :icon_image,
               :source_image, :teams, :organizations, :last_active_at,
               :internal_tag
  agent_large_fields :icon_image, :source_image
  agent_join_fields :first_name, :last_name, :email,
                   :last_active_at, :internal_tag
end

Parameters:

Returns:

  • (Array<Symbol>)

    the resulting join-projection list



269
270
271
272
273
274
# File 'lib/parse/agent/metadata_dsl.rb', line 269

def agent_join_fields(*names)
  return @agent_join_field_list ||= [] if names.empty?
  @agent_join_field_list = names.flatten.map(&:to_sym).freeze
  assert_agent_join_fields_subset!
  @agent_join_field_list
end

#agent_large_field_listArray<Symbol>

Read-only accessor for the large-field list.

Returns:

  • (Array<Symbol>)

    the declared large fields (empty if none)



309
310
311
# File 'lib/parse/agent/metadata_dsl.rb', line 309

def agent_large_field_list
  @agent_large_fields || []
end

#agent_large_fields(*names) ⇒ Array<Symbol>

Declare fields known to carry large payloads (full text, embedded documents, base64 blobs, long descriptions). Schema introspection annotates these with ‘large_field: true` so an LLM client can project them away proactively in its first `query_class` call rather than discovering the size by hitting the dispatcher’s response cap. Has no effect on Pointer/Relation type fields —the stored value is a small reference; size only materializes via ‘include:` resolution, which is a query-time concern. Called without arguments, returns the current list.

Examples:

Flag the long-text fields up-front

class Article < Parse::Object
  property :title, :string
  property :body, :string
  property :raw_html, :string
  agent_large_fields :body, :raw_html
end

Parameters:

Returns:



302
303
304
305
# File 'lib/parse/agent/metadata_dsl.rb', line 302

def agent_large_fields(*names)
  return @agent_large_fields ||= [] if names.empty?
  @agent_large_fields = names.flatten.map(&:to_sym).freeze
end

#agent_metadataHash

Get all agent metadata as a hash for serialization.

Returns:

  • (Hash)

    all agent metadata



613
614
615
616
617
618
619
620
621
622
623
# File 'lib/parse/agent/metadata_dsl.rb', line 613

def 
  {
    description: agent_description,
    usage: agent_usage,
    property_descriptions: property_descriptions.dup,
    property_enum_descriptions: property_enum_descriptions.dup,
    methods: agent_methods.dup,
    field_allowlist: agent_field_allowlist.dup,
    join_field_list: agent_join_field_list.dup,
  }
end

#agent_method(method_name, description = nil, permission: :readonly, supports_dry_run: false, permitted_keys: nil, parameters: nil) ⇒ Hash

Mark a method as callable by the agent with an optional description. Only methods marked with this DSL can be invoked via the ‘call_method` tool.

Examples:

Mark a readonly class method (default)

agent_method :find_popular, "Find songs with over 1000 plays"

Mark an instance method requiring write permission

agent_method :update_play_count, "Increment play count", permission: :write

Mark a method requiring admin permission

agent_method :reset_all_counts, "Reset all play counts to zero", permission: :admin

Mark a write method that explicitly supports dry-run preview

agent_method :archive, "Archive this record", permission: :admin, supports_dry_run: true
def archive(dry_run: false)
  return { would_archive: id } if dry_run
  self.status = "archived"; save!
end

Parameters:

  • method_name (Symbol, String)

    the name of the method to expose

  • description (String, nil) (defaults to: nil)

    optional description for LLM context

  • permission (Symbol) (defaults to: :readonly)

    required permission level (:readonly, :write, :admin)

  • supports_dry_run (Boolean) (defaults to: false)

    whether the method accepts dry_run: true for preview-only execution. When false (default), passing dry_run: true in arguments is refused at dispatch time with :invalid_argument.

  • permitted_keys (Array<Symbol,String>, nil) (defaults to: nil)

    when provided, call_method refuses any arguments key not in this list. Without this, an LLM (or a prompt-injection payload) can pass arbitrary keys through a method that splats with **, reaching protected columns like _hashed_password or ACL. Highly recommended on any agent_write/agent_admin method that takes a kwargs splat.

  • parameters (Hash, nil) (defaults to: nil)

    when provided, a JSON Schema (as a Ruby Hash) describing the arguments object. Surfaced in tools/list so the LLM submits properly-shaped inputs and stricter MCP clients can validate before dispatch.

Returns:

  • (Hash)

    the method metadata



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# File 'lib/parse/agent/metadata_dsl.rb', line 464

def agent_method(method_name, description = nil, permission: :readonly,
                 supports_dry_run: false, permitted_keys: nil, parameters: nil)
  method_sym = method_name.to_sym

  unless AGENT_METHOD_PERMISSIONS.include?(permission)
    raise ArgumentError, "Invalid permission level: #{permission}. Must be one of: #{AGENT_METHOD_PERMISSIONS.join(", ")}"
  end

  if permitted_keys && !permitted_keys.is_a?(Array)
    raise ArgumentError, "permitted_keys must be an Array of Symbol/String, got #{permitted_keys.class}"
  end

  # Determine if this is an instance or class method
  # Note: method_defined? checks instance methods, respond_to? checks class methods
  method_type = if method_defined?(method_sym)
      :instance
    elsif respond_to?(method_sym) || singleton_methods.include?(method_sym)
      :class
    else
      # Method not yet defined - we'll check again at runtime
      :unknown
    end

  agent_methods[method_sym] = {
    description: description&.to_s&.freeze,
    type: method_type,
    permission: permission,
    supports_dry_run: supports_dry_run == true,
    permitted_keys: permitted_keys&.map(&:to_sym)&.freeze,
    parameters: parameters,
  }
end

#agent_method_allowed?(method_name) ⇒ Boolean

Check if a specific method is allowed for agent invocation.

Parameters:

Returns:

  • (Boolean)

    true if the method is agent-allowed



652
653
654
# File 'lib/parse/agent/metadata_dsl.rb', line 652

def agent_method_allowed?(method_name)
  agent_methods.key?(method_name.to_sym)
end

#agent_method_info(method_name) ⇒ Hash?

Get metadata for a specific agent-allowed method.

Parameters:

Returns:

  • (Hash, nil)

    the method metadata or nil if not allowed



660
661
662
# File 'lib/parse/agent/metadata_dsl.rb', line 660

def agent_method_info(method_name)
  agent_methods[method_name.to_sym]
end

#agent_methodsHash<Symbol, Hash>

Storage hash for agent-allowed methods. Maps method names (symbols) to their metadata hashes.

Returns:



412
413
414
# File 'lib/parse/agent/metadata_dsl.rb', line 412

def agent_methods
  @agent_methods ||= {}
end

#agent_methods_for(agent_permission) ⇒ Hash<Symbol, Hash>

Get all methods available to an agent with given permission level.

Parameters:

  • agent_permission (Symbol)

    the agent’s permission level

Returns:



682
683
684
685
686
# File 'lib/parse/agent/metadata_dsl.rb', line 682

def agent_methods_for(agent_permission)
  agent_methods.select do |_name, info|
    permission_allows?(agent_permission, info[:permission] || :readonly)
  end
end

#agent_readonly(method_name, description = nil) ⇒ Hash

Convenience method: mark a method as readonly-accessible (default)

WARNING: This method checks if the method name suggests write behavior (save, update, delete, etc.) and emits a warning. This helps developers catch potential security misconfigurations early.

Examples:

agent_readonly :find_popular, "Find songs with over 1000 plays"

Parameters:

  • method_name (Symbol, String)

    the method to expose

  • description (String, nil) (defaults to: nil)

    optional description

Returns:

  • (Hash)

    the method metadata



509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/parse/agent/metadata_dsl.rb', line 509

def agent_readonly(method_name, description = nil)
  method_str = method_name.to_s

  # Warn if method name suggests it performs write operations
  if WRITE_METHOD_PATTERNS.any? { |pattern| method_str.match?(pattern) }
    warn "[Parse::Agent::MetadataDSL] WARNING: Method '#{method_name}' on #{name} " \
         "is marked as agent_readonly but its name suggests it may perform writes. " \
         "Consider using agent_write or agent_admin if this method modifies data."
  end

  agent_method(method_name, description, permission: :readonly)
end

#agent_tenant_scope(field, from:) ⇒ Object

Declare a tenant scope rule for this class.

When declared, every agent read tool (query_class, count_objects, get_sample_objects, export_data query-mode, aggregate, get_object, get_objects) will enforce that data access is limited to the agent’s bound tenant. An agent with no tenant binding (tenant_id: nil) hitting a scoped class is refused with :access_denied unless the bypass condition is satisfied.

Examples:

class Order < Parse::Object
  property :org_id, :string
  agent_tenant_scope :org_id, from: ->(agent) { agent.tenant_id }
end

Parameters:

  • field (Symbol, String)

    the Parse field to scope on (e.g. :org_id)

  • from (Proc)

    callable receiving the agent, returning the scope value (return nil to mean “this agent has no tenant binding”)



565
566
567
568
569
570
571
# File 'lib/parse/agent/metadata_dsl.rb', line 565

def agent_tenant_scope(field, from:)
  unless from.respond_to?(:call)
    raise ArgumentError, "agent_tenant_scope :from must be a callable (Proc/lambda)"
  end
  parse_class_name = respond_to?(:parse_class) ? parse_class : name
  Parse::Agent::MetadataRegistry.register_tenant_scope(parse_class_name, field, from: from)
end

#agent_tenant_scope_bypass {|agent| ... } ⇒ Object

Declare a bypass condition for this class’s tenant scope.

When the block returns truthy for the given agent, tenant scope enforcement is skipped entirely for that agent on this class. A bypass block that raises is treated as not-bypassed (fail closed).

Without a bypass declaration, any agent whose tenant_id is nil hitting a scoped class is refused.

Examples:

Allow admin agents to read across tenants

class Order < Parse::Object
  agent_tenant_scope :org_id, from: ->(agent) { agent.tenant_id }
  agent_tenant_scope_bypass { |agent| agent.permissions == :admin }
end

Yields:

  • (agent)

    the agent instance

Yield Returns:

  • (Boolean)

    truthy to bypass, falsy to enforce

Raises:

  • (ArgumentError)


591
592
593
594
595
# File 'lib/parse/agent/metadata_dsl.rb', line 591

def agent_tenant_scope_bypass(&block)
  raise ArgumentError, "agent_tenant_scope_bypass requires a block" unless block_given?
  parse_class_name = respond_to?(:parse_class) ? parse_class : name
  Parse::Agent::MetadataRegistry.register_tenant_scope_bypass(parse_class_name, block)
end

#agent_unhiddenBoolean

Reverse a previous ‘agent_hidden` declaration on this class. Clears the per-class hidden flag and removes the class from the registry’s hidden set so that every agent tool surface treats the class as visible again (subject to the per-tool ‘agent_fields` allowlist and other policy). The field-level `INTERNAL_FIELDS_DENYLIST` floor still strips credential columns from every response.

The intended use is to opt back in to a built-in class that parse-stack marks hidden by default — for example ‘Parse::Product`, which is hidden in `lib/parse/agent.rb` because the `_Product` collection is a vestigial iOS IAP feature, but an application that actually does use the collection can call:

Parse::Product.agent_unhidden

at boot time (after ‘require ’parse/stack’‘) to expose it. The same mechanism applies to any application-defined class that was marked `agent_hidden` and needs to be re-enabled for a specific deployment.

Returns:

  • (Boolean)

    true if a previous ‘agent_hidden` declaration was actually reversed; false when the class was not hidden to begin with (idempotent no-op). Matches `Hash#delete?`/`Set#delete?` “did anything change” semantics so callers can branch on the return value.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/parse/agent/metadata_dsl.rb', line 129

def agent_unhidden
  was_hidden = @agent_hidden == true
  @agent_hidden = false
  @agent_hidden_except = nil
  Parse::Agent::MetadataRegistry.unregister_hidden_class(self)
  # Only audit on a real state flip — calling `agent_unhidden` on a
  # class that was never hidden is a no-op and shouldn't emit a banner
  # that trains operators to suppress the warning globally.
  if was_hidden && !(defined?(Parse::Agent) && Parse::Agent.respond_to?(:suppress_master_key_warning?) && Parse::Agent.suppress_master_key_warning?)
    warn "[Parse::Agent:SECURITY] #{name} (#{respond_to?(:parse_class) ? parse_class : name}) was marked agent_unhidden — " \
         "this class is now reachable from every agent tool surface (query_class, aggregate, get_schema, etc.). " \
         "Master-key agents bypass per-row ACL/CLP enforcement, so per-class agent_fields / agent_canonical_filter / " \
         "tenant_id are the only remaining access boundary. Credential columns are still stripped by the " \
         "INTERNAL_FIELDS_DENYLIST floor regardless of class visibility. Confirm this is intentional. " \
         "Silence with Parse::Agent.suppress_master_key_warning = true."
  end
  was_hidden
end

#agent_usage(text = nil) ⇒ String?

Class-level analytics usage hint, surfaced inside agent schema output. Distinct from ‘agent_description` (a short human summary): use this for specific guidance the LLM needs to query the class well — enum values, denormalization caveats, recommended aggregations, etc.

Examples:

agent_usage <<~USAGE
  `status` values: "active" | "archived" | "frozen".
  `member_count` is denormalized; recompute via _User pointer.
USAGE

Parameters:

  • text (String, nil) (defaults to: nil)

    the usage text to set, or nil to read

Returns:

  • (String, nil)

    the current usage hint



403
404
405
406
# File 'lib/parse/agent/metadata_dsl.rb', line 403

def agent_usage(text = nil)
  return @agent_usage unless text
  @agent_usage = text.to_s.strip.freeze
end

#agent_visibleBoolean

Mark this class as visible to agents. Only classes marked with agent_visible will be included in schema listings. If no classes are marked, all classes are shown (backwards compatible).

Examples:

Mark a class as agent-visible

class Song < Parse::Object
  agent_visible
  agent_description "A music track"
end

Returns:

  • (Boolean)

    true



46
47
48
49
50
# File 'lib/parse/agent/metadata_dsl.rb', line 46

def agent_visible
  @agent_visible = true
  Parse::Agent::MetadataRegistry.register_visible_class(self)
  true
end

#agent_visible?Boolean

Check if this class is marked as visible to agents

Returns:

  • (Boolean)


54
55
56
# File 'lib/parse/agent/metadata_dsl.rb', line 54

def agent_visible?
  @agent_visible == true
end

#agent_write(method_name, description = nil) ⇒ Hash

Convenience method: mark a method as requiring write permission

Examples:

agent_write :update_play_count, "Increment the play count"

Parameters:

  • method_name (Symbol, String)

    the method to expose

  • description (String, nil) (defaults to: nil)

    optional description

Returns:

  • (Hash)

    the method metadata



530
531
532
# File 'lib/parse/agent/metadata_dsl.rb', line 530

def agent_write(method_name, description = nil)
  agent_method(method_name, description, permission: :write)
end

#has_agent_metadata?Boolean

Check if this model has any agent metadata defined.

Returns:

  • (Boolean)

    true if any metadata is present



600
601
602
603
604
605
606
607
608
# File 'lib/parse/agent/metadata_dsl.rb', line 600

def has_agent_metadata?
  !agent_description.nil? ||
    !agent_usage.nil? ||
    !property_descriptions.empty? ||
    !property_enum_descriptions.empty? ||
    !agent_methods.empty? ||
    !agent_field_allowlist.empty? ||
    !agent_join_field_list.empty?
end