# Practice review operations

Use this reference to configure the runtime and monitor a deployed instance. Workspace admins should start with [Practice Review](/admin/practice-review.md).

Public feedback addresses the author’s work. `PUBLIC_SUBJECT_INELIGIBLE` means the recorded developer or its support does not qualify for that public copy. Reviewer results remain recorded for the reviewer. Private feedback must pass its own evidence, recipient and delivery checks.

## Deployment configuration

Server configuration metadata defines property names, types, and defaults. The reference Compose files determine which environment variables reach the application. Agent execution alone does not enable repository collection. Each workspace enables practice review and selects its own coverage.

1. Enable the agent runtime on both the submitting server and claiming worker.
2. Enable `GIT_CHECKOUT_ENABLED` for repository access.
3. Give every server and worker container its own storage under `HEPHAESTUS_FABRIC_ROOT` for repository mirrors and attempt inputs.
4. Set campaign, request-rate, retry, and retention limits appropriate to the deployment's budget and governance policy.

### Work budgets

By default, groups with more than six practices are split into smaller sets. Each set has a separate work budget. Each practice gets a fresh model session.

`PRACTICE_REVIEW_PRACTICE_CONCURRENCY` sets the maximum number of practices that an item review evaluates at once. The default is 3. Valid values are 1 through 32.

The server starts item-review practices without a current observation first, then the ones with the oldest last verdict. The runner fills each free slot with the next practice ready to start. Practices with the same declared sources share a cache warm-up: followers wait for the first response, for at most 20 seconds. Other source sets can start during this wait. Slots can span practice groups. Results and the admission request use practice order, independent of completion order.

The review's technical details show **Input from cache** for every protocol. The fact shows the percentage of all input tokens read from cache and the total input count, across all attempts. Total input includes ordinary input, cache reads and cache writes. For example, **7.7% of 26,000 tokens** includes 24,000 ordinary input tokens and 2,000 cache reads.

No recorded input shows **No usage recorded**. When both cache counts are zero, the fact shows **None reported**. This can mean no cache use or that the provider does not report it. A positive percentage below 0.1% shows **Under 0.1%**. The figure does not measure feedback quality.

OpenAI Responses keeps provider-default cache retention and sends no five-minute override. Completions providers use their own cache policy. Use the percentage of input read from cache to compare reviews on the same provider.

With concurrent evaluation, each practice reserves an equal share of its set's calls and output tokens before evaluation starts. The runner assigns any remainder in catalog order. A practice cannot spend another practice's reservation. Unused reservations remain unused. Each model request is capped at the practice's remaining output tokens. The direct model account stays shared across all practice scripts in the review attempt.

Higher concurrency can cover more practices before the deadline when model response time limits progress. It also increases peak memory use and simultaneous model requests. The model proxy still paces upstream requests. A provider limit or model contention can reduce the benefit.

Set the value to 1 to use the serial group pool. Scan reviews always use that serial path. A deadline stops active sessions and leaves queued practices `NOT_REACHED` with reason `DEADLINE`. The runner retains completed results for admission. The debug report and logs include the configured concurrency, effective concurrency, and time for each concurrent practice.

`PRACTICE_REVIEW_PRACTICE_MODEL_CALLS` (default 12) and `PRACTICE_REVIEW_PRACTICE_OUTPUT_TOKENS` (default 16000) determine each set's budget. Each value is multiplied by the number of practices in the set, with a minimum of three. A set of five practices can make 60 model calls and write 80,000 output tokens. A set of one practice can make 36 model calls and write 48,000 output tokens. Concurrent practices reserve shares of this pool. Serial review sessions share the pool without reservations.

