---
title: "Limits"
description: "Every value Chickpea enforces on attachments, tables, memory, handles, schedules, skill imports, management requests, MCP registration, the coding sandbox, telemetry, and request bodies."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.chickpea.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Limits

A **limit** is a value Chickpea refuses to exceed, checked in code at the moment of the request. Where a setting is chosen in Admin and a quota is billed by Cloudflare or Slack, a limit is compiled into the deployed version and is identical on every deployment of it. This page lists the limits an admin or an operator meets, each with its scope and what happens when a request crosses it.

The source of truth is the Chickpea repository. Every value below was read from the file named at the head of its section. One value is operator-configurable and marked as such: the coding sandbox monthly session cap.

## Quick reference

| Limit | Value | Scope |
|---|---|---|
| Files read from one message | 4 | per Slack message |
| Bytes across one message's files | 12 MiB | per Slack message |
| Rows in a native Slack table | 200 | per table |
| Stored Agent memory | 64 KiB | per Agent |
| Agent handle | 80 characters | per Agent |
| Active schedules | 100 | per deployment |
| Active schedules | 20 | per channel |
| Minimum interval between runs | 5 minutes | per schedule |
| Run-now starts in a rolling 24 hours | 10 | per deployment |
| Operations in one management request | 25 | per request |
| Skill directories scanned on import | 40 | per import |
| Coding sandbox sessions | operator setting, uncapped when unset | per calendar month |

## Attachments

Enforced in `src/slack/attachment-normalization.ts`. Attachments are read for the turn and never stored.

| Limit | Value | At the limit |
|---|---|---|
| Files per message | 4 | No file is read. Every file in the message returns `attachment_count_limit_exceeded`. |
| Bytes per file | 8 MiB (8,388,608 bytes) | The retrieval is bounded to that size, so a larger file fails with a next action of `reduce_file_size` instead of arriving truncated. |
| Bytes per message | 12 MiB (12,582,912 bytes) | Once the running total is spent, each remaining file returns `attachment_aggregate_byte_limit_exceeded`. |
| Characters per file after normalization | 32,000 | The file returns `attachment_character_limit_exceeded`. |
| Characters per message after normalization | 48,000 | The file that crosses the total returns `attachment_aggregate_character_limit_exceeded`. |
| Pages in a PDF | 100 | The file returns `pdf_page_limit_exceeded`. |
| Normalization time | 13,000 ms per file in the message | The file returns `attachment_deadline_exceeded`. |

Files are read in the order they appear on the message, so a small file behind an oversized one can still succeed. Each failure carries a next action: `retry`, `reupload_file`, `reconnect_slack`, `convert_file`, `reduce_file_size`, `split_file`, `use_text_pdf`, or `remove_unsupported_file`.

## Native Slack tables

Enforced in `src/slack/table-presentation.ts`, which validates the `present_table` tool call before Slack sees it.

| Limit | Value | At the limit |
|---|---|---|
| Data rows | 7 to 200 | Six or fewer rows are refused: "Six or fewer data rows should use a compact Markdown table, not a native Slack table." More than 200 fails schema validation. |
| Columns | 20 | Schema validation fails, for the column list and for any row. |
| Characters across all cells and headers | 20,000 | "Slack table cells exceed the 20,000-character data-table limit." |
| Characters in one text cell | 1,000 | Schema validation fails. |
| Characters in a column header | 80 | Schema validation fails. |
| Characters in the caption | 200 | Schema validation fails. |
| Page size on an explorable table | 1 to 100 | Schema validation fails. Unset, the page size is the row count or 10, whichever is smaller. |
| Row header column index | 0 to 19 | Schema validation fails. |
| Native tables in one answer | 1 | "A Slack answer may contain at most one native table presentation." |
| Characters in the Markdown fallback | 12,000 | Rows stop and the fallback ends with `_Showing N of M rows._` |

The model is instructed to summarize instead of calling `present_table` when a result would exceed 200 rows, 20 columns, or 20,000 cell characters.

## Agent memory

Enforced in `src/memory/validation.ts` and `src/memory/store.ts`. An Agent has one memory, and a remembered entry is appended to it.

| Limit | Value | At the limit |
|---|---|---|
| Description on one write | 512 bytes | `memory_content_too_large`, "Description must be at most 512 bytes." |
| Body on one write | 8 KiB (8,192 bytes) | `memory_content_too_large`, "Body must be at most 8192 bytes." |
| Stored memory document | 64 KiB (65,536 bytes) | `memory_entry_too_large`, "Agent memory is too large." |

Memory content is also rejected for control characters (`memory_invalid_control_character`) and for credential-like content (`memory_credential_rejected`), at any size.

## Agent handles

Enforced in `src/slack/agent-presence/handles.ts`. A handle is a Slack user group with no members.

