Agent access
An agent lives in one workspace: the one where it runs, which decides its
deploy regions, its secrets, where its new collections go
(AETHERFY_WORKSPACE_ID) and its starting level. It can access the
collections of other workspaces you grant it, each at a level. The level decides
what the agent’s code may do there. This page is the reference for the levels,
protected collections and the switch that lets an agent write its own secrets.
Only you can change any of them, never the agent.
Who the rules apply to
The levels and protection apply to an agent’s key only: the key an agent receives when it runs. Your own API key, whether you use it from the SDKs, the CLI or plain HTTP, and the dashboard are never limited by a level or by protection. You can do everything an agent is refused.
The checks add no database query beyond the ones a request already makes. Protection rides the lookup the edge makes to resolve the collection, which is answered from a Redis-backed cache that holds the collection’s flag for up to 60 seconds (a change of protection tells the cache to forget it at once). A request that is forwarded to another region re-reads the collection by its id there. The agent’s levels are an in-memory copy refreshed within 60 seconds. The one place a check reads the database again is changing an alias, a rare administrative action, where an agent’s change reads the alias’s target once more.
The model: lives in, can access
- Lives in: exactly one workspace. Where it lives is not a grant, so it cannot be removed; to change it, move the agent.
- Can access: any other workspace you grant it. A grant belongs to the agent,
names a workspace and carries a level. A workspace you did not grant is refused
to the agent (
403 AGENT_KEY_WORKSPACE_FORBIDDEN), and so is one that does not exist, so an agent cannot find out which workspace names exist.
The three access levels
In words (the dashboard and the CLI say the same):
- Read access: can search, query, retrieve and count points, and read a collection’s information and schema.
- Read and write access: everything read access can do, plus add, change and delete points, and create collections and indexes.
- Full access: everything read and write access can do, plus delete collections, delete indexes and change their structure (schema, aliases), which changes them for everyone who uses them.
A request needs one level and any higher level passes. The next table lists every operation and the level it needs.
| Operation | Needs |
|---|---|
| Search, query, scroll, retrieve and count points; read a collection, its schema and its aliases; list collections and aliases; analyze a schema | Read access |
| Add or update points (upsert), set a payload, overwrite a payload, update vectors | Read and write access |
| Delete points, delete vectors, delete payload fields or clear a payload, by id or by filter | Read and write access, and refused on a protected collection |
| Create a collection | Read and write access |
| Create a payload index | Read and write access |
| Delete a payload index | Full access |
| Set or delete a schema | Full access |
| Change a collection’s description | Full access |
| Create, rename or delete an alias | Full access, and refused when the alias points at a protected collection |
| Delete a collection | Full access, and refused on a protected collection |
| Rename a collection | Never an agent: a person’s change (403 AUTH_AGENT_KEY_OUT_OF_SCOPE) |
Creating adds something and removes nothing, which is why creating a collection or an index needs read and write access while deleting either does not.
How an agent’s level in a workspace is decided
An agent holds one level in each workspace it reaches, decided in this order:
- In the workspace where it lives: the agent’s own setting if it has one, otherwise that workspace’s default for the agents living there. The default is read and write access.
- In a workspace it can access: the grant’s level, and nothing else. The agent’s own setting and the home workspace’s default never apply there. A new grant is read access.
Worked examples. Two workspaces: support, whose default for its agents is read
and write access, and research, whose default is read access.
| Agent | Lives in | Own setting | Grants | Level in support | Level in research |
|---|---|---|---|---|---|
support-bot | support | none | research: read | Read and write (the default of support) | Read (the grant) |
analyst | research | full | none | Not reachable | Full (its own setting; the default of research is read) |
triage | support | read | research: full | Read (its own setting is lower than the default, and wins) | Full (the grant is higher than its level at home, and applies only there) |
nightly | support | none | research: read and write | Read and write (follows the default) | Read and write (the grant) |
Two more rules that follow:
- Changing a workspace’s default changes every agent living there that has no setting of its own, and leaves an agent with a setting of its own unchanged.
- Full at home never carries over. An agent at full access in its own workspace is not at full access in a workspace it can only access: there it holds the level of its grant. A high level where an agent lives never raises what it can do somewhere else.
An agent’s key cannot change a level, a grant or a default, its own or another’s,
and aetherfy.yaml has no field for one.
What a change does and how fast
A change takes effect within 60 seconds on the Aetherfy vector API. So does lowering a level or removing a grant. When you lower a level or revoke a grant the dashboard and the CLI say it plainly and do not ask for a confirmation:
Takes effect within 60 seconds; requests above the new level are refused.
A change that lets the agent’s code do more asks first: raising a level, adding a grant, turning the secrets switch on, turning protection off.
What happens when an agent tries more than its level
A request above the agent’s level is refused 403 AGENT_ACCESS_LEVEL_INSUFFICIENT
and nothing is applied. An agent at read access that tries to write gets:
{
"error": {
"code": "AGENT_ACCESS_LEVEL_INSUFFICIENT",
"message": "This agent's key holds read access in workspace \"research\", and this request needs read and write access. Ask the account owner to raise this agent's access there (the agent's ACCESS tab in the dashboard, or afy access).",
"workspace": "research",
"level": "read",
"required_level": "read_write",
"documentation_url": "https://docs.aetherfy.com/platform/agent-access"
}
}The message says the levels in words; level and required_level carry the
machine values (read, read_write, full). It is decided on the workspace the
request addresses, before anything is forwarded or read, and it is not retryable
until you change the level.
What the agent’s own log shows
A refusal is also written into the agent’s own log, so the person reading the
agent’s logs sees what its key was refused without reading a response body. Each
refusal of an agent’s request (its access, a protected collection or the plan’s
collection limit) and each time the agent deleted a collection or points is one
line on the system stream, in the same plain sentences as the refusal itself
and never the raw level names:
afy logs invoice-bot --stream system --jsonEvery line has stream system and level SYSTEM. Examples:
| What happened | The line |
|---|---|
| The agent tried to delete a collection with read and write access | Refused DELETE /api/v1/workspaces/research/collections/kb: This agent's key holds read and write access in workspace "research", and this request needs full access. … |
| The agent tried to delete points of a protected collection, three times | Refused POST /api/v1/workspaces/research/collections/kb/points/delete: Collection "kb" is protected: … (3 times) |
| The agent created a collection past the plan’s limit | Refused POST /api/v1/workspaces/research/collections: Collection limit reached. Your free plan allows 3 collections. |
| The agent deleted 12 points | Deleted 12 points from collection "kb" in workspace "research". |
| The agent deleted a collection | Deleted collection "kb" in workspace "research". |
A delete says how many points it removed; a delete by filter counts the points the
filter selected. The same refusal of the same kind of request repeated within a
minute is one line ending (3 times), and deletes from one collection within
a minute add up to one line ((4 requests)). An agent that refuses itself in a
loop cannot flood its own log: past 50 distinct lines in a minute, one further line
says how many were not listed. The lines appear within about a minute of the
event, from any region, in the order of the logs. Reading data, and anything your
own key does, writes no line. In the dashboard they are on the agent’s LOGS tab
with the stream system. The full list of streams is in
runs and logs.
Memories and collections follow the same rules
A memory namespace is a collection, so the levels apply to it exactly as to any other collection. This is the whole mapping, for the Python and the JavaScript memory SDKs alike:
| Memory operation | What it does on the vector API | The level it needs |
|---|---|---|
| Search, get, list | Search, query, retrieve, scroll | Read access |
| Add, update | Upsert points | Read and write access |
| Delete by ids, by filter, and delete metadata keys | Delete points, delete payload fields | Read and write access, and refused on a protected collection |
| Create a namespace | Create a collection | Read and write access |
Delete a namespace, clear() on a namespace | Delete the collection | Full access, and refused on a protected collection |
clear() on a thread, delete a thread | Delete the thread’s points by filter | Read and write access, and refused on a protected collection |
| The first thread in a workspace | Creates the shared threads collection and two payload indexes on it | Read and write access: creating a collection or an index adds and removes nothing |
| Set or delete a schema | Change the collection’s schema | Full access |
At each level, memory behaves like this:
| Level | An agent using the memory SDK can |
|---|---|
| Read access | Search, get and list memories and threads |
| Read and write access (the default) | Everything above, and create namespaces and threads, add, update and delete memories, and clear a thread |
| Full access | Everything above, and delete or clear a namespace and set a schema |
At the default level, memory works out of the box. Deleting or clearing a namespace needs full access, and the refusal says so and how to raise it.
Why the default is read and write
An agent that reads, writes and creates can do its job, and cannot delete a collection until a person raises it to full access. That keeps a mistake that destroys gigabytes out of reach by default, without taking memory away: creating a namespace creates a collection and its indexes, which read and write access allows.
To raise or lower it, change the workspace’s default (every agent that follows it), or one agent’s own setting. Both are in Changing the levels.
Protected collections
A collection can be protected. Protected means no agent removes data from the collection. On a protected collection, at any level, no agent’s key may:
- delete the collection;
- delete points, delete vectors or delete payload fields, by id or by filter, including clearing a payload;
- delete, rename or repoint an alias that points at it.
Each is refused 403 COLLECTION_PROTECTED, with collection_name.
Protection stops an agent from deleting the collection or deleting data from it. An agent with write access can still change or overwrite points. An agent that may write can still add points, upsert an existing point id, set or overwrite a payload and update vectors on a protected collection. To stop all changes, give the agent read access.
You, with your own API key, the dashboard or the CLI, can still do all of it, and only you can turn protection off. An agent’s key cannot.
How fast it applies. Protection applies within a minute: at once when the
change reaches the region that answers the agent, and within 60 seconds at the
latest everywhere. If the vector service cannot confirm the change at once, the
dashboard and the CLI say it is saved and may take up to a minute to apply (the
request answers 503 SERVICE_UNAVAILABLE with a Retry-After); sending the same
request again applies it. If Aetherfy cannot read a collection’s protection, an
agent’s request that removes data is refused 503 rather than assumed to be
unprotected.
Two layers
The level and protection answer different questions. The level is what an agent may do in a workspace, for all of its collections. Protection is about one collection, whatever the agent’s level: it holds even for an agent at full access. A request meets the level first, then protection. Neither is a backup: they stop an agent from removing data, and do not bring it back once a person has.
Turning protection on and off
| Where | How |
|---|---|
| Dashboard | The collection’s page, its settings: the protection switch. Turning it off asks you to type the collection’s name |
| CLI | afy collections protect <name>, afy collections unprotect <name>; afy collections list shows which collections are protected |
| Aetherfy agents API | PATCH /api/v1/collections/{id} with {"protected": true}, sent on its own (400 COLLECTION_PROTECTED_COMBINED when it comes with regions, or with a workspace_id other than the collection’s own) |
Turning protection off asks first, since it lets agents delete again: “Unprotecting a collection lets agents with read and write access delete its points, and agents with full access delete the collection.”
Where an agent’s secrets come from
An agent receives the secrets of the workspace it lives in, plus its own. A grant never brings a granted workspace’s secrets. A grant means “you may use this data”; it does not hand over the keys that workspace’s agents run with. If a task needs a credential, give it to the agent explicitly, on the agent or in the workspace it lives in.
The secrets switch
Each agent has a switch, off by default. While it is on, the agent’s own key
may create, update, rotate and delete its own secrets, and nothing else. It
never reaches the secrets of its workspace (shared by every agent living there:
a switched-on agent could redirect its siblings or break them, so workspace
secrets are written by people only), of a workspace it was granted, or of another
agent. It cannot list secrets, so a secret’s value is never read back by the
agent. Names beginning AETHERFY_ stay reserved, and a value is at most 32 KiB
(32 768 UTF-8 bytes; one byte more is refused with 422 SECRET_VALIDATION_ERROR).
The use case is a scheduled task that rotates a credential, such as an OAuth
refresh token, and saves it for its next run. A secret the agent writes is stored
at once and read when the agent starts: a service sees the new value after its
next deployment (a redeploy, afy deploy, a push to a linked repository, or a
rollback); a task sees it on its next run, which is why the switch suits tasks. A
running machine never changes its environment.
The switch is read on every request, so turning it off closes the routes on the
agent’s next request, with no redeploy. Moving the agent turns it off. Turn it on
in the dashboard (the agent’s ACCESS tab, which asks first), with
afy access <agent> --secrets on, or PUT /api/v1/agents/{agent}/access/secrets
with {"enabled": true}. With the switch off, the secret routes answer
403 AUTH_AGENT_KEY_OUT_OF_SCOPE, and the message says the account owner has not
turned the switch on.
What a move resets
Moving an agent to another workspace (afy update <name> --workspace <workspace>, or
MOVE on its ACCESS tab) changes where it lives, and so:
- Its own level is cleared. It follows the new home’s default, because a level chosen for one workspace is not carried into another whose owner chose a different default.
- A grant to the new home is dropped: the agent lives there now, and the grant would otherwise come back to life if it moved again. Its other grants stay.
- Its secrets switch is turned off. The switch was your decision about the agent in the place it lived.
- The old home is no longer reachable unless you grant it, and protection is not affected: a protected collection stays protected.
The confirmation lists what the move changes
Both surfaces ask before they move an agent (afy update --workspace asks you to
answer y; MOVE on the ACCESS tab opens a dialog), and both list what the move
does to this agent, in these sentences. A line appears only when it applies.
| Line | Shown when |
|---|---|
Its own level in '<old>' (<level>) is cleared; in '<new>' it holds that workspace's default. | the agent has its own level set |
Its secrets switch is turned off. | the switch is on |
Its grant to '<new>' is dropped: it is its home now. | the agent holds a grant to the new home |
It keeps its other grants: '<workspace>' (<level>), '<workspace>' (<level>). | it has other grants; each is named with its level, separated by commas |
It has no other grants. | it has none |
It no longer reaches '<old>' unless it is granted there. | always |
--yes skips the question, not the list: the CLI prints the same lines to
stderr, and a script without --yes and without a terminal is refused (exit 2)
before anything is sent. If the lines cannot be read, the CLI says so and still
asks; the move itself is never refused for it. An agent already in the target
workspace has nothing to confirm.
What happens when a collection is deleted
The collection’s record goes at once, and the name is free to use again. The
physical removal of its data in each region runs in the background, with retries.
Nothing refuses the delete for being in use. Before afy collections delete asks
you to confirm, it tells you which agents used the collection in the last 35 days
(“Used by agent invoice-bot in the last 35 days.”), and the delete is your call.
The collection’s page in the dashboard shows the same agents in its USED BY list
(the dashboard deletes points, not collections). The 35 days is how far back Aetherfy records
which agent used which collection, so an agent whose only trigger is a monthly
scheduled task is named too. It is not a retention promise, and the delete is
never refused for it. What stops an agent deleting a collection is its level (the
default cannot delete one) and protection (no agent can, at any level).
When the plan’s limit is hit
Creating more collections than the plan allows is refused 403 PLAN_LIMIT_EXCEEDED
by the vector API, with the same body the agents API sends for its own plan limits:
code, message, upgrade_url, limit (the limit that fired), allowed (what
your plan allows) and current (what you hold now). allowed is a number, or
false for a feature the plan lacks; a feature that is simply on or off has no
current.
{
"error": {
"code": "PLAN_LIMIT_EXCEEDED",
"message": "Collection limit reached. Your free plan allows 3 collections.",
"upgrade_url": "https://app.aetherfy.com/dashboard/settings/billing",
"limit": "max_collections",
"allowed": 3,
"current": 3
}
}{
"detail": {
"code": "PLAN_LIMIT_EXCEEDED",
"message": "Your plan does not support always-on mode. Upgrade to enable it.",
"upgrade_url": "https://app.aetherfy.com/dashboard/settings/billing",
"limit": "allows_always_on",
"allowed": false
}
}The agents API carries the same fields inside detail, the vector API inside
error. allowed is null for a limit your plan sets no cap on; a refusal is
only sent when there is a cap to reach, so a client normally sees a number or
false. A memory size the schedule does not price is not a plan limit: it is
422 MEMORY_SIZE_UNSUPPORTED, with the supported sizes in its body.
Spawning does not pass access on
Levels are not transitive through spawn. An agent at read access can spawn a worker
listed in its allowed_workers, and that worker runs with its own level, which
may be full access. Listing a worker is a decision you take, so give a worker the
level you want it to hold and list it deliberately.
Changing the levels
| Where | How |
|---|---|
| Dashboard | The agent’s page, ACCESS tab: the workspaces it can access, each with a level, set when granting and changeable after, and the level where it lives. The workspace’s page, SETTINGS tab: its default level. The workspace’s page, AGENTS & ACCESS tab: which agents live there or can access it, each with its level |
| CLI | afy access <agent> --add <workspace> --level read_write; afy access <agent> --own-level read (or default); afy workspaces update <workspace> --agent-access-level read |
| Aetherfy agents API | POST /api/v1/agents/{agent}/access/workspaces with level; PUT /api/v1/agents/{agent}/access/own-workspace with level (null follows the workspace’s default); PATCH /api/v1/workspaces/{workspace} with agent_access_level |
A change that lets the agent’s code do more asks first, in the dashboard and in
the CLI (--yes skips the question, and is required without a terminal; a “no”
exits 1 and changes nothing). A change that changes nothing asks nothing, and a
change that narrows asks nothing; it only says when it takes effect.
An agent can read its own access, never another’s:
GET /api/v1/agents/{agent}/access with its injected key answers the levels it
holds and whether its secrets switch is on.
Error codes
| Code | Status | Meaning, and the way out |
|---|---|---|
AGENT_ACCESS_LEVEL_INSUFFICIENT | 403 | The request needs more than the level the agent holds in that workspace. Raise its level in that workspace, or stop the request |
AGENT_KEY_WORKSPACE_FORBIDDEN | 403 | The workspace is not the agent’s and was not granted to it, or does not exist. Grant it, or use a workspace it reaches |
AUTH_AGENT_KEY_OUT_OF_SCOPE | 403 | A route the agent’s key may not call at all (an account route, renaming a collection), or a secret route while the switch is off. Use your own key, or turn the switch on |
COLLECTION_PROTECTED | 403 | An agent’s key tried to delete a protected collection, remove data from it, or delete, rename or repoint an alias that points at it. Turn protection off first, or do it with your own key |
COLLECTION_PROTECTED_COMBINED | 400 | protected was sent with regions, or with a workspace_id other than the collection’s own, in one request. Send it on its own |
AGENT_ACCESS_UNAVAILABLE | 503 | Aetherfy could not read the agent’s levels; nothing was applied. Retry |
COLLECTION_LOOKUP_UNAVAILABLE | 503 | Aetherfy could not look the collection up just now, which includes its protection when the request removes data. Retry |
SERVICE_UNAVAILABLE | 503 | On a protection change: it is saved but the vector service did not confirm it yet, so it may take up to a minute to apply. Send the same request again to apply it now |
PLAN_LIMIT_EXCEEDED | 403 | The plan’s collection limit is reached; the answer names limit, allowed and current. Delete a collection or upgrade |
What an agent catches for each (the full mapping is on errors):
| Code | Python and JavaScript raise | What it carries (Python / JavaScript) |
|---|---|---|
AGENT_KEY_WORKSPACE_FORBIDDEN | AgentWorkspaceForbiddenError | .workspace |
AGENT_ACCESS_LEVEL_INSUFFICIENT | AccessLevelInsufficientError | .workspace, .level, .required_level / .requiredLevel, each one of the machine values read, read_write, full |
COLLECTION_PROTECTED | CollectionProtectedError | .collection_name / .collectionName |
AUTH_AGENT_KEY_OUT_OF_SCOPE | AgentKeyOutOfScopeError | .agent_id / .agentId, .required_scope / .requiredScope |
PLAN_LIMIT_EXCEEDED | QuotaExceededError | .limit, .allowed, .current, .upgrade_url / .upgradeUrl |
The first four are kinds of PermissionDeniedError, so one handler for
PermissionDeniedError catches every refusal of a valid key. A plan limit is not
one of them: the agent may do the thing, the account is at its cap.
Frequently asked questions
Does this limit my own key or the dashboard?
No. Levels and protection apply to an agent’s key only. Your own API key and the dashboard can do everything an agent is refused.
Where does an agent live, and what is a grant?
An agent lives in exactly one workspace. A grant gives one agent access to another workspace’s collections, never its secrets. Grants belong to agents, not to workspaces: a workspace has a default level for the agents living in it, and each agent that can access it from elsewhere holds the level of its own grant.
How do the levels combine?
In the workspace where it lives, an agent holds its own setting, or the workspace’s default when it has none. In a workspace it can access, it holds the grant’s level. The home level never carries over: see the worked examples above.
What happens when an agent at read access tries to write?
It is refused with 403 AGENT_ACCESS_LEVEL_INSUFFICIENT, naming the workspace,
the level held and the level needed, and a line is written into its log. The full
answer is shown above.
Is it enough to protect a collection?
Protection and levels are two layers. Protection is about one collection and holds at any level; the level is about everything an agent may do in a workspace. Protection does not stop an agent with write access from changing or overwriting points. To stop all changes, give the agent read access.
How do schema changes relate to levels?
Reading needs read access; changing a schema, deleting an index or changing an alias needs full access. Creating an index needs read and write access.
Why is the default read and write? How do I raise or lower it?
See “Why the default is read and write” above: agents read, write and create, including memory namespaces, and cannot delete a collection until a person raises them to full access. Raise or lower the workspace’s default, or one agent’s own setting, as described under “Changing the levels”.
Where does an agent’s configuration come from?
Its secrets come only from where it lives: its own and its workspace’s. A grant never brings secrets. The secrets switch lets the agent write its own secrets only, and a new value takes effect at its next run or deploy, which is why it is for tasks.
Why doesn’t a grant bring the other workspace’s secrets?
A grant means “you may use this data”. It should not silently hand over the keys that workspace’s agents run with. If a task needs a credential, give it to the agent explicitly, on the agent or in the workspace it lives in.
Do memories follow the same rules as collections?
Yes: a namespace is a collection. See the table under “Memories and collections follow the same rules”.
What happens when a collection is deleted?
The record goes at once; the physical removal in each region runs in the background with retries. You are warned which agents used it in the last 35 days.
How fast does a change take effect?
Within a minute on the vector API: 60 seconds at most. Lowering a level or removing a grant takes effect within the same window: requests above the new level are refused.
Can an agent change its own access?
No. Its key cannot change a level, a grant, a default or a collection’s
protection, and aetherfy.yaml has no field for them.
Does an agent pass its access on when it spawns another?
No. See “Spawning does not pass access on”.