# Observance — Agent Integration Guide

Observance is an agent-native memory API. It provides structured memory storage with types, cognitive roles, namespace scoping, relational linking, and machine-readable guidance (`agent_contract`) on every operational response.

---

## Discovery

Before calling any authenticated endpoint, read the discovery surface:

| Endpoint | Purpose |
|---|---|
| `GET /.well-known/agent.json` | Entry point — links to all discovery resources |
| `GET /v1/capabilities` | Full API limits, error codes, action codes, rate limits |
| `GET /v1/tool` | MCP-compatible tool manifest with input schemas |
| `GET /v1/schema` | OpenAPI 3.1 JSON schema |
| `GET /v1/guide` | Operational guidance for memory discipline (pull-based) |
| `GET /v1/skill` | Onboarding and usage guidance (structured JSON) |
| `GET /health` | Health check |

All discovery endpoints are public (no authentication required) and rate-limited at 60 requests per minute per IP.

---

## Authentication

1. Register an API key:

```
POST /v1/keys/register
Content-Type: application/json

{}
```

Response:

```json
{
  "apiKey": "obs_live_...",
  "accountId": "key_01JXZK..."
}
```

2. Store the `apiKey`. It is returned exactly once — the server stores only a SHA-256 hash.

3. Use the key on all authenticated requests:

```
Authorization: Bearer obs_live_...
```

Keys placed in query strings are rejected with `400 invalid_request`.

---

## Core Workflow

The typical agent workflow is: **recall → process → record → link → maintain**.

### 1. Recall context

```
GET /v1/memories?agent_id=agt_...&sort=created_at&limit=10
Authorization: Bearer obs_live_...
```

### 2. Create a memory

```
POST /v1/memories
Authorization: Bearer obs_live_...
Content-Type: application/json

{
  "type": "episodic",
  "event_at": "2026-03-14T10:00:00Z",
  "content_text": "User requested a summary of Q4 sales data.",
  "cognitive_role": "observation",
  "namespace": "project-alpha",
  "importance_score": 0.7,
  "idempotencyKey": "sess-123:mem-1"
}
```

Required fields: `type`, `event_at`, and at least one of `content_text` or `content_json`.

### 2b. Batch create memories

```
POST /v1/memories/batch
Authorization: Bearer obs_live_...
Content-Type: application/json

{
  "memories": [
    { "type": "episodic", "event_at": "2026-03-14T10:00:00Z", "content_text": "First memory." },
    { "type": "semantic", "event_at": "2026-03-14T10:01:00Z", "content_text": "Second memory." }
  ]
}
```

Up to 50 memories per batch. Supports partial success (201 if all succeed, 207 if partial). Each item can include its own `idempotencyKey` for safe retries. Quota is counted per memory created, not per request.

### 3. Link related memories

```
POST /v1/memories/{id}/links
Authorization: Bearer obs_live_...
Content-Type: application/json

{
  "to_memory_id": "mem_01TARGET...",
  "relation_type": "derived_from"
}
```

### 4. Find related memories

```
GET /v1/memories/{id}/related?direction=both
Authorization: Bearer obs_live_...
```

### 5. Archive stale memories

```
POST /v1/memories/{id}/archive
Authorization: Bearer obs_live_...
```

---

## Memory Lifecycle

Every memory has an explicit lifecycle status:

| Status | Meaning | Transitions |
|--------|---------|-------------|
| `active` | In use, visible to queries and traversal | Can be archived or deleted |
| `archived` | Preserved but deprioritized, still visible to traversal | Can be unarchived or deleted |
| `deleted` | Soft-deleted, invisible to queries | Terminal -- retained for `retention_days` then permanently removed |

Key rules:

- Archived memories still count toward the active memory cap. To free capacity, delete instead.
- Deleted memories are excluded from list queries and traversal results.
- When a memory is deleted, connected links are deactivated (set to `inactive`).
- Archiving a memory does NOT affect its links -- they remain active.

---

## Traversal

`GET /v1/memories/{id}/related` returns memories connected through the knowledge graph.

Each result includes the related memory (with `eventAt` and `createdAt` for temporal reasoning) and the link metadata (relation type, direction, strength).

Filters:

- `direction` -- `outgoing`, `incoming`, or `both` (default: `both`)
- `relation_type` -- filter by a specific relation type
- `limit` -- max results (1-50, default 20)

Traversal depth is 1 in V1 (direct neighbors only).

Use temporal context for reasoning:

- For `supersedes`: the source memory should have a later `eventAt` than the target
- For `contradicts`: prefer the memory with the later `eventAt` as more current, but consider context
- For `derived_from`: the source typically has a later `createdAt`

---

## Memory Types

