> ## Documentation Index
> Fetch the complete documentation index at: https://docs.epode.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Submitting Outcome Reviews from Feedback-Aware Agents

> Submit success, partial, or failure outcome reviews from your agent runtime using the Agent Feedback SDK helpers for Node, Python, Go, and Rust.

Once your agent runtime has completed its product task and parsed the feedback contract from the response, you have everything you need to submit a compact outcome review. The review is two fields: an outcome label and one short sentence describing what happened. The Agent Feedback SDK validates both fields locally, checks that the submit destination is trusted, and delivers the review over HTTPS before your agent returns its final answer to the user.

## Outcome values

Every review must carry exactly one of three outcome values.

| Value     | Meaning                                                                                 |
| --------- | --------------------------------------------------------------------------------------- |
| `success` | The product response fully satisfied the agent's task requirement.                      |
| `partial` | The product response was useful but incomplete — the task was only partly accomplished. |
| `failure` | The product response did not meet the task requirement.                                 |

Choose the outcome that reflects the agent's view of the product interaction, not the overall task result. A product may return a valid 200 response that still fails to satisfy what the agent needed.

## The note field

The `note` field is a single sentence between 8 and 500 characters. Write it from the agent's perspective, describing only the product interaction. Do not include the user's prompt, conversation history, credentials, personally identifying information, or raw product content.

Good examples:

* `"The search result contained the checkout URL the task required."`
* `"The status endpoint returned the expected field but the value was stale."`
* `"The document API returned a 200 but the requested section was missing from the body."`

<Warning>
  The outcome submission endpoint rejects notes that contain prompts, transcripts, credentials, or personal data. The `privacy` field in every contract states this constraint explicitly. Keep notes short, factual, and product-scoped.
</Warning>

## Submitting with `submitProductOutcome`

Pass the parsed envelope from `feedbackFromResponse` along with an `outcome` and a `note`. The function validates both the envelope and the review locally before making any network call.

<Tabs>
  <Tab title="Node">
    ```ts theme={null}
    import { feedbackFromResponse, submitProductOutcome } from "@agent-feedback/node/agent";

    const response = await fetch("https://api.example.com/search?q=checkout");
    const body = await response.json();

    const feedback = feedbackFromResponse(response, body);
    if (feedback) {
      const result = await submitProductOutcome(feedback, {
        outcome: "success",
        note: "The search result contained the checkout URL the task required."
      });
      console.log(result.accepted, result.interactionId);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from agent_feedback import feedback_from_response, submit_product_outcome
    import urllib.request
    import json

    with urllib.request.urlopen("https://api.example.com/search?q=checkout") as resp:
        raw = resp.read()
        headers = dict(resp.headers)

    body = json.loads(raw)
    feedback = feedback_from_response(headers, body)
    if feedback:
        result = submit_product_outcome(
            feedback,
            outcome="success",
            note="The search result contained the checkout URL the task required."
        )
        print(result["accepted"], result.get("interactionId"))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "io"
        "net/http"
        agentfeedback "github.com/open-software-network/os-epode/sdk/go"
    )

    resp, _ := http.Get("https://api.example.com/search?q=checkout")
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)

    envelope, err := agentfeedback.FeedbackFromResponse(resp, body)
    if err == nil {
        result, err := agentfeedback.SubmitProductOutcome(
            context.Background(),
            envelope,
            agentfeedback.OutcomeReview{
                Outcome: "success",
                Note:    "The search result contained the checkout URL the task required.",
            },
            nil,  // nil uses the Agent Feedback default origin
            nil,  // nil uses http.DefaultClient (the standard library client)
        )
        if err == nil {
            _ = result // map[string]any with accepted, interactionId, review
        }
    }
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use agent_feedback::{feedback_from_response, submit_product_outcome, OutcomeReview};

    let response = reqwest::get("https://api.example.com/search?q=checkout").await?;
    let headers = response.headers().clone();
    let body = response.bytes().await?;

    if let Some(envelope) = feedback_from_response(&headers, &body) {
        let client = reqwest::Client::new();
        let result = submit_product_outcome(
            &client,
            &envelope,
            OutcomeReview {
                outcome: "success".to_string(),
                note: "The search result contained the checkout URL the task required.".to_string(),
            },
            &[], // empty slice uses the default trusted origin
        ).await?;
        println!("{:?}", result);
    }
    ```
  </Tab>