| Limit | Value | At the limit |
|---|---|---|
| Handle length | 80 characters | The normalized handle is truncated to 80 characters, then any trailing `-` is removed. |
| Characters allowed | `a-z`, `0-9`, `_`, `-` | Every other run of characters becomes a single `-`. Leading and trailing `-` are stripped. |
| Empty result | none | A handle that normalizes to nothing becomes `agent`. |
| Collision suggestions | 3 by default, suffixes `-2` through `-9999` | The base is shortened so the suffixed handle still fits in 80 characters. |

## Schedules

Enforced in `src/routines/limits.ts`, checked in `src/routines/store.ts`, `src/routines/schedule.ts`, and `src/routines/validation.ts`. Schedules run on Cloudflare only. Their error codes carry the word `routine`, which is what a schedule is called in the source.

### Capacity

| Limit | Value | At the limit |
|---|---|---|
| Active schedules per deployment | 100 | `routine_active_limit`, "This deployment has reached its active routine limit." |
| Active schedules per channel | 20 | `routine_channel_limit`, "This channel has reached its active routine limit." |
| Projected starts per schedule per day | 300 | `routine_scheduled_capacity`, "This schedule exceeds deployment capacity." |
| Projected starts per deployment per day | 600 | `routine_scheduled_capacity`, raised when the new schedule's projection pushes the deployment total past it. |
| Starts in any rolling 15 minutes | 8 | `routine_cluster_capacity`, at creation ("Too many routines run near that time.") and at start time ("This deployment has reached its rolling routine start-rate limit."). |
| Starts in a rolling 24 hours | 610 | `routine_total_start_limit`, "This deployment has reached its rolling routine start limit." |
| Run-now starts in a rolling 24 hours | 10 | `routine_run_now_limit`, "This deployment has reached its rolling run-now limit." |
| Concurrent runs per deployment | 4 | `routine_concurrent_capacity`, "This deployment has reached its active routine run limit." |
| Concurrent runs per schedule | 1 | A second occurrence is not admitted while the first is running. |

### Cadence

| Limit | Value | Note |
|---|---|---|
| Minimum interval between occurrences | 5 minutes | A tighter cadence is refused before the schedule is stored. |
| Occurrence projection horizon | 370 days | A schedule with no occurrence in that window is refused as `routine_schedule_out_of_range`. |
| Reservation lookahead | 48 hours | How far ahead occurrences are reserved against the rolling 15 minute ceiling. |
| Admission grace | 15 minutes | An occurrence older than the grace period is recorded as skipped, with reason `admission_grace_expired` or, for one-time work, `missed_one_time`. |
| Occurrence deadline | 15 minutes | The window in which a result may be delivered. |
| Due claims per heartbeat | 25 | The most occurrences one scheduler wake claims. |
| Admission lease, delivery lease | 2 minutes each | How long a claim is held before another attempt may take it. |
| Delivery retries on a Slack rate limit | 1, and only when the retry-after is 30 seconds or less | Nothing else is retried, because a repeat could post a private result twice. |
| Pending recovery delivery held open | 24 hours | A claimed recovery notice that never settles is recorded as `unknown`. |

### Fields

| Field | Limit |
|---|---|
| Name | 80 code points and 320 bytes |
| Description | 280 code points and 1,120 bytes |
| Task text | 8,192 bytes |
| Schedule input as written | 1,024 bytes |
| Normalized schedule JSON | 4,096 bytes |
| Time zone | 128 bytes, and a valid IANA name |
| Originating request text kept as provenance | 8,192 bytes |
| Change key | 1,024 bytes |
| Public error text | 512 bytes |

### Retention

| Record | Kept for |
|---|---|
| Schedule metadata | 365 days |
| Schedule actions | 30 days |
| Pending confirmations | 15 minutes to expiry, purged 24 hours later |

## Skill import

Enforced in `src/config/skill-import.ts`, which reads `SKILL.md` files from a GitHub repository.

| Limit | Value | At the limit |
|---|---|---|
| Skill directories scanned | 40 | The extra directories are not read and the result reports `capped: true`, so the source can be narrowed with an `@skill` filter. |
| Characters kept from a description | 1,024 | Truncated, not rejected. |
| Characters kept from instructions | 100,000 | Truncated, not rejected. |
| Bytes read from one `SKILL.md` | 512 KiB | `source_too_large`, "GitHub skill document is too large to import safely." |
| Bytes read from repository metadata | 64 KiB | `source_too_large`, "GitHub repository metadata is too large to import safely." |
| Bytes read from the repository tree | 16 MiB | `source_too_large`, "GitHub repository tree is too large to import safely. Use a direct skill-directory URL." |
| Bytes read from one directory listing page | 4 MiB | `source_too_large`, "GitHub skill directory is too large to import safely." |
| Time for one GitHub request | 10,000 ms | The request is aborted. |

The scan cap keeps one import under the Workers subrequest ceiling: resolution costs two subrequests plus one for every skill scanned.

## Management requests

Enforced in `src/management/contracts.ts` and `src/management/schemas.ts`. One request is one atomic set of operations, whether it arrives from Admin, from Slack, or from the management MCP server.

