Skip to main content
Glama
tatsuro-sys

outcome-router

by tatsuro-sys
README.md
# Outcome Router

**Article extraction fallback for agents. MCP · HTTP API · CLI.**

Give your agent a public URL or HTML and an acceptance contract. Outcome Router tries eligible extractors, checks the result, falls back when necessary, and uses independent local observations to rank future requests.

**Public developer beta · free · runs on your machine.** This release supports static article HTML using three real open-source extractors. It does not render JavaScript, bypass access controls, invoke arbitrary MCP servers, or offer a hosted endpoint. Quality checks are explicit heuristics, not a guarantee of semantic completeness.

## Try it

Install [uv](https://docs.astral.sh/uv/getting-started/installation/) first. No API key is needed for the demo or local extractors.

```sh
uvx --from git+https://github.com/tatsuro-sys/outcome-router@v0.1.0b1 outcome demo
```

The demo uses a synthetic article and a temporary database. It does not contact the network after installation or train your real routing history.

For repeated use, install the release:

```sh
uv tool install git+https://github.com/tatsuro-sys/outcome-router@v0.1.0b1
outcome extract https://example.com --min-chars 30 --require "Example Domain"
```

The `extract` command explicitly requests a public URL fetch. JSON API/MCP calls require `allow_network: true`. Extracted page text is untrusted data; agents must not execute instructions embedded in it.

## Connect your agent with MCP

Add this server configuration to a host that supports MCP stdio:

```json
{
  "mcpServers": {
    "outcome-router": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/tatsuro-sys/outcome-router@v0.1.0b1", "outcome", "mcp"]
    }
  }
}
```

If the host cannot find `uvx`, use its absolute executable path. The first launch needs network access to install the package and dependencies. Runtime URL fetching is a separate, explicit permission.

Tools:

- `search_capability(request)` — inspect eligible providers and ranking; does not fetch or execute.
- `run_capability(request)` — extract, check, fall back, and save the outcome locally.
- `get_outcome(run_id)` — retrieve a local run.
- `report_outcome(run_id, report)` — record consumer feedback separately from trusted routing evidence.

Example `run_capability` arguments:

```json
{
  "request": {
    "need": {"capability": "web.article_text"},
    "source": {"url": "https://example.com"},
    "contract": {"min_chars": 30, "required_terms": ["Example Domain"]},
    "constraints": {"allow_network": true, "max_provider_cost_usd": 0, "deadline_ms": 10000}
  }
}
```

The parent agent maps the user's request to `web.article_text`. The router does not pretend to understand arbitrary tasks. Unsupported capabilities and unknown constraints are rejected.

## Use the local HTTP API

```sh
export OUTCOME_API_TOKEN="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
outcome serve --port 8787
```

In a second terminal, set the same token, then:

```sh
curl http://127.0.0.1:8787/v1/runs \
  -H "Authorization: Bearer $OUTCOME_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"need":{"capability":"web.article_text"},"source":{"url":"https://example.com"},"contract":{"min_chars":30},"constraints":{"allow_network":true}}'
```

Open `http://127.0.0.1:8787/docs` for interactive API documentation. `serve` binds to localhost and requires a token of at least 24 characters. This is a single-owner service; do not expose it directly to the public internet.

| Endpoint | Purpose |
|---|---|
| `POST /v1/resolve` | Rank eligible extractors |
| `POST /v1/runs` | Execute with checks and fallback |
| `GET /v1/runs/{run_id}` | Retrieve a saved result |
| `POST /v1/runs/{run_id}/outcome` | Save first consumer report for a run |
| `GET /v1/providers` | Inspect installed provider manifests |

Completed executions return HTTP 200 with `status: succeeded` or `status: failed`. A failed result may include partial text: always check `status`. CLI executions return exit code 2 on contract failure. Invalid inputs return 422 in HTTP; auth errors 401, unknown runs 404, idempotency conflicts 409, oversized bodies 413.

## What is measured

The engine checks minimum length, required/forbidden phrases, grounding in the original visible source, and navigation/footer contamination. Each attempt records provider version, outcome, checks, elapsed time and allocated time. It stops work at the deadline using killable extractor and evaluator processes.

Three curated providers: Trafilatura fast/no-fallback, readability-lxml, and BeautifulSoup article/main extraction. Independent observations update a versioned, time-decayed posterior. Repeated identical inputs cannot inflate observation counts. Timeout attempts starved by an earlier stage do not penalize a provider's learned ranking. Consumer reports do not directly train the router.

**Do not assume this beats a single extractor.** The included synthetic benchmark also has single-provider baselines with the same evaluator. Some baselines match the router. Real workload advantage remains to be demonstrated. See [benchmark notes](docs/benchmarks.md).

## Data and cost

- Runtime history stays in a local SQLite database. No analytics are sent to us.
- Raw HTML, URL and free-form need descriptions are not stored in that database. **Extracted output is stored** for retrieval and idempotent replay.
- Default storage uses your OS's application-data directory. Set `OUTCOME_DB` or `--db PATH` to choose another location. API and MCP use the same default.
- `outcome purge --days 7` deletes old runs, outputs and observations when invoked. No background scheduler is installed. Backup copies are outside its scope.
- Provider charges are zero for the included local software. Infrastructure cost is reported as unknown, not free. No billing, paid provider calls or subscription is enabled.
- Public URLs are fetched directly with public-IP validation, redirect checks, no ambient proxy/cookies and a 1 MB HTML limit.

## Develop and reproduce

```sh
git clone https://github.com/tatsuro-sys/outcome-router.git
cd outcome-router
uv sync --frozen
uv run pytest -q
uv run ruff check src tests examples benchmarks
uv run python benchmarks/run.py
uv build
```

Python 3.11+. See the [CI workflow](.github/workflows/ci.yml) for the tested OS/Python matrix. Examples: [JSON](examples/request.json), [Python](examples/python_client.py), [MCP client](examples/mcp_client.py). Interfaces: [OpenAPI](schemas/openapi.json), [request](schemas/run-request.schema.json), [result](schemas/run-result.schema.json), [MCP tools](schemas/mcp-tools.json), [manifests](schemas/providers.json).

## Help shape the next release

Have a public page your existing extractor gets wrong? [Submit an extraction failure](https://github.com/tatsuro-sys/outcome-router/issues/new?template=extraction-failure.yml), including the expected result and your current baseline. Only submit pages and content you have permission to share publicly. Do not post tokens, personal data or private pages.

The next capability should follow real failures: rendering, a paid BYOK provider, or stronger evaluation. This beta makes no promise of a general software marketplace or automatic improvement on every workload.

[Architecture](docs/architecture.md) · [Security](SECURITY.md) · [Privacy](docs/privacy.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)

MIT licensed. Third-party components retain their own licenses; see [THIRD_PARTY.md](THIRD_PARTY.md).