# Prompt: Native Ollama SDK provider (`a2c-core[ollama]`)

Implementation prompt for a **future milestone** (after M5 provider-backed decomposition).
Save and use when M5’s OpenAI-compatible `ollama` preset is in place and you want a richer
native integration.

---

## Context

By this point, the following should already exist:

- M4: Epic → Task decomposition (`plan decompose-epic`, `apply tasks-from-epic`, mock provider, review-first pipeline).
- M5: Provider-backed decomposition with at least:
  - `mock` (default, supports `--fixture` for Cursor-as-model),
  - `openai` (hosted OpenAI-compatible),
  - `local` (generic OpenAI-compatible endpoint),
  - `ollama` (convenience preset: default `http://127.0.0.1:11434/v1`, reachability probe, clear errors).

M5’s `ollama` provider intentionally uses **httpx + OpenAI-compatible `/v1`** with no extra
dependencies. That is sufficient for many users but has limitations:

- Ollama’s OpenAI compatibility layer is **experimental** and may lag native `/api/chat`.
- Structured JSON output (`response_format`, JSON mode) behavior can differ between `/v1` and native API.
- Native Ollama features (options like `num_ctx`, format hints) are not available through `/v1`.
- Users who already depend on `ollama-python` may prefer the official SDK.

This milestone adds an **optional** native Ollama path without changing the M4/M5 artifact workflow.

## Goal

Provide an opt-in native Ollama integration so that:

```bash
pip install "a2c-core[ollama]"
a2c plan decompose-epic EPIC-0001 --provider ollama-native
```

uses the **official `ollama` Python SDK** (`/api/chat`) for decomposition,

while users without the extra continue to use M5’s `ollama` preset (OpenAI-compatible) unchanged.

The review-first pipeline (validate → cache → review → apply) must remain identical.

## Scope

### 1. Optional dependency extra

Add a PyPI optional extra:

```toml
[project.optional-dependencies]
ollama = ["ollama>=0.3"]
```

Rules:

- **Core install** (`pip install a2c-core`) must not require `ollama`.
- Import of native provider must fail with a **clear, actionable message** when the package is missing:
  - e.g. `pip install "a2c-core[ollama]"` or `pip install ollama`.
- Do not add `ollama` to default/runtime dependencies.

### 2. Provider name and selection

Introduce a distinct provider id, e.g. **`ollama-native`** (or `ollama_sdk` — pick one and document it).

| Provider | Transport | Dependency |
|----------|-----------|------------|
| `ollama` (M5) | OpenAI-compatible `/v1` via httpx | core only |
| `ollama-native` (this milestone) | Native `/api/chat` via `ollama` SDK | `a2c-core[ollama]` |

Provider resolution (unchanged precedence from M5):

```
CLI --provider  >  config.decomposition.provider  >  "mock"
```

Config example:

```yaml
decomposition:
  provider: ollama-native
  model: llama3.2
  # host read from OLLAMA_HOST or default http://127.0.0.1:11434
```

CLI examples:

```bash
a2c plan decompose-epic epic-onboarding --provider ollama-native
OLLAMA_HOST=http://192.168.1.10:11434 a2c plan decompose-epic epic-onboarding --provider ollama-native
```

**Do not** remove or break M5’s `ollama` preset.

### 3. Native provider implementation

Implement `OllamaNativeDecomposeProvider` in `a2c_core.workflows.providers` that:

- Implements existing `DecomposeEpicProvider` (`decompose_epic(context) -> dict[str, Any]`).
- Builds the same decomposition prompt as other providers (reuse M5 `prompts/phases/epic-decompose.md` + prompt builder — do not fork prompt text).
- Calls `ollama.Client` (or module-level `ollama.chat`) with:
  - `host` from `OLLAMA_HOST` env or config override, defaulting per ollama-python conventions (`http://127.0.0.1:11434`),
  - `model` from config / CLI override,
  - reasonable `options` (temperature, num_predict/max tokens) from config.
- Requests **structured JSON** using native mechanisms where supported:
  - Prefer Ollama `format` / JSON schema hints if available for the installed `ollama` version,
  - Fall back to strict prompt instructions + `json.loads` on message content,
  - Document which path is used and version assumptions.
- Parses assistant message content into the **same JSON shape** M4/M5 expect:

```json
{
  "summary": "...",
  "tasks": [
    {
      "id": "task-...",
      "title": "...",
      "status": "todo",
      "epic_id": "epic-...",
      "rationale": "...",
      "body": "## Summary\n...\n\n## Acceptance criteria\n- ..."
    }
  ]
}
```

Then return that dict to existing `parse_provider_response()` — **no changes** to validation/cache/apply unless a shared parser improvement is genuinely needed.

### 4. Connection and configuration behavior

Clarify in docs (do not over-promise “auto-detect”):

- The SDK **defaults** to `http://127.0.0.1:11434` (and `OLLAMA_HOST`); it does **not** scan the OS for a running Ollama process.
- On plan, optionally probe reachability before the chat call:
  - Native: list models / version endpoint if cheap,
  - On failure: clear error, e.g. *“Cannot reach Ollama at … — is `ollama serve` running?”*
