Managed agents

Agent manifest

The tools vocabulary, what each key grants and its permission gate, read scopes, limits, and how a draft test differs from a saved run.

An agent manifest is JSON or YAML with these top-level keys:

kind: agent
name: report-extractor
description: Extracts structured data from uploaded reports.
model: <a model from the org's catalog>
system: |
  You extract ...
tools:
  app_files: [report-console]
  app_data_write: [report-console]
limits:
  max_tokens_total: 2000000

Anything else is rejected.

Three things that are not manifest keys:

  • visibility is a sibling field. Set it with --visibility org|personal on create / update / test.
  • triggers / cron: a schedule is a separate resource under the agent.
  • input_schema is not supported. Agent input is free-form: input_json reaches the model unvalidated, and the system prompt is the input contract.

The CLI has no local schema validator. create / update / test send the manifest straight to the API. Treat server validation and ratification warnings as authoritative. Confirm the live shape with railcode agent pull on an existing agent.

tools keys

KeyTypeGrantsPermission gate
saved_queriesstring[]query(name, params)Must be a ratified saved query in the org
connectors{connector: endpoint[]}connector(name, method, path, body)Each connector:endpoint must be ratified
docsstring[]docs(connector), which reads that connector's stored docsConnector must exist
emailbool or {emails, domains}email(to, subject, html/text, cc, bcc)email grant (org default *); recipients restricted to the allowlist when one is set
adhoc_sqlstring[]sql(connection, text, params): raw SQL, no firewallconnector grant, plus a ratification warning
app_datastring[] (app slugs)app_kv_get, app_kv_query, app_kv_countcan_access_app, the same bar as opening the app
app_data_writestring[] (app slugs)app_kv_set, app_kv_delete, publish_artifact_to_appcan_manage_app: the app's owner or an org admin only. Declaring it for an app you do not manage is a hard save failure
app_filesstring[] (app slugs; no wildcard)search_app_files (metadata only) and load_app_filecan_access_app
agent_kvboolagent_kv_get/set/query/delete against the agent's own private, cross-run storeNone beyond holding the agent
personal_connectorsstring[] of toolkit names or toolkit:tool pairspersonal_tools(toolkit) and personal_call(toolkit, tool, args)visibility: personal only. A save with this key on an org agent is a hard failure

load_app_file(app, name) downloads one object into the sandbox at /workspace/in/apps/{app}/{name}. It returns the local path plus metadata to the model, never bytes, object keys, or storage URLs. Loading the same path again in one run is idempotent.

Tool slugs are lowercase and case-sensitive. Copy them from discovery output rather than guessing:

railcode connector list
railcode connector tools <name>
railcode query list
railcode db list

A save that adds authority you do not hold fails with the missing operations named. Get the grant first, or drop it from the manifest. personal_connectors is the one exception. There is no resource for a third party to be granted, so the only save gate is visibility: personal. Execution then uses the owner's connected account, and returns a 403 if it is not connected.

Read scopes

App KV and files hold three parallel scopes: shared, user, and role. Every app read tool takes an optional scope argument (shared, the default; user, the principal's own; or role:<role_id>). Results report the scope they were read from, so a key that exists in more than one scope stays unambiguous. app_scopes_list(app) discovers which scopes the agent may read.

The principal is a personal agent's owner. An org agent has no user principal, so it reads shared only. Scope selection is authorized exactly like a human caller, with the admin cross-scope override disabled on the agent path. So an agent can never reach another user's scope.

Writes take no scope argument. Each agent writes to one fixed scope: shared for an org agent, the owner's user scope for a personal one.

limits

FieldDefaultMax
max_steps300300
timeout_seconds12001200
max_tool_calls300600
max_tokens_total1500006000000
max_tokens_turn150000200000

max_tokens_total is the cumulative run budget (input and output across all turns). max_tokens_turn is the per-turn output ceiling. max_tokens is a legacy alias for max_tokens_total.

Sizing them

Use the narrowest useful tool set, but size limits the other way. A run that exhausts any cap dies as limit_exceeded and loses the work in flight. An unused ceiling costs nothing. Limits are caps rather than reservations, so set them high.

Token usage is dominated by what flows through the model. Every turn resends the conversation, and every tool result lands in it.

  • Light tasks (classify a record, draft a short reply, one saved-query lookup) fit the defaults.
  • Document editing and file analysis use far more tokens than you expect. Loaded file content, sandbox command output, and repeated re-reads all count against max_tokens_total. A long rewritten document pushes max_tokens_turn. Raise max_tokens_total into the millions rather than leaving the 150k default.
  • If a test run ends limit_exceeded or stops at max_steps, raise the ceiling first rather than trimming the task.

max_steps and timeout_seconds already default to their maximums. Tokens are the knob that needs sizing.

Draft tests behave differently

railcode agent test runs a draft with no saved agent row, so persistent storage behaves differently than under a saved run:

  • app_data_write is simulated, not persisted. app_kv_set / app_kv_delete are validated exactly as a saved run would be, then recorded into a per-run in-memory overlay. Within that same test run, app_kv_get reads the overlay first (read-your-writes, scoped to the one run). app_kv_query / app_kv_count still compile to SQL over the live store and do not reflect the run's simulated writes.
  • publish_artifact_to_app is validated and accepted but marks nothing, because a draft has no run-end artifact sink.
  • agent_kv tools are omitted entirely, since there is no agent row to key that store on.

So an untouched store after test is expected. Verify persistence with agent create + agent run.

On this page