| Type | Use for |
|---|---|
| `episodic` | Events, experiences, conversation records |
| `semantic` | Facts, preferences, domain knowledge |
| `procedural` | Learned workflows, behavioral rules, decision patterns |

---

## Cognitive Roles

| Role | Use for |
|---|---|
| `observation` | Raw experience records (default) |
| `reflection` | Synthesized insights |
| `fact` | Established knowledge |
| `rule` | Behavioral guidelines |
| `skill` | Learned procedures |
| `preference` | User or agent preferences |

---

## Link Relation Types

| Type | Meaning |
|---|---|
| `derived_from` | Target was source material |
| `summarizes` | This memory summarizes the target |
| `contradicts` | Conflicting information |
| `supports` | This memory provides evidence for the target |
| `relates_to` | General association |
| `supersedes` | This memory replaces the target |

---

## Agent Identity

Generate your `agent_id` once and reuse it across all requests.

- Format: `agt_{ULID}` — generate client-side or use the ID returned by your first request.
- Store `agent_id` alongside your API key — treat it as a persistent credential.
- Do NOT generate a new `agent_id` per request or per session.

Agents are auto-provisioned on first memory creation. No separate registration step is needed.

---

## Guide Customization

Observance provides strong default guidance via `GET /v1/guide`. Developers can customize specific hint sections for their use case via `PATCH /v1/guide/hints`.

### Override semantics

`PATCH /v1/guide/hints` replaces the **entire stored override state**. The request body represents the complete desired overrides — it is not merged with previous overrides.

Example: if the stored override is:

```json
{ "write_policy": { "importance": { "high": "..." }, "store_when": { ... } } }
```

and you send:

```json
{ "write_policy": { "importance": { "high": "Critical.", "medium": "Moderate.", "low": "Minor." } } }
```

the resulting override state becomes **only** `{ "write_policy": { "importance": { ... } } }`. The previous `store_when` override is removed.

### Overridable sections

| Section | Override behavior |
|---|---|
| `write_policy.store_when` | Full replacement. All 6 categories must be provided (preferences, decision_relevance, pattern_detection, identity_or_context, task_outcomes, conflict_resolution). |
| `write_policy.avoid_storing` | Full replacement. Entire array replaced. |
| `write_policy.importance` | Full replacement. Object with high/medium/low keys replaced. |

All other guide sections (core_rules, reading_discipline, search_discipline, writing_discipline, linking, operations) are owned by Observance and cannot be overridden.

### Reset to defaults

Sending an empty body `{}` is rejected. To revert to Observance defaults, send overrides that match the default values.

### Guide policy mutability

Guide hints represent memory policy for your account. How agents should treat them depends on the operational model:

- **Operator-managed agents**: Treat guide hints as read-only configuration set by your developer or operator. Do not modify them automatically.
- **Fully autonomous agents**: If no human operator manages your memory policy, you may adjust guide hints deliberately as part of self-governance.

In all cases:
- Do not rewrite hints in reaction to a single task failure or temporary constraint.
- Policy changes should be rare, intentional, and stable — based on sustained patterns, not one-off events.
- Treat guide policy changes like configuration changes, not like memory updates.

---

## Namespaces

Namespaces provide organizational scoping within an account.

- Default: `default`
- Format: lowercase letters, digits, hyphens. 2-100 characters.
- Examples: `personal`, `project-alpha`, `client-123`
- Cross-namespace links are allowed.

---

## Billing and Plan Limits

Each account operates under a plan (free, pro, business) with usage limits enforced per billing cycle.

### What counts as a write

- Creating a memory (`POST /v1/memories`)
- Creating a new link (`POST /v1/memories/:id/links` — only truly new links; duplicate returns do not count)
- Updating a memory (`PATCH /v1/memories/:id`)
- Updating an agent (`PATCH /v1/agents/:id`)

### What does NOT count as a write

- Deleting a memory or link
- Archiving or unarchiving a memory
- Idempotency replays of already-created memories

### Active memory cap

Only memories with `status = 'active'` or `status = 'archived'` count toward the cap. Deleted memories do not count. To free capacity, delete memories — archiving does NOT free capacity.

### Read limits

Reads use a two-stage limit:
- **Soft limit (100%)**: Responses include `quota_warning` so agents can notice and throttle.
- **Hard limit (200%)**: Requests are rejected with `read_quota_exceeded`.

### Introspection endpoints (free)

`GET /v1/billing/usage` and `GET /v1/billing/status` do NOT count toward read quota. Use them freely to monitor usage.

### Billing endpoints

| Endpoint | Purpose |
|---|---|
| `GET /v1/billing/usage` | Current usage, limits, and cycle info |
| `GET /v1/billing/status` | Plan state and purchase history |
| `POST /v1/billing/checkout` | Start a plan upgrade (returns PayPal approve URL) |
| `POST /v1/billing/confirm` | Confirm payment and activate the new plan |

