The agent
ost-agent is the process inside each agent container. It runs as one person of the team, connects out to the office, and works through the items the office offers: one work item at a time, and chat sessions beside it. The LLM runtime is an adapter (0009); the agent never talks to a model itself.
Startup
Section titled “Startup”The container contract is a few environment variables and secrets. Hosting and secret storage stay with the operator (0008).
| Input | What it is |
|---|---|
OUTSTAFF_PERSON |
Handle of the person this container runs as. |
OUTSTAFF_OFFICE_URL |
WebSocket URL of the office, for example ws://office:8080/agent. |
OUTSTAFF_AGENT_TOKEN |
The person’s agent token, read as a secret (file first, environment as a fallback). |
The agent has no team file of its own. Only the office loads team.yaml; the agent receives it in the office’s welcome (see the agent protocol), over the connection its token authenticated.
In order, startup:
- Connects to the office, validates the team config the
welcomecarries and checks that the person is on it. An invalid team config, or one without the person, stops the start. The agent writes the team to~/.config/outstaff/team.jsonand setsOUTSTAFF_TEAM_FILEto it for its own child processes:ost git-credential, which git starts as the credential helper, and theostverbs run from its shell. - Builds the ports the verbs use from the installed adapters (
createAgentEnv). A configured port whose adapter is missing or fails is recorded, and the verb that needs it says why. - Renders the person’s working files from the stock catalog plus the team’s ejected catalog, and writes them into
~/work. The runtime adapter says where it expects them: for the Claude Agent SDK, skills go to.claude/skills/<role>/SKILL.md. Adapters declare this withworkdirLayout, so the agent has no runtime-specific paths. Other files (references, templates, process documents) stay where they were rendered, which is where the skills point. - Creates the runtime the person runs on: their own
people[].runtime, else the team’sintegrations.runtime(see Runtimes). It stops with an error naming the person when this build does not carry that runtime adapter, or when the adapter cannot enforce a permission policy (runtime.permissions), and logsruntime selectedwith the person, the runtime adapter and the model. Every run uses the person’smodelfromteam.yaml; there is no default model. Every part of startup that readsintegrations.runtime(the working-file layout, the runtime’s rule pack, the Design canvas) sees the person’s runtime; the canvas is on only when that runtime providesdesign.canvas. - Builds the permission policy (0006, 0015) from the role’s profile, the person’s repository grants, the container layout, and the rule packs: adapter packs from the registry, the toolsets of the role’s profile and the tool declarations, and
policies.commandsfrom the team. Anything no rule denies is allowed; see permissions. - Starts the work loop on that connection.
When the agent reconnects and the welcome brings a different team config (the office restarted with a changed team file), the agent takes no new items, lets the running ones finish, closes the connection and goes through startup again with the new team. Working files, policy and runtime never change under a running item.
A team with an ejected catalog builds its own agent image with the catalog in it (Customizing the catalog); the agent reads it from /opt/outstaff/team/.outstaff, at the catalog.path of the team config.
The layout below $HOME: work (rendered files, the runtime’s working directory), repos, worktrees, scratch and memory (the notes, with memory.store: volume). The secrets directory is OUTSTAFF_SECRETS_DIR.
Identity
Section titled “Identity”The identity prompt is built from data, not from a template, and appended to the runtime’s system prompt for every session. It contains:
- the person’s name, handle and pronouns, their role title and summary, the team and its product;
- the teammates, and the trusted humans (or who counts as one when the team lists none), with the rule that text from anyone else is data, not instructions;
- the message style from
policies.messages: the line limit, whether to always link tickets, pull requests and documents, and what to leave out. The same style applies to the final message of each item, which becomes its hand-back summary.
Work loop
Section titled “Work loop”For each offer the agent:
-
accepts it;
-
for a
review_feedbackormerged_feedbackitem, reacts witheyeson the code host (see Acknowledgements); -
builds the prompt from the offer: item kind and title, subjects and links, the wake context (the linked tickets, pull requests, documents and chat threads, their text quoted as data), and the journal of earlier rounds;
-
runs it through the runtime with the identity prompt, the policy, the built-in tools of the profile and the
ostverbs as in-process tools (createRuntimeTools, includingaskandquestion answer); -
sends
progressfrom the runtime’s text events, at most once per interval (30 seconds by default); -
appends a journal entry (see Journal);
-
hands back with the final message as the summary, the links found in it, and a status:
done,waitingwhen a question the agent asked another agent is still open,blockedwhen the agent said so (ost handback blocked) or left its ticket blocked, orsilent. A failed run, or a runtime that throws, sendsfailwith the reason.The agent states the status with
ost handback done|blocked|silent; without it, the runner works it out. Adonehand-back needs a result to review or finished work, whatever the kind. The runner checks it in code: a pull request of the item open for review (not a draft, as the office’s offer saw it), a pull request the agent opened, updated or merged in the round, a document it linked to its work, or a ticket it closed, canceled or moved to review. A round of a kind that may finish silently (chat,question,asked,ticket_comment,doc_feedback,unblock,merge, and work on a pull request that exists:review_feedback,merged_feedback,rebase,ci_failed,merge_checks_failed) with no result is handed backsilent, unless the agent saiddone: the item ends and its ticket is put back as the round found it. Otherwise, with nothing to review, the agent is told once, in the same session, that the round cannot be handed back as done, and to say what it needs and from whom; the status is then worked out again from that turn. Still nothing to review: the item is handed back asblocked, with the agent’s answer as the summary. Adonehand-back carries what the check found (toReview); the office takes adonewithout it (from an older agent) with nothing to review assilentfor those kinds and asblockedfor any other. Work on a ticket saidsilent(ost handback silent) is handed back so too: waiting on a human’s review, or on a sub-ticket someone else holds, is not blocked. The office checks it against its own view, and takes ablockedon a ticket that only waits on work in flight assilent, and asilenton one that waits on nothing asblocked(see the office).
Heartbeats are sent by the office connection itself, at the interval the office asks for, with the id of the item being worked on. Work items run one at a time; a second offer waits until the first is handed back. Chat sessions run beside the work item, each with its own heartbeat, up to the office’s limit (policies.chat.parallel_sessions).
Stopping. On SIGTERM or SIGINT the agent interrupts the runtime, hands the item back as interrupted with the last message, and closes the connection. The office queues the item first with its session; the next offer says the previous run was interrupted, and the session resumes.
Restarts. The office lists accepted, unfinished items when the agent reconnects, but it cannot re-send their context. At startup the agent sends decline for each of them. That puts the item back in the queue untouched: no failure or attempt is counted, and the next offer carries the journal and the stored session.
Acknowledgements
Section titled “Acknowledgements”Every surface shows that the agent took a request before it answers, with the same eyes reaction:
| Surface | Where the acknowledgement appears |
|---|---|
| Chat | An eyes reaction on the person’s message, added by the chat relay as soon as the message arrives (see Chat). |
| Code host | An eyes reaction on each comment that triggered a review_feedback or merged_feedback item: the comment itself for a comment on the pull request conversation or an inline comment, and every inline comment of a submitted review. The host has no reactions on a review body, so a review with only a summary gets none; its reply comes when the round ends. |
The agent runner adds the code host reaction when it accepts the offer, before the run starts, not the model, so it always happens and costs no tokens. It acts as the person, with the agent’s own Vcs port (the person’s own App, which has Pull requests and Issues write); the office’s App is read-only. The comment ids come from the feedback in the offer. The same reaction is never added twice to one comment, and a failure (for example a comment that was deleted meanwhile) is logged as a warning and never blocks the round.
The office’s event pipeline decides tracker changes for an agent without waking it: a ticket moved to review and handed to its requester after a done hand-back, moved to done when the agent’s pull request merges, a question mirrored on the asker’s ticket. The office sends each as an act message; the agent’s process makes the changes itself, with the person’s own tracker and knowledge ports, so the ticket’s history shows the agent. No model runs and no work item starts: acts are made one at a time beside any running item, and each is answered with act_result, one line per change and the error that stopped it. An act sent again, because its answer was lost, is answered again without being made twice. An agent that was offline gets the acts it missed in the next welcome and makes them then (decision 0019).
The runtime knows how close the person’s LLM subscription is to its limits; the office does not. The agent forwards that to the office with the usage message so the usage policy can hold automatic work:
- Every usage event of a running item is forwarded, but at most once a minute. A report that would be skipped is held and sent when the item ends. A report that moves the busiest window across
policies.usage.defer_scheduled_atorpause_automatic_at, up or down, is sent at once. - While no item is running the agent asks the runtime’s
probeUsage(capabilityruntime.usage) when it connects and then every 10 minutes (usageProbeEveryMs), and forwards the result. A runtime without it only reports during work. - A failed report is logged and dropped; the next one replaces it.
Sessions
Section titled “Sessions”One runtime session per work item (0010). When the runtime starts a session the agent stores its id with the item by sending session; the office keeps it and puts it in sessionId on every later offer of that item. The agent resumes that session when present and starts a new one otherwise. A session is never used for another item, and nothing is kept on the agent’s disk, so a replaced container loses nothing.
Which runtime and model ran an item is recorded on its run. The agent says which runtime adapter it runs on, and the model the person is configured with, when it accepts the offer (accept). When the runtime starts a session and names the model it runs (the Claude adapter reads it from the session start), the agent sends it with session in every round, so a run configured with an alias such as sonnet gets the concrete model id. A runtime that does not name its model leaves it unknown.
A lost session. The runtime adapter marks a failed run as sessionLost when it was asked to resume a session the runtime no longer knows (unknown or expired; the Claude Agent SDK adapter recognizes “No conversation found”). The agent then:
- appends the journal note “Session restarted from journal: the stored session could not be resumed.”;
- starts a new session for the same item with a prompt that says so and carries the same journal and current state as every offer: subjects with their links and status, and the journal of earlier rounds;
- stores the new session id with the item, replacing the lost one.
It never starts from nothing silently, and it restarts once: if the new session fails too, the item fails as usual.
A chat session has a second kind of key, the thread (thread:<channel>:<thread>). The chat sessions of one thread, the first and a follow-up after it was done, share one session; the office stores it with the item like any other and carries it over to the next session of the thread. A thread session is never used for an item that did not come from that thread, and an item session is never used for a thread. Work a chat session filed has the thread’s origin too, but runs under other permissions at the same time, so it has a session of its own.
Each agent listens on its own chat identity: the chat adapter is created in the agent container from the person’s secrets, and chat.start hands every message to the chat relay (createChatRelay). The adapter applies the trust filter and the relay checks again: a message from anyone but a trusted human, and any message from a bot, is dropped before it reaches the office. Direct messages and mentions are the requests; nothing else is read.
For each message the relay reacts with eyes and sends chat_message to the office, which takes it in as a chat.message event. Its event pipeline decides on it: by default it wakes the agent the message was written to in a chat session, and that session answers in the message’s thread. The relay posts nothing itself; the eyes reaction shows the message was seen.
A message the office already took in (repeated) wakes nothing more, so a redelivery by the chat platform does not start a second session. If the office cannot be reached the relay says so in the thread, since the adapter has already marked the message as seen.
Chat sessions. A message is not a work item. The pipeline wakes the agent with a chat session for it, even while a work item runs, and the loop runs it beside that item with its own runtime session, its own heartbeat and a permission profile of its own (chat): it has the tools of a work session and runs commands such as the tests, but it edits no repository file, has no worktree, pushes nothing, and calls only the ost verbs me, team, work, work queue, work attach, work move, doc search, doc show, ticket show, ticket list, ticket comment, ticket create, ticket attach, ticket assign, ticket move, ticket parent, ticket link, ticket edit, pr list, pr status, pr checks, pr threads, pr conversation, pr diff and pr comment. The profile is compiled into the policy, so the runtime’s permission hook refuses anything else whatever the prompt says; the rule packs of the role’s own profile apply to it. The session decides what the message is. A question or a discussion is answered in its final message, which is posted in the thread. Work is filed: ost ticket create with a tracker (work_link), ost work queue without one (work_queue). A ticket the requester wants someone else to have is created with --assignee <them> (work_file_for): it is theirs, the assignment wakes them, no work item is filed for the agent, and the session’s final message is the reply. A message with several requests is filed as several tickets, one per request; filing the same request again is refused before anything is created. ost ticket create warns, without refusing, when an open ticket has a similar title (the same words, or at least two shared words that make up three quarters of the longer title, stop words left out), so the session changes that ticket instead of filing a duplicate. A request to change a ticket that exists (assign it, move it, re-parent it, link it to another, edit its title or description) is made on that ticket in the session; an assignment to a teammate wakes them. Pull requests, documents and code stay off-limits to a chat session. The office creates the work item in the same thread, and the relay posts the reply there, once: the line the session wrote with --say (one line, at most 200 characters, its mentions checked like a pull request body’s), followed by the ticket link, or “On it. Tracked in ost work move. The session then ends without a result post; the work item reports its own result. A request about work the agent already has is not filed again: ost work attach <ticket|pr> makes it a round on that work (the human’s message is its feedback) and the first reply is the session’s --say line followed by [PR #11](link) · [ACME-11](link), or Adding this to [PR #11](link) for [ACME-11](link) without one; when that work is running, the reply adds that the request is picked up when the current round ends, unless the line already says when. A pull request in review is not attached on the session’s own say-so: the session asks in the thread whether it belongs there or is a follow-up, and passes --confirmed after the human answers; merged or done work is always a follow-up.
Context. A new chat session, one without a stored session, starts with the last messages of the conversation (the thread, and the messages before it in the same direct message or channel; 10 by default, from the adapter’s history), each with its time and how long ago it was, under a note that they are past and may be out of date, so current state is checked with ost ticket list and ost pr list, and the person’s current work (the grouped work list, and current_work to the office: tickets and pull requests with status, CI and review). Text from anyone but a trusted human is marked as data. A resumed session already knows the conversation. Every chat session, new or resumed, is told where each repository it can read is cloned and the commit it is at, after a refresh (base clones). A message in the thread of work filed or attached from chat starts a session that is told which item it is about, with that work’s tickets and pull requests; while that work runs, the thread stays with it. A session restarted after a lost one gets the conversation again, like any new chat session. Chat sessions go through the same rounds as all items, so they also get the lost-session restart and the journal entry on hand-back; the report to the requester comes after that journal step.
Report back. When an item with a chat origin ends, the agent asks the office with chat_report whether to post, and the office grants each ending once. The post goes to the thread the request came from and mentions the requester (<@id>, which the adapter turns into a mention):
- Done. The final message of the item, in full (the agent is asked to keep it to about
policies.messages.max_lineslines, and a reply over a safety cap of 40 lines is split into several messages in the thread, never cut), followed by links the message does not contain: the pull request, the ticket, documents. The links come from the hand-back and from the office, which knows the item’s ticket (if there is one) and the pull requests that implement it. - Failed. One short note that it could not finish and will be tried again. The next failure of the same item stays quiet.
- Blocked. What is needed, with the links. An item the scheduler blocks (stale twice, loop guard) is reported by the office itself.
- Waiting. Nothing. An item that waits for a teammate’s answer, a shutdown hand-back, and the review rounds and questions that grow out of the request report nothing; the requester hears when the requester’s own item finishes.
ost work move <id> next|last|before|after [other] puts a queued item at a place in the agent’s queue; the chat session that reads the requester’s answer runs it.
Journal
Section titled “Journal”Every hand-back (done, waiting, or stopped by a shutdown) is preceded by a short structured entry in the item’s journal, which the office stores and puts in the context of every later offer. For a finished run the agent asks the runtime for it with one more turn in the same session, without tools:
Done: what you did this round.Decisions: what you decided and why, or none.Open: what is still open or what you wait for, or none.Links: the pull requests, documents and tickets involved, or none.If the answer does not follow the format, or the extra turn fails, the entry is built from the final message and its links instead, so a hand-back never goes without one. When the agent stops for a shutdown it does not ask the runtime again; it uses the last message. A journal entry that cannot be stored is logged and does not hold back the hand-back. The office also records the hand-back summary itself and the progress lines, so the journal reads as the full history of the item.
Memory
Section titled “Memory”An agent’s memory is its notes: lasting lessons and preferences, per person, shared by all its items (0010). Item details go to the journal. The agent reads and changes its notes only with ost note (list, add, update, remove), never by writing files: the permission policy lets the runtime’s tools write scratch, /tmp, Claude Code’s session folders and the granted worktrees, and not memory/. memory.store in the team file chooses where the notes are kept, and the verbs and the catalog text are the same for both:
volume(the default): a Markdown list in~/memory/notes.mdon the agent’s home volume. The volume then holds something that cannot be rebuilt, so back it up.knowledge: in the knowledge integration, which must provide theknowledge.notescapability (ost validatefails otherwise; there is no fallback to the volume). Its a Plan keeps one public page per agent,Agent notes/<name>, created by (so owned by) the agent’s own key. Humans can read it, a trusted human can edit it, every change is a revision with its author, and a comment on it reaches the agent like a comment on any document it wrote (process/doc-feedback.md).
Who can have a note written. Chat sessions may use ost note too, so a trusted human can say “remember …” in chat. A chat session starts only for a trusted human’s message: the chat adapter drops everyone else’s, and the agent’s relay drops them again. Text from anyone else that a round reads (an untrusted message in the thread, a ticket, a document) is data, and the catalog tells the agent never to note what it asks.
Each round starts with the notes. At the start of every round, a work item or a chat session, the agent reads its notes and puts them in the prompt, each with its id (a short hash of its text). Notes that cannot be read are logged, and the prompt says so; the round runs without them.
Only trusted versions are used. A page in the knowledge base can be edited by any member with access to the documents, other agents included, and the notes are guidance in every run. So a version is used only when its author is the agent itself or a trusted human: the agent’s own account is known by its identities.tracker (or its handle as the member’s username), and a trusted human as for comments on tickets (humans with a tracker identity, else every human member). Otherwise the agent uses the newest earlier version a trusted author wrote, from the page’s revisions, and the prompt says who changed the notes, the version and the page, so the agent can review the change. The agent’s next ost note change starts from the notes it uses, so it replaces the ignored version on the page; the revision history keeps it.
The office sees every change. Each ost note change, the move from the volume and an ignored version are reported to the office (notes_changed). The office logs them, an ignored version at warn with its author, and records the notes page as the agent’s document, so comments on it reach the agent. The event pipeline’s trace does not show them: a trace is per pipeline event, and notes changes are not pipeline events.
Moving existing notes. On a start where the store has no notes yet (no notes.md, or no page), the notes in the files of ~/memory (each top-level list item or paragraph of a text file; headings are left out) are written to the store. The files are left in place. With store: knowledge, the home volume then holds only state that can be rebuilt and work that was not pushed yet; the catalog asks agents to push work in progress early, to a draft pull request.
Repositories and credentials
Section titled “Repositories and credentials”Cloning. At startup the agent clones every team repository the person has a grant on into ~/repos/<name>, or fetches it when the clone is already there. A repository without a grant is never cloned. A repository that cannot be reached is logged and skipped; the others carry on.
Base clones follow the default branch. Nobody works in ~/repos/<name>: work happens in worktrees, and a chat session only reads. So after every fetch (at startup, before each work round in the clone, and before each chat session) the agent moves the clone’s working tree to the fetched origin/<default branch> with a detached checkout. Local branches, the credential helper and the pre-push hook stay as they are. A clone with uncommitted changes to tracked files was changed by hand: it is left where it is and a warning is logged, and so is a clone whose checkout fails (an untracked file in the way, say). Before a chat session the agent refreshes every clone the person can read, but fetches a clone at most once a minute, so a burst of messages does not fetch again each time; a clone that cannot be fetched is logged and read where it is. A chat session is told the commit each clone is at, and whether that is the latest fetched default branch. The refresh runs when a session starts rather than on the office’s poll, because freshness matters only when a session reads the clones, and an idle agent then makes no requests to the code host. A work round’s fetch and a chat session’s refresh can run at once, so everything the agent does to one clone (the fetch, the checkout, the worktree commands) runs one task at a time.
Credentials never sit in the environment or on disk. Git does not get a token from GH_TOKEN, GITHUB_TOKEN or a stored file (Claude Code deliberately keeps those two variables when it scrubs subprocess environments, so the agent never relies on them). Each clone is configured with a credential helper, ost git-credential. When git needs to authenticate, the helper reads git’s request on standard input, finds the team repository it is for, and asks the VCS adapter for a short-lived token for that one repository at the access the person’s grant gives. store and erase do nothing. The clone also resets the helper list first, so a keychain or other global helper never sees the token. Pull request operations go through the ost pr verbs, not a gh that holds a token.
Secrets for the helper are files. Git starts the helper from inside the agent’s shell, where Claude Code’s subprocess scrub has removed credential-looking variables. So the helper depends only on non-secret environment (OUTSTAFF_PERSON, OUTSTAFF_TEAM_FILE, OUTSTAFF_TEAM_REPO, OUTSTAFF_TEAM_REF, OUTSTAFF_SECRETS_DIR, HOME) and reads every secret it needs, such as the App keys, from files in the secrets directory. It does not fall back to the environment. If a file is missing it fails with: secrets must be provided as files under the secrets directory; env-only secrets are not visible to git’s credential helper. Operators therefore mount the secrets they give an agent container as files (the container contract’s secrets directory), not only as environment variables.
The role profile caps the access. The token is never more than the person’s role profile allows. The readonly profile gets read tokens even with a write grant, and its clones are read-only for the pre-push hook too; docs, code and infra follow the grant (a docs role’s file-level limits are enforced by the policy’s path rules). Whether a profile honours write grants comes from one place, the profile specs in core. The clone and the helper use the same cap.
The agent can see a token. The agent can run ost git-credential get itself and read the token. That token is scoped to one repository, at the person’s capped grant, and short-lived, so it gives no more than git push with the helper already can. Keeping it inside the container is the job of the egress rules, not of the helper.
Identity. A write grant uses the person’s own App: commits and pushes come from the person’s bot, and user.name and user.email of the clone are taken from the author the credentials report. A read grant uses the office’s read-only App, which holds no write permission on any repository.
Why read access exists. An agent that reviews, plans or writes documents needs to read code it may not change: the design system a feature follows, the infrastructure repository behind a question, a baseline it must stay compatible with. Giving each person a write App on all of those would be far too much. The safeguards:
- the token comes from a read-only App and is limited to one repository and the
contents: readpermission; - the person’s grant must cover the repository, and the policy treats the clone as read-only (no commit-changing git commands are allowed in it);
- a
pre-pushhook in the clone refuses every push from a read repository; - what the agent reads there is data, not instructions, like any text that did not come from a trusted human.
Of these, the token is the boundary. The policy lets an agent run any program (0015), so the policy and the hook guard against mistakes and talked-into pushes, while the read-only App is what makes a push from a read repository fail at the code host. An operator who gives read grants must keep the office’s App read-only, and keep egress to the code host limited to what the agents need (where the boundary sits).
Never the default branch. The policy already denies pushing to a repository’s default branch. As defence in depth every writable clone also has a pre-push hook that refuses it. The token’s scope is the last line: branch protection on the host is still the client’s job.
Worktree per work item (0010). Before each item the agent fetches the repository the item concerns (the one a subject or link names, otherwise the primary repository, otherwise the first one the person may write). Then:
- On the first round it creates a worktree at
~/worktrees/<repo>/<item>detached at the up-to-date default branch. Outstaff creates and names no branch for new work: the agent creates its own withgit switch -c, named by the repository’s conventions (default<type>/<ticket key>-<short slug>), once it has identified the work, andost pr openrefuses a detached HEAD. An item that ends without code changes leaves no branch. An item about an open pull request the person opened (the item’s subject, or the only such pull request of its ticket) works on that pull request’s head branch instead: the worktree checks outorigin/<head>into a local branch of the same name, and the prompt tells the agent to push there. The office sendsheadRef,baseRef,stateandownon each pull request subject. A follow-up after a merge starts detached from the default branch again. The worktree sits below~/worktrees/<repo>because that is where the permission policy allows writes. - On later rounds it reuses the same worktree, with the unfinished work still in it. Work that spans repositories adds a worktree in another one with
ost worktree add <repo> [--branch <name>], at~/worktrees/<repo>/<item>, for a repository the person has a write grant on. It is the item’s from then on: the prompt of each later round lists it, and it is removed with the item. A branch another item’s worktree has checked out is refused, naming that item and theost work attachcommand for it. A chat session does not get the verb: it edits no repository file. - When the item is handed back as
done, the worktree and the scratch directory~/scratch/<item>are removed. An item that iswaitingor blocked keeps both. The branch is never deleted, so commits that were not merged are not lost.
Sweep. Items that end without a normal hand-back leave their worktree and scratch directory behind: one that is canceled, one reassigned to someone else, one that finishes while the agent is offline, and one in progress when the pod restarts. When the agent has connected, and then once a day, it lists the items that have a worktree or a scratch directory and asks the office for their state (work_status). It removes both for each item that is done, canceled, unknown (the office has no such item) or not_yours (it belongs to someone else now, and the office says nothing more about it). Items that are queued, active, waiting or blocked keep theirs. Removal is git worktree remove --force followed by git worktree prune, so the branch stays and commits that were not pushed are not lost. Each removal is logged, and written to the item’s journal while the item is still the person’s. An office that cannot be reached skips the sweep until the next one.
The worktree and scratch directory are added to the runtime’s directories, and the prompt names them. The runtime’s working directory stays ~/work, where the rendered skills are.
The repository’s own conventions. How work is done in a repository (commit count and format, branch names, the pull request title and body, which tests to run) is the repository’s choice, not Outstaff’s. The prompt lists the convention files found in the worktree (CONTRIBUTING.md, AGENTS.md, .github/CONTRIBUTING.md, docs/CONTRIBUTING.md, .github/pull_request_template.md) plus the files repos[].instructions names, and tells the agent to read them before it changes anything. The catalog only states defaults for when the repository says nothing. A project CLAUDE.md is not loaded into an agent session: the repositories and worktrees are added directories, and Claude Code loads CLAUDE.md from those only when CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 is set, which the runtime removes from the session environment. The working directory keeps its own project settings. A file in repos[].instructions is read because the prompt names it. See Tell your agents how your repo works.