</Tabs>

## Options (Node)

The Node `submitProductOutcome` function accepts an optional third argument with these fields.

<ParamField body="allowedSubmitOrigins" type="string[]" default="[&#x22;https://api.example.com&#x22;]">
  A list of HTTPS origins your runtime trusts to receive outcome reviews. The function validates that the submit URL in the contract resolves to one of these origins and throws if it does not. Override this only when pointing at a non-production Agent Feedback environment.
</ParamField>

<ParamField body="timeoutMs" type="number" default="5000">
  Maximum milliseconds to wait for the outcome submission response. Defaults to 5000 ms. The function uses `AbortSignal.timeout` internally.
</ParamField>

<ParamField body="fetch" type="function">
  A custom `fetch` implementation. Defaults to `globalThis.fetch`. Provide this in environments where the global `fetch` is unavailable or when you need to route submissions through a proxy.
</ParamField>

## Security: origin validation

The SDK refuses to submit to any origin that is not in the allowlist. This prevents a malicious or misconfigured product from redirecting your agent to post outcome data — including the capability token — to an untrusted server.

Specifically, `submitProductOutcome`:

1. Parses the `submit.url` from the envelope and checks its scheme is `https:`.
2. Compares the URL's origin against every entry in `allowedSubmitOrigins`.
3. Throws `"Refusing to submit feedback to untrusted origin <origin>"` if no match is found.

Never add an origin to `allowedSubmitOrigins` unless you control it or it is the Agent Feedback production service.

## Response shape

In Node, `submitProductOutcome` returns `Promise<ProductOutcomeSubmission>`. A successful submission resolves to an object with these fields.

<ParamField body="accepted" type="boolean">
  `true` when the review was stored. `false` is not currently returned by the hosted service — non-acceptance surfaces as an HTTP error instead.
</ParamField>

<ParamField body="interactionId" type="string (optional)">
  The interaction ID the product embedded in the capability token. Use this to correlate the outcome review with your own telemetry.
</ParamField>

<ParamField body="review" type="object (optional)">
  The stored review object, which echoes back `id`, `outcome`, and `note`.
</ParamField>

## Error handling

Handle submission errors without letting them interrupt your agent's primary task. The product response has already been received and acted on — a failed submission is a telemetry loss, not a product failure.

```ts theme={null}
if (feedback) {
  try {
    const result = await submitProductOutcome(feedback, {
      outcome: "success",
      note: "The search result contained the checkout URL the task required."
    });
    // outcome recorded
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    if (message.includes("HTTP 5")) {
      // retryable — retry once after a short delay
    } else if (message.includes("HTTP 4")) {
      // not retryable — expired capability, malformed payload, or duplicate
    }
    // continue; do not block the user's final answer
  }
}
```

| HTTP status range | Retryable        | Reason                                                         |
| ----------------- | ---------------- | -------------------------------------------------------------- |
| 5xx               | Yes — retry once | Transient server error                                         |
| 4xx               | No               | Expired capability, malformed payload, or duplicate submission |

<Note>
  Duplicate submissions are idempotent. If you submit twice for the same interaction, the service returns the original stored review rather than creating a second one. You can safely retry a 5xx exactly once without risk of double-counting.
</Note>

## MCP products: no HTTP call needed

If the product you called is an MCP server instrumented with Agent Feedback, the `report_product_outcome` tool is registered directly on the server. Call it as a normal MCP tool call — no `feedbackFromResponse` or `submitProductOutcome` is needed.

```json theme={null}
{
  "tool": "report_product_outcome",
  "arguments": {
    "outcome": "success",
    "note": "The search result contained the checkout URL the task required."
  }
}
```

MCP interactions are classified as `confirmed` from the moment the tool call is made, giving Agent Feedback full protocol-backed attribution rather than the `best_effort_without_agent_adapter` classification used for HTTP/HTML surfaces.

<Tip>
  The `when` field in every contract is `after_outcome_known_before_final_response`. Submit before you deliver the final answer to the user — not after. This sequencing ensures the outcome reflects what the agent actually did with the product response, not a retroactive judgement.
</Tip>
