# Agent API

The Agent API allows external AI agents (Sentinel, Flint, etc.) to send content to Slack and store it in persistent memory. All Agent API endpoints are hosted on the orchestrator (port 3200).

> **Note:** For new integrations, prefer the [A2A protocol](a2a.md) (`POST /a2a` with JSON-RPC 2.0). The REST endpoints documented here remain fully supported for backward compatibility.

## Base URL

```
https://your-domain.com/api/
```

The nginx reverse proxy strips the `/api/` prefix, so the orchestrator receives requests at the root path. For example, `POST https://your-domain.com/api/ingest` routes to `POST /ingest` on the orchestrator.

For local development, use `http://localhost:3200/` directly (no `/api/` prefix).

## Authentication

All `/ingest` and `/reminders` requests require a bearer token in the `Authorization` header:

```
Authorization: Bearer <your-token>
```

The token must match the `ORCHESTRATOR_TOKEN` environment variable. If `ORCHESTRATOR_TOKEN` is empty, auth is disabled.

The `/health` and `/docs` endpoints are public (no auth required).

## Endpoints

### `GET /health`

Health check. No auth required.

**Response:**
```json
{
  "status": "ok",
  "busy": false,
  "timestamp": "2026-03-02T00:00:00.000Z"
}
```

### `GET /docs`

Returns this documentation as `text/markdown`. No auth required.

### `POST /ingest`

Send content to Slack and optionally store it in persistent memory.

**Headers:**
```
Content-Type: application/json
Authorization: Bearer <your-token>
```

**Request Body:**

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `content` | string | Yes | -- | The main text content or message |
| `title` | string | No | null | Title for news or idea items |
| `url` | string | No | null | Link URL (used with `type: "news"`) |
| `type` | string | No | `"message"` | One of: `"news"`, `"message"`, `"idea"`, `"raw"` |
| `tags` | string[] | No | [] | Tags for memory storage |
| `store` | boolean | No | true | Whether to store in persistent memory |
| `channel` | string | No | default | Slack channel ID override |
| `metadata` | object | No | {} | Arbitrary metadata attached to the memory |

**Content Types:**

- `"news"` -- Formats as a shared link with title and URL. Best for articles and links.
- `"idea"` -- Formats as a project idea. Stored with higher importance (0.7) in memory as type "Insight".
- `"message"` -- Plain text message. Default. Stored with importance 0.5 as type "Context".
- `"raw"` -- Same as message, no special formatting.

**Response:**
```json
{
  "ok": true,
  "slack_ts": "1234567890.123456",
  "memory_id": "uuid-string-or-null"
}
```

- `slack_ts` is the Slack message timestamp. Null if Slack posting failed.
- `memory_id` is the AutoMem memory ID. Null if `store: false` or AutoMem store failed.

**Error Responses:**

| Status | Meaning |
|---|---|
| 400 | Missing or invalid `content` field, or invalid `type` value |
| 401 | Missing or invalid auth token |
| 500 | Internal error |

### `POST /reminders`

Create a reminder. Fires to Slack when due (checked every 60 seconds).

**Headers:**
```
Content-Type: application/json
Authorization: Bearer <your-token>
```

**Request Body:**

| Field | Type | Required | Description |
|---|---|---|---|
| `text` | string | Yes | What to be reminded about |
| `due` | string | Yes | ISO 8601 datetime for when to fire |
| `channel` | string | No | Slack channel ID (defaults to `SLACK_CHANNEL_ID`) |

**Response:**
```json
{
  "ok": true,
  "reminder": {
    "id": "uuid",
    "text": "Check deployment status",
    "due": "2026-03-15T14:00:00.000Z",
    "channel": "D0AH0TC4VFC",
    "fired": false
  }
}
```

**Error Responses:**

| Status | Meaning |
|---|---|
| 400 | Missing `text`, missing `due`, or invalid date format |
| 401 | Missing or invalid auth token |

### `GET /reminders`

List active reminders. Add `?all=true` to include fired reminders.

**Response:**
```json
{
  "ok": true,
  "reminders": [
    {
      "id": "uuid",
      "text": "Check deployment status",
      "due": "2026-03-15T14:00:00.000Z",
      "channel": "D0AH0TC4VFC",
      "fired": false
    }
  ]
}
```

### `DELETE /reminders/:id`

Delete a reminder by ID.

**Response (200):**
```json
{
  "ok": true
}
```

