Team context
Team context is where your team writes down what your agents should know: decisions, conventions, runbooks, warnings and notes that are true of your workspace, or of one Area, Space or project. Each one is an entry with a stable key. People and agents both read entries, and both write them. What an agent writes waits as a proposal until a person confirms or edits it.
You can work with Team context in three places, and they all read and write the same entries:
- the Team context page in the web app, under Knowledge in the rail, at the workspace and at each Area, Space and project;
- the REST API, under
/api/context/...and/api/projects/{slug}/context/...; - the MCP server, so the agents you connect can read context before they work and propose what they learn.
What an entry holds
| Field | What it is |
|---|---|
key | The entry's address, unique among the live entries at its level. It must work as a single URL path segment: no /, %, backslash or control characters, not . or .., not digest, and no leading or trailing whitespace. Use - as a separator, as in release-freeze. |
kind | One of decision, convention, runbook, warning, note or flag (shown as Feature flag in the app). |
title | A short, readable title. |
summary | One line an agent can act on without reading the body. Digests show this line. |
body_md | The full entry, in Markdown. It can be empty. |
data | An optional JSON object for structured detail. Hyrax doesn't impose a schema on it. |
tags | Optional labels, stored exactly as you write them. |
scope | Where the entry applies. See Workspace and project entries. |
Each entry also records who created it and who last changed it, when, its status, and a version that goes up by one on every change.
Workspace and project entries
An entry lives at one level: the workspace, or one Area, Space or project (see Organizing work). In the API, Areas and Spaces are addressed like projects, by their slug, so "project" below covers all three.
- A workspace entry applies everywhere: at the workspace and at every level inside it. Its scope is always
subtree. - An entry on an Area, Space or project applies at that level. With
scope: "project"(the default) it applies there only. Withscope: "subtree"it also applies at every level beneath it: an Area's reaches its Spaces and their projects, and a Space's reaches its projects.
When you read context at a level, you get the effective set: the workspace's entries, the subtree entries of the levels above it, and the level's own entries. When two of them share a key, the nearest one wins: the level's own entry, then the nearest level above it, then the workspace. So a project can override a workspace convention by writing an entry with the same key, and the losing entries are listed on the winner under replaces. An entry on a disabled level no longer applies anywhere beneath it.
Writes act only on the level you address. You can read an inherited entry from a project, but to change it you go to the level where it lives, or you override it by creating an entry with the same key at your own level.
If an inherited entry comes from a project you can't see, it still applies, but its source project isn't named: source_project_slug is null and source_hidden is true.
Context for a repository
Agents usually start from a repository, not a project. The repository digest finds the deepest Space or project that contains the repository and returns that level's digest. A Space's Inbox, and a Space set to hold every repository (like the Default Space), count only when nothing else contains the repository. When several levels tie at that depth, the first by slug is used and resolved_from.ambiguous is true. A repository that is in no project gets the workspace's digest.
Digests
A digest is the short form an agent reads before it starts: the effective entries' summaries, in reading order. The workspace's organization context comes first whenever the digest includes it, whether it is live or proposed. Then come live entries, then proposed entries that another API key has corroborated, then other proposed entries. Within each of those groups, warnings come first, then nearer levels, then the most recently confirmed and most recently changed. A digest returns 40 entries by default and at most 100. truncated says whether there were more. An agent reads an entry's full body with a separate request.
Organization context
Your workspace's organization context is the text Hyrax adds to every agent prompt in the workspace. It is kept as the workspace entry with the key organization-context, titled Organization context, so it is listed and read like any other workspace entry and applies everywhere. If no such entry is listed, the workspace's notes (below) are still its organization context.
In the web app it is the workspace's Notes for every repository: the card at the top of the workspace's Team context page, and that is where you change it. Someone who can't open Team context finds the same notes under Workspace settings → Agents → Instructions. An agent changes it with the MCP server's Set guidance / context tool. Both need permission to manage workspace settings. The Team context routes and tools refuse to create, change, disable or restore that key, at the workspace or in any project, with key_reserved. A person can still confirm it, and another API key can still corroborate it.
Organization context is used in prompts as soon as it is saved, including while it is proposed. It is limited to 8,192 characters, and the secret check applies to it.
Proposed and live
Every entry is either proposed or live.
- Anything written with an API key is proposed. That covers every write an agent makes over the REST API or the MCP server, even though every key belongs to a person. An agent's change to a live entry also moves it back to proposed until a person confirms it.
- What a person writes in the web app is live. If that person may confirm entries, their create or edit also counts as confirming the entry. If they may not, the entry is still saved and live, but it carries the
unconfirmedlabel, and their edit clears any earlier confirmation.
A person confirms an entry from the web app. Confirming a proposed entry makes it live, and confirming a live entry records that someone has checked it again. Confirming needs a signed-in person: an API key can't confirm, whatever its scopes. The Team context page also lets you confirm proposed entries in bulk.
Proposed entries are not hidden. Agents can still read them by key and in lists, marked as proposed. But digests list live entries before them, so when a level has many entries, a proposed one may not make the cut. The organization context is the exception: when a digest includes it, it comes first.
Corroboration
An API key can corroborate a proposed entry that a different key wrote: it records that this key checked that exact version. The entry stays proposed, because only a person can confirm it, but it gains a corroborated label and ranks above other proposed entries in digests.
- A key can't corroborate an entry it created or last changed.
- Only a proposed entry can be corroborated, and by one key at a time: once a key has corroborated it, another key can't until the entry next changes.
- Any later change, confirmation, disable or restore clears the corroboration.
Several API keys can belong to the same person, so a corroboration shows that a different key checked the entry. It doesn't show that a different person did, or that an independent review took place.
Who can do what
Four permissions control Team context. Members and Admins hold all four by default; Viewers hold none. They're described under Members & roles.
On top of the permission, a write needs access to the level you're writing at:
- At an Area, Space or project, you need a role there or on a level above it. Workspace admins and the account owner can write anywhere. Belonging to a Space only because it's open to everyone in the workspace, as the Default Space is, doesn't count: writing context there needs a role you were given.
- At the workspace, and for a
subtreeentry on an Area or a Space, you also need permission to manage projects (manage_projects, an Admin default). Those entries reach many projects at once, so a Member can't write them by default.
These rules apply to creating, editing, disabling, restoring, confirming and corroborating. Reading follows what your access lets you see. The workspace's context is readable with view_context. A project's context is readable through the repositories your access covers: if your access is limited to certain repositories, you can read a project's context only when one of the project's repositories is among them. A project you can't read this way is answered with 404 project_not_found, exactly like one that doesn't exist.
An entry is guidance. Nothing in an entry grants a permission to the agent that reads it.
History
Every change is recorded in the entry's history: when it was created, updated, disabled (deleted), restored, confirmed and corroborated. Each event names who acted and keeps the values the entry held before that change, so you can see exactly what was replaced. History is listed newest first. For a key with no live entry, it shows the most recently disabled entry's history.
Disable and restore
There is no hard delete. Disabling an entry needs a reason (up to 500 characters). The entry stops applying at once, its last state stays in its history, and its key is free for a new entry. Lists include a level's own disabled entries when you ask for them (include_disabled=true), with the reason. Inherited disabled entries never appear.
Restoring brings back the most recently disabled entry for a key at that level. If a new live entry has taken the key since, the restore is refused with 409 key_taken and nothing changes. An entry a person restores comes back with the status it had; an entry an agent restores comes back as proposed.
Links
In the web app, an entry's body can link to other entries and to issues:
[[key]]links to the entry with that key, looked up at the level where the linking entry lives.- An issue reference such as
ISS-42links to that issue, for people who can open issues.
Links inside inline code and code blocks stay as plain text. Over the REST API and the MCP server, the body is returned exactly as written.
Editing safely
Every entry carries an ETag of the form "<entry id>:<version>". Changes are conditional on it, so two writers can't silently overwrite each other:
- An update, replace, disable, confirm or corroborate must say which version it read. If the entry has changed since, the write is refused with
409 version_conflict, and the response carries the current ETag so you can re-read, re-apply your change, and try again. - An ETag names one entry. If an entry was disabled and a new one created with the same key, the old ETag is refused, even though the new entry's version may match.
- A change that changes nothing leaves the entry as it was.
The REST API carries the ETag in the ETag, If-Match and If-None-Match headers. The MCP tools take it as expected_etag. See Conditional requests.
Limits and refusals
Every limit is a refusal, never a silent truncation.
| Field | Limit | Measured in |
|---|---|---|
title | 500 | characters |
summary | 200 | characters |
body_md | 64 KiB (65,536) | bytes of UTF-8 |
data | 64 KiB (65,536) | bytes of UTF-8, serialized as JSON |
tags | 20 tags, each up to 50 characters, no blanks or duplicates | |
| disable reason | 500 | characters |
agent_label | 200 | characters |
A field over its size limit, including a single tag over 50 characters, is refused with field_too_large. The response's details names the field, the limit, the actual size and the unit. A list of more than 20 tags, or one with a blank or duplicate tag, is refused with tags_invalid. A key too long to store is refused with key_too_long. The key organization-context is refused with key_reserved: see Organization context.
Secrets
Team context is read by the agents in your workspace, so Hyrax refuses a write that looks like it carries a credential, with secret_detected. It checks the key, title, summary, body, tags, agent label, disable reason, and every string and object key inside data. The refusal names the field but never repeats the value.
Write down where a credential is kept, never the credential itself: the secret's name in your secrets manager, its vault path, or "the deploy token in 1Password". The entry then tells an agent where to look without handing the secret to everyone who can read Team context.
The check recognizes known credential formats, such as GitHub, GitLab, Slack, Stripe, OpenAI and Anthropic tokens, Hyrax API keys, AWS access key IDs, an AWS secret access key written after its name (as in aws_secret_access_key = …), private key blocks, JSON Web Tokens, a Bearer credential of 20 or more characters that contains a digit, and a Basic credential that decodes to a user name and password. It also recognizes a password of three or more characters embedded in a URL whose host is a dotted name or IPv4 address. That rule skips a placeholder password (such as ${DB_PASSWORD} or the word password) and these hosts: a host with no dot (such as db or localhost), a 127.x.x.x address, and hosts ending in .example, .test, .invalid or .localhost or under example.com, example.org or example.net. A skipped URL can still be refused by another check, for example when its password is a GitHub token.
The formats above are examples, not the full list. The check can't recognize every secret, so a write that passes isn't proof it holds none.
If a credential does get stored in an entry, rotate it. Changing or disabling the entry doesn't erase it, because the entry's history keeps every earlier value and there is no hard delete.
NUL characters and unpaired surrogates
The NUL character (U+0000) can't be stored. A request that carries one anywhere, in a path, query parameter or body field, is refused with nul_byte, and details.field says where it was.
Neither can an unpaired surrogate (U+D800 to U+DFFF), which is what a lone JSON escape such as \ud800 decodes to. It is refused the same way, with lone_surrogate. On the MCP server, some arguments are checked by the tool's own schema first, and a surrogate in one of those is refused with invalid_payload instead. A correctly paired escape such as \ud83d\ude42 is one ordinary character and is stored as it is.
See also
- Endpoint reference: Team context: every route, with its permission and errors.
- MCP server: Team context tools: the tools your agents call.
- Members & roles: roles and the Team context permissions.