A practice script that calls `ctx.review()` opens review sessions, and their calls count against these same set budgets. The direct model calls of practice scripts have their own limits for each review attempt: a token limit and a search unit limit. [Cost and limits](/admin/ai-providers.md#cost-and-limits) owns their settings and defaults. [Practice scripts](#practice-scripts) tells you how to read what each practice script did.

The defaults cap excessive work at several times a set's normal use. A review spends what its practices need, and no more. Each set has its own budget, independent of previous sets' spend. Near the budget limit, the model receives a request to record its current results. Each reservation ends after the call that spends its budget. The serial set ends after the call that spends its pool.

The worker writes the feedback after the sandbox ends, with the same model. These calls count against the workspace's model budget like the review's own calls. Each feedback session makes at most the model calls of one practice's turn for each practice that it decides, and at most three practices' worth. `hephaestus.practice-review.composition-timeout` bounds the time that the worker spends on it (default 10 minutes, at least 1 minute). The step also ends 1 minute before the stale-job sweep would end the attempt, about 14 minutes after the assignment's timeout. The whole review can therefore take that much longer than the timeout of its **Practice reviews** assignment.

While the worker writes, the review holds no sandbox, so the worker can start the next review. `AGENT_MAX_CONCURRENT_COMPOSITIONS` (default 16) sets how many reviews one worker runs at once, with or without a sandbox. This bounds the feedback that it writes at once.

Feedback that the worker cannot write is withheld with the most specific reason:

* **No AI** when the workspace's AI choice no longer permits the model.
* **Evidence not permitted** when the captured work was erased.
* A refusal from the server when the review on the work cannot be published.
* **Not composed** for every other cause. For example, the budget ran out, or the model decided nothing.

These causes withhold at once, because another attempt would meet them again. A review whose results were admitted never fails because of its feedback. The `agent.job.composition.stopped` metric below counts each cause.

A passing cause withholds nothing. Examples are a provider outage, an unexpected failure, or the composition timeout. Under load, the model calls wait for the shared call limit, so the time can run out on calls that never ran.

A new attempt of the same job then writes only the feedback that is still open. It starts no sandbox and uses the job's retry limit with its usual backoff. The attempt at the retry limit withholds what a passing cause leaves open. If that attempt is lost too, one more attempt only withholds what is open. A handed-on attempt that the developer's AI choice, the budget or a missing model would refuse also only withholds, with that reason.

A sandbox that cannot prepare its workspace runs again in a new attempt, within the same shared retry limit. This failure occurs before any model call, so it is a failure of the infrastructure, not of the review. A failure that repeats on every attempt uses all attempts of the retry limit, five by default, before the job fails. The worker log names it with its exit code.

After the last attempt, the job keeps the initializer's exit code and the last 20 lines of its log under `capture.initializationFailure`. Known credential formats in those lines are masked. The lines can name repository paths. [Kubernetes § Troubleshooting](/admin/kubernetes.md#troubleshooting) shows how to read them. A failure before the sandbox starts is logged with its stage, its kind, such as a repository operation, and the exception types.

1. For a model that reasons at length, raise the output tokens.

The timeout of the workspace's **Practice reviews** assignment is the whole-run safety ceiling. It can stop a productive review and leave later practices `NOT_REACHED`. Independent set budgets do not reserve time for every set.

A session stops after five minutes without a model or tool event. On the serial path, two consecutive stalls end measurement with its recorded results. A stalled concurrent practice releases its slot after the session stops.

A timeout below 61 seconds runs at 61 seconds. Each practice script runs inside the same timeout, with no time reserved before the review, as the [workspace ABI](/contributor/agent/agent-workspace-abi.md#practice-script-validation-and-limits) describes.

`PRACTICE_REVIEW_SAMPLING_TEMPERATURE` sets the sampling temperature for practice-review model calls, not Hephaestus calls. If unset, the provider default applies. A low temperature does not guarantee repeatable judgments. Do not set `0` for an open model whose vendor advises against greedy decoding. Such a model can repeat the same tool call until the review stops that practice group. Use the model's recommended sampling or the provider default.

Before you change an environment variable:

1. Read the [server configuration](https://github.com/hephaestus-build/Hephaestus/blob/main/server/application/src/main/resources/application.yml).
2. Read the [reference Compose files](https://github.com/hephaestus-build/Hephaestus/tree/main/docker).
3. Use a value that the selected model supports.
4. Confirm that the deployment forwards the setting.
5. Evaluate changes against representative cases.

Unknown variables or variables that the deployment does not forward cannot configure the application. Repository mirrors and attempt folders are disposable container-local storage, not a shared evidence store. Each container keeps bare mirrors under its own `HEPHAESTUS_FABRIC_ROOT`, which defaults to `/data/git-repos`. The shipped Compose files mount `git-repos` into the server and `worker-git-repos` into the worker. A missing mirror is cloned again on the next sync or review.

Git runs in-process through JGit, with no `git` binary or second image. Commit ingestion uses the server role and its mirrors. Review preparation uses the worker role and its mirrors. A server with its worker role enabled uses one root for both. Attempt folders use the same root. Sandbox inputs and results pass through the authenticated worker gateway.

Preparation failures record the stage and a known failure type without exception text. File and database failure types remain visible when preparation wraps the cause.

A closed preparation reason distinguishes unavailable reviewed work, source authority, pinned ranges, and input contracts. Required source collection and folder rendering have separate reasons. The terminal job retains the attempt, stage, type and reason in `metadata.preparation_failure`. Other metadata fields stay unchanged.

Structured preparation logs and spans carry the same reason without exception text. An unclassified reason remains explicit. A diagnostic type does not grant a retry. Eligibility and retry limits are unchanged. Earlier failures without a retained cause cannot be classified retrospectively.

PostgreSQL retains admitted results and their citation verification results. Delivery does not require the worker's source files. [ADR 0041](https://github.com/hephaestus-build/Hephaestus/blob/main/docs/decisions/0041-compose-1x-kubernetes-2.md#evidence-admission-and-deletion) defines the storage and cleanup contract.

Source authorization is not a deployment switch. The active source contract fixes which governed sources each purpose can use. Integrations only determine which authorized sources are available. See [Source governance](/admin/dsms/item-source-governance.md).

## Running instance

For workspace model readiness, coverage, feedback mode, spend, and review activity, see [Practice review](/admin/practice-review.md).

### Queue health

A worker claims work that already started before fresh reviews. One kind is a review on a later attempt, such as one that a drain put back. The other is a review whose results were admitted and whose feedback is still open. Within each group, the oldest job comes first.

The job queue exposes its own metrics. Use them for alerts instead of application logs.

* **`agent.queue.oldest_age_seconds`** — age of the oldest eligible queued job. It is `0` when empty. Sustained growth needs an alert. Queue depth alone does not distinguish a temporary burst from a persistent backlog.
* **`agent.queue.depth`** — queued jobs eligible to run now.
* **`agent.queue.held`** — jobs held on an exhausted budget.
* **`agent.queue.running`** — jobs active across the fleet.
* **`agent.queue.health.sampler.failures`** — failed samples. Gauges keep their last-good value instead of a false empty-queue report.
* **`agent.job.claim.latency`** — time from a job's eligibility to its claim.
* **`agent.job.infra.retry.requeued`** — attempts that run again after a retryable failure, tagged by `cause`. `preparation`: the attempt failed before its sandbox started. `sandbox`: the sandbox could not start, initialize or finish. `server_unreachable` and `provider_unreachable`: the sandbox could not reach the server's gateway or the model provider.
* **`agent.job.orphan.failed`** — jobs that failed because their workers stopped during every attempt.
* **`agent.job.execution.duration`** — tagged by `jobType` and outcome `status`.
* **`agent.job.delivery.recovered`** — deliveries stopped by an executor crash and successfully retried.
* **`agent.job.composition.stopped`** — destinations whose feedback the worker did not write, tagged by `cause` and `outcome`. `withheld` counts a recorded withholding, and `handed_on` counts an attempt handed on to compose again. `refused` counts a withholding that the server refused, which leaves its destination without a recorded reason. `unrecorded` counts a review whose last attempt was lost as well, so it completed with open destinations. The causes that withhold at once are `no_ai`, `budget`, `deadline`, `model_refused`, `context_window`, `publication_refused`, `no_decision`, `inputs_gone`, `inputs_erased` and `retries_exhausted`. The passing causes are `provider`, `routing_unavailable` and `unexpected`.
* **`agent.job.retention.stripped`** / **`agent.job.retention.deleted`** — rows pruned by the retention sweep.
* **`llm.budget.exhausted`** / **`llm.budget.blocked`** — a crossed cap and work refused at a cap.
* **`llm.proxy.budget.blocked`** — model calls of a running job that the LLM proxy refused at a cap, tagged by `apiProtocol` and `cap`. `instance` names the shared-model budget, and `byo` names the provider cap. An admin raises it. `practice_script_attempt` names the [direct model token limit or search unit limit of the attempt](/admin/ai-providers.md#cost-and-limits). These exclude issue opening and closing.
* **`practice.review.refused`** — refused reviews, tagged by `phase` (`submission` or `execution`) and `reason`. Submission refusals cannot establish the item version (`VERSION_UNAVAILABLE` or `SOURCE_NOT_READY`). Execution refusals occur during claim or execution. Refusal counters measure attempts, including rolled-back transactions. The event trace shows committed outcomes.
* **Delivery uncertainty** appears on feedback and copy records. Inspect Unconfirmed feedback and UNCERTAIN sends before considering a retry. Unknown visibility never authorizes automatic posting.

1. Query `/actuator/health/readiness` on the [management listener](/admin/observability.md#scrape-metrics), or `/readyz` on the application port, for aggregate readiness.

An authorized response includes the **`practiceReview`** component. An enabled review worker reports `OUT_OF_SERVICE` with `GIT_CHECKOUT_DISABLED` when repository checkout is disabled. It reports `SOURCE_AUTHORIZATION_EXPIRED` when an automated-review source authorization has expired.

Missing model assignments are workspace-specific. They stay visible under **Practices → Practice reviews → Work** and in the refusal counter. They do not remove the worker from service. Gauges have periodic samples. Timers and counters record their corresponding events. Metric names in the instrumented code are authoritative for dashboards and alerts.

### Practice scripts

Each practice of a review runs one script: its practice script, or the built-in script when it has none. For each review, Hephaestus records how each script ran. Workspace admins see it on the review's sheet. Open the review under **Practice reviews → Reviews**.

* **The trace line.** Under **Every practice**, each practice that ran a script has one line. An admin also sees the line under **What it saw** in a review on their own Practice profile.
* **The section Practice scripts.** It follows **Every practice** on the same sheet.

[Write a practice script](/admin/practice-scripts.md) tells how to write and try a script.

#### The trace line

The line starts with how the script ended. It names the script: **Practice script** or **Built-in**. If some model calls of the script were not rated, the line adds their count. An example is `5 calls were not rated.` Then it has at most one link that fixes the cause. On the Practice profile, the line also has **Details**, which opens the review's section **Practice scripts**.

A script that returned a candidate observation has not yet passed admission. The server can still withhold that candidate observation, as [What admission checks](/admin/practice-scripts.md#return-a-result) tells.

For `undeclared_source_cited`, compare the cited source with the pinned practice's Reads block. For a scan, use the Scan Reads block. Add a source to Reads only if the practice needs it. Otherwise, correct the script or citation. A source used only to check Applies when does not grant the review session access to it.

The refused candidate records no observation and supplies no feedback about the developer's work. It does not establish that evidence is missing or that the work has a defect. The [glossary](/contributor/practice-review-glossary.md#admission-reasons) owns this reason.

| The line starts with                          | What it means                                                                                                                                                                                                                                                                                                                                   | What to do                                                                                                                                                                                                                                     |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **… returned a candidate observation**        | The script ran to its end and returned a valid candidate observation. The line can say how: from its code alone, with its models, through the review, or with its models and the review.                                                                                                                                                        | Nothing. If the line links **Check the decision model**, or the model that it names, a model caused calls that were not rated. Open its row and check it.                                                                                      |
| **… did not run**                             | The review had no model that the script requires. Either no model is assigned for the reviewed developer's AI choice, or the assigned model does not meet the rule in [Privacy](/admin/ai-providers.md#privacy). The line names the model, such as `no decision model was set for this review`. The practice has no observation in this review. | Select **Assign a decision model**, or the link for the model that the line names. Assign a model for that tier. If the review ran on an In-house model, assign an In-house model.                                                             |
| **… stopped without a candidate observation** | The script stopped. The line gives the reason, such as `it raised an error`, `it ran out of time`, `what it returned is not a candidate observation`, or `it is not a script the review can run`. The practice has no observation in this review.                                                                                               | For a script error, select **Edit the script**. The review's section shows the first line of the error. To reproduce it, [run the script on the same change](/admin/practice-scripts.md#test-a-script-locally). For time or budget, see below. |
| **The review ended before … started**         | The attempt ran out of time or budget, or was cancelled, before it reached this practice.                                                                                                                                                                                                                                                       | Same as for a script that ran out of time.                                                                                                                                                                                                     |
| **… did not finish**                          | The review's report does not name the script, for example because the container stopped.                                                                                                                                                                                                                                                        | Same as for a script that ran out of time. If it continues, read the container logs that the job record keeps.                                                                                                                                 |

A built-in review that stopped without a candidate observation can give one of two more exact reasons:

* `its candidate observation was refused because it had not read the evidence it needed`. The evidence check refused a candidate observation that cited or needed a source that the session did not read completely.
* `its review session ended without an accepted candidate observation`. The session ended, and no refusal of a wrongly formed call was counted. Reviews recorded by earlier releases also show this reason.

For a built-in review, `a model answered in the wrong format` means that the tool refused at least one call of the wrong shape and the evidence check refused no candidate observation. A practice script always shows the reason that it stopped for.

A script that ran out of time or budget, or did not finish, links **Why scripts stop early**. It opens this section. The practices of a review run one after the other in the time of the **Practice reviews** assignment. If many practices in one review run out of time, raise the timeout of that assignment on **AI models**. Otherwise, make the slow script faster. [Limits](/admin/practice-scripts.md#limits) gives the time and token limits of a script.

[Upgrade a script from an earlier release](/admin/practice-scripts.md#upgrade-a-script-from-an-earlier-release) tells what to do.

A script that ran to its end can have calls that were not rated only for causes outside the workspace. These causes are a spent token limit, budget or cap, a slow model, and a call with too many entries. Then the line links **Why calls go unrated**. It opens [Calls that were not rated](#calls-that-were-not-rated), which tells who fixes each cause, and where.

#### The review's practice scripts

The section **Practice scripts** is on the review's sheet, after **Every practice** and the review's delivery checks. It has one row for each practice of the review:

| Column            | What it shows                                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Practice**      | The practice. It links to the practice. A practice that the workspace no longer has shows its identifier and **Not a current practice**.                                                                                                                                                                                                                                                       |
| **Run**           | How the script ended. Examples are **Returned a candidate observation**, **Withheld**, **Failed: ran out of time**, **Not reached** and **Did not finish**. Below it is the same fix link as in the trace line. A practice that the workspace no longer has, or that runs the built-in script, shows no **Edit the script**. For a script that failed, the first line of the error comes last. |
| **Review calls**  | The model calls of the practice's review sessions, as the review runner counted them. It is not the billed count: the proxy bills these calls for the whole review. A dash means no calls. **Not recorded** means that the review is from an earlier release, or that the runner did not report the practice.                                                                                  |
| **Script models** | Each decision, embedding or reranking model that the script declared or called. An icon shows the tier of the model that the review had for it. A declared model that the review did not have and that made no calls shows **No model**. The review's own chat model never shows here.                                                                                                         |
| **Not rated**     | The model calls that were not rated, with one line below for each reason, such as `5 too slow` and `1 wrong format`. [Calls that were not rated](#calls-that-were-not-rated) gives what to do for each reason.                                                                                                                                                                                 |
| **Script calls**  | The decision, embedding and reranking calls that the proxy recorded for the script. A script that did not run, or that uses no such model, shows a dash.                                                                                                                                                                                                                                       |
| **Time**          | How long the script ran, such as `4.2 s` or `2 min 5 s`. A run shorter than 0.1 seconds shows `< 0.1 s`. A script that did not run, was not reached, or did not finish shows a dash.                                                                                                                                                                                                           |

The section shows no cost. Its note says `Spend is on AI usage.` **AI usage** shows the cost of models for practice scripts, by practice. **Technical** on the review's sheet shows the review's **Model** with an icon for its tier. Its **Review model calls** and token counts include the review sessions that scripts open with `ctx.review()`, and the direct chat calls of the scripts. They do not include the decision, embedding and reranking calls. Its **Cost** includes them: it is what the review's attempts and its practice scripts' models cost together.

#### Calls that were not rated

A model call that was not rated has a reason. The count is model calls, not places. A reason never means that the script found nothing. This table is the one list of reasons. The column **In a script** gives the name that a script and the review input use.

| Reason        | In a script   | What it means                                                                                                                                                                | What to do                                                                                                               |
| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| model missing | `unavailable` | No model of that slot was available to the script.                                                                                                                           | Assign the model. **Check the decision model**, or the link for the model that the row names, opens its row.             |
| limit reached | `budget`      | A token limit cannot hold the call, or the shared-model budget or the provider cap is spent. For `agent`, no `submit` call came within `maxSteps`.                           | Raise the limit that was spent. The list below tells who raises each limit, and where.                                   |
| too slow      | `deadline`    | The script's deadline passed before the model answered, for example because the model was slow or had to load first. A call that the script aborted also has this reason.    | Ask the operator of the model's host to keep the model loaded, or assign a faster model.                                 |
| too large     | `too-large`   | The call held more entries than one call can send, or the script found an item too large to send. The host refuses a call with too many entries before it reaches the model. | Ask the script's author to send fewer entries in one call. [Limits](/admin/practice-scripts.md#limits) gives the limits. |
| wrong format  | `off-format`  | The model's answer did not have the expected shape.                                                                                                                          | The model is not suitable for this purpose. Choose another model.                                                        |
| declined      | `refused`     | The model declined to answer. The script sets this reason.                                                                                                                   | Choose another model.                                                                                                    |
| failed        | `error`       | Any other failure, such as an error from the provider or a rate limit.                                                                                                       | Check the provider and the connection.                                                                                   |

The column **Not rated** counts only the model calls that the host could not finish. Thus, it never shows `refused`. The limitations that a script gives to `ctx.review()` reach only the review session, as [What the review reads](/admin/practice-scripts.md#the-context) tells.

A token limit, a budget or a cap stops calls in one of these places:

* **The `maxSteps` of `agent`.** The script's author raises it in the script, or makes the script send less.
* **The direct model token limit or the direct model search unit limit of the attempt.** An operator changes them with the settings in [Cost and limits](/admin/ai-providers.md#cost-and-limits).
* **The shared-model budget.** Only an instance admin raises it on **Instance admin → AI usage** with **Change budget**.
* **The provider cap of the workspace's own provider.** A workspace admin raises it on **AI usage** with **Change provider cap**.

On **AI usage**, the tile of a purse whose budget or cap is spent shows **Paused**. A purse with a budget or cap also pauses when some of its usage has no price. Its tile then shows **No price set**, and an alert gives the number of runs that have usage without a price.

A script that failed, was skipped, or was not reached gives its practice no result in that review. It never becomes a claim about the developer's work.

#### Practice script metrics

Two metrics count practice script events on the worker:

* **`agent.review.practice_scripts`** — one count for each practice that the report of a review attempt records, tagged by `status`: `ok`, `skipped`, `failed`, `not_reached` or `not_finished`. It counts built-in scripts and practice scripts. An admitted practice that a complete report does not name counts as `not_finished`. A missing report adds no count. A truncated report adds no count for the practices that it does not name. An entry that is not valid adds no count. It counts when the attempt's results are stored. A growing share of `failed`, `not_reached` or `not_finished` needs attention. `skipped` usually means a missing assignment.
* **`llm.proxy.practice_script.unattributed`** — direct model calls that the proxy refused with HTTP 400, because they named no valid practice. It is 0 when the server and the agent image come from the same release. A value above 0 means that the agent image does not send the practice of each call. Run the agent image of the server's release.

[Queue health](#queue-health) lists `llm.proxy.budget.blocked`, which also counts the direct model calls that a budget, a cap or the direct model limit of the attempt refused.

### Log correlation

The application emits one JSON object per log line. Worker logs use correlation keys under `mdc`. The shipped Compose stack already has the necessary filter tools.

1. From your deployment directory, filter the logs:

```
# All log lines for one agent job

docker compose logs --no-log-prefix application-worker \

  | jq -cR 'fromjson? | select(.mdc."job.id" == "<job-uuid>")'



# All errors for one workspace

docker compose logs --no-log-prefix application-worker \

  | jq -cR 'fromjson? | select(.mdc."workspace.id" == "<workspace-id>" and .level == "ERROR")'
```

A deployment with its own log collector can use the same `mdc` fields for dashboards and alerts. The collector's label names are deployment-specific.

### Delivery recovery

Preparation persists the exact feedback, supporting basis and physical copy records before provider access. Each on-work send owns a lease and stable copy write identity. There is no second projection transaction that must reconstruct the feedback ledger after posting.

A copy write fence can mean the provider accepted the request before the receipt arrived. FOUND readback records the existing copy. ABSENT records proved failure. UNKNOWN keeps visibility Unconfirmed. Automatic recovery never repeats a POST from unknown visibility. Only an explicit admin retry after proved absence can reopen the write, subject to current policy and approval.

A confirmed historical copy is never posted again.

When one sibling is posted and another is unknown, the parent remains Unconfirmed. The confirmed sibling's appearance time remains recorded. Copy-level errors explain failed or withheld siblings without inventing another parent delivery state.

Before a new write, the server checks source version, developer, consent, scope, feedback mode and exact approval. A failure does not erase the supporting result. A correction waits for any started POST to settle before the send changes to RETRACT. Unsupported provider correction keeps the actual posted appearance and its recorded reason.

GitHub and GitLab attach line notes to the selected diff lines. The [delivery contract](/contributor/practice-review-pipeline.md#compose-feedback) defines GitLab positions, confirmed-rejection fallback and uncertain writes.

Existing ordinary comments retain their original placement.

Historical write keys, markers, sides and provider references stay immutable. Never clear a fence or edit an internal send row to force a retry. Use the [review controls](/admin/practice-review.md#approve-reject-and-retry) after authoritative lookup proves absence.

### Retention

Payload expiry is independent of terminal job deletion and product review history. A row lifetime shorter than its payload lifetime is invalid. Treat every worker fabric root as sensitive storage.

The [durable retention](#scans-and-durable-retention) section owns default windows. The [data map](/admin/dsms/personal-data-map.md) owns source-person erasure and physical custody. Scope selection follows account membership and the monitored repository. Separate feedback pieces retain their own words and responses after a runtime job expires.

## Limits you should know about

Configuration metadata describes deployment settings. The source catalog defines capture semantics. These behaviors matter independently of configured capacity:

* **Background task cadences are fixed in code.** Their thresholds can be configurable even if their poll schedules are not.
* **Feedback preparation recovery has a bounded window.** The sweeper selects recent completed pull request and issue reviews with missing preparation marks. It does not automatically recover older unfinished preparation. A successful empty result is complete. Recovery does not rerun the model.
* **Retryable occurrences eventually lapse.** A misconfigured workspace can accumulate retries and then become quiet when their deadline passes.
* **Unreferenced terminal item events expire** after their configured window. Referenced product history remains available after runtime cleanup.
* **Repository capture includes every regular and executable file at the reviewed commit**. This includes binary content and large files. It includes sanitized Git history reachable from that commit, mirrored branches, and tags. Native bash, Git, ripgrep, and file-discovery tools are available. File count and content size do not truncate the tree. Symbolic links become regular files with the link target, without traversal of the link. Submodules are not hydrated. Their presence and excluded unsafe paths cause a partial-capture report. A snapshot entry that is not a regular file or directory fails capture. Capture keeps committed Git LFS pointers instead of downloads. Model-facing output limits do not bound capture storage. The captured worktree and Git metadata are read-only, with no repository credentials or upstream configuration. Reviews use an isolated internal Docker network. The authenticated worker gateway must be reachable on it.
* **Repository preparation is bounded per snapshot and per walk.** Server and worker roles use JGit for capture. This needs no sandbox admission and does not count against `SANDBOX_MAX_CONCURRENT`. `GIT_MAX_CONCURRENT_INGESTIONS` (default 2) caps captured commits that a container writes to PostgreSQL simultaneously. A checkout plus mirrored history over `GIT_MAX_SNAPSHOT_BYTES` (default 8 GiB) causes refusal of the whole repository. Measurement occurs before the snapshot is written. Refusal fails the operation and never selects a smaller tree. The mirror itself has no measurement against this limit. A commit-history walk reads pages of 256 commits. It pauses after 32 pages that needed capture. Pages whose commits were all previously captured do not count. The next sync cycle resumes from captured commits.
* **Linked Outline documents are retrieval-limited, and the Hephaestus and review paths do not agree.** A review first resolves documents linked from the work, then adds a small number by search. Hephaestus searches the whole corpus for the words of the question. It reads at most 15 documents and truncates each one to 4,000 characters. Neither path can report that it found everything. Neither substitutes for the other in diagnosis of missing context.
* **Work that has been deleted at the provider yields a review with no evidence, not an empty one.** After reconciliation finds work missing upstream, evidence capture reports its sources as unavailable. The review stops without a result about that work. If the work reappears, the next ordinary sync clears the mark. A later review reads the work again.
* **No screen edits what the model is told.** Each practice's criteria shape what Hephaestus observes. A prompt that ships with the release shapes its text. A prompt change is a code change.

1. Provision storage for the mirror, captured content, and per-job staging.
2. For larger monorepos, raise `GIT_MAX_SNAPSHOT_BYTES`.
3. Size the fabric root for the repositories you monitor.

## Rollback

Return to a previous release only through [Upgrade § Rollback](/admin/upgrade.md#rollback).

## Observation text availability

Operator review views keep the outcome, practice, reviewed work and delivery history when source access cannot be verified. They withhold the observation summary, rationale and evidence in that state. The API returns `summary: null`, as specified by the [ReviewResult contract](https://github.com/hephaestus-build/Hephaestus/blob/main/server/openapi.yaml). Private practice-page and conversation feedback bodies remain unavailable to operators.

## Scans and durable retention

A scan freezes the monitored default branch before practice filters. At most one scan per workspace and one execution job per review can be active. Each bounded file job retains its own attempt. Durable membership retains every reached or unreached practice/file pair.

Default inventory limits are one million entries and 268,435,456 metadata bytes. Enumeration has a five-minute deadline. A scan has a 24-hour deadline. A limit or source failure does not mark repository files removed. The [scan guide](/admin/practice-scans.md) owns report counts, comparison and the removal guard.

[Data retention](/admin/data-retention.md) gives the retention of jobs, item events, results, usage records and evidence-copy receipts. No result expires while retained feedback still cites it. Aggregate reports remain with expired-proof availability after raw scan proof expires.

An unknown priced cost is not zero spend. Unknown provider visibility permits readback, never automatic repeat posting. See [review delivery](/admin/practice-review.md#approve-reject-and-retry) for safe admin retry.
