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 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, for the workspace and for each 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.
Team context is in private beta and is not part of either plan. Until it is enabled for your workspace, the Team context page does not appear, every Team context route in the REST API is refused with 402 plan_restricted, and the MCP tools are refused the same way. Contact Hyrax if you want it turned on.
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 project. Projects can sit inside other projects, so a project can have sub-projects beneath it.
- A workspace entry applies everywhere: at the workspace and in every project. Its scope is always
subtree. - A project entry applies at that project. With
scope: "project"(the default) it applies there only. Withscope: "subtree"it also applies in every project beneath it.
When you read context at a project, you get the effective set: the workspace's entries, the subtree entries of the projects above it, and the project's own entries. When two of them share a key, the nearest one wins: the project's own entry, then the nearest project above it, then the workspace. So a sub-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 project 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 project that contains the repository and returns that project's digest. When several projects 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. Live entries come first, 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.
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 first, so when a level has many entries, a proposed one may not make the cut.
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 a project, you need a role on that project or on a project above it. Workspace admins and the account owner can write at any project. A role on the workspace's default project counts for that project only, not for the projects beneath it.
- At the workspace, and for a
subtreeentry on a top-level project, 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.
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.
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, except 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. It can't recognize every secret, so a write that passes isn't proof it holds none. Keep credentials out of Team context, and refer to them indirectly instead ("uses our standard deploy token").
NUL characters
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.
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.