Concepts
Profiles
Agent playbook — written for the agent connected to your account; you can read along
A profile is an identity in one org that an agent or bot acts as instead of acting as you. You decide its starting access when you create it. It gets more only when someone explicitly grants it something, such as sharing a package with it, or when it creates something and gets the creator grant on it. It never holds more than you and its parents hold.
Profiles are an experiment. Turn them on at /account/experiments or with
experimentSet({ experiment: "profiles", enabled: true }). You can create,
list, shorten, and revoke profiles, share packages with them, and mint tokens
that let an agent act as one (see
Hand a sub-agent its own token).
The Agent profiles page at /account/profiles (or /@{org}/-/profiles in a
team org) shows the same profiles as a tree of parents and sub-profiles. From
the browser you can create, list, shorten, and revoke profiles and mint or
revoke profile tokens; share packages from a package's Collaborators tab. It
is not your public profile.
How access is checked
A request running as a profile reaches something only when all of these allow it: your own live access, the profile's grants, every parent profile's grants, and the credential's scopes. Being an org Owner does not skip a profile's limits. A denial names the layer that blocked it:
profile_grant:Profile "helper" was not granted package:execute on package @acme/tools(orParent profile "agent" does not hold ...).profile_inactive: the profile or a parent was revoked or expired.
Share a package with a profile
Open the package's Collaborators tab and use Add one of your profiles, or call:
packageCollaboratorSet({
package_id: 'pkg_...',
profile_id: 'prf_...',
preset: 'use',
})A sub-profile can only get what its parent already holds on that package, and it
still never gets more than you. packageCollaboratorList shows profile grants
with kind: "profile"; packageCollaboratorRemove takes their grant_id.
Which profile is running
whoami (and meta_get_current_user) returns
profile: { id, name, ancestors } when the request runs as a profile, ancestors
root first, or null when it runs as you. Use it to branch behavior, never as a
security check: grants enforce access.
What a profile creates
When a request running as a profile creates a package, app, or credential, both
the profile and you get manage on it, so nothing is stranded when the profile
ends. Publishing under a profile is refused when the profile chain cannot run a
package the new version imports.
Create a profile
Call profileCreate with a name and the grants it starts with:
{
"name": "release-bot",
"expires_at": "2026-12-31T00:00:00Z",
"grants": [
{
"resource_type": "package",
"resource_id": "<package id>",
"permissions": ["package:read", "package:execute"]
}
]
}- Each grant names one resource, or the org itself for org-level permissions
only:
org:read,org:execute,search:read,member:read,team:read,audit:read,profile:manage, and any<type>:create. A grant that covers every resource of a kind is refused. - You can only hand over permissions you hold on that resource right now.
- Grants are a snapshot. When you or a parent later gain access, the profile does not.
Sub-profiles
Pass parent_profile_id to create a sub-profile, for an agent your agent
starts. A sub-profile:
- gets only permissions every parent holds (
profile_grant_exceeds_parentotherwise); - never outlives its parents: without
expires_atit inherits the earliest parent expiry, and a later one is refused; - stops working when any parent is revoked or expires
(
status: "parent_inactive").
Profiles nest at most 5 levels deep.
Hand a sub-agent its own token
To start a sub-agent with less access than yours, call profileHandoff through
the api tool (POST /v1/profiles/handoff) with a name, the grants it
needs, and token scopes. It creates a sub-profile of the calling profile and
returns { profile, token }; give the sub-agent token.token as its
KODY_API_TOKEN. To mint another token for an existing profile, call
tokenCreate with profile_id. A profile token:
- can only be minted for the calling profile or a sub-profile below it;
- never outlives the earliest expiry in its chain;
- works with the Open API, the
apitool, and the CLI, not/mcp.
What runs as a profile
- Credentials, memory, and execution contexts need a grant in the profile chain;
without one they are refused or skipped (
profile_grant). - Host commands on a context need
execution_context:commandin every layer of the profile chain, even on a machine you operate. - Jobs, triggers, and app visits that a profile turns on keep running as that
profile. When it or a parent is revoked or expires, they pause with
paused_reason: "profile_inactive"and stay paused until an active credential turns them on again. - Runs record the profile id and chain, so activity shows which agent did it.
Shorten or revoke
profileUpdate({ profile_id, expires_at })moves expiry earlier. It never moves later; create a new profile for more time.profileRevoke({ profile_id })ends a profile and everything below it. It cannot be undone.
Only you (or a profile above it) can change your profile. A profile cannot
change itself, its parents, or its siblings. An org Owner can list every profile
(profileList({ all_members: true })) and revoke any of them, but cannot change
one.
Lifecycle
- Leaving an org revokes your profiles in it.
- Revoked and expired profiles are deleted 30 days later.
profileListshows each profile'sstatus,chain, andeffectiveExpiresAt.
Field reference: docs/use/profiles.