### Checkout behavior

- Attempting checkout for the current plan returns `409 already_on_plan`.
- A pending purchase for the same plan is reused if not expired (1-hour TTL).
- Counters do NOT reset on plan upgrade — usage carries over within the billing cycle.
- Downgrades are non-destructive: no data is deleted, but new writes may be blocked if over the new limit.

### Two-step payment flow

PayPal payments require two API calls because capture is server-side and authenticated:

1. **Checkout** — `POST /v1/billing/checkout` with `{plan_id}`. Response includes `approve_url` and `purchase_id`. Direct the user (or open in their browser) to `approve_url`.
2. **Approve** — User logs into PayPal and approves the payment. PayPal redirects them to `/checkout?status=approved&purchaseId=...` (visual confirmation only — no plan activation happens here).
3. **Confirm** — Your agent must call `POST /v1/billing/confirm` with `{purchase_id}` and the API key in the Authorization header. This captures the payment with PayPal and activates the plan.

The redirect page (`/checkout`) intentionally does NOT capture the payment. Capture requires the account's API key, which must never be exposed in URLs or browser pages. The agent that initiated the checkout is responsible for calling `/v1/billing/confirm` after the user approves, using its own authenticated session.

If the user cancels at PayPal, they are redirected to `/checkout?status=cancelled`. No charge is made and no confirm call is needed.

---

## Error Handling

Every error response includes `agent_contract` with recommended recovery actions:

| Error Code | HTTP | Recovery |
|---|---|---|
| `missing_api_key` | 401 | `authenticate` — register or provide a key |
| `invalid_api_key` | 401 | `authenticate` — register a new key |
| `invalid_request` | 400 | `fix_request` — correct parameters |
| `memory_not_found` | 404 | `list_memories` — find valid memory IDs |
| `content_required` | 400 | `fix_request` — provide content_text or content_json |
| `immutable_field` | 400 | `fix_request` — remove immutable field from request |
| `invalid_transition` | 409 | `check_memory_status` — re-read state |
| `self_link` | 400 | `fix_request` — use a different to_memory_id |
| `idempotency_conflict` | 409 | `create_memory` — use a different key |
| `idempotency_in_flight` | 503 | `retry_after_wait` — retry with same key |
| `quota_exhausted` | 403 | `upgrade_plan` or `retry_next_cycle` |
| `active_memory_limit` | 403 | `delete_memory` to free capacity or `upgrade_plan` |
| `agent_limit_reached` | 403 | `upgrade_plan` or reuse existing agent_id |
| `read_quota_exceeded` | 429 | `upgrade_plan` or `retry_next_cycle` |
| `invalid_plan` | 400 | Use a valid plan_id (pro, business) |
| `already_on_plan` | 409 | Already on this plan — `check_usage` |
| `checkout_failed` | 502 | `retry_checkout` after brief wait |
| `purchase_not_found` | 404 | `create_checkout` — start a new checkout |
| `purchase_expired` | 410 | `create_checkout` — start a new checkout |
| `capture_failed` | 502 | `retry_confirm` with same purchase_id |
| `rate_limited` | 429 | `retry_after_wait` |
| `server_error` | 500 | `retry_after_wait` |

---

## Agent Contract

Every operational response includes an `agent_contract` with machine-readable guidance:

- `state` -- what happened (e.g. `memory_created`, `quota_exhausted`)
- `retryable` -- whether the same request can be retried
- `next_actions` -- recommended follow-up actions with endpoints

Read `state` to understand the outcome. Read `next_actions` to decide what to do next. Do not parse error messages or HTTP status codes for decision-making.

For the full contract specification, see [docs/agent-contract.md](docs/agent-contract.md).

---

## Rate Limits

| Operation | Limit | Key |
|---|---|---|
| Memory creation | 120/hour | API key |
| Batch creation | 1 request counted; quota per memory | API key |
| Memory reads | 3000/hour | API key |
| Memory mutations | 600/hour | API key |
| Link operations | 600/hour (mutations), 3000/hour (reads) | API key |
| Discovery | 60/minute | IP |
| Key registration | 10/hour | IP |
| Global | 6000/hour | IP |

---

## Suite Integration

Observance is part of the agent infrastructure suite:

| Service | Role |
|---|---|
| **Observance** | remember — long-term memory and knowledge graph |
| **Orchestrion** | decide — task orchestration and work scheduling |
| **OutputLayer** | produce — artifact storage and delivery |

Memories can reference resources from other services via `metadata_json.external_refs`:

```json
{
  "metadata_json": {
    "external_refs": [
      { "service": "orchestrion", "resource_type": "task", "resource_id": "tsk_..." },
      { "service": "outputlayer", "resource_type": "output", "resource_id": "out_..." }
    ]
  }
}
```