**Response (404):**
```json
{
  "error": "Reminder not found"
}
```

## Examples

### Send a news link

```bash
curl -X POST https://your-domain.com/api/ingest \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "content": "New framework for building AI agents with tool use and memory.",
    "title": "AgentKit 2.0 Released",
    "url": "https://example.com/agentkit",
    "type": "news",
    "tags": ["ai", "agents", "tools"]
  }'
```

### Send a project idea

```bash
curl -X POST https://your-domain.com/api/ingest \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "content": "A CLI tool that watches your git commits and suggests refactors based on patterns it learns over time.",
    "title": "Git Refactor Buddy",
    "type": "idea",
    "tags": ["dev-tool", "git", "ai"]
  }'
```

### Send a plain message

```bash
curl -X POST https://your-domain.com/api/ingest \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "content": "Hey, found something cool you might want to look at."
  }'
```

### Send without storing in memory

```bash
curl -X POST https://your-domain.com/api/ingest \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "content": "Quick note, no need to remember this.",
    "store": false
  }'
```

### Set a reminder

```bash
curl -X POST https://your-domain.com/api/reminders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-token>" \
  -d '{
    "text": "Check deployment status",
    "due": "2026-03-15T14:00:00Z"
  }'
```

### List reminders

```bash
curl https://your-domain.com/api/reminders \
  -H "Authorization: Bearer <your-token>"
```

### Delete a reminder

```bash
curl -X DELETE https://your-domain.com/api/reminders/<reminder-id> \
  -H "Authorization: Bearer <your-token>"
```

### `GET /api/memory/stats`

Memory system health and statistics. Returns AutoMem status, memory count, vector count, and graph node count.

**Response:**
```json
{
  "ok": true,
  "data": {
    "memory_count": 192,
    "vector_count": 192,
    "graph_node_count": 45
  }
}
```

### `GET /api/memory/duplicates`

Scan for duplicate memories. Returns candidate clusters with content previews and similarity scores.

**Query Parameters:**

| Param | Type | Default | Description |
|---|---|---|---|
| `threshold` | number | `0.85` | Minimum similarity score (0-1) to consider a duplicate |
| `limit` | number | `50` | Max memories to scan per tag group |

**Response:**
```json
{
  "candidates": [
    {
      "keep": { "id": "abc", "content": "Memory to keep...", "importance": 0.8 },
      "duplicates": [
        { "id": "def", "content": "Similar memory...", "score": 0.92 }
      ]
    }
  ],
  "count": 1
}
```

### `POST /api/memory/dedup`

Run deduplication. Finds duplicate clusters and optionally deletes them.

**Request Body:**

| Field | Type | Default | Description |
|---|---|---|---|
| `dryRun` | boolean | `true` | If true, find duplicates but don't delete |
| `threshold` | number | `0.85` | Minimum similarity score |

**Response:**
```json
{
  "scanned": 50,
  "clusters": 3,
  "deleted": 0
}
```

## For AI Agents

If you are an AI agent reading this, here is what you need to know:

1. **Discover Homura** via `GET /.well-known/agent-card.json` -- returns skills, capabilities, and auth info.
2. **Prefer A2A protocol** for new integrations: `POST /a2a` with JSON-RPC 2.0. See [A2A docs](a2a.md). Skills: `chat`, `run-ticket`, `ingest-spec`, `share-knowledge`, `recall`.
3. **Legacy REST still works**: `POST /api/ingest` with your bearer token to send content to Homura's operator.
4. **Choose the right `type`** -- use `"news"` for links you found interesting, `"idea"` for project proposals, `"message"` for general communication.
5. **Add `tags`** to help with memory recall later. Use lowercase, hyphenated tags like `"dev-tool"`, `"ai"`, `"creative-coding"`.
6. **The `metadata` field** accepts any JSON object. Use it for structured data your agent wants to attach (source context, confidence scores, reasoning, etc.). Note: `source`, `title`, and `url` keys in metadata are overwritten by the system.
7. **Check `/health`** before sending if you want to verify the service is up.
8. **Responses include `slack_ts`** (the Slack message timestamp) and `memory_id` (the persistent memory ID, if stored). You can reference these in follow-up messages.
9. **Set reminders** via `POST /api/reminders` with `text` and `due` (ISO 8601). The system checks every 60 seconds and posts to Slack when a reminder is due.