| Limit | Value | At the limit |
|---|---|---|
| Operations per request | 1 to 25 | "A management request must contain between 1 and 25 operations." |
| Serialized request | 128 KiB | "The management request is too large." |
| `dependsOn` entries per operation | 25 | Schema validation fails. |
| Operations a recipe may compile to | 25 | "The recipe compiles to more than 25 operations. Split it into smaller recipes." |

A request is also refused when its serialized form carries a control character or credential-like content, at any size: "Management operations cannot contain credentials or control characters."

## MCP client registration

Enforced in `src/auth/mcp-oauth.ts`, for dynamic client registration against the management MCP.

| Limit | Value | At the limit |
|---|---|---|
| Client metadata document | 16 KiB | `client_metadata_too_large`, HTTP 413. |
| Registration request body | 32 KiB | `request_too_large`, HTTP 413. |
| `client_name` | 120 characters, non-empty | `invalid_client_name`. |
| `redirect_uris` | 1 to 10 | `invalid_redirect_uri`. A URI with a wildcard, credentials, or a fragment is refused, and plain HTTP is refused except on a loopback host. |
| `response_types` | exactly `["code"]` | `unsupported_response_type`. |
| `grant_types` | `authorization_code`, optionally with `refresh_token` | `unsupported_grant_type`. |
| `scope` | only `chickpea:workspace` | `invalid_scope`. |

Only public clients register: `token_endpoint_auth_method` must be `none`, and a request carrying `jwks`, `jwks_uri`, or `token_endpoint_auth_signing_alg` is refused as `public_clients_only`.

## The coding sandbox

Enforced in `src/config/sandbox-settings.ts`, `src/sandbox/session-cap.ts`, and `src/sandbox/egress-handler.ts`. The coding sandbox is a Cloudflare container and is optional.

| Limit | Value | At the limit |
|---|---|---|
| Container instance type | `standard-1` | Fixed. It is not an operator choice. |
| Sessions per UTC calendar month | operator setting; unset means uncapped | The turn fails with `sandbox_session_cap_reached`, "The coding workspace monthly session limit has been reached." |
| Session cap accepted in Admin | 0 to 100,000, where 0 means no cap | Values outside the range are refused. **Settings → Coding sandbox → Advanced** pre-fills 200 until the cap is saved. |
| Package registry hosts | `registry.npmjs.org`, `pypi.org`, `files.pythonhosted.org` | Any other host is denied. An unset allowlist permits all three; an explicit empty list blocks all three. |
| Methods allowed to those hosts | `GET`, `HEAD`, `POST` | Denied as `method-denied`. |

A retry of the same durable turn reuses its reservation, so one turn cannot spend the monthly cap twice.

## Telemetry delivery

Enforced in `src/telemetry/client.ts`. Product telemetry is advisory and never changes a customer-visible operation.

| Limit | Value | At the limit |
|---|---|---|
| Serialized batch | 32 KiB | The batch is dropped without a request and without a retry. |
| Delivery time | 5,000 ms | The request is aborted. |

## Request bodies

Enforced by the bounded reader in `src/security/request-body-limit.ts`, which rejects a body whose declared `Content-Length` is malformed and stops reading once the actual bytes cross the limit. A body that is too large returns HTTP 413; a malformed `Content-Length` returns HTTP 400 on the Slack and MCP paths.

| Route | Limit | Error |
|---|---|---|
| `/channels/slack/events`, `/channels/slack/interactions` | 1 MiB | 413 |
| `/mcp` | 1 MiB | `request_too_large` |
| Admin mutation routes | 1 MiB | `request_too_large` |
| `/webhooks/composio` | 256 KiB | `payload_too_large` |
| Slack installation routes in Admin | 64 KiB | `payload_too_large` |
| `/api/auth/*` | 32 KiB | `request_too_large` |
| `/auth/mcp/consent` | 16 KiB | `request_too_large` |
| `/setup/*` | 16 KiB | `request_too_large` |
| `/admin/setup`, `/admin/setup/*`, `/admin/recovery`, `/auth/slack/install/*`, `/auth/slack/oidc/*`, `/auth/slack/invite`, `/auth/slack/invite/*` | 8,192 bytes | `invalid_request` |
| Team membership updates | 2,048 bytes | `invalid_request`, HTTP 400 |

## On Node

Two groups above do not apply on Node. Schedules are Cloudflare only, and the conversational tool fails closed with `routines_unavailable_on_target`. The coding sandbox is a Cloudflare container, so its session cap and package registry allowlist have no effect; Node uses the standard sandbox. Every other limit on this page is enforced the same way on both targets.

## What is not covered

Model context windows, provider rate limits, and provider token ceilings belong to the model provider, not to Chickpea. Slack's own limits on message length, user groups, and API rate apply on top of these and are documented by Slack. Cloudflare's platform limits on Workers, Durable Objects, and D1 apply on top of these and are documented by Cloudflare.

Source: https://docs.chickpea.co/reference/limits/index.mdx