- Support `OLLAMA_API_KEY` when talking to authenticated remote Ollama (Bearer), per SDK behavior.
- Timeouts must be configurable; default sensibly (e.g. 120s for local).

### 5. Error handling

Add or reuse provider error codes (extend M5 `A2C_PROVIDER_*` if present):

| Situation | Behavior |
|-----------|----------|
| `ollama` package not installed | Exit before HTTP; suggest `pip install "a2c-core[ollama]"` |
| Connection refused / unreachable host | Actionable message with host tried |
| Model not found | Suggest `ollama pull <model>` |
| Timeout | No repo changes; report provider + timeout |
| Non-JSON / malformed model output | `A2C_WORKFLOW_002` or provider parse error; no writes |
| Auth failure | Clear message referencing `OLLAMA_API_KEY` if applicable |

**Never** silently fall back to mock or to M5’s `ollama` preset when `ollama-native` was explicitly selected.

### 6. JSON mode quirks (explicit deliverable)

Document and test known differences:

- Compare native `format`/JSON output vs M5 `/v1` `response_format` for the same fixture epic.
- Handle common failure modes: markdown fences around JSON, trailing prose, partial JSON.
- Centralize JSON extraction in one helper (e.g. `extract_json_object(text) -> dict`) shared by OpenAI-compatible and native providers if not already present in M5.

### 7. Wiring into CLI

Extend provider resolver in `a2c_cli.commands.plan`:

- Register `ollama-native`.
- Lazy-import native provider module so missing optional dep only errors when that provider is selected.
- Keep `--fixture` **mock-only** (regression).

No changes to `apply`, `show draft-task`, or draft cache format.

### 8. Tests

Use **stubs/mocks** — no live Ollama in CI.

- Mock `ollama.Client.chat` (or patch module `chat`) to return fixture JSON.
- Provider selection: `ollama-native` resolves when extra installed (or import mocked).
- Missing optional dep: selecting `ollama-native` without `ollama` installed → clear error.
- Success path: fixture response → same validation/cache behavior as mock.
- Failure paths: connection error, model not found, malformed JSON, timeout.
- Regression: M5 `ollama` preset and `mock` + `--fixture` unchanged.

### 9. Documentation

Update:

- `docs/cli.md` — provider table distinguishing `ollama` vs `ollama-native`.
- `docs/epic-decomposition.md` — when to use which Ollama path.
- `docs/product-development.md` — optional extra install line.
- `README.md` or dev setup — `pip install -e ".[dev,ollama]"` for local Ollama SDK work.

Include:

```bash
# M5 preset (no ollama package required)
a2c plan decompose-epic EPIC-0001 --provider ollama

# Native SDK (requires extra)
pip install "a2c-core[ollama]"
a2c plan decompose-epic EPIC-0001 --provider ollama-native
```

Explicit note: **default host is not auto-discovery** — it assumes the conventional Ollama listen address unless `OLLAMA_HOST` or config overrides it.

## Out of scope

- Removing M5’s httpx-based `ollama` preset.
- Auto-installing models (`ollama pull`) during plan.
- Sprint planning or other workflows via native Ollama.
- Provider pooling, routing, or telemetry.
- Making `ollama-native` the default provider.

## Package boundary rules

- Native provider implementation lives in `a2c_core.workflows.providers` (e.g. `ollama_native.py`).
- Lazy import boundary: optional dep must not break `import a2c_core` for users without `[ollama]`.
- CLI only selects provider and passes context; no duplicate validation logic.
- No dependency from `a2c_core` → `a2c_cli`.

## Implementation guidance

- Reuse M5 prompt builder and response parser; native provider is a **transport swap**.
- Prefer one JSON extraction helper used by all LLM providers.
- Keep the native client thin — avoid wrapping every Ollama API surface.
- Version-gate advanced `format`/schema features if needed; document minimum `ollama` package version.

## Acceptance criteria

This milestone is complete when:

- `pip install "a2c-core[ollama]"` enables `--provider ollama-native`.
- Without the extra, selecting `ollama-native` fails with a clear install hint.
- Successful decomposition uses native `/api/chat` (verified via mocks) and produces the same validated draft cache as mock/M5.
- Provider failures produce actionable errors and **no repo mutations**.
- M5 `ollama` (OpenAI-compatible) and `mock` + `--fixture` still work unchanged.
- Tests are deterministic (no network, no running Ollama daemon).
- Docs explain `ollama` vs `ollama-native`, configuration, and limitations.

## Prerequisites

- **M5 complete** (provider config, httpx OpenAI-compatible client, `ollama` preset, prompt asset, provider error codes).
- Epic decomposition workflow stable (M4).

## Output style (for implementer)

- Make repository changes directly.
- Summarize provider naming (`ollama` vs `ollama-native`).
- Note minimum `ollama` package version and JSON strategy chosen.
- Call out any behavior that still differs from `/v1` and whether docs warn users.
