Metadata-Version: 2.4
Name: agent-framework-harny
Version: 0.1.0b1
Summary: Long-term memory for Microsoft Agent Framework agents: a context provider backed by Harny.
Author-email: Harny <platform@harny.ai>
License: Apache-2.0
Project-URL: Homepage, https://harny.ai
Project-URL: Documentation, https://harny.ai/docs
Keywords: harny,agent-framework,memory,context-provider,ai-agents
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: agent-framework-core<2,>=1.13.0
Requires-Dist: httpx<1.0.0,>=0.27.2
Provides-Extra: dev
Requires-Dist: pytest>=8.3.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
Requires-Dist: pytest-socket>=0.7.0; extra == "dev"
Dynamic: license-file

# agent-framework-harny

Long-term memory for Microsoft Agent Framework agents. `HarnyContextProvider` recalls what each person's memory holds before every run (and, when you switch saving on, saves what they say after it), so your agent remembers across sessions, restarts and devices. Pre-release (`0.1.0b1`), in the same maturity band as the framework's own integrations.

## Install

```bash
pip install agent-framework-harny --pre
```

Python 3.10+. Needs `agent-framework-core>=1.13,<2` (tested against 1.23.0).

## Start without a key

You do not need an account first. From the project folder:

```bash
pip install harny
harny init --name "<business>" --about "<what the app does>" --customers "<who its customers are>" --preset cost_first
```

That creates the app and saves `HARNY_API_KEY` to `.env` without printing it. Never print, log or commit the key. (Raw API route: `POST https://api.harny.ai/v1/auth/provision` with `{"app_name": "<business>"}`; the key is shown once.)

## One-minute example

```python
import asyncio

from agent_framework import Agent
from agent_framework_harny import HarnyContextProvider

client = ...  # any Agent Framework chat client you already use


async def main() -> None:
    async with HarnyContextProvider(user_id="nadia", save_user_messages=True) as memory:  # reads HARNY_API_KEY
        agent = Agent(client, "You help an account manager.", context_providers=[memory])
        session = agent.create_session()
        await agent.run("Nadia raised the Acme pilot budget from 32k to 40k.", session=session)
        await memory.wait_until_ready()  # facts are processed in the background; demos and tests wait, apps need not
        new_session = agent.create_session()  # a different session: only memory carries the fact over
        print((await agent.run("What is the Acme pilot budget?", session=new_session)).text)


asyncio.run(main())
```

Saving conversation messages is off by default; the examples switch it on with `save_user_messages=True`. `user_id` is your own id for the person. One provider can serve many people: leave `user_id` out and set `session.state["harny_user_id"] = "<id>"` on each session.

## The canonical example, as an agent

Same facts as every Harny surface (Nadia, the Acme pilot, the budget that moved from 32k to 40k):

```python
async with HarnyContextProvider(user_id="nadia", save_user_messages=True) as memory:
    agent = Agent(client, "You help an account manager.", context_providers=[memory])
    session = agent.create_session()
    await agent.run("Nadia confirmed the Acme pilot ships March 14. Budget is 32k.", session=session)
    await agent.run("Nadia raised the Acme pilot budget from 32k to 40k.", session=session)
    await memory.wait_until_ready()
    reply = await agent.run("What is the Acme pilot budget?", session=agent.create_session())
    print(reply.text)  # the model sees: "Acme pilot budget: currently 40k."
```

## Options

| Argument | Default | Meaning |
|---|---|---|
| `api_key` | `HARNY_API_KEY` | App key (`fsa_`) or a person's own key (`fsk_`); text or `SecretString` |
| `user_id` | `None` | Your id for the person. Needed with an app key (sent as `X-Harny-User`) |
| `base_url` | `https://api.harny.ai` | `/v1` is added when missing. HTTPS only; `http://localhost` allowed for local runs |
| `level` / `max_tokens` | `medium` | How much evidence to recall (`low`, `medium`, `high`, `extra-high`, or a token budget) |
| `limit` | API default | Most facts recalled (1 to 200) |
| `save_user_messages` | `False` | Off by default. `True` saves what the person says after each run |
| `expose_tools` | `False` | Also give the agent `harny_recall` and `harny_remember` tools |
| `context_prompt` | "## Memories ..." | Heading above the recalled facts |
| `http_client` | new client | Your own `httpx.AsyncClient` (proxy, test transport); not closed by the provider |

## Behaviour an agent should know

- **Saving conversation messages is off by default** (`save_user_messages=True` switches it on). When on, only the person's own messages are saved, never the assistant's replies or system text. `expose_tools=True` lets the agent save a fact on request.
- **A saved fact has a stable id** (hash of the session id and the words), so sending the same words in the same session twice is one fact and costs nothing the second time.
- **A memory problem never fails the run.** If recall or save is refused, the agent answers without memory and one warning with the error code is logged (never content, never keys).
- **`pending`**: facts saved a moment ago may still be processing. The model is told, in one line, that the newest information may be missing.
- **Errors** are `HarnyAPIError` with `code`, `status`, `debug_message`, `retry_after` (a per-minute rate limit has no body; `code` is `rate-limited`).
- Forget a fact: `await memory.client.delete_data_points([source_id])`.

## The memory API underneath

The provider is a thin layer over Harny's public memory API (`/v1/memory`: ingest, status, recall, delete). The same calls from Python with the Harny SDK:

```python
from harny import SyncHarnyClient

with SyncHarnyClient() as harny:  # reads HARNY_API_KEY
    harny.ingest_data_point(
        "Nadia confirmed the Acme pilot ships March 14. Budget is 32k.",
        id="call-8820", label="Account review call", timestamp="2026-02-03T09:00:00Z", user="nadia",
    )
    receipt = harny.ingest_data_point(
        "Nadia raised the Acme pilot budget from 32k to 40k.",
        id="call-8821", label="Budget change", timestamp="2026-03-01T09:00:00Z", user="nadia",
    )
    harny.wait_for_ingestion(receipt.ingestionId, user="nadia")

    recall = harny.recall("What is the Acme pilot budget?", level="medium", user="nadia")
    for result in recall.results:
        print(result.rank, result.content)
```

## Tests and pinned versions

```bash
scripts/ci.sh            # fresh venv, installs the pins in ci-pins.txt, runs the tests (no network, no paid call)
scripts/ci.sh --latest   # the same tests against the newest framework release (exit 3 = a framework update breaks us)
```

Pinned: `agent-framework-core==1.23.0`, `httpx==0.28.1`.

Apache-2.0. https://harny.ai
